Android编程权威指南总结(八)

第二十二章      样式与主题

一、颜色资源

      颜色资源就是 res/values/colors.xml 文件,里面可以配置各种颜色的 item。

      使用颜色资源,可以方便地在一处定义各种颜色值,然后在整个应用里引用。

二、样式

      样式是能够应用于视图组件的一套属性。res/values/styles.xml 是样式文件。

<style name="BeatBoxButton">
    <item name="android:background">@color/dark_blue</item>
</style>

      样式可以为很多组件共用,更新修改属性时,只修改公共样式定义就行了。定义好样式,把它添加给各个组件。

1、样式继承

      样式支持继承。一个样式能继承并覆盖其他样式的属性:

<style name="BeatBoxButton.Strong"> 
    <item name="android:textStyle">bold</item> 
</style>

      这个新样式继承了BeatBoxButton样式的属性。

      除了通过命名表示样式继承关系,也可以采用指定父样式的方式:

<style name="StrongBeatBoxButton" parent="@style/BeatBoxButton"> 
    <item name="android:textStyle">bold</item> 
</style>

三、主题

      样式很有用。在styles.xml公共文件中,可以为所有组件定义一套样式属性共用。可惜,定义公共样式属性虽方便,实际应用却很麻烦:需要逐个为所有组件添加它们要用到的样式。要是开发一个复杂应用,涉及很多布局、无数按钮,仅仅添加样式就累死人了。

      该是主题闪亮登场的时候了!主题可看作样式的进化加强版。同样是定义一套公共主题属性,样式属性需要逐个添加,而主题属性则会自动应用于整个应用。主题属性能引用颜色这样的外部资源,也能引用其他样式。使用主题,不用找到每个按钮,告诉它们要用哪个主题。一句话就搞定:“所有按钮都使用这个样式。”

1、修改默认主题

      

      theme属性指向的主题叫AppTheme。它也定义在styles.xml文件中。可见,主题实际就是一种样式,可以修改该样式里的属性,自定义主题样式。

四、添加主题颜色

      现在,基于AppTheme主题模板,我们来定制它的属性:

      虽然这三个主题属性看上去和前面的样式属性差不多,但它们的应用范围不一样。样式属性仅适用于单个组件;主题属性则适用所有使用同一主题的组件。例如,工具栏会以主题的 colorPrimary 属性设置自己的背景色。

      colorPrimary 属性主要用于工具栏。由于应用名称是显示在工具栏上的,colorPrimary也可以称为应用品牌色。

      colorPrimaryDark 用于屏幕顶部的状态栏。从名字可以看出,它是深色版colorPrimary。注意,只有Lollipop以后的系统支持状态栏主题色。对于之前的系统,无论指定什么主题色,状态栏都是不变的黑底色。

      colorAccent 主题色应该和colorPrimary形成反差效果,主要用于给EditText这样的组件着色。按钮组件不支持着色。

五、覆盖主题属性

      第一个任务是修改主题以更换BeatBox应用的背景色。当然,你可以打开res/layout/fragment_ beat_box.xml文件,手工设置RecyclerView视图的android:background属性。如果还有其他fragmentactivity要改,都照此处理。这简直是浪费:浪费你的时间,浪费应用资源。

      主题已经设置了背景色,在此基础上再设置其他颜色,就是自己给自己找事。而且,在应用里到处复制使用背景属性设置代码也不利于后期维护。

1、主题探秘

      要解决上述问题,应设法覆盖主题背景色属性。为了找出可覆盖属性的名字,先来看看这个属性在其Theme.AppCompat父主题里是怎么设置的。你需要找出主题继承的源头。主题继承树有多深,谁也不知道,只能一层层向上找,一直找到目标为止。

      打开styles.xml文件,按住Command键(Windows系统是Ctrl键)点击Theme.AppCompat,来看看继承有多深。(如果无法直接在Android Studio里追溯主题属性,或是想在工具之外查找,可以在your-SDK-directory/platforms/android-24/data/res/values目录找到主题源码。)

      <style name="Theme.AppCompat" parent="Base.Theme.AppCompat" /> 可 知 Theme.AppCompat主题属性继承自 Base.Theme.AppCompat。有趣的是, Theme. AppCompat本身没有覆盖任何属性,仅仅指向了其父主题。

      按住Command键再点击Base.Theme.AppCompat,Android Studio会提示,这个主题有资源修饰符,有多个版本可选。选择values/values.xml版本。(BeatBox支持19及以上API级别,所以这里选择了无修饰版本。如果选择v21版本,很可能还会看到API 21级里添加的新特性。)

      Base.Theme.AppCompat这个主题没任何自己的定义,也就是说没覆盖任何属性。继续定位到它的父主题:Base.V7.Theme. AppCompat

      距离目标越来越近了。Base.V7.Theme.AppCompat有许多属性,但还是没找到改变背景色的属性。继续定位到Platform.AppCompat。这个主题也有多个版本,选择values/values.xml版本。

      终于,在这里看到了Platform.AppCompatandroid:Theme父主题。注意,这里引用的不是Theme,而是android:Theme。前面的android命名空间不能丢。

      AppCompat库可以看作BeatBox应用的一部分。编译项目时,工具会引入AppCompat库和它的一堆JavaXML文件。这些文件已包含在应用里,如同你自己编写的文件。如果想引用AppCompat库里的资源,像Theme.AppCompat这样,直接引用就可以了。

      有些主题包含在Android操作系统里,如Theme,引用时必须加上指向归属地的命名空间。在引用Theme主题时,AppCompat库使用了android:Theme这样的形式,这是因为Theme来自于Android操作系统。

      总算找到了。在这里,终于可以看到所有可以覆盖的主题属性。当然,还可以继续定位到Theme主题,不过没这个必要。我们想要的属性已经找到了。查看代码,可以看到windowBackground这个属性。顾名思义,这就是用于主题背景色的属性。

      回到styles.xml文件中,覆盖windowBackground这个属性:

<style name="AppTheme" parent="Theme.AppCompat"> 
    <item name="colorPrimary">@color/red</item> 
    <item name="colorPrimaryDark">@color/dark_red</item> 
    <item name="colorAccent">@color/gray</item> 
 
    <item name="android:windowBackground">@color/soothing_blue</item> 

</style>

      注意,windowBackground这个属性来自Android操作系统,所以别忘了使用android命名空间。

      总结一下,刚才我们定位查看了以下主题:

  • Theme.AppCompat
  • Base.Theme.AppCompat
  • Base.V7.Theme.AppCompat
  • Platform.AppCompat

      刚才我们自下而上逐层定位,直到找到AppCompat根主题。将来,越来越熟练之后,你很可能会跳过中间步骤而直达目标。不过,建议还是按部就班,以此看清楚究竟哪个是根主题。

      最后再提个醒,主题继承关系和层次可能有变(发布新系统),但上面介绍的方法不会变。想要知道该覆盖哪个属性,就沿着继承树找吧!

六、修改按钮属性

      可以在主题中定义一个用于所有按钮的样式。

      再次逐级定位查找主题。这次,我们找到Base.V7.Theme.AppCompat里的buttonStyle属性。这个属性指定应用中普通按钮的样式:

<item name="buttonStyle">@style/Widget.AppCompat.Button</item>

      这个buttonStyle属性没有设置值,而是指向了一个样式资源。定位并查看Widget.AppCompat.Button样式。Widget. AppCompat.Button样式没有定义任何属性,继续定位找其指向的父样式。你会发现有两个版本可选,选values/values.xml版本:

      BeatBox应用的所有按钮都使用了这些属性。

      在BeatBox应用里复用Android自身主题:

<style name="BeatBoxButton" parent=" Widget.AppCompat.Button">
    <item name="android:background">@color/dark_blue</item> 
</style>

      继承Widget.AppCompat.Button样式,就是首先让所有按钮都继承常规按钮的属性。然后根据需要,有选择性地修改一些属性。

      BeatBoxButton样式已重新定义完毕,可以使用了。经过前面主题深挖,我们知道要覆盖buttonStyle属性。下面覆盖buttonStyle属性,让它指向BeatBoxButton样式:

七、深入学习:样式继承拾遗

      在进行主题探秘时,你可能已经注意到了,样式继承的表示法时有切换。AppCompat主题都是使用主题名表示继承,直到碰到Platform. AppCompat这个主题:

      这里,继承是直接使用parent属性来表示的。为什么呢?

      要以主题名的形式指定父主题,有继承关系的两个主题都应处于同一个包中。因此,对于Android操作系统内部主题间的继承,就可以直接使用主题名继承表示法。同理,AppCompat库内部也是这样。然而,一旦AppCompat库要跨库继承,就一定要明确使用parent属性。

      在开发自己的应用时,应遵守同样的规则。如果是继承自己内部的主题,使用主题名指定父主题即可;如果是继承Android操作系统中的样式或主题,记得使用parent属性。

八、深入学习:引用主题属性

      在主题中定义好属性后,可以在XML或代码中直接使用它们。

      在XML中引用具体值(如颜色值)时,我们使用@符号。@color/gray指向某个特定资源。在XML中引用主题属性时,使用?符号:

<Button xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:tools="http://schemas.android.com/tools" 
    android:id="@+id/list_item_sound_button" 
    android:layout_width="match_parent" 
    android:layout_height="120dp" 

    android:background="?attr/colorAccent" 

    tools:text="Sound name"/>

      上述XML中 符号的意思是使用 styles.xml 文件中 colorAccent 属性指向的资源。

      也可以在代码中使用主题属性,但是比较嗦:

