DIYAndroidStyle

Android 关于自定义属性.

     自定义属性的步骤

1. 自定义一个CustomView(extends View)2. 编写values/attrs.xml, 在其中编写styleable和item等标签元素  
3. 在布局文件中CustomView使用自定义的属性。  
4. 在CustomView的构造方法中通过TypedArray获取。  

常见的例子

  • 自定义属性的声明文件

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="test">
            <attr name="text" format="string"/>
            <attr name="testAttr" format="integer"/>
        </declare-styleable>
    </resources>
    
  • 自定义View类

    public class MyTextView extends View {
        private static final String TAG = MyTextView.class.getSimpleName();
        public MyTextView (Context context, AttributeSet attrs) {
            super(context, attrs);
            TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.test);
            String text = ta.getString(R.styleable.test_testAttr);
            int textAttr = ta.getInteger(R.styleable.test_text, -1);
            Log.e(TAG, "text = " + text + ",testAttr=" + textAttr);
            ta.recycle();
        }
    }
    
  • 布局文件里面:

    <RelativeLayout xmln: android="http://schemes.android.com/apk/res/android"
        xmlns:tools="http:schemes.android.com/tools"
        xmlns:cus="http://schemes.android.com/apk/res/com.example.test"
        android:layout_width="match_parent"
        android:layout_height="match_parent"?
    
        <com.example.test.MyTextView
            android:layout_width="100dp"
            android:layout_height="200dp"
            cus:testAttr="520"
            cus:text="helloworld" />
    </RelativeLayout>
    

    结果是:MyTextView: text = helloworld , textAttr = 520

分析: styleable的name写的是test,所以不要求一定是自定义view的名字。

AttributeSet与TypedArray

构造方法里面有个参数叫做AttributeSet,包含的参数的集合 ,能不能通过它去获取我的自定义属性呢?

首先AttributeSet 中确保存的是该view声明的所有的属性。并且外面的确可以通过它去获取(自定义的)
属性。看AttributeSet的方法就明白了,如下:

public   MyTextView(Context context, AttributeSet attrs) {
    super(context, attrs);
    int count = atts.getAttributeCount();
    for (int i = 0; i < count; i++) {
        String attrName = attrs.getAttributeName(i);
        String attrVal = attrs.getAttributeValue(i);
        Log.e(TAG, "attrName = " + attrName + ",attrVal = " + attrVal);
    }
    //=>use typedArray...
}    

输出:

MyTextView(4136): attrName = layout_width , attrVal = 100.0dip  
MyTextView(4136): attrName = layout_height , attrVal = 200.0dip
MyTextView(4136): attrName = text , attrVal = helloworld  
MyTextView(4136): attrName = testAttr , attrVal = 520

获取到了所以属性。通过AttributeSet 可以获得布局文件中定义的所以属性的key和value.
TypedArray的用处:
通过修改布局文件,发现当布局文件里面的属性是:@dimen或是@string…,
利用AttributeSet获取到的为数字串,不是我们想要的,而利用TypedArray
可以获取到原值。
TypedArray是用来简化工作的,利用AttributeSet第一步获取到id,再去解析id,
TypedArray简化了这个工作。
利用AttributeSet获取最终值的代码:

int widthDimensionId = attrs.getAttributeResourceValue(0, -1);
Log.e(TAG, "layout_width=" + getResources().getDimension(widthDimensionId));

declare-styleable

在布局文件里面有一个属性是:cus:text. 系统里自带的属性是android:text,
当然也可以利用它,例如:
在attrs.xml中:

<declare-styleable name="test">
    <attr name="android:text"/>
    <attr name="testAttr" format="integer"/>
</declare-styleable>

注意到这里不需要为android:text设置format属性。
同时意识到:声明和使用的区别是有没有format.

然后在类中获取:ta.getString(R.styleable.test_android_text);
布局文件里面直接:android:text="helloworld".

系统中定义的属性,可以在sdk/platforms/android-xx/data/res/values
该目录下看到系统中定义的属性。同时可以在系统提供的view(eg:TextView)的构造
方法中发现TypedArray获取属性的代码。

是否需要declare-styleable这个标签

可以不用,attrs.xml为:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="testAttr" format="integer"/>
</resources>

MyTextView

public class MyTextView extends View {
    private static final String TAG = MyTextView.class.getSimpleName();
    private static final int[] mAttr = {android.R.attr.text, R.attr.testAttr};
    private static final int ATTR_ANDROID_TEXT = 0;
    private static final int ATTR_TESTATTR = 1;

    private MyTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //=>use typedArray  
        TypedArray ta = context.obtainStyledAttributes(attrs, mAttr);
        String text = ta.getString(ATTR_ANDROID_TEXT);
        int textAttr = ta.getInteger(ATTR_TESTATTR, -1);
        //输出:text = hello world, textAttr = 520;
        Log.e(TAG, "text = " + text + ", textAttr = " + textAttr);

        ta.recycle();
    }
}

声明了一个int数组,数组中的元素是我们想要获取的attr的id,而且定义了两个整型
常量代表其下标,通过TypedArray进行获取,ta.getString(0).

说明:

R.styleable.test => mAttr  
R.styleable.test_text => ATTR_ANDROID_TEXT(0)
R.styleable.test_testAttr => ATTR_TEXTATTR(1);

在android内部,默认会这莫做,它会在R.java生成如下代码:

public static final class attr {
    public static final int testAttr = 0x7f0100a9;
}
public static final class styleable {
    public static final int test_android_text = 0;
    public static final int test_testAttr = 1;
    public static final int[] test = {0x0101014f, 0x7f0100a9};

}

styleable的出现,系统可以为我们完成很多常量(int[]数组,下标常量) 等
的编写,简化我们的开发工作。

declare-styleable 的name属性,一般是自定义View的类名,这样更加直观。

总结:

  • att.xml 里面的declare-styleable以及item, android会根据其在R.java中生成一些常量方便我们使用,本质上,可以不声明declar-styleable,仅仅声明所需的属性即可。
  • 在view的构造方法中,可以通过AttributeSet去获得自定义属性的值,但是比较麻烦,而TypedArray可以很方便的便于获取。
  • 自定义view的时候,可以使用系统已经定义的属性。

参考zhy的博客

chenzhao@hustunique.com