每每看到别人写的好看又实用的控件总是羡慕不已,你是不是也跟我一样,想自己动手写属于自己的控件?希望这篇文章可以帮到你们。
谈到自定义控件就少不了属性的配置和获取,通常需要以下几个步骤:

1.通过<declare-styleable>为自定义View添加属性
    2.自定义View的Java文件
    3.在xml中为引用自定义View并给相应的属性声明属性值
    4.在运行时(一般为构造函数)获取属性值
    5.将获取到的属性值应用到View

一、定义控件属性

在res/values/attrs.xml下通过元素声明需要的属性
通常格式为

<declare-styleable name="CustomizeStyle">
        <attr name="attr_name" format="string" />
 </declare-styleable>

当然直接用attr 定义属性也是可以的,如下

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

关于二者区别稍后通过实例做介绍

其中name为属性的名字,format 为属性的格式
format共有十种值可选

属性值

说明

reference

参考某一资源ID

color

颜色值

boolean

布尔值

dimension

尺寸值

float

浮点值

integer

整型值

string

字符串

fraction

百分数

enum

枚举值 多值选一

flag

位或运算 多值组合

下面为本文所使用的属性文件
/res/values/attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- reference:参考某一资源ID
    color:颜色值
    boolean:布尔值
    dimension:尺寸值
    float:浮点值
    integer:整型值
    string:字符串
    fraction:百分数
    enum:枚举值 多值选一
    flag:位或运算,多值组合,用“|”分隔 -->

    <declare-styleable name="CustomTextViewStyle">
        <attr name="description" format="string"/>
        <attr name="text_size" format="dimension"/>
        <attr name="text_color" format="color"/>
        <attr name="text_content" format="string"/>
        <attr name="padding" format="dimension"/>

        <attr name="float_value" format="float"/>
        <attr name="integer_value" format="integer"/>
        <attr name="boolean_value" format="boolean"/>
        <attr name="fraction_value" format="fraction"/>
        <attr name="string_value" format="string"/>
        <attr name="dimension_value" format="dimension"/>
        <attr name="color_value" format="color"/>
        <attr name="reference_drawable_value" format="reference"/>
        <attr name="reference_array_value" format="reference"/>
        <attr name="enum_value" format="enum">
            <enum name="horizontal" value="0"/>
            <enum name="vertical" value="1"/>
        </attr>
        <attr name="flag_value">
            <flag name="normal" value="0"/>
            <flag name="bold" value="1"/>
            <flag name="italic" value="2"/>
        </attr>
    </declare-styleable>
    <attr name="CustomizeStyle" format="reference"/>
</resources>

关于二者定义方法的异同:
相同:都会在R文件attr类中生成ID

public static final class attr {
    public static final int CustomizeStyle=0x7f010000;
    public static final int description=0x7f010041;
    public static final int text_size=0x7f010042;
    public static final int text_color=0x7f010043;
    public static final int text_content=0x7f010044;
    public static final int padding=0x7f010045;
    public static final int float_value=0x7f010046;
    public static final int integer_value=0x7f010047;
    public static final int boolean_value=0x7f010048;
    public static final int fraction_value=0x7f010049;
    public static final int string_value=0x7f01004a;
    public static final int dimension_value=0x7f01004b;
    public static final int color_value=0x7f01004c;
    public static final int reference_drawable_value=0x7f01004d;
    public static final int reference_array_value=0x7f01004e;
    public static final int enum_value=0x7f01004f;
    public static final int flag_value=0x7f010050;

}

不同:通过< declare-styleable>标签定义的属性还会在R文件styleable类中生成相关属性

public static final class styleable {
    public static final int[] CustomTextViewStyle = {
            0x7f010041, 0x7f010042, 0x7f010043, 0x7f010044,
            0x7f010045, 0x7f010046, 0x7f010047, 0x7f010048,
            0x7f010049, 0x7f01004a, 0x7f01004b, 0x7f01004c,
            0x7f01004d, 0x7f01004e, 0x7f01004f, 0x7f010050
        };
    public static final int CustomTextViewStyle_description = 0;    
    public static final int CustomTextViewStyle_text_size = 1;
    public static final int CustomTextViewStyle_text_color = 2;
    public static final int CustomTextViewStyle_text_content = 3;
    public static final int CustomTextViewStyle_padding = 4;
    public static final int CustomTextViewStyle_float_value = 5;
    public static final int CustomTextViewStyle_integer_value = 6;
    public static final int CustomTextViewStyle_boolean_value = 7;
    public static final int CustomTextViewStyle_fraction_value = 8;
    public static final int CustomTextViewStyle_string_value = 9;
    public static final int CustomTextViewStyle_dimension_value = 10;
    public static final int CustomTextViewStyle_color_value = 11;
    public static final int CustomTextViewStyle_reference_drawable_value = 12;
    public static final int CustomTextViewStyle_reference_array_value = 13;
    public static final int CustomTextViewStyle_enum_value = 14;
    public static final int CustomTextViewStyle_flag_value = 15;

 }

可以看出通过< declare-styleable>标签定义的属性,
在styleable类中生成了一个数组,数组下标索引用别名进行了定义
如“CustomTextViewStyle_description = 0”,数组中的值与attr类中的值一一对应。
在后面取值时会用到这个数组和下标索引值,
如果是直接通过attr标签定义的属性,可以自己构建这个数组,然后获取值,后面会讲到