Resources.Theme theme = getActivity().getTheme(); 
int[] attrsToFetch = { R.attr.colorAccent }; 
TypedArray a = theme.obtainStyledAttributes(R.style.AppTheme, attrsToFetch); 
int accentColor = a.getInt(0, 0); 
a.recycle();

      先取得Theme对象,然后要求它找到定义在AppThemeR.style.AppTheme中的R.attr.colorAccent属性。结果得到一个持有数据的TypedArray对象。接着,向TypedArray对象索要int值以取出颜色。颜色值取出之后就可以使用了,比如,用来更改按钮背景色。

 

第二十三章      XML  drawable

      Android世界里,凡是要在屏幕上绘制的东西都可以叫作drawable,比如抽象图形、Drawable类的子类代码、位图图像等。还有更多的drawablestate list drawable、shape drawable 和 layer list drawable。这三个drawable都定义在XML文件中,可以归为一类,统称为XML drawable

一、统一按钮样式

      不论屏幕大小,recycler视图总是显示三列按钮。如果还有多余的空间,它会拉伸列格以适配屏幕。不过,BeatBox应用的按钮不应拉伸,所以把它们封装在frame布局里。这样,frame布局会被拉伸,而按钮不会:

二、shape  drawable

      使用ShapeDrawable,可以把按钮变成圆。XML drawable和屏幕像素密度无关,所以无需考虑创建特定像素密度目录,直接把它放入默认的drawable文件夹就可以了。

      打开项目工具窗口,在res/drawable目录下创建一个名为button_beat_box_normal.xml的文件:

<shape xmlns:android="http://schemas.android.com/apk/res/android" 
    android:shape="oval"> 

    <solid 
        android:color="@color/dark_blue"/> 

</shape>

      XML文件定义了一个背景为深蓝色的圆形。也可使用shape drawable定制其他各种图形,如长方形、线条以及梯形等。

      在styles.xml中,使用新建的button_beat_box_normal作为按钮背景:

      此时,按钮背景已变成了圆形。不过,按钮点击之后样子没有任何变化。按钮按下去时,如果能切换显示状态,用户体验应该会更好。

三、state  list  drawable

      为解决这个问题,首先定义一个用于按钮按下状态的shape drawable。在res/drawable目录下再创建一个名为button_beat_box_pressed.xml的文件,除了背景颜色是红色外,这个shape drawable和前面的正常版本是一样的:

<shape xmlns:android="http://schemas.android.com/apk/res/android" 
    android:shape="oval"> 
 
    <solid 
        android:color="@color/red"/> 

</shape>

      接下来,要在按钮按下时使用这个新建的shape drawable。这需要用到state list drawable。根据按钮的状态,state list drawable可以切换指向不同的drawable。按钮没有按下的时候指向button_beat_box_normal按下的时候就指向button_beat_box_pressed

      在drawable目录中,定义一个state list drawable(res/drawable/button_beat_box.xml):

<selector xmlns:android="http://schemas.android.com/apk/res/android"> 
    <item android:drawable="@drawable/button_beat_box_pressed" 
        android:state_pressed="true"/> 
    <item android:drawable="@drawable/button_beat_box_normal" /> 
</selector>

      现在,在styles.xml中修改按钮样式,改用button_beat_box作为按钮背景:

      按钮没有按下的时候使用button_beat_box_normal作背景,按下时就使用button_beat_ box_pressed作背景

      除了按下状态,state list drawable还支持禁用、聚焦以及激活等状态。若想详细了解,请访问网页:developer.android.com/guide/topics/resources/drawable-resource.html#StateList

四、layer  list  drawable

      layer list drawable能让两个XML drawable合二为一。借助这个工具,可以为按下状态的按钮添加一个深色的圆环:

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape xmlns:android="http://schemas.android.com/apk/res/android"
            android:shape="oval">
            <solid
                android:color="@color/red"/>
        </shape>
    </item>
    <item>
        <shape android:shape="oval">
            <stroke
                android:width="4dp"
                android:color="@color/dark_red"/>
        </shape>
    </item>
</layer-list>

      现在,layer list drawable中指定了两个drawable。第一个是和以前一样的红圈。第二个则会绘制在第一个圈上,它定义了一个4dp粗的深红圈。这会产生一个暗红的圈。

      这两个drawable可以组成一个layer list drawable。多个当然也可以,会获得一些更复杂的效果。

