Android Theme Style Attr

参考
Attr、Style和Theme详解
Android 深入理解Android中的自定义属性
Android 中的Theme和Style使用
总结一下Android中主题(Theme)的正确玩法
Android Design与Holo Theme详解
为什么Material Design没在国产App中流行起来?

我刚开始学Android的时候,也总对这三个概念很迷惑,不知道什么是属性,什么是风格,什么是主题,它们之间又有什么关系?它们在Android框架中又充当什么角色?又如何自己去定义?但随着学习的深入,越发觉得这三块内容真是Android框架的一大神器,有时你不用改动代码,只要换一个theme,应用马上焕发青春。而且也尝试用所学内容去写自己的theme,不但可以让自己的布局文件更加清晰明了,而且还让自己的代码具有更高的扩展性,真是好处多多,希望对这块还不了解的童鞋多多研习。

一、概念说明

Attr:属性,风格样式的最小单元;
Style:风格,它是一系列Attr的集合用以定义一个View的样式,比如height、width、padding等;
Theme:主题,它与Style作用一样,不同于Style作用于个一个单独View,而它是作用于Activity上或是整个应用。

TIPS:框架使用Attr的顺序是:View中的Style会优先于Activity中的Theme,Activity中的Theme会优先于Application中的Theme,所以说你可以定义整个应用的总体风格,但局部风格你也可以做出自己的调整。

二、Attr的定义
创建一个简单的TextView布局

其中layout_width对应到框架中的attr信息如下:

<declare-styleable name="ViewGroup_Layout">
    <attr name="layout_width" format="dimension">
        <enum name="fill_parent" value="-1" />
        <enum name="match_parent" value="-1" />
        <enum name="wrap_content" value="-2" />
    </attr>
    ...
</declare-styleable>

从上可以看到layout_width可以使用三个枚举值,并且其中fill_parent和match_parent的value值都为-1。做过Android开发的童鞋肯定知道,从2.2开始Android框架就推荐用match_parent代替fill_parent,而以上代码正实现了兼容,因为它们对应的值都为-1。

以上的textStyle的属性信息在源码中如下:

<attr name="textStyle">
    <flag name="normal" value="0" />
    <flag name="bold" value="1" />
    <flag name="italic" value="2" />
</attr>

它也对应了三个值,但这里却使用了flag标签。细心的童鞋可能已经明白了flag与enum的差别,flag表示这几个值可以做或运算,比如上面的textStyle,你可以叠加使用,如用bold|italic表示既加粗也变成斜体,而enum只能让你选择其中一个值。

看完上例后,我们来试着自己自定义一个自己的属性,在values目录下创建一个attrs.xml文件,在<resources>元素里面首先申明一个自己的<declare-styleable>表示一个属性组,再在里面加上属性就行。如下我们定义一个DogStyle的属性组,其中有三个属性一个是dogSex,一个是dogName,dogName的格式我们设置为string,最后一个是dogColor,这样一个属于我们自己的属性就定义成功了。

attrs.xml

format表示了Attr的类型,可取的值有以下类型:

  • color:颜色值,如#000000
  • reference:引用某一资源ID。如@drawable/xxx
  • boolean:布尔值,true或false
  • dimension:尺寸值,可以为wrap_content、match_parent或是具体大小(xx dp)
  • float:浮点型
  • fraction:百分数
  • integer:整型
  • string:字符串类型
  • enum:枚举类型,各个取值互斥
  • flag:标记位,各个取值可用“|”连接

attr的format根据字面意思也挺容易理解的,这里我解释下reference的用法。它用在一些可以设置引用值的情况,比如@drawable/myImage、@color/myColor等。当然format也可以进行或运算,一般我们定义color类型的属性时,也一般会把format写成format="reference|color",这样我们不但可以设置颜色值,如#FFFFFF,还可以使用我们自己定义的狗图片,如@drawable/dog_pic。

