Android自定义属性

一、Android自定义属性的定义、类型、使用

1.1 属性

.
自定义的属性,以及Andriod系统控件的属性,都是为了在xml进行值、类型设置,然后在View的代码中动态获取,根据这些具体的值进行适配View的相关样式属性。说白了就是为了 增加View的适配性、灵活性。

1.2 系统属性的定义

.
最常见的View属性——宽高设置
android:layout_width="wrap_content" 
android:layout_height="wrap_content" 

属性声明定义位置:framework/base/core/res/res/values/attr.xml:

    <!-- This is the basic set of layout attributes that are common to all
         layout managers.  These attributes are specified with the rest of
         a view's normal attributes (such as {@link android.R.attr#background},
         but will be parsed by the view's parent and ignored by the child.
        <p>The values defined here correspond to the base layout attribute
        class {@link android.view.ViewGroup.LayoutParams}. -->

    <!-- 这是所有常见的布局属性的基本集合。这些属性指定视图的一部分正常属性,将由视图的父代解析并由子项忽略。这里定义的值对应于基本布局属性。-->

    <declare-styleable name="ViewGroup_Layout">
        <!-- Specifies the basic width of the view.  This is a required attribute
             for any view inside of a containing layout manager.  Its value may
             be a dimension (such as "12dip") for a constant width or one of
             the special constants. -->
        <!-- 指定视图的基本宽度。这是一个必需的属性。任何视图里面含有的布局管理器。它的值可能
是一个维度(如“12dip”)为固定宽度或一特殊的常数  -->
        <attr name="layout_width" format="dimension">
            <!-- The view should be as big as its parent (minus padding).
                 This constant is deprecated starting from API Level 8 and
                 is replaced by {@code match_parent}. -->
            <!-- fill_parent为枚举类型,代表的值为-1 -->
            <enum name="fill_parent" value="-1" />
            <!-- The view should be as big as its parent (minus padding).
                 Introduced in API Level 8. -->
            <!-- match_parent为枚举类型,代表的值为-1 -->
            <enum name="match_parent" value="-1" />
            <!-- The view should be only big enough to enclose its content (plus padding). -->
            <!-- wrap_content为枚举类型,代表的值为-2 -->
            <enum name="wrap_content" value="-2" />
        </attr>

        ......
    </declare-styleable>

declare-styleable定义属性组,attr 定义属性类型,enum枚举值,为attr的子属性。

1.3 自定义属性类型:

// 引用于sanxiaochengyu(start..)

属性值说明
reference参考某一资源ID
color颜色值
boolean布尔值
dimension尺寸值
float浮点值
integer整型值
string字符串
fraction百分数
enum枚举值 多值选一
flag位或运算 多值组合

来,自定义了一组属性,用上了所有属性类型:(/res/values/attrs.xml)

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- reference:参考某一资源ID
    color:颜色值
    boolean:布尔值
    dimension:尺寸值
    float:浮点值
    integer:整型值
    string:字符串
    fraction:百分数
    enum:枚举值 多值选一
    flag:位或运算,多值组合,用“|”分隔 -->

    <declare-styleable name="CustomTextViewStyle">
        <attr name="description" format="string"/>
        <attr name="text_size" format="dimension"/>
        <attr name="text_color" format="color"/>
        <attr name="text_content" format="string"/>
        <attr name="padding" format="dimension"/>

        <attr name="float_value" format="float"/>
        <attr name="integer_value" format="integer"/>
        <attr name="boolean_value" format="boolean"/>
        <attr name="fraction_value" format="fraction"/>
        <attr name="string_value" format="string"/>
        <attr name="dimension_value" format="dimension"/>
        <attr name="color_value" format="color"/>
        <attr name="reference_drawable_value" format="reference"/>
        <attr name="reference_array_value" format="reference"/>
        <attr name="enum_value" format="enum">
            <enum name="horizontal" value="0"/>
            <enum name="vertical" value="1"/>
        </attr>
        <attr name="flag_value">
            <flag name="normal" value="0"/>
            <flag name="bold" value="1"/>
            <flag name="italic" value="2"/>
        </attr>
    </declare-styleable>
    <attr name="CustomizeStyle" format="reference"/>
