安卓入门之样式管理

前两天还在抱怨为啥安卓中写样式这么麻烦,今天才发现原来安卓中也是有样式表的,并且还十分规范!!像这样蒙着头一通乱学真是刺激啊。废话少说,赶紧理清安卓中样式表的使用方法,接下来整个布局就感觉没啥问题了。

<!--more-->

Android Studio中,所有的资源文件都被整合的res文件夹下面,所以,你懂的。

1. 变量

在一个应用在,样式风格应该统一。在web页面中使用类来达到样式复用的目的,而在Android应用中,是通过将对应的属性先定义在xml资源文件中(比如某个颜色,某个边距尺寸等等),然后引用这些资源文件,减少代码中的硬编码,便于样式维护和风格统一。

是不是很眼熟?没错,这就跟SCSS中的定义变量一样嘛,只是安卓本身就支持这种做法,所以我会在前面提到“ 十分规范 ”这个说法。更规范的是,甚至将变量都进行了分类

1.1. colors

颜色是UI的第一印象,重要性就不说了。在CSSMagic这个项目中,我专门写了一个_colors的scss文件,用来声明常用的颜色变量,然后定义了对应的背景颗粒类.bg-*和字体颜色颗粒类.text-*。当时还被我的机智折服了,要是早点学安卓这些方法肯定早就知道了吧~擦又跑题了。

打开res/values/colors.xml,就可以看见对应的颜色资源,大概是下面这个样子

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>

    <color name="white">#fff</color>
    <color name="red">#ff0000</color>
</resources>

其中前3个是AS自动生成的,我们也可以使用color标签自定义对应的颜色变量,颜色名可以是任何有效的变量名,对应的色值是十六进制表示法。

<color name="mycolor">#00ff00</color>

定义好颜色变量之后,就可以直接在布局文件或者样式表(后面马上提到)使用了

<TextView
        ...
        android:textColor="@color/mycolor"/>

如果设计突然说整个应用的色彩要调换,修改colors配置文件比从layout文件中去挨个修改方面得多。这在web开发中深有体会,安卓既然本身就提供了这些功能,一定要好好把握啊。

1.2. strings

更神奇的是,就连组件中需要展示的文字,都可以使用strings.xml文件进行配置,相当于建议在layout文件中不出现任何硬编码,而将对应的值单独进行配置。

打开res/values/strings.xml中,可以看见

<resources>
    <string name="app_name">UI</string>
</resources>

额不要吐槽我凑字数了。同理,我们可以使用string标签自定义字符串

<string name="mytext">Txm</string>

引用方式也跟color类似,@string/mytext即可,AS强大的语法提示功能让我们的代码写起来轻松又愉快

2. 样式类

在web开发中,如果一组样式在多个地方出现,我们肯定会使用类进行封装,然后在需要的标签上添加这些类即可(关于CSS样式重用的问题我折腾了非常长一段时间)。同理,就UI布局来说,我认为Android和CSS的思路应该是相同的:减少重复代码,提高开发和维护效率。 尽管安卓中并没有样式类的概念,也必定会存在某种机制,将一组样式封装在一起,然后可以在多个组件上进行调用。千呼万唤使出来,终于轮到了今天的主角:style标签。

2.1. style标签

打开res/values/styles.xml文件,可以看见

<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>
</resources

这个结构看起来十分清晰(本来XML文件的语义化就比JSON强多了,啊呸又跑题了),一个style标签对应一组样式,一个item标签对应某个组中的单条样式,这里还遇见了前面提到的color变量。没错,把style标签的name属性值看做是类名就可以了,一个style标签就是 一个CSS样式类。

那么,怎么在组件上引用这个样式类呢?在HTML中我们使用的是class属性,在Android中,对应的属性是style,即

<!-- 定义样式 -->
<style name="mytext">
    <item name="android:layout_width">match_parent</item>
    <item name="android:layout_height">wrap_content</item>
    <item name="android:textColor">@color/red</item>
</style>

<!-- 引用样式 -->   
<TextView style="@style/mytext" android:text="Hello"/>
<TextView style="@style/mytext" android:text="World"/>

可以在多个组件上使用相同的样式,这样整个布局代码看起来清爽多了,之前满屏幕的android:xxx看起来真是眼花缭乱(想想一个HTML文件每个标签上面都用style来指定样式是多么可怕的事情)。伟大的HTML规范告诉我们,样式结构和行为要分离[/掩面]。

2.2. 文件分离

了解了style标签,可以在很大程度上减少重复的代码。但是随着页面的增加,这个styles.xml文件想必也会很庞大,将样式文件进行拆分是非常有必要的(样式在项目层次上的复用,样式白哦的维护等)。在SCSS中,可以使用@import解决这个问题,在Android中又是怎么解决这个问题的呢?

答案是:完!全!不!用!管!只要喜欢,你可以把样式表拆分成多个文件,只要按照正确的style标签格式书写,反正都会被R文件进行统计并赋予惟一的检索值,因此最后也只需要使用@style/mystyle进行引用就可以了。

我的理解是:样式类并不是在底层并不是按照文件进行管理的(在网页中需要使用link标签引入样式表)拆分文件主要是为了开发人员便于维护。安卓在我心中一下子变得高大上起来。

2.3. 样式继承

在网页中,一个标签是可以同时具备多个类的,但是在安卓中,一个组件的style属性貌似只能有一个值,这样,就不能使用组合类来更大程度地实现样式重用(哈这个词我是跟它杠上了)。安卓提供了另外一种方式来解决这个问题:样式继承。

