Android自定义属性

Android自定义View是程序猿从初级阶段进阶的必由之路,而自定义View必然会伴随自定义属性,本篇先来讲讲安卓自定义属性

1、自定义View的属性,首先在res/values/  下建立一个attrs.xml , 在里面定义我们的属性和声明我们的整个样式。

<?xml version="1.0" encoding="utf-8"?>
<resources>    
    <declare-styleable name="SwipeListView">
        <attr name="swipeOpenOnLongPress" format="boolean"/>
        <attr name="swipeAnimationTime" format="integer"/>
        <attr name="swipeOffsetLeft" format="dimension"/>
        <attr name="swipeBackView" format="reference"/>
        <attr name="swipeMode" format="enum">
            <enum name="none" value="0"/>
            <enum name="both" value="1"/>
            <enum name="right" value="2"/>
            <enum name="left" value="3"/>
        </attr>
    </declare-styleable>
</resources>

attr子元素:
定义具体的属性,format表示这个属性的值的类型,类型有以下几种:
     1.reference:参考指定Theme中资源ID,这个类型意思就是你传的值可以是引用资源
     2.string:字符串,如果你想别人既能直接写值也可以用类似"@string/test"引用资源的方式,可以写成format="string|reference"
     3.Color:颜色
     4.boolean:布尔值
     5.dimension:尺寸值
     6.float:浮点型
     7.integer:整型
     8.fraction:百分数
     9.enum:枚举 ,如果你提供的属性只能让别人选择,不能随便传入,就可以写成这样
        <attr name="language">
                   <enum name="china" value="1"/>
                   <enum name="English" value="2"/>
         </attr>
     10.flag:位或运算
declare-styleable子元素:
定义一个styleable对象,每个styleable对象就是一组attr属性的集合,注意:这里的name属性并不是一定要和自定义类名相同,只是为了好区分对应类的属性而已
注意:上面的属性资源文件定义了该属性之后,至于到底是哪个自定义View组件中来使用该属性,该属性到底能发挥什么作用, 就不归该属性资源文件管了,也就是说这个属性资源文件是个公共的,大家都可以用,但是为了方便管理,一般都是一个自定义View里的属性写成一个declare-styleable集合.属性资源所定义的属性到底可以返回什么作用,取决于自定义组件的代码实现
2.然后在布局中声明我们的自定义View

  <com.huaxun.view.SwipeListView.SwipeListView
        xmlns:swipe="http://schemas.android.com/apk/com.huaxun.test"
        android:id="@+id/music_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        swipe:swipeAnimationTime="500"
        swipe:swipeBackView="@+id/back"
        swipe:swipeFrontView="@+id/front"
        swipe:swipeMode="both"
        swipe:swipeOffsetLeft="160dp"
        swipe:swipeOpenOnLongPress="true" />
一定要引入 xmlns:swipe="http://schemas.android.com/apk/res/com.huaxun"我们的命名空间,后面的包路径指的是项目的package,不然组件的属性设置不了

更新: 对于自定义属性资源,现在可以不使用http://schemas.android.com/apk/res/<Packge name> 的形式了, 统一用http://schemas.android.com/apk/res-auto

延伸:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
......
</RelativeLayout>

Android应用程序将所有的静态资源都封装在了APK文件中,并根据这些资源文件名(不包括扩展名)或key属性的值生成资源ID。这些ID将作为变量的形式被定义在R类的相应子类中。例如,所有的图像资源(res/drawable目录中的资源文件)都会在R.drawable类中生成相应的变量,变量名就是图像资源的文件名。当使用这些资源时,只要引用R类中相应的变量,系统就会知道上哪去寻找相应的资源。例如,"@string/hello"引用了字符串资源hello。"@drawable/icon"引用了图像资源文件(可能是icon.png、icon.jpg等图像) 
系统内部有一个系统级的R.java文件,所有的系统资源生成的ID都在该文件中的R类相关子类中定义。而在这个R类中有一个attr子类,用于定义系统中所有的属性,也就是XML标签设置的属性名,而这个R类的Package就是android。也正是由于上面申明了系统的命名空间,我们才可以使用诸如android:layout_width,android:background等安卓系统属性。

3.在View的构造方法中,获得我们的自定义的属性信息

      TypedArray styled = getContext().obtainStyledAttributes(attrs, R.styleable.SwipeListView,defStyle,0);
      swipeMode = styled.getInt(R.styleable.SwipeListView_swipeMode, SWIPE_MODE_BOTH);
      swipeOffsetLeft = styled.getDimensionPixelSize(R.styleable.SwipeListView_swipeOffsetLeft, 0);
      swipeOpenOnLongPress = styled.getBoolean(R.styleable.SwipeListView_swipeOpenOnLongPress, true);
      swipeAnimationTime = styled.getInteger(R.styleable.SwipeListView_swipeAnimationTime, 0);
      swipeFrontView = styled.getResourceId(R.styleable.SwipeListView_swipeFrontView, 0);
      swipeBackView = styled.getResourceId(R.styleable.SwipeListView_swipeBackView, 0);
      styled.recycle();