跳转查看

二、自定义View的实现(.Java)

在包中新建CustomTextView.java 文件 继承自TextView,简单写几个构造函数,其他后续再扩展

public class CustomTextView extends TextView {
    //在java代码中通过new CustomTextView(context) 时会调用此方法
    public CustomTextView(Context context) {
        super(context);
    }
    //在XML中引用自定义控件,会调用此方法
    public CustomTextView(Context context, AttributeSet attrs) {
        super(context, attrs);   
    }

    public CustomTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public CustomTextView(Context context, AttributeSet attrs, int defStyle, int defStyleRes) {
        super(context, attrs, defStyle, defStyleRes);    
    }

三、在XML中使用自定义控件并给属性赋值

首先要在布局XML文件中申明控件所使用的命名空间.
命名空间跟C++中的概念很相同
using namespace std;
就可以在代码中直接使用命名空间中的成员 cout 等等

先看下系统所使用的命名空间
xmlns:android=”http://schemas.android.com/apk/res/android”
对于这个我们我们不陌生,我们最长用的就是

android:id="",android:layout_width=""

如果去掉这个命名空间,我们就不能在控件中使用android这个标签了

接下来这个是我们自定义的命名空间
xmlns: myAttr=”http://schemas.android.com/apk/res-auto”
myAttr是命名空间名字,后面的是地址
“res-auto”在早期是要替换成/res/自己项目的包名

使用自定义控件
格式如下

<com.antex.customview.CustomTextView
        android:id="@+id/textview0"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        myAttr:text_color="@color/colorAccent"
        myAttr:text_content="text from xml"
        myAttr:text_size="12sp"/>

下面为本文布局文件
res/fragment_custom_view.xml

<LinearLayout
    android:id="@+id/linearLayout"
    xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:myAttr="http://schemas.android.com/apk/com.antex.customview"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".CustomViewActivityFragment"
    tools:showIn="@layout/activity_custom_view"
    >
    <com.antex.customview.CustomTextView
        android:id="@+id/textview0"
        text="@string/app_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        myAttr:text_color="@color/colorAccent"
        myAttr:text_content="text from xml"
        myAttr:text_size="12sp"
        style="@style/CustomStyle"/>
    <com.antex.customview.CustomTextView
        android:id="@+id/textview1"
        style="@style/CustomStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />
    <com.antex.customview.CustomTextView
        android:id="@+id/textview2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        myAttr:boolean_value="true"
        myAttr:color_value="@color/colorAccent"
        myAttr:description="Show all type Attribute"
        myAttr:dimension_value="10.5dp"
        myAttr:enum_value="horizontal"
        myAttr:flag_value="bold|italic"
        myAttr:float_value="10.1"
        myAttr:fraction_value="30%p"
        myAttr:integer_value="@integer/default_int"
        myAttr:reference_array_value="@array/String_Array"
        myAttr:reference_drawable_value="@mipmap/yxs"
        myAttr:string_value="stringvalue"
        />