五、深入学习:为什么要用 XML  drawable

      应用总需要切换按钮状态,所以state list drawableAndroid开发不可或缺的工具。那shape drawablelayer list drawable呢?应该用吗?

      XML drawable用起来方便灵活,不仅用法多样,还易于更新维护。搭配使用shape drawable和layer list drawable可以做出复杂的背景图,连图像编辑器都省了。更改BeatBox应用的配色更是简单,直接修改XML drawable中的颜色就行了。

      另外,XML drawable独立于屏幕像素密度,可在不带屏幕密度资源修饰符的drawable目录中直接定义。如果是普通图像,就需要准备多个版本,以适配不同屏幕像素密度的设备;而XML drawable只要定义一次,就能在任何设备的屏幕上表现出色。

六、深入学习:使用 mipmap 图像

      资源修饰符和drawable用起来都很方便。应用要用到图像,就针对不同的设备尺寸准备不同尺寸的图片,再分别放入drawable-mdpidrawable-hdpi这样的文件夹。然后,按名字引用它们。剩下的就交给Android了,它会根据当前设备的屏幕密度调用相应的图片。

      但是,有个问题不得不提。发布应用到Google应用商店时,APK文件包含了项目drawable目录里的所有图片。这里面有些图片甚至从来不会用到。这是个负担。

      为解决这个问题,有人想到针对设备定制APK,比如mdpi APK一个,hdpi APK一个,等等。(有关APK分包的详细信息,可参阅工具文档网页:tools.android.com/tech-docs/new-build-system/user-guide/apk-splits。)

      但问题解决得不够彻底。假如想保留各个屏幕像素密度的启动图标呢?

      Android启动器是个常驻主屏幕的应用。按下设备的主屏幕键,会回到启动器应用界面。有些新版启动器会显示大尺寸应用图标。想让大图标清晰好看,启动器就需要使用更高分辨率的图标。对于hdpi设备,要显示大图标,启动器就会使用xhdpi图标。找不到的话,就只能使用低分辨率的图标。可想而知,放大拉伸后的图标肯定很糟。

      Android的另一解决办法是使用mipmap目录。目前,Android Studio中的新项目已经可以使用mipmap资源了,根据屏幕分辨率将各类型图片放入对应文件夹下。

七、深入学习:使用 9-patch 图像

      有时候(也可能经常),按钮背景图必须用到普通图片。那么,如果按钮需要以不同尺寸显示,背景图该如何变化呢?

      如果按钮的宽度大于背景图的宽度,图片会被拉伸。拉伸的图片会有很好的效果吗?朝一个方向拉伸背景图很可能会让图片失去原样,所以得想个办法控制图片拉伸方式,不然背景图会很丑~~

      使用9-patch图像能解决这个问题。9-patch图像是一种特别处理过的文件,能让Android知道图像的哪些部分可以拉伸,哪些部分不可以。只要处理得当,就能确保背景图的边角与原始图像保持一致。

      为什么要叫作9-patch呢?9-patch图像分成3×3的网格,即由9部分或9 patch组成的网格。网格角落部分不会被缩放,边缘部分的4patch只按一个维度缩放,而中间部分则按两个维度缩放:

      9-patch图像和普通PNG图像十分相似,只有两处不同:9-patch图像文件名以.9.png结尾,图像边缘具有1像素宽度的边框。这个边框用以指定9-patch图像的中间位置。边框像素绘制为黑线,以表明中间位置,边缘部分则用透明色表示。

      任意图形编辑器都可用来创建9-patch图像,但Android SDK自带的draw9patch工具用起来更方便。

      在项目工具窗口中,右键单击ic_button_beat_box_ default.png,选择Refactor Rename...菜单项将其改名为ic_button_beat_box_default.9.png。(如果Android Studio提示有同名资源,直接点Continue按钮继续。)再用相同的步骤得到另一个文件:ic_button_beat_box_pressed.9.png

      然后,双击默认图片在Android Studio内置的9-patch工具中打开。(如果Android Studio没能顺利打开9-patch编辑器,请先关闭图片文件,并在项目工具窗口中展开drawable目录,再尝试重新打开它。)

      在9-patch工具中,首先,为让图片更醒目,勾选上Show patches选项。然后,把图像顶部和左边框填充为黑色,以标记图像的可伸缩区域。

      图片的顶部黑线指定了水平方向的可拉伸区域。左边的黑线标记在竖直方向哪些像素可以拉伸。

      顶部以及左边框标记了图像的可拉伸区域,那么底部以及右边框又该如何处理呢?它们定义了9-patch图像的可选内容区。内容区是绘制内容(通常是文字)的地方。如果不标记内容区,那么默认与可拉伸区域保持一致。

      使用内容区让按钮上的文字居中。现在继续编辑ic_button_beat_box_default.9.png,在图片上添加上右边和底部两条线。同时勾选上Show content选项。这个选项会让预览器高亮显示图片的文字显示区。

      下面为做好之后的状态:

      上下左右四边的黑线,可以根据个人需要去拉长或缩短,选择区域并不一定非要按上图来。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值