注意这里的样式继承就跟CSS中的继承不太一样了:

  • CSS中的继承是子元素在不用指定对应样式的情况下,继承父元素的某些样式属性,比如字体颜色,大小等
  • Android中的继承是针对两个style标签而言的,通过继承可以让另外一个标签具有与父元素相同的样式,然后可以在style标签内部指定额外的样式

是不是很熟悉?没错,这不就是跟SCSS中的混合器和继承差不多的东西嘛~在style标签上使用parent属性指定继承的样式类。

<style name="header">
    <item name="android:layout_width">match_parent</item>
    <item name="android:layout_height">wrap_content</item>
    <item name="android:textColor">#ff0000</item>
</style>

<style name="footer" parent="header">
    <item name="android:textSize">30sp</item>
</style>        

此外,也可以通过另外一种形式使用继承

<style name="header.another">
    <item name="android:textSize">30sp</item>
</style>

具体采用哪种形式由具体情况决定了

利用继承,我们可以进一步减少我们的代码量,比如整理一些基础布局的基类,然后再派生出不同的样式类...打住,我感觉我又要开始折腾了!

2.4. 动态样式

安卓中提供了许多操作样式的接口,比如setTextsetTextColor啥的,但是这些接口都是针对单条样式的。在逻辑代码操作单条样式并不是一个明智的做法,用JavaScript修改样式的时候,一般的做法是先定义好对应的类,然后通过切换样式类的做法来达到动态设置样式的效果,这样可以保证行为和样式分离。还是那个问题:安卓中怎么实现动态设置样式类呢?

// todo: 这个问题貌似还没有查到,貌似有setTextAppearance之类的接口

3. 图片

在App应用中,图片是UI的重要组成部分。这跟Web中或多或少有些区别:在Web中处于网络性能的影响,图片更多的是作为内容进行展示,而大部分UI布局都是通过样式属性实现的;而在App中,更多的是考虑实现更完美的UI布局,因此很多地方需要使用图片资源来实现特定的效果。

3.1. 适配图片

安卓分辨率在数量上远超IOS,因此屏幕适配是一个很大的问题。根据分辨率的大小,可以将其划分为hdpimdpixhdpixxhdpixxxhdpi等(是不是又很熟悉,栅格系统啊~)

在布局上可以采用伸缩式的布局,而针对图片,安卓提供了drawable-*的方式进行处理,其中的*对应的就是上面的分辨率区域,在不同的分辨率区域下使用一张图片的不同形式(这特么就是多张图片好不~)。另外,图片的命名跟变量名命名规则相同,不能使用数字开头哦。

在AS中,文件树被简化了,我们打开项目的实际位置,可以发现实际上都有对应的文件夹,用于存放不同分辨率下的图片。系统是如何自动加载对应的图片呢?这里参考玩转Android drawable图片适配:

  • 先查找和屏幕密度最匹配的文件夹
  • 如果在最匹配的目录没有找到对应图片,就会向更高密度的目录查找,直到没有更高密度的目录
  • 如果一直往高密度目录均没有查找,Android就会查找drawable-nodpi目录,该目录表示包含的资源所有密度的设备,可能需要自己新建
  • 最后,如果在drawable-nodpi目录也没有查找到,系统就会向比最匹配目录密度低的目录依次查找,直到没有更低密度的目录

上面是不是意味着需要针对分辨率出多套设计图感觉不靠谱啊,设计肯定只给一套图这倒是个问题。

另外,你可能还发现了res/mipmap文件夹,这里面存放的是APP的图标,也需要针对屏幕分辨率进行处理。总之,安卓的分辨率貌似是个坑啊。

3.2. Nine-Patch

安卓里面有个叫做Nine-Patch图片格式,即指定图片特定位置拉伸和填充内容的一种特殊的png图片格式,这是一个多么神奇的东西啊,之前网页上各种屏幕适配的背景图问题这里可以完美解决,相关的知识暂时没有深入,附个传送门。(想想之前在网页上实现一个由内容撑开的聊天对话框,要达到设计师的花式需求,可不是一份轻松的活)

Android Studio中实现Nine-Patch图片十分简单,将*.png格式的图片重命名为*.9.png,然后将图片资源放在drawable文件下就可以了,不过要注意的是仍旧需要手动去设置拉伸参照的"黑实线"。

3.3. xml图形绘制

drawable直译为“可拉的,可画的。在res/drawable文件夹中,除了保存不同分辨的图片之外,我们还可以自己绘制相关的图形,并将其视作常规的图片使用,这是多么强大的功能啊。

xml图形绘制看起来跟svg的语法十分相似,这里参考Android 图像绘制之 Drawable。由于也是刚接触,先练练手再回来总结xml图形绘制的问题。讲道理,搞明白了这个,大部分样式布局几乎都可以实现了。

4. 自定义属性

HTML5规范了标签上的自定义属性,即使用data-*声明自定义属性,然后在DOM中就可以使用datset获取该节点上的自定义属性集合。在安卓中同样可以使用自定义属性,不过用法有很大的不同。

自定义属性大多用在自定义view里面(我之前以为自定义view是基础知识,后来发现原来是进阶门槛[/掩面])。

5. 小结

一个人从打环境开始摸索安卓开发,到现在为止走了很多弯路(跟学前端的时候一模一样啊)。走弯路也没啥大不了的,只要能在摸索中把握好前进的方向就好了。安卓跟web前端,实际上真的挺像的:搭界面,接数据,处理交互,添加动画啥的。

现在差不多弄了两周,再来一个月应该就能达到自己的初期目标了,加油。

2018年五月面试发现的一些问题 BFC及其应用