</resources>

CustomTextViewStyle与CustomizeStyle二者定义方法的异同:
相同:都会在R文件attr类中生成ID

public static final class attr {
    public static final int CustomizeStyle=0x7f010000;
    public static final int description=0x7f010041;
    public static final int text_size=0x7f010042;
    public static final int text_color=0x7f010043;
    public static final int text_content=0x7f010044;
    public static final int padding=0x7f010045;
    public static final int float_value=0x7f010046;
    public static final int integer_value=0x7f010047;
    public static final int boolean_value=0x7f010048;
    public static final int fraction_value=0x7f010049;
    public static final int string_value=0x7f01004a;
    public static final int dimension_value=0x7f01004b;
    public static final int color_value=0x7f01004c;
    public static final int reference_drawable_value=0x7f01004d;
    public static final int reference_array_value=0x7f01004e;
    public static final int enum_value=0x7f01004f;
    public static final int flag_value=0x7f010050;

}

不同:通过< declare-styleable>标签定义的属性还会在R文件styleable类中生成相关属性

public static final class styleable {
    public static final int[] CustomTextViewStyle = {
            0x7f010041, 0x7f010042, 0x7f010043, 0x7f010044,
            0x7f010045, 0x7f010046, 0x7f010047, 0x7f010048,
            0x7f010049, 0x7f01004a, 0x7f01004b, 0x7f01004c,
            0x7f01004d, 0x7f01004e, 0x7f01004f, 0x7f010050
        };
    public static final int CustomTextViewStyle_description = 0;    
    public static final int CustomTextViewStyle_text_size = 1;
    public static final int CustomTextViewStyle_text_color = 2;
    public static final int CustomTextViewStyle_text_content = 3;
    public static final int CustomTextViewStyle_padding = 4;
    public static final int CustomTextViewStyle_float_value = 5;
    public static final int CustomTextViewStyle_integer_value = 6;
    public static final int CustomTextViewStyle_boolean_value = 7;
    public static final int CustomTextViewStyle_fraction_value = 8;
    public static final int CustomTextViewStyle_string_value = 9;
    public static final int CustomTextViewStyle_dimension_value = 10;
    public static final int CustomTextViewStyle_color_value = 11;
    public static final int CustomTextViewStyle_reference_drawable_value = 12;
    public static final int CustomTextViewStyle_reference_array_value = 13;
    public static final int CustomTextViewStyle_enum_value = 14;
    public static final int CustomTextViewStyle_flag_value = 15;

 }

通过< declare-styleable>标签定义的属性,在styleable类中生成了一个数组,数组下标索引用别名进行了定义(如“CustomTextViewStyle_description = 0”,数组中的值与attr类中的值一一对应)
在后面取值时会用到这个数组和下标索引值,如果是直接通过attr标签定义的属性,可以自己构建这个数组,然后获取值

// 引用于sanxiaochengyu(..end)

1.4 自定义属性的使用:

1.4.1 属性的定义1.3有讲,这里忽略
1.4.2 布局调用

<?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">
    <com.businesstravel.widget.autoScrollViewPager.AutoScrollPoint
         android:id="@+id/scroll_point"
         android:layout_width="match_parent"
         app:point_layout1="right"
         android:layout_height="120dp"/>
</RelativeLayout>

创建布局文件的时候,系统会在根布局上自动生成xmlns:android=”http://schemas.android.com/apk/res/android”
若删除,则每行android:就会报错,所以其实我们使用的android: 就是来自这行声明,这个声明其实是命名空间

  • xmlns:android的是系统的
  • 我们自己定义的控件属性需要手动声明:xmlns:app=”http://schemas.android.com/apk/res-auto”
    以前还有个包名的写法:xmlns:app=”http://schemas.android.com/apk/res/包名”
  • 还有第三种命名空间xmlns:tools=”http://schemas.android.com/tools”
    详细用法请参考:精通 Android 中的 tools 命名空间