    <com.antex.customview.CustomTextView
        android:id="@+id/textview3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        myAttr:description="default none attr setting"/>
    <com.antex.customview.CustomTextView
        android:id="@+id/textview4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        myAttr:description="default none attr setting"/>
    <RadioGroup
        android:id="@+id/radiaGroup"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <RadioButton
            android:id="@+id/rb1"
            android:layout_width="wrap_content" android:layout_height="wrap_content"
            android:text="AppTheme.WithCustomizeStyle.Activity.WithValues.withCustomizeStyle"/>
        <RadioButton
            android:id="@+id/rb2"
            android:layout_width="wrap_content" android:layout_height="wrap_content"
            android:text="AppTheme.WithCustomizeStyle.Activity.WithValues"/>
        <RadioButton
            android:id="@+id/rb3"
            android:layout_width="wrap_content" android:layout_height="wrap_content"
            android:text="AppTheme.WithCustomizeStyle.Activity.NoValues"/>
        <RadioButton android:id="@+id/rb4"
                     android:layout_width="wrap_content" android:layout_height="wrap_content"
                     android:text="AppTheme.NoCustomizeStyle.Activity.NoValuess"/>
    </RadioGroup>

</LinearLayout>

说明:
id为 textview0 直接在XML中设置了属性值,并且也指定了Style
id为 textview1 通过Style 设置属性
id为 textview2 用来介绍 attr 十种类型数据获取 和defStyleAttr=0 defStyleRes=0时取值情况
id为 textview3 演示defStyleAttr=0时取值情况
id为 textview4 演示defStyleAttr!=0 defStyleRes!=0时取值情况
还有一个RadioGroup 用来切换当前应用的Application和Activity的theme

四、java文件中获取属性

获取属性值函数:obtainStyledAttributes
其函数定义为

public final TypedArray obtainStyledAttributes(
            AttributeSet set, @StyleableRes int[] attrs, @AttrRes int defStyleAttr,
            @StyleRes int defStyleRes) {
        return getTheme().obtainStyledAttributes(
            set, attrs, defStyleAttr, defStyleRes);
    }

参数说明:
4个参数的意思分别是:

attrs

R.styleable
.CustomTextViewStyle

R.attr.CustomizeStyle

R.style.DefaultCustomizeStyle

函数返回值:
 TypedArray 是一个类,包含要获取值的一个集合   

下面我们通过代码来讲解

4.1 TypedArray使用详解:attr十种属性值获取

4.1.1 TypedArray中的方法

返回类型

方法

说明

boolean

getBoolean(int index, boolean defValue)

返回index索引位置的布尔值.

int

getChangingConfigurations()

Return a mask of the configuration parameters for which the values in this typed array may change.(没弄懂这个意思)

int

getColor(int index, int defValue)

返回index索引位置的颜色.

ColorStateList

getColorStateList(int index)

返回index索引位置的ColorStateList

float

getDimension(int index, float defValue)

返回index索引位置的尺寸.

int

getDimensionPixelOffset(int index, int defValue)

返回index索引位置的尺寸.

int

getDimensionPixelSize(int index, int defValue)

返回index索引位置的尺寸

Drawable

getDrawable(int index)

返回index索引位置的Drawable对象.

float

getFloat(int index, float defValue)

返回index索引位置的浮点数.

float

getFraction(int index, int base, int pbase, float defValue)

返回index索引位置的百分数.

int

getIndex(int at)

返回index索引位置的整形数.

int

getIndexCount()

返回数组中含有数值的元素总个数.

int

getInt(int index, int defValue)

返回index索引位置的整形数.

int

getInteger(int index, int defValue)

返回index索引位置的整形数.

int

getLayoutDimension(int index, String name)

返回指定位置和名称的layout_width或layout_height属性值.

int

getLayoutDimension(int index, int defValue)

返回layout_width或layout_height属性值.

String

getNonResourceString(int index)

返回字符串,这个属性必须是在XML文件中直接指定.

String

getPositionDescription()

返回错误信息字符串.

int

getResourceId(int index, int defValue)

返回资源ID.

Resources

getResources()

返回TypedArray所使用的Resources.

String

getString(int index)

返回字符串.

CharSequence

getText(int index)

返回字符串.

CharSequence[]

getTextArray(int index)

返回数组.

int

getType(int index)

返回值类型在TypedValue中有定义

boolean

getValueAt(int index, TypedValue outValue)

判断index索引位置的值是否与给定的TypedValue值相等.

boolean

hasValue(int index)

判断index索引位置是否有值.

boolean

hasValueOrEmpty(int index)

判断index索引位置是否有值,有值或者值为@empty时返回true,没有定义时返回false.

int

length()

返回TypedArray中所有元素个数.

void

recycle()

回收TypedArray, 以便后面再用.

String

toString()

返回TypedArray拼接成的字符串.

4.1.2 TypedValue中值类型

hehe

/** The value contains no data. */
    public static final int TYPE_NULL = 0x00;

    /** The <var>data</var> field holds a resource identifier. */
    public static final int TYPE_REFERENCE = 0x01;
    /** The <var>data</var> field holds an attribute resource
     *  identifier (referencing an attribute in the current theme
     *  style, not a resource entry). */
    public static final int TYPE_ATTRIBUTE = 0x02;
    /** The <var>string</var> field holds string data.  In addition, if
     *  <var>data</var> is non-zero then it is the string block
     *  index of the string and <var>assetCookie</var> is the set of
     *  assets the string came from. */
    public static final int TYPE_STRING = 0x03;
    /** The <var>data</var> field holds an IEEE 754 floating point number. */
    public static final int TYPE_FLOAT = 0x04;
    /** The <var>data</var> field holds a complex number encoding a
     *  dimension value. */
    public static final int TYPE_DIMENSION = 0x05;
    /** The <var>data</var> field holds a complex number encoding a fraction
     *  of a container. */
    public static final int TYPE_FRACTION = 0x06;

    /** Identifies the start of plain integer values.  Any type value
     *  from this to {@link #TYPE_LAST_INT} means the
     *  <var>data</var> field holds a generic integer value. */
    public static final int TYPE_FIRST_INT = 0x10;

    /** The <var>data</var> field holds a number that was
     *  originally specified in decimal. */
    public static final int TYPE_INT_DEC = 0x10;
    /** The <var>data</var> field holds a number that was
     *  originally specified in hexadecimal (0xn). */
    public static final int TYPE_INT_HEX = 0x11;
    /** The <var>data</var> field holds 0 or 1 that was originally
     *  specified as "false" or "true". */
    public static final int TYPE_INT_BOOLEAN = 0x12;

    /** Identifies the start of integer values that were specified as
     *  color constants (starting with '#'). */
    public static final int TYPE_FIRST_COLOR_INT = 0x1c;

    /** The <var>data</var> field holds a color that was originally
     *  specified as #aarrggbb. */
    public static final int TYPE_INT_COLOR_ARGB8 = 0x1c;
    /** The <var>data</var> field holds a color that was originally
     *  specified as #rrggbb. */
    public static final int TYPE_INT_COLOR_RGB8 = 0x1d;
    /** The <var>data</var> field holds a color that was originally
     *  specified as #argb. */
    public static final int TYPE_INT_COLOR_ARGB4 = 0x1e;
    /** The <var>data</var> field holds a color that was originally
     *  specified as #rgb. */
    public static final int TYPE_INT_COLOR_RGB4 = 0x1f;

    /** Identifies the end of integer values that were specified as color
     *  constants. */
    public static final int TYPE_LAST_COLOR_INT = 0x1f;

