Android自定义View构造函数详解

Posted changhaiSmile

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android自定义View构造函数详解相关的知识,希望对你有一定的参考价值。

初始Custom View的构造函数

通常我们在实现Custom View的时候,都会先继承View并实现View的三个构造函数,例如:

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;

public class MyCustomView extends View 
    /**
     * 第一个构造函数
     */
    public MyCustomView(Context context) 
        this(context, null);
    

    /**
     * 第二个构造函数
     */
    public MyCustomView(Context context, AttributeSet attrs) 
        this(context, attrs, 0);
    

    /**
     * 第三个构造函数
     */
    public MyCustomView(Context context, AttributeSet attrs, int defStyle) 
        super(context, attrs, defStyle);
        // TODO:获取自定义属性
    

    @Override
    protected void onDraw(Canvas canvas) 
        super.onDraw(canvas);
    

   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

网上有很多关于三个构造函数使用时机的说法,但是说法正确的却没有几家,这里正式的给大家科普一下:

  1. 在代码中直接new一个Custom View实例的时候,会调用第一个构造函数.这个没有任何争议.
  2. 在xml布局文件中调用Custom View的时候,会调用第二个构造函数.这个也没有争议.
  3. 在xml布局文件中调用Custom View,并且Custom View标签中还有自定义属性时,这里调用的还是第二个构造函数.

也就是说,系统默认只会调用Custom View的前两个构造函数,至于第三个构造函数的调用,通常是我们自己在构造函数中主动调用的(例如,在第二个构造函数中调用第三个构造函数).

至于自定义属性的获取,通常是在构造函数中通过obtainStyledAttributes函数实现的.这里先介绍一下如何生成Custom View的自定义属性.


生成Custom View的自定义属性

Custom View添加自定义属性主要是通过declare-styleable标签为其配置自定义属性,具体做法是: 在res/values/目录下增加一个resources xml文件,示例如下(res/values/attrs_my_custom_view.xml):

<resources>
    <declare-styleable name="MyCustomView">
        <attr name="custom_attr1" format="string" />
        <attr name="custom_attr2" format="string" />
        <attr name="custom_attr3" format="string" />
        <attr name="custom_attr4" format="string" />
    </declare-styleable>
    <attr name="custom_attr5" format="string" />
</resources
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在上述xml文件中,我们声明了一个自定义属性集MyCustomView,其中包含了custom_attr1,custom_att2,custom_attr3,custom_attr4四个属性.同时,我们还声明了一个独立的属性custom_attr5.

所有resources文件中声明的属性都会在R.attr类中生成对应的成员变量:

public final class R 
    public static final class attr 
        public static final int custom_attr1=0x7f010038;
        public static final int custom_attr2=0x7f010039;
        public static final int custom_attr3=0x7f01003a;
        public static final int custom_attr4=0x7f01003b;
        public static final int custom_attr5=0x7f010000;
    

   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

但是声明在标签中的属性,系统还会在R.styleable类中生成相关的成员变量:

public static final class styleable 
        public static final int[] MyCustomView = 
            0x7f010038, 0x7f010039, 0x7f01003a, 0x7f01003b
        ;
        public static final int MyCustomView_custom_attr1 = 0;
        public static final int MyCustomView_custom_attr2 = 1;
        public static final int MyCustomView_custom_attr3 = 2;
        public static final int MyCustomView_custom_attr4 = 3;

   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

可以看出,R.styleable.MyCustomView是一个数组,其中的元素值恰好就是R.attr.custom_attr1~R.attr.custom_attr4的值.而下面的MyCustomView_custom_attr1~MyCustomView_custom_attr4正好就是其对应的索引.

知道了这些之后,我们就可以来学习一下,如何在Custom View的构造函数中获取自定义属性的值了.


在Custom View的构造函数中获取自定义属性

在第三个构造函数中获取自定义属性的代码如下:

public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) 
    super(context, attrs, defStyleAttr);

    TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView);

    String attr1 = ta.getString(R.styleable.MyCustomView_custom_attr1);
    String attr2 = ta.getString(R.styleable.MyCustomView_custom_attr2);
    String attr3 = ta.getString(R.styleable.MyCustomView_custom_attr3);
    String attr4 = ta.getString(R.styleable.MyCustomView_custom_attr4);

    Log.e("customview", "attr1=" + attr1);
    Log.e("customview", "attr2=" + attr2);
    Log.e("customview", "attr3=" + attr3);
    Log.e("customview", "attr4=" + attr4);
    ta.recycle();
 
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

