Android自定义Style

霸气de小男生 提交于 2019-12-05 12:42:25

1 背景介绍

最近接触了一款APP,从Android5.1.1升级到Android8.1,整个APP使用的主题为”@android:style/Theme.DeviceDefault.Light”。测试在Bugzilla上提了好几个关于UI方面的Bug。研发一看就知道这些不是Bug,但是测试他们毕竟不是开发,只会看表面现象,同一个APP在不同版本Android平台上UI不一致都是Bug。要给测试、PM说清楚这些不是Bug,就必须了解Android系统的Style.Style可以简单理解为一组属性的集合,方便对APP的样式做一个统一处理。下面从属性开始了解Style.

2 属性

属性(attr)可以简单理解为特性。比如名字、皮肤颜色等等都是属性。

2.1申明属性

假如要实现人类皮肤和名字这样一组属性,那在Android中要如何实现呢?

首先在“res/values”目录下创建一个文件名为attrs.xml。具体内容如下:

<?xml version="1.0" encoding="utf-8"?>

<resources>

<!-- 自定义属性skinColor -->

<attr name="customSkinColor" format="color" />

<!-- 自定义属性name -->

<attr name="customName" format="string" />

<!-- 申明一个人类的属性的集合 -->

<declare-styleable name="customPerson">

<attr name="customSkinColor" />

<attr name="customName" />

</declare-styleable>

</resources>

 

这样申明之后发生了什么?

2.2 属性实现

当申明了属性之后,在R.java中会有如下定义存在:

public final class R {

public static final class attr {

public static final int customSkinColor=0x7f03006f;

public static final int customName=0x7f03006d;

}

public static final class styleable {

public static final int[] customPerson={

0x7f03006d, 0x7f03006f

};

public static final int customPerson_customName=0;

public static final int customPerson_customSkinColor=1;

}

}

 

Android中的每个资源,都有它唯一的编号。编号是一个32位数字,用十六进制表示就是0xPPTTEEEE。PP为package id,TT 为type id,EEEE 为entry id.比如上面这个例子,执行下面的命令:

aapt d resources app-debug.apk | grep 0x7f03006d

spec resource 0x7f03006d com.crab.test:attr/customName: flags=0x00000000

resource 0x7f03006d com.crab.test:attr/customName: <bag>

这就表示customName这个资源的编号是 0x7f03006d 。它的package id是0x7f,资源类型的id为0x03(代表attr),它的entry id为0x006d。

备注:package id的值为0x01是系统资源,也就是包名为”android”的资源。type id代表的是drawable,string,layout等等类型的资源,默认是从值0x01开始。entry id代表这个资源在type id中的位置,默认是从0x0000开始。

2.3 属性的使用

2.3.1 属性组使用

属性组(styleable)是属性的集合。前面定义了一个属性组(customPerson),它有两个属性(customName、customSkinColor)。下面来使用属性组:

public class PersonView extends TextView {

public PersonView(Context context, @Nullable AttributeSet attrs) {

super(context, attrs);

//customPerson是一个数组,在刚才R.java中定义过

final TypedArray a = context.obtainStyledAttributes(

attrs, R.styleable.customPerson, 0, 0);

//customPerson_customSkinColor的值为1,数组第二个元素

int skinColor = a.getColor(R.styleable.customPerson_customSkinColor, Color.WHITE);

//customPerson_customName的值为0,数组第一个元素

String name = a.getString(R.styleable.customPerson_customName);

setText(name);

setTextColor(skinColor);

a.recycle();

}

}

 

属性组的使用是不是很简单,下面设置具体属性值。

2.3.2 属性值的设定

现在来给具体的属性设置值:

<?xml version="1.0" encoding="utf-8"?>

<android.support.constraint.ConstraintLayout

xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:app="http://schemas.android.com/apk/res-auto"

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

app:layout_behavior="@string/appbar_scrolling_view_behavior"

tools:context="com.crab.test.MainActivity"

tools:showIn="@layout/activity_main">


<com.crab.test.PersonView

android:layout_width="wrap_content"

android:layout_height="wrap_content"

app:customName="xiechaojun" //自己定义的属性

app:customSkinColor="#FFFF0000" //自己定义的属性

app:layout_constraintBottom_toBottomOf="parent"

app:layout_constraintLeft_toLeftOf="parent"

app:layout_constraintRight_toRightOf="parent"

app:layout_constraintTop_toTopOf="parent"

/>


</android.support.constraint.ConstraintLayout>

 

属性的使用也很简单。现在考虑一个问题,如果APP里面有10个PersonView,想让所有PersonView的皮肤颜色都是黄色,要怎么办?下面介绍Style.

3 Android Style

3.1 定义集合属性

10个PersonView?咋办?首先在attrs.xml中创建一个属性名字以及使用这个属性的集合

<?xml version="1.0" encoding="utf-8"?>

<resources>

<!-- 自定义属性skinColor -->

<attr name="customSkinColor" format="color" />

<!-- 自定义属性name -->

<attr name="customName" format="string" />

<!-- 申明一个人类的属性的集合 -->

<declare-styleable name="customPerson">

<attr name="customSkinColor" />

<attr name="customName" />

</declare-styleable>


<!-- 自定义属性personViewStyle -->

<attr name="personViewStyle" format="reference" />

<!-- 申明一整个应用人类要使用的属性的集合 -->

<declare-styleable name="customPersonStyle">

<attr name="personViewStyle" />

</declare-styleable>

