1.自定义View
自定义View三部曲:onMeasure、onLayout、onDraw。
注意:自定义ViewGroup可以不重写onDraw,自定义View可以不重写onLayout。
2.构造方法和自定义属性
1)构造方法
View有四种形式的构造方法,它们的参数不同分别对应不同的创建方式。
只有一个Context参数的构造方法通常是通过代码初始化控件时使用。
两个参数的构造方法通常对应布局文件中控件被映射成对象时调用(需要解析属性)。
三个参数的构造方法也在布局文件中使用,但是会有style。
通常可以让前两个构造方法最终调用三个参数的构造方法,然后在第三个构造方法中进行一些初始化操作。
在res/values下有一个styles.xml,可以在里面定义需要的style:
< style name=“mystyle”>
< item name=“layout_width”>wrap_content</ item>
< item name=“layout_height”>wrap_content</ item>
</ style>
定义好style以后,只要在布局里用到宽高均为wrap_content的地方,均可以用style代替:
style="@style/mystyle"
2)自定义属性
系统已经自带一些属性,系统定义的所有属性在\sdk\platforms\Android-xx\data\res\values\attrs.xml这个文件里面。
因为所有控件都是View的子类,所以为View定义的属性所有的控件都能使用。如果自定义控件不自定义属性,就只能使用View的属性。
为了扩展一些属性,就需要自定义属性。自定义属性首先需要在res/values下面新建attrs.xml文件。attrs.xml文件中的属性,都是以declare-styleable为一个组合。
自定义属性有两种形式,这两种形式的区别就是attr标签后面带不带format属性。如果带format就是在定义属性,如果不带format是在使用已有的属性。name的值就是属性的名字,format是限定当前定义的属性能接受什么值。
比如系统已经定义了android:text属性,如果自定义控件也需要一个文本的属性,可以有两种方式:
第一种:不用系统定义的属性,自己定义一个名为text或者mText的属性(属性名称可以随便起的,不能使用系统已有的属性名称???待验证)
< resources>
< declare-styleable name=“MyTextView”>
<attr name=“text" format=“string” />
< /declare-styleable>
< /resources>
第二种:使用系统已经定义的text的属性,不用自己定义,只需要在自定义属性中申明要使用这个text属性。(注意加上android命名空间,这样才知道使用的是系统的text属性)
< resources>
< declare-styleable name=“MyTextView”>
<attr name=“android:text"/>
< /declare-styleable>
< /resources>
为什么系统定义了此属性,我们在使用的时候还要声明呢?因为系统定义的text属性是给TextView使用的,如果不声明就不能使用text属性。
属性值的类型format一共有11种:
①reference:参考某一资源ID
使用:
②color:颜色值
使用:
③boolean:布尔值
使用:
④dimension:尺寸值
使用:
⑤float:浮点值
使用:
⑥integer:整型值
使用:
⑦string:字符串
使用:
⑧fraction:百分数
使用:
⑨enum:枚举值
< declare-styleable name=“名称”>
< attr name=“orientation”>
< enum name=“horizontal” value=“0” />
< enum name=“vertical” value=“1” />
< /attr>
< /declare-styleable>
使用:
< LinearLayout
android:orientation = “vertical”>
< /LinearLayout>
注意:枚举类型的属性在使用的过程中只能同时使用其中一个,不能android:orientation = “horizontal|vertical"
⑩flag:位或运算
< declare-styleable name=“名称”>
< attr name=“gravity”>
< flag name=“top” value=“0x30” />
< flag name=“bottom” value=“0x50” />
< flag name=“left” value=“0x03” />
< flag name=“right” value=“0x05” />
< flag name=“center_vertical” value=“0x10” />
…
< /attr>
< /declare-styleable>
使用:
< TextView android:gravity=“bottom|left”/>
注意:位运算类型的属性在使用的过程中可以使用多个值。
⑩混合类型:属性定义时可以指定多种类型值
< declare-styleable name = “名称”>
< attr name = “background” format = “reference|color” />
< /declare-styleable>
使用:
< ImageView
android:background = “@drawable/图片ID” />
或者:
< ImageView
android:background = “#00FF00” />
在布局文件中使用属性的时候,前面都带有一个android:,这个android就是引入的命名空间xmlns:android="http://schemas.android.com/apk/res/android”,表示到android系统中查找该属性来源。只有引入了命名空间,XML文件才知道下面使用的属性应该去哪里找。
如果自定义属性,这个属性应该去应用程序包中找,所以要引入应用包的命名空间xmlns:testdemo="http://schemas.android.com/apk/res-auto”,res-auto表示自动查找,还有一种写法xmlns:testdemo=“http://schemas.android.com/apk/com.example.test.myview”,其中com.example.test.myview是应用程序包名。
举例:先定义一些属性,并写好布局文件。
①首先在res/values目录下创建attrs.xml,自定义需要的属性:
< ?xml version=“1.0” encoding=“utf-8”?>
< resources>
< declare-styleable name=“MyTextView”>
//声明MyTextView需要使用系统定义过的text属性,注意前面需要加上android命名
< attr name=“android:text” />
< attr name=“mTextColor” format=“color” />
< attr name=“mTextSize” format=“dimension” />
< /declare-styleable>
< /resources>
②然后,在布局文件中,使用属性(注意引入应用程序的命名空间,这样才能找到自定义的属性):
< LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android” xmlns:testdemo=“http://schemas.android.com/apk/res-auto”
android:orientation=“horizontal”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
<com.example.testdemo.myview.MyTextView
android:layout_width=“200dip”
android:layout_height=“100dip”
testdemo:mTextSize=“25sp”
android:text=“我是文字”
testdemo:mTextColor ="#0000ff"
android:background="#ff0000"/>
< /LinearLayout>
③最后,需要在构造方法中获取属性值:
public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyTextView);
String text = ta.getString( R.styleable.MyTextView_android_text);
int mTextColor = ta.getColor( R.styleable.MyTextView_mTextColor, Color.BLACK);
int mTextSize = ta.getDimensionPixelSize( R.styleable.MyTextView_mTextSize, 100);
ta.recycle(); //注意回收
}
到此为止,自定义属性就完成了。
AttributeSet和TypedArray这两个类是怎么把属性值从布局文件中解析出来的?
AttributeSet是属性的集合,实际上它内部就是一个XML解析器,可以将布局文件中控件的所有属性解析出来,并以key-value键值对的形式维护起来。完全可以只用它通过下面的代码来获取自定义的属性。
public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
int count = attrs.getAttributeCount();
for (int i = 0; i < count; i++) {
String attrName = attrs.getAttributeName(i);
String attrVal = attrs.getAttributeValue(i);
Log.e(tag, “attrName = " + attrName + " , attrVal = " + attrVal);
}
}
log输出:
attrName = background , attrVal = @2131427347
attrName = layout_width , attrVal = 200.0dip
attrName = layout_height , attrVal = 100.0dip
attrName = text , attrVal = 我是文字
attrName = mTextSize , attrVal = 25sp
attrName = mTextColor , attrVal = #0000ff
通过AttributeSet获取属性值时,它会将布局文件中的值原原本本的获取出来,比如宽度为200.0dip,如果使用宽度值,还需要将dip去掉然后转换成整形,太麻烦。而且backgroud是一个color资源ID,它直接取到了这个ID值,前面还加了@,接下来要自己获取资源,并通过这个ID值获取到真正的颜色,特别不方便。
这时候就需要使用TypedArray。
在这里穿插一个知识点,定义属性的时候有一个declare-styleable,它是用来干嘛的,如果不要可不可以?答案是可以的,自定义属性完全可以写成下面的形式:
< ?xml version=“1.0” encoding=“utf-8”?>
< resources>
< attr name=“mTextColor” format=“color” />
< attr name=“mTextSize” format=“dimension”/>
< /resources>
所有的资源文件在R中都对应一个整型常量,可以通过这个ID值找到资源文件。属性在R中对应的类是public static final class attr,如果写了declare-styleable,在R文件中就会生成styleable类,这个类其实就是将每个控件的属性分组,然后记录属性的索引值,而TypedArray正好需要通过此索引值获取属性。
public static final class styleable
public static final int[] MyTextView = {
0x0101014f, 0x7f010038, 0x7f010039
};
public static final int MyTextView_android_text = 0;
public static final int MyTextView_mTextColor = 1;
public static final int MyTextView_mTextSize = 2;
}
使用TypedArray获取属性值:
public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta =context.obtainStyledAttributes( attrs, R.styleable.MyTextView);
String mText = ta.getString( R.styleable.MyTextView_android_text);
int mTextColor = ta.getColor( R.styleable.MyTextView_mTextColor, Color.BLACK);
int mTextSize = ta.getDimensionPixelSize( R.styleable.MyTextView_mTextSize, 100);
ta.recycle(); //注意回收
Log.v(”", “mText:”+mText);
Log.v("", “mTextColor:”+mTextColor);
Log.v("", “mTextSize:”+mTextSize);ext, 0, mText.length(), mBound);
}
log输出:
mText:我是文字
mTextColor:-16777216
mTextSize:100
这样就得到了想要的宽高(float型)、背景颜色(color的十进制)等。TypedArray提供了一系列获取不同类型属性的方法,可以直接得到需要的数据类型。
注意:TypedArray使用完毕后要调用ta.recycle();回收 。
3.onMeasure度量
①MeasureSpec
译为“测量规格”或“测量参数”,MeasureSpec是View中的一个静态内部类,由尺寸和模式组成,它封装了从父容器传递给子容器的布局要求。更准确的说MeasureSpec是由父view的MeasureSpec和子view的LayoutParams通过计算得出的一个针对子view的测量要求,而子view的LayoutParams就是在xml里设置的layout_width和layout_height转化而来的。
MeasureSpec源码:
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT; // 父控件不强加任何约束给子控件,它可以是它想要的任何大小
public static final int EXACTLY = 1 << MODE_SHIFT; // 父控件为子控件确定了一个确切的大小,子控件将被