    /** Identifies the end of plain integer values. */
    public static final int TYPE_LAST_INT = 0x1f;

4.1.3 利用TypedArray获取属性值

为了方便,我们指定defStyleAttr和defStyleRes都为0
我们来打印 id为 textview2 这个控件中的各个值

/**
     * 介绍TypedArray使用方法
     * 获取 attr中formate 十种类型属性值
     * float,integer,boolean,fraction,string,dimension,color,reference,enum,flag
     * <p/>
     *
     * @param context 上下文环境
     * @param attrs   属性集合
     */
    private void printAttributes(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable
                .CustomTextViewStyle, 0, 0);

        float float_value = typedArray.getFloat(R.styleable.CustomTextViewStyle_float_value, 0f);
        int integer_value = typedArray.getInteger(R.styleable.CustomTextViewStyle_integer_value, 0);
        boolean boolean_value = typedArray.getBoolean(R.styleable
                .CustomTextViewStyle_boolean_value, false);

        //public float getFraction (int index, int base, int pbase, float defValue)
        // 如果值为10% 则 fraction_value=10%*base
        //如果值格式为10%p,则fraction_value=10%*pbase
        float fraction_value = typedArray.getFraction(R.styleable
                .CustomTextViewStyle_fraction_value, 1, 1, 0);
        String string_value = typedArray.getString(R.styleable.CustomTextViewStyle_string_value);

        //获取像素值,浮点数  eg:27.5625
        float dimension_value_float = typedArray.getDimension(R.styleable
                .CustomTextViewStyle_dimension_value, 0f);
        //将取得浮点像素值四舍五入 eg:28
        int dimension_value = typedArray.getDimensionPixelSize(R.styleable
                .CustomTextViewStyle_dimension_value, 0);
        //将取得浮点像素值直接截取整数部分 eg:27
        int dimension_value_offset_ = typedArray.getDimensionPixelOffset(R.styleable
                .CustomTextViewStyle_dimension_value, 0);


        int color_value = typedArray.getColor(R.styleable.CustomTextViewStyle_color_value, 0);
        int reference_drawable_value = typedArray.getResourceId(R.styleable
                .CustomTextViewStyle_reference_drawable_value, 0);
        int reference_array_value = typedArray.getResourceId(R.styleable
                .CustomTextViewStyle_reference_array_value, 0);
        int enum_value = typedArray.getInt(R.styleable.CustomTextViewStyle_enum_value, -1);
        int flag_value = typedArray.getInt(R.styleable.CustomTextViewStyle_flag_value, -1);


        System.out.println("float_value = [" + float_value + "], integer_value = [" +
                integer_value + "], " +
                "boolean_value = [" + boolean_value + "], fraction_value = [" +
                fraction_value + "], string_value = [" + string_value + "], dimension_value = [" +
                dimension_value + "], color_value = [" + color_value + "], " +
                "reference_drawable_value =" +
                " [0x" +
                Integer.toHexString(reference_drawable_value) + "], enum_value = [" + enum_value
                + "], " +
                "flag_value1 = [" +
                flag_value + "]");


        //后期数据处理,设置左边图片
        Drawable drawable;
        drawable = typedArray.getDrawable(R.styleable.CustomTextViewStyle_reference_drawable_value);
        //or
        drawable = context.getDrawable(reference_drawable_value);
        drawable.setBounds(new Rect(0, 0, 50, 50));
        setCompoundDrawables(drawable, null, null, null);
        //设置文字是否大写,斜体
        if (flag_value >= 0) {
            Typeface typeface = getTypeface();
            setTypeface(Typeface.defaultFromStyle(flag_value));
        }

        //其他方法getTextArray
        CharSequence[] arrays;
        arrays = typedArray.getTextArray(R.styleable.CustomTextViewStyle_reference_array_value);
        //or
        arrays = context.getResources().getTextArray(reference_array_value);
        for (int i = 0; i < arrays.length; i++) {
            System.out.println("arrays[" + i + "] = " + arrays[i]);
        }

        //遍历TypedArray
        for (int i = 0, m = typedArray.getIndexCount(); i < m; i++) {
            System.out.println("typedArray" + i + " type= " + typedArray.getType(i) + " value=" +
                    typedArray.getString(i));
        }

        int textsize = typedArray.getDimensionPixelSize(R.styleable
                .CustomTextViewStyle_text_size, 10);
        int textcolor = typedArray.getColor(R.styleable.CustomTextViewStyle_text_color, Color
                .BLACK);
        String text = typedArray.getString(R.styleable.CustomTextViewStyle_text_content);
        int padding = typedArray.getDimensionPixelSize(R.styleable.CustomTextViewStyle_padding, 0);


        setTextColor(textcolor);
        setTextSize(textsize);
        setText(text);
        setPadding(padding, padding, padding, padding);

        typedArray.recycle();

4.2 obtainStyledAttributes方法详解

TypedArray obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr,int defStyleRes)

在4.1.3节中,我们对这个方法有了简单的认识
下面我们来看看后面两个参数不为0的情况下,是如何取值的.

4.2.1首先我们需要定义一些style

res/values/styles.xml
我们在Application的theme中增加几个基础属性

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="text_size">8sp</item>
        <item name="padding">15dp</item>
        <item name="text_content">@string/from_App_theme</item>
        <item name="text_color">#f00</item>
    </style>

