深入解析Android declare-styleable attr style theme(上)

有过Android自定义控件经验的同学都用过attr属性,
通常情况下都是在attrs.xml 文件中声明一个styleable,
并定义一些attr属性,在自定义控件中通过TypedArray来获取,设置到自定以View上
其实attr属性,也不只能和View配合使用,我们还可以通过attr属性来自定义theme和style来对
自定义View的appearance进行改变。

实例

首先声明,attr并非一定要定义在styleable中,可以使用android内置的attr
获取自定义属性也并非一定需要使用TypedArray和TypedValue

我们来看一个例子,首先在values目录下新建attrs文件,并键入如下属性。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="test">
        <attr name="size" format="dimension" />
        <attr name="android:textColor" />
    </declare-styleable>
</resources>

自定义View:

/** * Created by bobomee on 16/1/25. */
public class MyView extends View {

    String T = this.getClass().getSimpleName();

    private float mSize;
    private int mBackGround;
    private Paint mPaint;

    public MyView(Context context) {
        this(context, null);
    }

    public MyView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        handleAttrs(context, attrs, defStyleAttr);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public MyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        handleAttrs(context, attrs, defStyleAttr);
    }

    private void handleAttrs(Context context, AttributeSet attrs, int defStyleAttr) {
        //Retrieve styles attributes
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.test, defStyleAttr, 0);

        int defaultSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, context.getResources().getDisplayMetrics());
        mSize = ta.getDimension(R.styleable.test_size, defaultSize);
        mBackGround = ta.getColor(R.styleable.test_android_textColor, Color.WHITE);

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(mBackGround);

        Log.d(T, "mSize = " + mSize + " , mBackGround = " + mBackGround);
        ta.recycle();
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(0,0,mSize,mPaint);
    }
}

布局xml文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:background="@color/colorAccent">

    <com.bobomee.android.attrstest.MyView  android:layout_width="200dp" android:layout_height="200dp" android:background="@color/colorPrimary" android:textColor ="#00ffab" app:size="50dp" />
</RelativeLayout>

通过上面的实例,可以看到我们定义的declare-styleable不是自定义View的名字,同时我们使用了android:textColor属性,并且没有format,因为android 内置的属性已经format了,我们只需要引用即可
说明styleable 不要求一定是自定义View的名字,而且attr可以直接使用android定义好的一些属性
运行效果如下:

AttributeSet&&TypedArray

上面说了获取自定义属性并不一定要用TypedArray,其实在自定义View的构造函数中,我们可以看到除了第一个使用new出来的外,都包含一个参数AttributeSet
很明显这个就是一个包含自定义View属性参数的集合。修改上面代码如下:

  private void handleAttrs(Context context, AttributeSet attrs, int defStyleAttr) {
        //Retrieve styles attributes
// TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.test, defStyleAttr, 0);
//
// int defaultSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, context.getResources().getDisplayMetrics());
// mSize = a.getDimension(R.styleable.test_size, defaultSize);
//
// mBackGround = a.getColor(R.styleable.test_android_textColor, Color.WHITE);
//
// mPaint = new Paint();
// mPaint.setAntiAlias(true);
// mPaint.setColor(mBackGround);

// Log.d(T, "mSize = " + mSize + " , mBackGround = " + mBackGround);

        int count = attrs.getAttributeCount();
        for (int i = 0; i < count; i++) {
            String attrName = attrs.getAttributeName(i);
            String attrVal = attrs.getAttributeValue(i);
            Log.e(T, "attrName = " + attrName + " , attrVal = " + attrVal);
        }
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
// canvas.drawCircle(0,0,mSize,mPaint);
    }

可以看到logcat打出如下信息‘

01-25 23:45:04.989 16613-16613/com.bobomee.android.attrstest E/MyView: attrName = textColor , attrVal = #ff00ffab
01-25 23:45:04.989 16613-16613/com.bobomee.android.attrstest E/MyView: attrName = background , attrVal = @2131427347
01-25 23:45:04.989 16613-16613/com.bobomee.android.attrstest E/MyView: attrName = layout_width , attrVal = 200.0dip
01-25 23:45:04.989 16613-16613/com.bobomee.android.attrstest E/MyView: attrName = layout_height , attrVal = 200.0dip
01-25 23:45:04.989 16613-16613/com.bobomee.android.attrstest E/MyView: attrName = size , attrVal = 50.0dip

对应我们的布局文件

  <com.bobomee.android.attrstest.MyView  android:layout_width="200dp" android:layout_height="200dp" android:background="@color/colorPrimary" android:textColor ="#00ffab" app:size="50dp" />

可以看到除了我们在布局文件中写死的属性,像background得到的是一个id :@2131427347

既然得到了id,我们就可以根据id来得到响应的值了
接着修改代码如下:

dimens.xml

 <dimen name="width">200dp</dimen>
    <dimen name="height">200dp</dimen>
    <dimen name="size">50dp</dimen>

activity_main.xml

<com.bobomee.android.attrstest.MyView  android:layout_width="@dimen/width" android:layout_height="@dimen/height" app:size="@dimen/size" />

