一、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咯~~