再此基础上又定义了一个Style,包含属性值“CustomizeStyle“(方法中第三个参数defStyleAttr),它又引用一个Style

<style name="AppTheme.WithCustomizeStyle">
        <item name="CustomizeStyle">@style/CustomizeStyleInAppTheme</item>
    </style>

 <style name="CustomizeStyleInAppTheme">
        <item name="text_size">10sp</item>
        <item name="padding">2dp</item>
        <item name="text_color">@color/colorPrimaryDark</item>
        <item name="text_content">text from AppTheme refrence</item>
    </style>

还定义了个方法中第四个参数defStyleRes指向的的style

<style name="DefaultCustomizeStyle">
        <item name="text_content">text form DefaultCustomizeStyle</item>
        <item name="padding">4dp</item>
        <!--<item name="text_color">#7ff</item>-->
    </style>

此外定义了若干Activity的theme,有包含属性”CustomizeStyle“和不包含此属性的

全部代码为

<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="text_size">8sp</item>
        <item name="padding">15dp</item>
        <item name="text_content">@string/from_App_theme</item>
        <item name="text_color">#f00</item>
    </style>
    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/>
    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light"/>
    <style name="AppTheme.NoActionBar">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
        <item name="android:statusBarColor">@android:color/transparent</item>
    </style>

    <style name="AppTheme.NoCustomizeStyle"/>
    <style name="AppTheme.WithCustomizeStyle">
    <item name="CustomizeStyle">@style/CustomizeStyleInAppTheme</item>
</style>

    <style name="AppTheme.NoCustomizeStyle.Activity">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
        <item name="android:statusBarColor">@android:color/transparent</item>

    </style>
    <style name="AppTheme.WithCustomizeStyle.Activity">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
        <item name="android:windowDrawsSystemBarBackgrounds">true</item>
        <item name="android:statusBarColor">@android:color/transparent</item>

    </style>
    <style name="AppTheme.NoCustomizeStyle.Activity.NoValues"/>
    <style name="AppTheme.WithCustomizeStyle.Activity.NoValues"/>
    <style name="AppTheme.NoCustomizeStyle.Activity.WithValues">
        <item name="text_color">#7ff</item>
        <item name="text_size">12sp</item>
        <item name="text_content">@string/from_Activity_theme</item>
        <item name="padding">15dp</item>
    </style>
    <style name="AppTheme.WithCustomizeStyle.Activity.WithValues">
        <item name="text_color">#f0f</item>
        <item name="text_size">12sp</item>
        <item name="text_content">@string/from_Activity_theme</item>
        <item name="padding">15dp</item>
    </style>
    <style name="AppTheme.NoCustomizeStyle.Activity.WithValues.withCustomizeStyle">
        <item name="CustomizeStyle">@style/CustomizeStyleInActivityTheme</item>
    </style>
    <style name="AppTheme.WithCustomizeStyle.Activity.WithValues.withCustomizeStyle">
        <item name="CustomizeStyle">@style/CustomizeStyleInActivityTheme</item>
    </style>

    <style name="CustomizeStyleInAppTheme">
        <item name="text_size">10sp</item>
        <item name="padding">2dp</item>
        <item name="text_color">@color/colorPrimaryDark</item>
        <item name="text_content">text from AppTheme refrence</item>
    </style>

    <style name="CustomizeStyleInActivityTheme">
        <item name="text_size">10sp</item>
        <item name="padding">2dp</item>
        <item name="text_color">@color/colorPrimaryDark</item>
        <item name="text_content">text from ActivityTheme refrence</item>
    </style>

    <style name="CustomStyle">
        <item name="text_size">12sp</item>
        <item name="text_color">#0f0</item>
        <item name="text_content">text form Style</item>
        <item name="padding">3dp</item>
    </style>

    <style name="DefaultCustomizeStyle">
        <item name="text_content">text form DefaultCustomizeStyle</item>
        <item name="padding">4dp</item>
        <!--<item name="text_color">#7ff</item>-->
    </style>

    <style name="DefaultCustomizeStyle_Notext">
        <item name="padding">4dp</item>
    </style>

</resources>

字符串在res/values/strings.xml中有定义

<resources>
    <string name="app_name">017_CustomView</string>
    <string name="action_settings">Settings</string>
    <string name="create_by_java">I am created by Java Code</string>
    <string name="from_App_theme">text from AppTheme</string>
    <string name="from_Activity_theme">text from ActivityTheme</string>
    <string name="from_Activity_theme_noCustomizeStyle">text from ActivityTheme with noCustomizeStyle </string>

</resources>

4.2.2下面我们再来更改下CustomTextView.java文件

/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.antex.customview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.TextView;


public class CustomTextView extends TextView {
    private static final String TAG = CustomTextView.class.getSimpleName();
    private String default_namespace = "http://schemas.android.com/apk/res/android";
    private String namespace = "http://schemas.android.com/apk/res-auto";

    public CustomTextView(Context context) {
        super(context);
        Log.d(TAG, "CustomTextView.CustomTextView1");
    }