MyView

int count = attrs.getAttributeCount();
        for (int i = 0; i < count; i++) {
            String attrName = attrs.getAttributeName(i);
            int resId = attrs.getAttributeResourceValue(i, 0);
            float attrVal = getResources().getDimension(resId);
            Log.e(T, "attrName = " + attrName + " , attrVal = " + attrVal);
        }

此时我们会看到logcat打出如下信息,自定义属性获取到了

01-26 00:21:14.189 28485-28485/com.bobomee.android.attrstest E/MyView: attrName = layout_width , attrVal = 400.0
01-26 00:21:14.189 28485-28485/com.bobomee.android.attrstest E/MyView: attrName = layout_height , attrVal = 400.0
01-26 00:21:14.189 28485-28485/com.bobomee.android.attrstest E/MyView: attrName = size , attrVal = 100.0

因此TypedArray其实是用来简化我们的工作的,可以直接获取到引用类型的值,而不是id

declare-styleable

上面说了我么的attr并非一定要定义在styleable中,那我么来尝试一下。修改attr文件如下

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--<declare-styleable name="test">-->
        <attr name="size" format="dimension" />
        <!--<attr name="android:textColor" />-->
    <!--</declare-styleable>-->
</resources>

layout

 <com.bobomee.android.attrstest.MyView  android:layout_width="@dimen/width" android:layout_height="@dimen/height" android:background="@color/colorPrimary" app:size="@dimen/size" android:textColor ="@color/textColor" android:textSize = "30sp" />

MyView

/** * Created by bobomee on 16/1/25. */
public class MyView extends View {

    String T = this.getClass().getSimpleName();

    private float mSize;
    private int mBackGround;
    private Paint mPaint;
    private float mTextSize;

    final int[] custom_attrs = {android.R.attr.textSize,android.R.attr.textColor,R.attr.size};
    final int TSIZE = 0;
    final int BACKGROUNG = 1;
    final int SIZE = 2;

    public MyView(Context context) {
        this(context, null);
    }

    public MyView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        handleAttrs(context, attrs, defStyleAttr);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public MyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        handleAttrs(context, attrs, defStyleAttr);
    }

    private void handleAttrs(Context context, AttributeSet attrs, int defStyleAttr) {
        //Retrieve styles attributes
// TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.test, defStyleAttr, 0);
//
          int defaultSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, context.getResources().getDisplayMetrics());
// mSize = ta.getDimension(R.styleable.test_size, defaultSize);
//
// mBackGround = ta.getColor(R.styleable.test_android_textColor, Color.WHITE);

        TypedArray ta = context.obtainStyledAttributes(attrs,custom_attrs);
        mSize = ta.getDimension(SIZE, defaultSize);
        mBackGround = ta.getColor(BACKGROUNG, Color.WHITE);
        mTextSize = ta.getDimension(TSIZE,defaultSize);

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(mBackGround);

        Log.d(T, "mSize = " + mSize + " , mBackGround = " + mBackGround+ " , mTextSize = " + mTextSize);

// int count = attrs.getAttributeCount();
// for (int i = 0; i < count; i++) {
// String attrName = attrs.getAttributeName(i);
// int resId = attrs.getAttributeResourceValue(i, 0);
// float attrVal = getResources().getDimension(resId);
// Log.e(T, "attrName = " + attrName + " , attrVal = " + attrVal);
// }

        ta.recycle();
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(0, 0, mSize, mPaint);
    }
}

上面的android:textColor如果不注釋掉,會報如下錯誤,也就是定义好的属性就可以直接使用了

Error:(3) Attribute "android:textColor" has already been defined

logcat

13541-13541/com.bobomee.android.attrstest D/MyView: mSize = 100.0 , mBackGround = -16711765 , mTextSize = 60.0

運行效果同上圖

在这里我们定义了一个attr数组,这里需要注意的是android本身定义的attr,需要放在数组的前面,如果有多个android属性,角标和数组定义需要对应即可,不用管layout中定义的顺序。
如果按照传统的方式,我们到android自动生成的R文件下看一下,会发现

  public final class R {
        public static final class attr {
            public static final int size=0x7f010110;
        }
        public static final class styleable {
            public static final int[] test = {
                    0x01010095, 0x01010098, 0x7f010110
            };
            public static final int test_android_textColor = 1;
            public static final int test_android_textSize = 0;
            public static final int test_size = 2;
        }
    }

通过declare-styleable可以在R文件下自动生成一个attr id 的数组 和下标,此外declare-styleable可以将相关属性分组,方便管理,同时以自定义View的名称命名,可以方便找到。

总结

  • 在android我们在attr下面申明属性,即可在R文件中生成相应的id,我们同时可以引用android定义好的attr属性。
  • 通过declare-styleable,可以在R文件中生成一个相应的attr集合的数组和角标,方便获取和分组管理,可以不用声明
  • TypedArray可以通过AttributeSet获取styleable数组中的自定义属性集合,从而获取属性值

参考:
Android 深入理解Android中的自定义属性

你可能感兴趣的:(android,style,attr,theme)