View的构造函数分析

作者: liwshuo | 来源:发表于2019-01-14 17:15 被阅读3次

    View的构造函数分析

    在自定义View的时候,我们在继承了View之后,需要重写4种构造函数,那么这四种构造函数是什么意义呢?

    1. 构造函数里的参数分别是什么意思
    2. 其被调用时机是什么时候

    构造函数中的参数

    看View源码的话,可以知道4个构造函数之间是互调用的,参数少的调用参数多的。因此看参数最多的那个构造函数即可知目前共有4个参数,这四个参数中,除了Context,其余三个参数都是用来为View指定初始化的参数的,这些参数来自于xml中指定的属性,包括layout、style和theme。

        super(context, attrs, defStyleAttr, defStyleRes);
    }
    
    1. context。类型为Context,这个就不用多说了,
    2. attrs。一个属性集合。保存了在xml中定义的属性。
    3. defStyleAttr。是当前Theme中的一个属性,指向style的一个引用,如果在layout和style中都没有指定属性的情况下,会从Theme中这个attribute指向的style中查找相应的属性值。如果这个值传入为0
    4. defStyleRes。是指向style的一个资源id,仅当defStyleAttr为0或者defStyleAttr不为0。但是Theme中没有为defStyleAttr属性赋值的情况下起作用。

    Attributes

    平时在xml中使用view的时候,会给view指定属性值,比如下面这样。

    <ImageView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@drawable/icon"
      />
    

    那么这些属性是从哪来的呢。有过自定义view的经验的话都会知道,可以在attrs文件中,通过declare-styleable来设置属性,系统view也不例外。

    <declare-styleable name="ImageView">
      <attr name="src" format="reference|color" />
    
    </declare-styleable>
    

    编译之后,生成一些资源id。R.styleable.[name]以及每一个属性R.styleable.[name]_[attribute]。

    • R.styleable.[name]是一个数组,里面保存了属性的资源id。可以利用这个数组,从AttributeSet中获取相关属性的值的集合。
    • R.styleable.[name]_[attribute],这个值保存了该属性在R.styleable.[name]这个数组中的位置,可以理解为一个索引,从前面取出的相关属性的值的集合中取出对应的资源值。

    obtainStyledAtributes

    在自定义View中,会经常使用这个方法来获取xml文件中指定的属性值,这个方法有4个参数

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

    AttributeSet中保存了在xml中设置的资源及其值。
    attrs是一个数组,即前面说的R.styleable.[name],里面保存了我们属性的资源ID。这个数组用于从属性值的数组中取出每个属性对应的属性值。利用这两个参数,可以取出xml中设置的属性值。

    public ImageView(Context context, AttributeSet attrs) {
      TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ImageView, 0, 0);
      Drawable src = ta.getDrawable(R.styleable.ImageView_src);
      setImageDrawable(src);
      ta.recycle();
    }
    

    给View设置属性默认值

    很多时候,View在初始化的时候,属性需要有一个默认值。如何指定这个默认值呢。当然,最简单的是在View内部取属性值的时候,在代码中为其指定默认值。如果这个默认值需要在不同的Theme中有不同的值怎么办?难道要在View中判断Theme然后来指定不同的默认值么?两个Theme还好,多个Theme的话,这样很不利于扩展。

    怎么解决这个问题呢?这时候就轮到defStyleAttr发挥了。前面说了,defStyleAttr是当前Theme中的一个属性,指向style的一个引用。obtainStyledAtributes方法的第三个参数就是defStyleAttr,这时候应该能大概理解defStyleAttr这个参数是做什么的了。

    比方说,有三个不同的Theme,ThemeA,ThemeB,ThemeC,在这三个Theme 下,有一个CustomView需要有三种不同的展示样式,怎么办呢。

    1. 在styles.xml中定义一个BaseTheme,作为其余Theme的基类
    <style name="BaseTheme" parent="AppTheme">
    </style>
    
    1. 在attrs.xml中为该Theme中定义一个属性:customStyle
    <declare-styleable name="BaseTheme">
        <attr name="customStyle" format="reference" />
    </declare-styleable>
    
    1. 为这个自定义View设置三个不同的customStyleA,customStyleB,customStyleC。
    <style name="customStyleA">
        <item name="customTextColor">@android:color/black</item>
    </style>
    <style name="customStyleB">
        <item name="customTextColor">@android:color/holo_red_light</item>
    </style>
    <style name="customStyleC">
        <item name="customTextColor">@android:color/holo_blue_light</item>
    </style>
    
    1. 定义三个Theme:ThemeA,ThemeB和ThemeC,每一个Theme都会引用一个不同的style
    <style name="ThemeA" parent="BaseTheme" >
        <item name="customStyle">@style/customStyleA</item>
    </style>
    <style name="ThemeB" parent="BaseTheme" >
        <item name="customStyle">@style/customStyleB</item>
    </style>
    <style name="ThemeC" parent="BaseTheme" >
        <item name="customStyle">@style/customStyleC</item>
    </style>
    
    1. 在代码中获取属性值,注意,这里调用obtainStyledAttributes方法的时候,第三个参数,传的是自定义的customStyle的属性id。根据前面的步骤,这个属性是Theme的一个attribute,并且在不同的Theme下指向了不同的style。这样就可以在不同的Theme下获取不同的属性值。
    TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomTextView, R.attr.customStyle, 0);
    int color = array.getColor(R.styleable.CustomTextView_customTextColor, Color.BLACK);
    setTextColor(color);
    array.recycle();
    

    通过前面的分析,defStyleAttr的作用应该讲清楚了,那么defStyleRes是做什么用的呢?defStyleRes相对defStyleAttr更简单,实际上就是一个style的资源id。如果需求比较简单,属性不需要和Theme绑定,或者有些Theme不想指定style。那么可以自己定义一个style。

    <style name="customStyle">
        <item name="customTextColor">@android:color/black</item>
    </style>
    

    然后调用obtainStyledAttributes方法,注意第三个参数传的是0,第四个参数传的是自定义style的id。

    TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomTextView, 0, R.style.customStyle);
    int color = array.getColor(R.styleable.CustomTextView_customTextColor, Color.BLACK);
    setTextColor(color);
    array.recycle();
    

    属性值设置的优先级

    通过前面的分析,可以知道有如下几种方式可以设置属性值。

    在XML文件中设置属性值

    1. 在layout中指定参数并赋值
    2. 在style中指定参数并赋值,然后将该style作为layout的参数赋值
    3. 在theme中指定参数并赋值,然后将该theme设置给application或者activity

    在代码中设置属性值

    1. 通过外部调用相关方法设置属性值
    2. 通过指定defStyleAttr来设置属性值
    3. 通过指定defStyleRes来设置属性值

    那么这些方式之间肯定有一个优先级顺序,否则同时使用的话肯定会出现混乱。这个顺序如下:

    1. layout定义 > style中定义 > defStyleAttr定义 > defStyleRes定义 > theme中定义
    2. 如果指定了defStyleAttr,而且该属性是存在的,那么defStyleRes将不起作用,即使defStyleRes中定义了defStyleAttr中不存在的参数,也不会生效。

    如果感兴趣的话,可以自己去实验下。

    构造函数调用时机

    通过前面的分析,构造函数的调用时机也就好分析了。

    1. 单参数构造函数(1)。当我们需要在代码中实例化一个View的时候,我们可以使用该构造函数,只需要传入一个Context,其余的属性通过方法设置。
    2. 两个参数构造函数(2)。我们在XML中写的View,最后转换成Java中可以引用的对象这个过程,都是通过反射的方式来实现的。具体可以参考 XML文件如何被加载这篇文章。反射调用的构造函数,即是两个参数的构造函数。
    3. 三个参数构造函数(3)。
    4. 四个参数构造函数(4)。这两个构造函数,系统是不会调用的,而是由View显示调用。比如,在2中调用3,并传入defStyleAttr。再比如,在3中调用4,并传入defStyleRes。
    public class CustomTextView extends TextView {
        public CustomTextView(Context context) {
            this(context, null);
        }
    
        public CustomTextView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            this(context, attrs, R.attr.customStyle, 0);
        }
    
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
            TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomTextView, defStyleAttr, R.style.customStyle);
            int color = array.getColor(R.styleable.CustomTextView_customTextColor, Color.BLACK);
            setTextColor(color);
            array.recycle();
        }
    }
    

    相关文章

      网友评论

        本文标题:View的构造函数分析

        本文链接:https://www.haomeiwen.com/subject/orsjdqtx.html