</resources>

 

3.2 自定义Style

在“res/values/styles.xml”文件中添加自定义的Style。

<resources>

<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>

<!-- 与上边属性名字对应 -->

<item name="personViewStyle">@style/PersonStyle</item>

</style>

<!-- 定义所有PersonView的皮肤颜色为黄色 -->

<style name="PersonStyle">

<item name="customSkinColor">#FFFFFF00</item>

</style>

</resources>

 

3.3 使用自定义的Style

3.3.1 PersonView的使用

请注意与2.3.1小节实现不同的地方:

public class PersonView extends TextView {

public PersonView(Context context, @Nullable AttributeSet attrs) {

super(context, attrs);

//系统主题

final Resources.Theme theme = context.getTheme();

//获取自定义的person style属性集合

final TypedArray a = theme.obtainStyledAttributes(

attrs, R.styleable.customPersonStyle, 0, 0);

//获取到具体某个person style ID.

int personStyleId =

a.getResourceId(R.styleable.customPersonStyle_personViewStyle,0);

if(personStyleId!=-1) {

TypedArray aa =

theme.obtainStyledAttributes(personStyleId, R.styleable.customPerson);

//获取到主题中设定的皮肤值

int skinColor = aa.getColor(R.styleable.customPerson_customSkinColor, Color.BLUE);

setTextColor(skinColor);

a.recycle();

}

a.recycle();

}

}

 

3.3.2 布局文件中使用

在布局文件中定义2个PersonView,不设置它皮肤颜色的属性,但是最后效果是两个PersonView的字体颜色都是黄色。

<?xml version="1.0" encoding="utf-8"?>

<android.support.constraint.ConstraintLayout

xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:app="http://schemas.android.com/apk/res-auto"

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

app:layout_behavior="@string/appbar_scrolling_view_behavior"

tools:context="com.crab.firstjniapplication.MainActivity"

tools:showIn="@layout/activity_main">

<LinearLayout

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:orientation="vertical"

app:layout_constraintBottom_toBottomOf="parent"

app:layout_constraintLeft_toLeftOf="parent"

app:layout_constraintRight_toRightOf="parent"

app:layout_constraintTop_toTopOf="parent"

>

<!-- 这里没有设置皮肤颜色,但是系统主题中有设置为黄色-->

<com.crab.firstjniapplication.PersonView

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="xiechaojun"

/>

<com.crab.firstjniapplication.PersonView

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="xiechaojun1"

/>

</LinearLayout>

</android.support.constraint.ConstraintLayout>

 

4 解Bug

比如想让APP UI在不同Android版本上表现一样,可以自定Style,让APP的样式统一。比如想让所有的EditText的样式保持统一不随平台改变,可以下面这样实现:

<style name="Theme.Base" parent="@android:style/Theme.DeviceDefault.Light">

<item name="android:buttonStyle">@style/Button.Base</item>

<!-- 使用自定义样式 -->

<item name="android:editTextStyle">@style/CustomEditTextStyle</item>

</style>


<style name="CustomEditTextStyle" parent="@android:style/Widget.Material.EditText">

<!-- 自定义样式 -->

</style>

5 Android support包属性遇到的问题

当整个应用使用support包的Theme时,自己定义了一个AlertDialog,继承制android.app.AlertDialog,想给这个自定义的AlertDialog设置主题,使用的下面方法:

<!-- 使用的是support包的主题 -->
 <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- 自定义的AlertDialog样式 --> 
    <item name="alertDialogTheme">@style/AppAlertDialogTheme</item>
 </style>
<style name="AppAlertDialogTheme" parent="ThemeOverlay.AppCompat.Dialog.Alert">
    <!-- 给AlertDialog的List项目添加一条分割线--> 
 <item name="listDividerAlertDialog">@drawable/preference_list_divider_material</item>
</style>

这样设置之后发现弹出的对话框包含ListView时并没有给ListView的每一个Item绘制一条下划线,那是什么原因呢?

我们来看下android.app.AlertDialog的源码:

    /**
     * Creates an alert dialog that uses the default alert dialog theme.
     * <p>
     * The default alert dialog theme is defined by
     * {@link android.R.attr#alertDialogTheme} within the parent
     * {@code context}'s theme.
     *
     * @param context the parent context
     * @see android.R.styleable#Theme_alertDialogTheme
     */
    protected AlertDialog(Context context) {
        this(context, 0);
    }

从源码可以看出alertDialogTheme是定义在属性android.R.attr.alertDialogTheme下的,也就是包名为android这个应用下的。而像我们自定义的alertDialogTheme:

    <!-- 自定义的AlertDialog样式 --> 
    <item name="alertDialogTheme">@style/AppAlertDialogTheme</item>

是在自己的包名.R.attr.alertDialogTheme下面的.知道原因了都很好改,可以使用下面两种方法:

1.在自定义的AlertDialog主题前都加上android,比如:

<!-- 使用的是support包的主题 -->
 <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- 自定义的AlertDialog样式 --> 
    <item name="android:alertDialogTheme">@style/AppAlertDialogTheme</item>
 </style>
<style name="AppAlertDialogTheme" parent="ThemeOverlay.AppCompat.Dialog.Alert">
    <!-- 给AlertDialog的List项目添加一条分割线--> 
 <item name="android:listDividerAlertDialog">@drawable/preference_list_divider_material</item>
</style>

2.自定义的AlertDialog继承自Support包里面的AlertDialog.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!