    public CustomTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        Log.d(TAG, "CustomTextView.CustomTextView2");
        init(context, attrs, R.attr.CustomizeStyle, R.style.DefaultCustomizeStyle);
    }

    public CustomTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        Log.d(TAG, "CustomTextView.CustomTextView3");
        init(context, attrs, defStyle, 0);
    }

    public CustomTextView(Context context, AttributeSet attrs, int defStyle, int defStyleRes) {
        super(context, attrs, defStyle, defStyleRes);
        Log.d(TAG, "CustomTextView.CustomTextView4");
        init(context, attrs, defStyle, defStyleRes);

    }

    public void init(Context context, AttributeSet attrs, int defStyle, int defStyleRes) {
        if (attrs != null) {
            try {
                int id = attrs.getAttributeResourceValue(default_namespace, "id", 0);
                switch (id) {
                    case R.id.textview2:
                        //defStyle=0 and defStyleRes=0
                        printAttributes(context, attrs);
                        return;
                    case R.id.textview3:
                        defStyle = 0;
                        break;
                }
                int textsize, textcolor, padding;
                String text = "no value";
                //获取自定义属性值的三种方法
                //方法一 在declare-styleable中定义的属性
                TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable
                        .CustomTextViewStyle, defStyle, defStyleRes);

                textsize = typedArray.getDimensionPixelSize(R.styleable
                        .CustomTextViewStyle_text_size, 10);
                textcolor = typedArray.getColor(R.styleable.CustomTextViewStyle_text_color, Color
                        .BLACK);
                text = typedArray.getString(R.styleable.CustomTextViewStyle_text_content);
                padding = typedArray.getDimensionPixelSize(R.styleable
                        .CustomTextViewStyle_padding, 0);

                System.out.println("textsize = " + textsize);
                System.out.println("textcolor = " + textcolor);
                System.out.println("text = " + text);
                System.out.println("padding = " + padding);
                //方法二、自定义属性数组,非常注意,数组中的值一定是从小到大排序,要不然后面会取不到正确的值
                int[] CustomTextViewStyle = {R.attr.text_size, R.attr.text_color, R.attr
                        .text_content, R.attr.padding};
                TypedArray typedArray2 = context.obtainStyledAttributes(attrs,
                        CustomTextViewStyle, defStyle, defStyleRes);

                int textsize1 = typedArray2.getDimensionPixelSize(0, 10);
                int textcolor1 = typedArray2.getColor(1, Color.BLACK);
                String text1 = typedArray2.getString(2);
                int padding1 = typedArray2.getDimensionPixelSize(3, 0);

                System.out.println("textsize1 = " + textsize1);
                System.out.println("textcolor1 = " + textcolor1);
                System.out.println("text1 = " + text1);
                System.out.println("padding1 = " + padding1);

                //方法三 利用命名空间,根据属性名获取属性值

                int textcolor2 = attrs.getAttributeIntValue(namespace, "textcolor", Color.BLACK);
                //or
                int resouceId = attrs.getAttributeResourceValue(namespace, "text_color", 0);
                if (resouceId > 0) {
                    textcolor2 = context.getResources().getColor(resouceId);
                } else textcolor2 = Color.BLACK;

                int textsize2 = attrs.getAttributeIntValue(namespace, "text_size", 10);
                String text2 = attrs.getAttributeValue(namespace, "text_content");

                int padding2 = attrs.getAttributeIntValue(namespace, "padding", 0);
                System.out.println("textsize2  = " + textsize2);
                System.out.println("textcolor2 = " + textcolor2);
                System.out.println("text2 = " + text2);
                System.out.println("padding2 = " + padding2);
                //namespace is null
                resouceId = attrs.getAttributeResourceValue(null, "text", 0);
                if (resouceId > 0) {
                    String null_namespace = context.getResources().getText(resouceId).toString();
                    System.out.println("null_namespace = " + null_namespace);
                }


                setTextColor(textcolor);
                setTextSize(textsize);
                setText(text + "");
                setPadding(padding, padding, padding, padding);


                //遍历attrs中所有属性和值
                for (int i = 0, m = attrs.getAttributeCount(); i < m; i++) {
                    System.out.println("AttributeName = " + attrs.getAttributeName(i) + ", " +
                            "AttributeValue = " + attrs.getAttributeValue(i));
                }

                //介绍getAttributeNameResource(int index)方法
                int[] ids = new int[attrs.getAttributeCount()];
                for (int i = 0; i < attrs.getAttributeCount(); i++) {
                    ids[i] = attrs.getAttributeNameResource(i);
                    //System.out.println("ids[" + i + "] = 0x" + Integer.toHexString(ids[i]));
                }

                TypedArray a = context.obtainStyledAttributes(attrs, ids, defStyle, 0);

                for (int i = 0; i < attrs.getAttributeCount(); i++) {
                    String attrName = attrs.getAttributeName(i);
                    if (attrName == null) continue;
                    System.out.println("attrName = " + attrName + ",attrValue = " + a.getString(i));
                }
                a.recycle();
                typedArray.recycle();
                typedArray2.recycle();
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

    }

    /**
     * 介绍TypedArray使用方法
     * 获取 attr中formate 十种类型属性值
     * float,integer,boolean,fraction,string,dimension,color,reference,enum,flag
     * <p/>
     *
     * @param context 上下文环境
     * @param attrs   属性集合
     */
    private void printAttributes(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable
                .CustomTextViewStyle, 0, 0);
        System.out.println("typedArray.getChangingConfigurations() = " + Integer.toHexString
                (typedArray.getChangingConfigurations()));
        float float_value = typedArray.getFloat(R.styleable.CustomTextViewStyle_float_value, 0f);
        int integer_value = typedArray.getInteger(R.styleable.CustomTextViewStyle_integer_value, 0);
        boolean boolean_value = typedArray.getBoolean(R.styleable
                .CustomTextViewStyle_boolean_value, false);

        //public float getFraction (int index, int base, int pbase, float defValue)
        // 如果值为10% 则 fraction_value=10%*base
        //如果值格式为10%p,则fraction_value=10%*pbase
        float fraction_value = typedArray.getFraction(R.styleable
                .CustomTextViewStyle_fraction_value, 1, 1, 0);
        String string_value = typedArray.getString(R.styleable.CustomTextViewStyle_string_value);

        //获取像素值,浮点数  eg:27.5625
        float dimension_value_float = typedArray.getDimension(R.styleable
                .CustomTextViewStyle_dimension_value, 0f);
        //将取得浮点像素值四舍五入 eg:28
        int dimension_value = typedArray.getDimensionPixelSize(R.styleable
                .CustomTextViewStyle_dimension_value, 0);
        //将取得浮点像素值直接截取整数部分 eg:27
        int dimension_value_offset_ = typedArray.getDimensionPixelOffset(R.styleable
                .CustomTextViewStyle_dimension_value, 0);


        int color_value = typedArray.getColor(R.styleable.CustomTextViewStyle_color_value, 0);
        int reference_drawable_value = typedArray.getResourceId(R.styleable
                .CustomTextViewStyle_reference_drawable_value, 0);
        int reference_array_value = typedArray.getResourceId(R.styleable
                .CustomTextViewStyle_reference_array_value, 0);
        int enum_value = typedArray.getInt(R.styleable.CustomTextViewStyle_enum_value, -1);
        int flag_value = typedArray.getInt(R.styleable.CustomTextViewStyle_flag_value, -1);


        System.out.println("float_value = [" + float_value + "], integer_value = [" +
                integer_value + "], " +
                "boolean_value = [" + boolean_value + "], fraction_value = [" +
                fraction_value + "], string_value = [" + string_value + "], dimension_value = [" +
                dimension_value + "], color_value = [" + color_value + "], " +
                "reference_drawable_value =" +
                " [0x" +
                Integer.toHexString(reference_drawable_value) + "], enum_value = [" + enum_value
                + "], " +
                "flag_value1 = [" +
                flag_value + "]");


        //后期数据处理,设置左边图片
        Drawable drawable;
        drawable = typedArray.getDrawable(R.styleable.CustomTextViewStyle_reference_drawable_value);
        //or
        drawable = context.getDrawable(reference_drawable_value);
        drawable.setBounds(new Rect(0, 0, 50, 50));
        setCompoundDrawables(drawable, null, null, null);
        //设置文字是否大写,斜体
        if (flag_value >= 0) {
            Typeface typeface = getTypeface();
            setTypeface(Typeface.defaultFromStyle(flag_value));
        }

        //其他方法getTextArray
        CharSequence[] arrays;
        arrays = typedArray.getTextArray(R.styleable.CustomTextViewStyle_reference_array_value);
        //or
        arrays = context.getResources().getTextArray(reference_array_value);
        for (int i = 0; i < arrays.length; i++) {
            System.out.println("arrays[" + i + "] = " + arrays[i]);
        }

        //遍历TypedArray
        for (int i = 0, m = typedArray.getIndexCount(); i < m; i++) {
            System.out.println("typedArray" + i + " type= " + typedArray.getType(i) + " value=" +
                    typedArray.getString(i));
        }


        //defStyle=0,defStyleRes =0
        int textsize = typedArray.getDimensionPixelSize(R.styleable
                .CustomTextViewStyle_text_size, 10);
        int textcolor = typedArray.getColor(R.styleable.CustomTextViewStyle_text_color, Color
                .BLACK);
        String text = typedArray.getString(R.styleable.CustomTextViewStyle_text_content);
        int padding = typedArray.getDimensionPixelSize(R.styleable.CustomTextViewStyle_padding, 0);


        setTextColor(textcolor);
        setTextSize(textsize);
        setText(text);
        setPadding(padding, padding, padding, padding);

        typedArray.recycle();
    }

}
4.2.3.1 主要方法介绍
1.使用系统生成的int[] attrs数组R.styleable.CustomTextViewStyle,获取属性值时调用系统生成的数组下标索引
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable
                        .CustomTextViewStyle, defStyle, defStyleRes);

                textsize = typedArray.getDimensionPixelSize(R.styleable
                        .CustomTextViewStyle_text_size, 10);