关于自定义属性的获取,我们主要是调用了context.obtainStyledAttributes这个函数,相信这个函数大家自定义View的时候都用的很熟练了.我们来看一下这个函数的源码实现:

public final TypedArray obtainStyledAttributes(AttributeSet set, @StyleableRes int[] attrs) 
    return getTheme().obtainStyledAttributes(set, attrs, 0, 0);

   
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

通过对源码的追踪,我们发现context的两个参数的obtainStyledAttributes方法最终是调用了Theme的4个参数的obtainStyledAttributes方法.我们来看一下这个函数的源码实现:

public TypedArray obtainStyledAttributes(AttributeSet set,
        @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) 
    final int len = attrs.length;
    final TypedArray array = TypedArray.obtain(Resources.this, len);

    // XXX note that for now we only work with compiled XML files.
    // To support generic XML files we will need to manually parse
    // out the attributes from the XML file (applying type information
    // contained in the resources and such).
    final XmlBlock.Parser parser = (XmlBlock.Parser)set;
    AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
            parser != null ? parser.mParseState : 0, attrs, array.mData, array.mIndices);

    array.mTheme = this;
    array.mXml = parser;

    if (false) 
        int[] data = array.mData;

        System.out.println("Attributes:");
        String s = "  Attrs:";
        int i;
        for (i=0; i<set.getAttributeCount(); i++) 
            s = s + " " + set.getAttributeName(i);
            int id = set.getAttributeNameResource(i);
            if (id != 0) 
                s = s + "(0x" + Integer.toHexString(id) + ")";
            
            s = s + "=" + set.getAttributeValue(i);
        
        System.out.println(s);
        s = "  Found:";
        TypedValue value = new TypedValue();
        for (i=0; i<attrs.length; i++) 
            int d = i*AssetManager.STYLE_NUM_ENTRIES;
            value.type = data[d+AssetManager.STYLE_TYPE];
            value.data = data[d+AssetManager.STYLE_DATA];
            value.assetCookie = data[d+AssetManager.STYLE_ASSET_COOKIE];
            value.resourceId = data[d+AssetManager.STYLE_RESOURCE_ID];
            s = s + " 0x" + Integer.toHexString(attrs[i])
                + "=" + value;
        
        System.out.println(s);
    

    return array;

   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

这里就不做过多的源码讲解,而是把这四个参数的含义解释给大家:

  1. AttributeSet set: 属性值的集合.
  2. int[] attrs: 我们自定义属性集合在R类中生成的int型数组.这个数组中包含了自定义属性的资源ID.
  3. int defStyleAttr: 这是当前Theme中的包含的一个指向style的引用.当我们没有给自定义View设置declare-styleable资源集合时,默认从这个集合里面查找布局文件中配置属性值.传入0表示不像该defStyleAttr中查找默认值.
  4. int defStyleRes: 这个也是一个指向Style的资源ID,但是仅在defStyleAttr为0或者defStyleAttr不为0但Theme中没有为defStyleAttr属性赋值时起作用.

由于一个属性可以在很多地方对其进行赋值,包括: XML布局文件中、decalare-styleable、theme中等,它们之间是有优先级次序的,按照优先级从高到低排序如下:

属性赋值优先级次序表: 
在布局xml中直接定义 > 在布局xml中通过style定义 > 自定义View所在的Activity的Theme中指定style引用 > 构造函数中defStyleRes指定的默认值

为了让大家有更清楚更直观的了解,再接下来设置自定义属性的章节中,我将对custom_attr1~4这4个属性分别在上述四个地方进行定义,然后在Custom View的构造函数中获取它的值,从而看一下,优先级顺序是否和我们预期的一样.


设置自定义属性值

以下的第几个参数均是针对Resources.Theme类的obtainStyledAttributes四参数构造方法来说明的.


第二个参数——在布局xml文件中为属性赋值

在设置自定义属性之前,我们首先要在主Activity的布局文件中调用我们的Custom View,并且为其设置特定的属性.

主布局文件内容如下:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.kevintan.eventbussample.view.MyCustomView
        android:id="@+id/id_custom_view"
        android:layout_width="400dp"
        android:layout_height="400dp"
        custom:custom_attr1="attr1_xml"
        style="@style/TestCustomView"/>

</FrameLayout>
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

示例结果:

05-28 17:19:56.542 23575-23575/? E/customview: attr1=attr1_xml
05-28 17:19:56.542 23575-23575/? E/customview: attr2=null
05-28 17:19:56.542 23575-23575/? E/customview: attr3=null
05-28 17:19:56.542 23575-23575/? E/customview: attr4=null