1.4.3 自定义View代码调用

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

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

    public AutoScrollPoint(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.AutoScrollPoint, defStyleAttr, 0);
        int pointLayout = typedArray.getInteger(R.styleable.AutoScrollPoint_point_layout, 3);
        switch (pointLayout) {
            case LAYOUT_RIGHT:
                view = LayoutInflater.from(context).inflate(R.layout.point_right_text, null);
                break;
            case LAYOUT_CENTER:
                view = LayoutInflater.from(context).inflate(R.layout.point_center_text, null);
                break;
            default:
                view = LayoutInflater.from(context).inflate(R.layout.point_center_text, null);
                break;
        }

        typedArray.recycle();
        init(context);
    }

其实xml里设置的属性值,都存在了AttributeSet之中,通过context.obtainStyledAttributes方法,将xml中设置的属性与定义的属性数组AutoScrollPoint进行匹配,得到了TypedArray,通过属性id获取对应的属性值。由于每个xml生成的View都创建一个TypedArray对象(内部需要创建几个数组),所以放入SynchronizedPool池中以便复用(5个),用完之后记得recycle释放资源方便它View。

TypedArray通过id获取属性的时候,借助了TypedValue,其中包含各种数据之间的转换方法,applyDimension ……


二、 View构造方法调用场景:

看看View源码的四个构造函数:

2.1 第一个构造函数

    /** 
     * Simple constructor to use when creating a view from code. 
     * 
     * @param context The Context the view is running in, through which it can 
     *        access the current theme, resources, etc. 
     */  
    public View(Context context) 

简单的构造方法,在代码中直接创建的时候会调用此方法(直接new)

2.2 第二个构造函数

    /** 
    * Constructor that is called when inflating a view from XML. This is called 
    * when a view is being constructed from an XML file, supplying attributes 
    * that were specified in the XML file. This version uses a default style of 
    * 0, so the only attribute values applied are those in the Context's Theme 
    * and the given AttributeSet. 
    * 
    * <p> 
    * The method onFinishInflate() will be called after all children have been 
    * added. 
    * 
    * @param context The Context the view is running in, through which it can 
    *        access the current theme, resources, etc. 
    * @param attrs The attributes of the XML tag that is inflating the view. 
    * @see #View(Context, AttributeSet, int) 
    */  
   public View(Context context, @Nullable AttributeSet attrs)  

从xml进行inflating创建view的时候会调用,支持attributes ,default style 为 0 ,所以只有体统主题以及给的AttributeSet中的attribute 能被使用。

2.3 第三个构造函数

    /** 
     * Perform inflation from XML and apply a class-specific base style from a 
     * theme attribute. This constructor of View allows subclasses to use their 
     * own base style when they are inflating. For example, a Button class's 
     * constructor would call this version of the super class constructor and 
     * supply <code>R.attr.buttonStyle</code> for <var>defStyleAttr</var>; this 
     * allows the theme's button style to modify all of the base view attributes 
     * (in particular its background) as well as the Button class's attributes. 
     * 
     * @param context The Context the view is running in, through which it can 
     *        access the current theme, resources, etc. 
     * @param attrs The attributes of the XML tag that is inflating the view. 
     * @param defStyleAttr An attribute in the current theme that contains a 
     *        reference to a style resource that supplies default values for 
     *        the view. Can be 0 to not look for defaults. 
     * @see #View(Context, AttributeSet) 
     */  
    public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr)  