2.自己封装int[] attrs数组,由于数组是自己定义的,所以获取什么属性用什么数组下标索引自己是清楚的

陷阱:这个数组并不是你想怎么组装就怎么组装的,数组里面值要从小到大排列,至于大小,到R文件attr类中查看

int[] CustomTextViewStyle = {R.attr.text_size, R.attr.text_color, R.attr
                        .text_content, R.attr.padding};
                TypedArray typedArray2 = context.obtainStyledAttributes(attrs,
                        CustomTextViewStyle, defStyle, defStyleRes);

                int textsize1 = typedArray2.getDimensionPixelSize(0, 10);
3.使用命名空间获取属性
private String namespace = "http://schemas.android.com/apk/res-auto";
int textcolor2 = attrs.getAttributeIntValue(namespace, "textcolor", Color.BLACK);
//还有另外一种写法,利用getAttributeResourceValue()获取该属性值是否在系统中定义了
int resouceId = attrs.getAttributeResourceValue(namespace, "text_color", 0);
if (resouceId > 0) {
    textcolor2 = context.getResources().getColor(resouceId);
} else textcolor2 = Color.BLACK;

如果没有使用命名空间,namespace可以置为null

<com.antex.customview.CustomTextView
        android:id="@+id/textview0"
        text="@string/app_name"
        ……
        />
