关于自定义view的构造函数详解

常见构造方式:

在Android 中要使用一个view,通常会有两种方式,1、xml中 2、通过构造函数new出一个指定的view对象。

/**
 * 关于view构造参数的详解
 */
public class MainActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    LinearLayout container = (LinearLayout) findViewById(R.id.activity_main);
    /**
     * 一个参数的构造函数
     */
    Button buttonOne = new Button(this);
    buttonOne.setText("(Context)");

    /**
     * 三个参数的构造函数
     */
    Button buttonThree = new Button(this, null, 0);
    buttonThree.setText("(Context,AttributeSet,0)");

    container.addView(buttonOne);
    container.addView(buttonThree);
  }
}
展示效果如图所示

显然使用三个构造函数生成的button,样式不正确而且无法完成点击

View的构造函数:

通用构造函数展示如下:
  public View(Context context);  

  public View(Context context, AttributeSet attrs);  

  public View(Context context, AttributeSet attrs, int defStyle);  

关于button的构造函数展示如下:

public class Button extends TextView {  
  public Button(Context context) {  
    this(context, null);  
  }  

  public Button(Context context, AttributeSet attrs) {  
    this(context, attrs, com.android.internal.R.attr.buttonStyle);  
  }  

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

Button 继承自 TextView,显然由构造函数我们可知,二者的区分主要来自com.android.internal.R.attr.buttonStyle这第三个参数。

View构造方法中的第三个参数:

  • 用来给View提供一个基本的style,如果我们没有对view设置某些属性,就使用这个style的属性。

通过三个参数的构造函数,进入TextView中查看构造方法,当中有一句关键代码如下,:

TypedArray a = theme.obtainStyledAttributes(attrs,
            com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);

obtainStyledAttributes方法介绍:

public TypedArray obtainStyledAttributes (AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)  
  • set:在XML中明确写出的属性集合。(比如:android:layout_width 等)
  • attrs:需要我们在上面的set集合中查询哪些内容,如果是自定义view,一般我们会把自定义的属性写在declare-styleable中,代表我们想要查询这些自定义属性。
  • defStyleAttr:这是一个定义在attrs.xml文件中的attribute 这个值起作用需要两个条件:1、值不为0 2、在Theme中出现过(出现即可)
  • defStyleRes:这是在styles.xml文件中定义的一个style。只有当defStyleAttr没有起作用,才会使用到这个值。

显然,一个属性最终的取值,是有一个顺序的问题,这个顺序的优先级从高到低依次是:

1、直接中在XML文件中定义。
2、在XML文件中通过style这个属性定义。
3、通过defStyleAttr定义。
4、通过defStyleRes定义。
5、直接在当前工程的theme主题下定义。

关于com.android.internal.R.attr.buttonStyle 这个位于,frameworks\base\core\res\res\values\attrs.xml 中:

<attr name="buttonStyle" format="reference" />

只是定义了一个引用,在theme.xml文件下,有这样一个style:

<style name="Theme">  
...  
   <item name="buttonStyle">@android:style/Widget.Button</item>  
...  
</style>  

在这里使用到了buttonStyle属性,它指向另外一个style,这个style在styles.xml文件下:

<style name="Widget.Button">  
    <item name="android:background">@android:drawable/btn_default</item>  
    <item name="android:focusable">true</item>  
    <item name="android:clickable">true</item>  
    <item name="android:textAppearance">?android:attr/textAppearanceSmallInverse</item>  
    <item name="android:textColor">@android:color/primary_text_light</item>  
    <item name="android:gravity">center_vertical|center_horizontal</item>  
</style>  

这些属性都是用来配置Button的,如果在XML文件中没有给Button配置背景、内容的位置等属性们就会默认的使用这里的属性。当前这是在使用了defStyleAttr的情况下才会出现的。

上面的themes.xml中的那个style的名称为Theme,而在我们自己的工程中,在配置menifest文件的时候,给application或者activity设置的主题android:theme一般都是这个style的子类,所以也就这样使用到了defStyleAttr定义的属性了

还有一个defStyleRes参数,我们可以发现在TextView、ImageView等控件中,这个值传的都是0,也就是不使用它。它的作用就像是一个替补,当defStyleAttr不起作用的时候它就上场,因为它也是一个style,这个参数是怎么起作用的在下面的实例中有提到。

public class DefineView extends View {
  static final String LOG_TAG = "DefineView";
  public DefineView(Context context) {
    this(context, null);
  }

  public DefineView(Context context, AttributeSet attrs) {
    /**
     * 在style中,theme style中建立对应的attrs和style之间的对应关系
     */
    this(context, attrs, R.attr.defineViewStyle);
  }