从XML解析创建View并且应用特定的主题风格属性。这个构造方法,允许所有子类在通过xml创建View的时候使用它们自己的样式。举个栗子:

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

    public Button(Context context, AttributeSet attrs) {
        // 系统View会调用R.attr.buttonStyle作为默认StyleAttr
        this(context, attrs, com.android.internal.R.attr.buttonStyle);
    }

    public Button(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

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

可以看到,Button继承了TextView之后,仅仅是添加了一个默认的Style:
com.android.internal.R.attr.buttonStyle。我们知道,button其实在TextView的基础之上增加了按钮的背景效果以及按钮按下去的press效果。这么一个Style文件可以搞定这件事情么?顺着这个style找下去,在Android的源码中找到style.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:textSize">20sp</item>   
    <item name="android:textStyle">normal</item>   
    <item name="android:textColor">@android:color/button_text</item>   
    <item name="android:gravity">center_vertical|center_horizontal</item>  
</style> 

到drwable目录中发现这个btn_default原来也是一个xml文件,内容如下:

<selector xmlns:android="http://schemas.android.com/apk/res/android">   
     <item android:state_window_focused="false" android:state_enabled="true"   
        android:drawable="@drawable/btn_default_normal" />   
     <item android:state_window_focused="false" android:state_enabled="false"   
        android:drawable="@drawable/btn_default_normal_disable" />   
    <item android:state_pressed="true"    
        android:drawable="@drawable/btn_default_pressed" />   
    <item android:state_focused="true" android:state_enabled="true"   
        android:drawable="@drawable/btn_default_selected" />   
    <item android:state_enabled="true"   
        android:drawable="@drawable/btn_default_normal" />   
    <item android:state_focused="true"   
        android:drawable="@drawable/btn_default_normal_disable_focused" />   
    <item android:drawable="@drawable/btn_default_normal_disable" />   
</selector>  
2.3.1 统一修改样式:
   <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="buttonStyle">@style/MineButton</item>
    </style>

    <style name="MineButton" parent="android:style/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:textSize">80sp</item>
        <item name="android:textStyle">normal</item>
        <item name="android:textColor">@color/colorAccent</item>
        <item name="android:gravity">center_vertical|center_horizontal</item>
    </style>
2.3.2 ☆自定义defStyleAttr☆

第一步:attrs.xml定义属性:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr format="reference" name="MimeStyle"/>
</resources>

第二步:styles.xml定义属性并赋值给AppTheme

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="MimeStyle">@style/MineButton</item>
    </style>

    <style name="MineButton" parent="android:style/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:textSize">80sp</item>
        <item name="android:textStyle">normal</item>
        <item name="android:textColor">@color/colorAccent</item>
        <item name="android:gravity">center_vertical|center_horizontal</item>
    </style>

</resources>

第三步:调用

public class MineButton extends android.support.v7.widget.AppCompatButton{


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

        public MineButton(Context context, AttributeSet attrs) {
        this(context, attrs, R.attr.MimeStyle);
    }

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

}

搞定~~~

2.4 第四个构造函数

    /**
     * Perform inflation from XML and apply a class-specific base style from a
     * theme attribute or style resource. This constructor of View allows
     * subclasses to use their own base style when they are inflating.
     * <p>
     * When determining the final value of a particular attribute, there are
     * four inputs that come into play:
     * <ol>
     * <li>Any attribute values in the given AttributeSet.
     * <li>The style resource specified in the AttributeSet (named "style").
     * <li>The default style specified by <var>defStyleAttr</var>.
     * <li>The default style specified by <var>defStyleRes</var>.
     * <li>The base values in this theme.
     * </ol>
     * <p>
     * Each of these inputs is considered in-order, with the first listed taking
     * precedence over the following ones. In other words, if in the
     * AttributeSet you have supplied <code><Button * textColor="#ff000000"></code>
     * , then the button's text will <em>always</em> be black, regardless of
     * what is specified in any of the styles.
     *
     * @param context The Context the view is running in, through which it can
     *        access the current theme, resources, etc.
     * @param attrs The attributes of the XML tag that is inflating the view.
     * @param defStyleAttr An attribute in the current theme that contains a
     *        reference to a style resource that supplies default values for
     *        the view. Can be 0 to not look for defaults.
     * @param defStyleRes A resource identifier of a style resource that
     *        supplies default values for the view, used only if
     *        defStyleAttr is 0 or can not be found in the theme. Can be 0
     *        to not look for defaults.
     * @see #View(Context, AttributeSet, int)
     */
    public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)