//namespace is null
resouceId = attrs.getAttributeResourceValue(null, "text", 0);
if (resouceId > 0) {
    String null_namespace = context.getResources().getText(resouceId).toString();
    System.out.println("null_namespace = " + null_namespace);
}

4.2.3 Activity代码

src/java/CustomViewActivityFragment.java

package com.antex.customview;

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;

/**
 * A placeholder fragment containing a simple view.
 */
public class CustomViewActivityFragment extends Fragment {
    private int Themes[] = {R.style
            .AppTheme_WithCustomizeStyle_Activity_WithValues_withCustomizeStyle, R.style
            .AppTheme_WithCustomizeStyle_Activity_WithValues, R.style
            .AppTheme_WithCustomizeStyle_Activity_NoValues, R.style
            .AppTheme_NoCustomizeStyle_Activity_NoValues};

    public CustomViewActivityFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
            savedInstanceState) {
        Intent intent = getActivity().getIntent();
        int checkedID = intent.getIntExtra("checkedID", 0);
        getActivity().setTheme(Themes[checkedID]);
        View view = inflater.inflate(R.layout.fragment_custom_view, container, false);
        LinearLayout linearLayout = (LinearLayout) view.findViewById(R.id.linearLayout);

        CustomTextView customTextView1 = new CustomTextView(getActivity());

        customTextView1.setText("New CustomTextView");

        linearLayout.addView(customTextView1, 0, new ViewGroup.LayoutParams(ViewGroup
                .LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));


        final RadioGroup radioGroup = (RadioGroup) view.findViewById(R.id.radiaGroup);
        ((RadioButton) radioGroup.getChildAt(checkedID)).setChecked(true);
        radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                int id = 0;
                switch (checkedId) {
                    case R.id.rb1:
                        id = 0;
                        break;
                    case R.id.rb2:
                        id = 1;
                        break;
                    case R.id.rb3:
                        id = 2;
                        break;
                    case R.id.rb4:
                        id = 3;
                        break;
                    default:
                        break;

                }

                startActivity(new Intent(getActivity(), CustomViewActivity.class).putExtra
                        ("checkedID", id));
                getActivity().finish();
            }
        });

        return view;
    }

}

4.2.4演示效果:

android AccessibilityEvent 获取全部控件 安卓获取控件_android


android AccessibilityEvent 获取全部控件 安卓获取控件_自定义控件_02


android AccessibilityEvent 获取全部控件 安卓获取控件_命名空间_03


android AccessibilityEvent 获取全部控件 安卓获取控件_自定义控件_04

4.2.5效果解释

页面下有四个theme

编号

Activity theme是否引用defStyleAttr

Application theme是否引用defStyleAttr

Activity values

Application values

1





2

×




3

×


×


4

×

×

×


总共6个TextView
第一个是在java文件中通过new生成的
第二个是在XML中直接指定的,并且也指定了style
第三个是设置了Style的
这三个无论是在哪个theme下显示效果都是一样的
说明了两点
1.在XML中指定值优先级>在style中指定值
2.在style中指定值的优先级>大于通过defStyleAttr,
和defStyleRes和在theme中指定
我们主要关注下下面三个TextView
第四个TextView:
defStyleAttr=0 && defStyleRes=0
3.不论在theme中是否引用defStyleAttr,是否指定defStyleRes,都是不起作用的
选择前两个theme时,Activity Theme中有属性值,显示的是Activity Theme中的值“text from ActivityTheme”
选择后两个theme时,Activity Theme中没有有属性值,显示的是Application Theme中的值“text from AppTheme”
说明
4.在Activity Theme指定值的优先级>在Application Theme中指定的值

第五个TextView:
defStyleAttr=0 defStyleRes!=0
“DefaultCustomizeStyle”中有指定文字,但没有指定文字颜色
四中情况下都是显示的“text form DefaultCustomizeStyle”,
但是选择前两个theme时,显示的是Activity Theme中颜色
选择后两个theme时,显示的是Application Theme中颜色
说明
5.defStyleRes>Activity Theme指定值的优先级>在Application Theme中指定的值

第六个TextView:
defStyleAttr!=0 && defStyleRes!=0

6.Activity Theme中指定 defStyleAttr>Application Theme中指定 defStyleAttr
7.Application Theme中指定 defStyleAttr>指定defStyleRes

优先级总结:
在XML中直接指定>在style中指定值>在Activity theme中指定了defStyleAttr(>0)>在Application theme中指定了defStyleAttr(>0)>指定了defStyleRes(>0)>在Activity theme中指定的值>在Application theme中指定的值

开发工具:Android Studio1.5
SDK: Android 6.0
API 23