三、style的使用

为每个View重复地指定字体,颜色等属性,无疑会增加大量的代码,而且不利于我们后期项目的维护,所以就引入样式(Style) 学过web的都知道,我们可以通过css的选择器对html中的元素进行设置;而在UI组件中,我们可以通过style属性来指定样式。

style文件需要保存在res/values目录下,文件名任意,但是必须是xml文件,sytle文件的根标记必须是<resources>。如下我们在res/values/styles.xml中定义一个雪纳瑞风格

<style name="SchnauzerStyle">
    <item name="dogName">雪纳瑞</item>
    <item name="dogColor">@drawable/schnauzer</item>
    <item name="dogSex">boy</item>
</style>
<style name="TextView">
  <item name="android:textSize">38sp</item>
  <item name="android:textColor">#128</item>
  <item name="android:shadowRadius">1.0</item>
  <item name="android:background">#035</item>
 </style>

首先我们自定义了一个View命名为DogView,然后创建res/layout文件下的activity_main.xml中加入该DogView视图,并让该View使用SchnauzerStyle风格。代码如下:

<cn.hadcn.test.DogView
    style="@style/SchnauzerStyle"
    android:layout_height="wrap_content"
    android:layout_width="wrap_content"/>
<TextView
    android:id="@+id/textView1"
    style="@style/TextView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/hello_world" />

也可以在程序中设置style
text.setTextAppearance(this, R.style.mystyle);

移步到DogView的Java代码中,我们可以通过theme的obtainStyledAttributes方法来获得我们刚刚定义的几个Attr属性在Style中的内容,如下我们举一个获得dogName的例子:

final Resources.Theme theme = context.getTheme();
TypedArray dogArray = theme.obtainStyledAttributes(attrs, 
R.styleable.DogStyle, defStyleAttr, defStyleRes);

String name = dogArray.getString(R.styleable.DogStyle_dogName);
Log.e("dog", "name = " + name);

dogArray.recycle();

以上obtainStyledAttributes有四个入参,前两个比较容易理解,后两个用作指定默认的Style,表示如果attrs中没有你想获得的属性,但如果你指定了默认Style,它会去从该默认的Style里面找你想要的属性。defStyleAttr和defStyleRes功能一样,指定的资源形式不同,前者表示一个默认的指向一个style风格的attr属性,而后者你可以直接传入一个style风格的id。注意以上定义的Style只能在这个DogView中被使用,如果你想在其他View使用,就需要再在需要使用的View中增加这个Style。这就是先前我们说的Style只能作用于一个View。

tips:
values-v11代表在API 11+的设备上,用该目录下的styles.xml代替res/values/styles.xml
values-v14代表在API 14+的设备上,用该目录下的styles.xml代替res/values/styles.xml
其中API 11+代表android 3.0 +
其中API 14+代表android 4.0 +

如果需要继承其他样式,可以使用parent属性来指定。引用其他样式表,如果是应用系统的,需要用android:style/(其实只写android即可,不过为了好看,最好还是这么写)打头,如果是自己定义的样式,用style/打头。继承时也一样。至于是用"@"还是"?",@符号表明了我们应用的资源是前边定义过的(或者在前一个项目中或者在Android 框架中)。问号?表明了我们引用的资源的值在当前的主题当中定义过。@引用的是之前定义好的资源当前项目或者android的framework里。而?则是引用的当前加载的样式文件里。意思就是说你在xml里某行定义了一个资源,在下面某行需要引用这个资源时用?即可

四、Theme的使用

Theme与Style使用同一个元素标签<style>,区别在于所包含的属性不同,并且使用的地方也不一样。Theme你需要设置到AndroidManifest.xml的<application>或者<activity>标签下,设置后,被设置的Activity或整个应用下所有的View都可以使用该<style>里面的属性了。