defStyleRes:为视图提供默认值的样式资源的资源标识符,仅在defStyleAttr为0或在主题中找不到时使用。可以为0,不查找默认值。(目前还没发现具体的使用例子)

2.5 自定义View构造方法this、super选择:

在自定义View的时候需要重写几个构造方法,这几个构造函数又有两种常见的写法(this与super):

2.51 super调用
public class MyView extends ListView {
    public MyView(Context context) {
        super(context);
        sharedConstructor();
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        sharedConstructor();
    }

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

    private void sharedConstructor() {
        // Do some initialize work.
    }
}
2.52 this调用
public class MyView extends ListView {
    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);

        // Other initialize work.
    }
}

从2.3可以得出结论了,最好使用第一种(super),如果用第二种方式Button就变成TextView了。


三、Java构造体系中的suepr与this(结论较为大胆,欢迎探讨)

直接上图:
这里写图片描述

得到那个耳熟能详的结论:

(1)this、super要放在第一行 (2)this、super不能同时使用
——虽然从逻辑上讲,因为this、super都只能放在第一行,所以this、super不能同时使用,但是究其根本,是什么原因呢?

这个问题先放着,先来看看Java的覆盖:

    package com.touch.Test;  

    //父类  
    class FatherClass {  
      public int value;   
      public void getValue(){  
          System.out.println("FatherClass: "+this.value);  
          System.out.println("this is:"+this);  
      }  
    }  

    //子类  
    class ChildClass extends FatherClass {  
      public int value = 100;  
      public void getValue(){  
          System.out.println("ChildClass: "+this.value);  
          System.out.println("this is:"+this);  
          super.getValue();  
      }  
    }  

    //孙子类,  
    class SunziClass extends ChildClass {  
      public int value = 200;  
      public void getValue(){  
          System.out.println("SunziClass: "+this.value);  
          System.out.println("this is:"+this);  
          super.getValue();  
      }  
    }  

    //这儿是调用的Main  
    public class TestInherit {  
        public static void main(String[] args) {  
                    SunziClass cc = new SunziClass();  
                    cc.getValue();  
       }  
    }  

结果为:

    SunziClass: 200  
    this is:com.touch.Test.SunziClass@2f57d162  
    ChildClass: 100  
    this is:com.touch.Test.SunziClass@2f57d162  
    FatherClass: 0  
    this is:com.touch.Test.SunziClass@2f57d162  

可以看到其实父类中被覆盖的属性以及方法都还在,还可以调用,如果子类没有这个方法,则会调用父类的此方法,那么可以说在优先级上子类的属性以及方法高与父类的,所以达到了貌似“覆盖”的效果。

总结:

结论一:

父类中被覆盖的属性以及方法都还在,在优先级上子类的属性以及方法高与父类的,所以达到了貌似“覆盖”的效果。

我们知道在同一个类中编写同名同参的方法编译器会报错。

结论二:

子类中其实存在着父类对象,父类存在着他自己的父类对象,直至Object对象。Java的继承其实是深度组合的实现。(个人猜想)

在子类的构造函数中,如果没有显式使用super() , 并且第一行没有使用this()调用子类的构造函数, 那么编译器就会自动在第一行补齐super()来调用父类的默认构造函数, 如果父类没有默认的构造函数就会报错.

结论三:

构造方法里的super就是父类初始化时选用的构造方法,而this调用只是方法调用。

有图为证:
这里写图片描述

那为何this、super不能同时使用呢?

结论四:

因为this调用到最后那个构造方法必定不会再有this调用(不然就是递归调用),如果没有手动super调用,则有默认的super()无参调用。此中间如果再有super调用,就会出现父类初始化两次的情况,故不能。

那为何this、super要放在第一行?

结论五:

如果先调用父类独有的方法,而父类又未能初始化,则bug咯~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值