  public DefineView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    /**
     * 打印各个属性的值
     */
    TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DefineView, defStyleAttr, 0);
    Log.d(LOG_TAG, "attr1 => " + array.getString(R.styleable.DefineView_attr1));
    Log.d(LOG_TAG, "attr2 => " + array.getString(R.styleable.DefineView_attr2));
    Log.d(LOG_TAG, "attr3 => " + array.getString(R.styleable.DefineView_attr3));
    Log.d(LOG_TAG, "attr4 => " + array.getString(R.styleable.DefineView_attr4));
    Log.d(LOG_TAG, "attr5 => " + array.getString(R.styleable.DefineView_attr5));
    Log.d(LOG_TAG, "attr6 => " + array.getString(R.styleable.DefineView_attr6));
    Log.d(LOG_TAG, "attr6 => " + array.getString(R.styleable.DefineView_attr7));

  }
}

<com.example.leiiiooo92.defineview.DefineView
    style="@style/xml_style"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:attr1="attr1 from xml"
    app:attr2="attr2 from xml" />

<resources>
  <!--定义自定义view的属性-->
  <declare-styleable name="DefineView">
    <attr name="attr1" format="string" />
    <attr name="attr2" format="string" />
    <attr name="attr3" format="string" />
    <attr name="attr4" format="string" />
    <attr name="attr5" format="string" />
    <attr name="attr6" format="string" />
    <attr name="attr7" format="string" />
  </declare-styleable>

  <attr name="defineViewStyle" format="reference" />

</resources>

<resources>

  <!-- Base application theme. -->
  <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
    <!--绑定attrs和style中对应项的关系-->
    <item name="defineViewStyle">@style/define_view_style</item>
  </style>
  <!-- 首先定义我们的defStyleAttr属性(在本项目中是defineViewStyle属性)需要用到的style(位于styles.xml文件中) -->
  <style name="define_view_style">
    <item name="attr3">attr3 from define_view_style</item>
    <item name="attr4">attr4 from define_view_style</item>
  </style>
  <!-- 定义一个在xml布局中需要使用到的style -->
  <style name="xml_style">
    <item name="attr2">attr2 from xml_style</item>
    <item name="attr3">attr3 from xml_style</item>
  </style>
</resources>
未设置defStyleAttr时

分析:
attr1只在xml布局文件中设置,所以值为attr1 from xml。
attr2在xml布局文件和xml style中都设置了,取值为布局文件中设置的值,所以为attr2 from xml。
attr3没有在xml布局文件中设置,但是在xml style和defStyleAttr定义的style中设置了,取xml style中的值,所以值为attr3 from xml_style。
attr4只在defStyleAttr定义的style中设置了,所以值为attr4 from custom_view_style。

下面测试一下defStyleRes这个参数,它是一个style

<style name="define_view_res_style">  
  <item name="attr4">attr4 from define_view_res_style</item>  
  <item name="attr5">attr5 from define_view_res_style</item>  
</style> 

当defStyleAttr这个参数定义为0(即不使用这个参数),或者是在theme中找不到defStyleAttr这个属性时(即使在theme中的配置是这样的:<item name="defStyleAttr">@null</item>,也代表找到了defStyleAttr属性,defStyleRes参数也不会生效),defStyleRes参数才会生效。

修改对应的获取自定义属性语句:

TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DefineView, 0, R.style.define_view_res_style);
设置defStyleAttr时

由于defStyleAttr已经失效,所以attr4和attr5都是从define_view_res_style中获取到的值。

在主题中添加指定的属性值:

<item name="attr5">attr5 from AppTheme</item>  
<item name="attr6">attr6 from AppTheme</item>  
主题中添加对应的属性值

attr5在define_view_res_style和theme下都定义了,取define-res-style下的值,所以为attr5 from define_view_res_style。
attr6只在theme下定义了,所以取值为attr6 from AppTheme。

当未设置def-res-style时,主题中的属性是否生效:

TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DefineView, defStyle, 0);
QQ截图20161031183605.png

总结:

View中的属性有多处地方可以设置值,这个优先级是:
1、直接在XML布局文件中设置的值优先级最高,如果这里设置了值,就不会去取其他地方的值了。
2、XML布局文件中有一个叫“style”的属性,它指向一个style,在这个style中设置的属性值优先级次之。
3、如果上面两个地方都没有设置值,那么就会根据View带三个参数的构造方法中的第三个参数attribute指向的style设置值,前提是这个attribute的值不为0。
4、如果上面的attribute设置为0了,我们就根据obtainStyledAttributes()方法中的最后一个参数指向的style来设置值。
5、如果仍然没有设置到值,就会用theme中直接设置的属性值,而不会去管第3步和第4步中是否设置了值。

必须要注意:要想让View构造方法的第三个参数生效,必须让它出现在我们自己的Application或者Activity的android:theme所指向的style中。设置Activity的theme一样可以

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容