1)先来看看obtainStyledAttributes的四个参数的作用

obtainStyledAttributes这个方法最终调用的的是obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes); 前两个参数一目了然,来看看第三四个参数

先看第四个参数defStyleRes,其实是用于指定一个style,我们在style.xml里面编写

    <style name="style_swipeListView">        
          <item name="swipeOpenOnLongPress">false</item>
          <item name="swipeAnimationTime">3000</item>
          <item name="swipeOffsetLeft">139dp</item> 
    </style>

以看到我们申明了一个style,然后我们修改刚才获取属性的代码 
 

TypedArray styled = getContext().obtainStyledAttributes(attrs, R.styleable.SwipeListView,0,R.style.style_swipeListView);

在布局文件中不设置任何属性

  <com.huaxun.view.SwipeListView.SwipeListView
        xmlns:swipe="http://schemas.android.com/apk/com.huaxun"
        android:id="@+id/music_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

运行后可以看到,swipeOpenOnLongPress=true, swipeAnimationTime=3000, swipeOffsetLeft=278 //139dp

从结果可以明显看出,如果我们不在布局中设置任何属性,会从style中读取相关属性。

接着看第三个参数defStyleAttr,它是一个引用类型属性,指向一个style,并且在当前Theme中进行设置,去style.xml里面,找到我们使用的Theme,添加一条Item:

    <style name="AppTheme" parent="AppBaseTheme">
        <item name="attrViewStyleRef">@style/AttrViewStyle</item>
    </style>    
    <style name="attrViewStyleRef">
        <item name="swipeOpenOnLongPress">true</item>
        <item name="swipeAnimationTime">5000</item>
        <item name="swipeOffsetLeft">888dp</item>
    </style>

 
 

然后我们再次修改刚才获取属性的代码

TypedArray styled = getContext().obtainStyledAttributes(attrs,R.styleable.SwipeListView,R.attr.attrViewStyleRef,0);

运行后可以看到,swipeOpenOnLongPress=false, swipeAnimationTime=5000, swipeOffsetLeft=1776 //888dp

对于第三个参数,实际上用的还是比较多的,比如系统的Button,EditText,它们都会在构造函数指定第三个参数

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

提供一些参数样式,比如background,textColor等,所以我们切换不同的主题,会发现控件的样式会发生一些变化,就是因为不同的主题设置了不同的style。推演到我们自定义View,如果你的属性非常多,你也可以提供默认style,然后让用户去设置到theme里面即可。

只有defStyleAttr设置为0或者Theme中没有找到相关属性时,才会去defStyleRes中读取,defStyleAttr的优先级更高。

2)构造函数中调用初始化代码有两种方式

第一种:

  public GifImageView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init();
  }
  public GifImageView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }
  public GifImageView(Context context) {
    this(context, null);
  }
第二种:

  public GifImageView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init();
  }
  public GifImageView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
  }
  public GifImageView(Context context) {
    super(context);
    init();
}
这两种写法有什么区别呢?
一,如果需要设置obtainStyledAttributes的第三个参数,即defStyledAttr,一般使用第一种方式,会在两个参数构造中调用三个参数的构造函数,(默认调用两个参数的构造函数)同时传入defStyledAttr。如果没有此参数,两种写法没有区别。

二,继承系统已有控件去实现自定义View,比如继承Button,第一种方式会覆盖Button默认在theme里面的style(默认调用两个参数的构造函数),相对来说第二种方式更合适。

3)获取自定义属性有两种方式

第一种:

我们上面的写法

第二种:

TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyle, 0);  
        int n = a.getIndexCount();  
        for (int i = 0; i < n; i++)  
        {  
            int attr = a.getIndex(i);  
            switch (attr)  
            {  
            case R.styleable.CustomTitleView_titleText:  
                mTitleText = a.getString(attr);  
                break;  
            case R.styleable.CustomTitleView_titleTextColor:  
                mTitleTextColor = a.getColor(attr, Color.BLACK);  
                break;  
            case R.styleable.CustomTitleView_titleTextSize:  
                mTitleTextSize = a.getDimensionPixelSize(attr, 10);  
                break;  
            }  

两种写法的区别:

第一种写法,不管你有没有在布局中使用该属性,都会执行getXXX方法,第二种只有你布局中使用了该属性才会执行getXXX

假设有以下场景:

private int attr_mode = 1;  //默认为1
attr_mode = a.getInt(attr, 0);

可能你自定义属性的默认值为1,然而你根本没有在布局文件中设置这个属性,这样运行时它变成了0(而不是默认值),而第二种方法就不存在这个问题了。

还有个场景,假如你是继承某个View,父类View已经对该成员变量进行了赋值,然后你这边需要根据用户的设置去更新这个值,第一种写法如果用户没有设置,你可能就将父类的赋值给覆盖了。














你可能感兴趣的:(Android自定义属性)