比如在上例中,我们直接把SchnauzerStyle设置到<activity>标签中,并把布局文件中DogView元素的style="@style/SchnauzerStyle"栏位删除,以此来测试下,这个Activity下的所有View是不是可以直接使用theme中声明的这些属性。

<activity
    android:name=".MainActivity"
    android:theme="@style/SchnauzerStyle">
    ...

以上理论上是可行的,不过运行后,程序却出现奔溃,出现以下错误提示:

java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.

有些同学一眼可能就看出,因为在这里Activity或Application的需要很多属性才能工作的,而此处我们只给它传一个SchnauzerStyle,这当然不行,所以我们需要对这个Style做下处理,让SchnauzerStyle继承一个系统主题,如下:

<style name="SchnauzerStyle" parent="Theme.AppCompat">
    <item name="dogName">雪纳瑞</item>
    <item name="dogColor">@drawable/schnauzer</item>
    <item name="dogSex">boy</item>
</style>

这样一个雪纳瑞主题就诞生了,而在这个Activity下的所有View都可以用雪纳瑞的信息了。

1.例二:
首先在res/values/themes.xml中定义Theme。

<resources xmlns:android="http://schemas.android.com/apk/res/android">
    <style name="Theme" parent="android:Theme.Light">
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowTitleSize">60dip</item>
        <item name="android:windowTitleStyle">@style/WindowTitle</item>
        <item name="android:background">#234</item>
    </style>
    <style name="WindowTitle">
        <item name="android:singleLine">true</item>
        <item name="android:shadowColor">#658</item>
        <item name="android:shadowRadius">2.75</item>
    </style>  
</resources>

然后在AndroidManifest.xml中使用刚才定义的主题。只要定义application的android:theme属性为style/Theme即可。

<application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/Theme" >
    <activity
        android:name="com.example.themedemo.MainActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

或者使用代码setTheme(R.style.Theme)

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setTheme(R.style.Theme);
        setContentView(R.layout.activity_main);
    }

2.主题的来源有三个

  1. 来自Android系统自带的。加上“android:”,如:android:Theme.Black
系统自带主题:
API 1:
android:Theme 根主题
android:Theme.Black 背景黑色
android:Theme.Light 背景白色
android:Theme.Wallpaper 以桌面墙纸为背景
android:Theme.Translucent 透明背景
android:Theme.Panel 平板风格
android:Theme.Dialog 对话框风格

API 11:
android:Theme.Holo Holo根主题
android:Theme.Holo.Black Holo黑主题
android:Theme.Holo.Light Holo白主题

API 14:
Theme.DeviceDefault 设备默认根主题
Theme.DeviceDefault.Black 设备默认黑主题
Theme.DeviceDefault.Light 设备默认白主题

API 21: (网上常说的 Android Material Design 就是要用这种主题)
Theme.Material Material根主题
Theme.Material.Light Material白主题
  1. 来自兼容包的(比如v7兼容包)。不需要前缀,直接:Theme.AppCompat
兼容包v7中带的主题:
Theme.AppCompat 兼容主题的根主题
Theme.AppCompat.Black 兼容主题的黑色主题
Theme.AppCompat.Light 兼容主题的白色主题

Theme.AppCompat主题是兼容主题,是什么意思呢?意思就是说如果运行程序的手机API是21则就相当于是Material主题,如果运行程序的手机API是11则就相当于是Holo主题,以此类推。

兼容包v7会被Google公司不断升级:
比如 appcompat-v7-21.0 表示升级到向 API 21 兼容
比如 appcompat-v7-23.2 表示升级到向 API 23 兼容

Android 5.0上指定用Material theme就可以。参考5.0的文档即可。

5.0之前的老版本,官方提供的途径是使用appcompat v7-21 support库。v7的意思是支持Android v7 (2.1)及以上的老版Android,21是appcompat这个库的版本。Google在文档里也经常直接叫做appcompat v21,不要混淆。

  1. 你自己写一个主题
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容