本文主要是学习:
http://mp.weixin.qq.com/s?__biz=MzAxMTI4MTkwNQ==&mid=2650820236&idx=1&sn=6dec4ff1efeda3224b5a40fdad862404#rd
http://blog.csdn.net/lmj623565791/article/details/45022631
总结的。
源码地址:
https://github.com/aloe-all/CustomView-1-
自定义View 的基本步骤:
1、在 res/values/attrs.xml 自定义View(CostomView)属性
2、在CostomView.java 的构造方法中获取自定义View的属性
3、onMesure //测量View的大小
4、onLayout //view 在屏幕展现的位置
5、onDraw //把view 画出来,展示在手机屏幕上
一、在 res/values/attrs.xml 自定义View(CostomView)属性
res/values/attrs.xml
<resources>
<declare-styleable name="CustomView">
<attr name="text" format="string"/>
<attr name="textsize" format="integer"/>
<attr name="textcolor" format="integer"/>
declare-styleable>
resources>
在CostomView.java 的构造方法中获取自定义View的属性
public class CustomView extends View {
private static final String TAG = CustomView.class.getSimpleName();
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
//拿到第一个自定义属性
String str = typedArray.getString(R.styleable.CustomView_text);
//拿到第二个自定义属,如果没有就赋值默认值10
int textSize = typedArray.getInteger(R.styleable.CustomView_textsize, 10);
//拿到第三个自定义属,如果没有就赋值默认值5
int textColor = typedArray.getInteger(R.styleable.CustomView_textcolor, 5); //拿到第一个自定义属
//xml布局文件的值通过Log打出来
Log.d(TAG, ">>>>>>str: " + str);
Log.d(TAG, ">>>>>>textSize: " + textSize);
Log.d(TAG, ">>>>>>textColor: " + textColor);
//资源回收
typedArray.recycle();
}
}
使用自定义的属性:
xmlns:custom="http://schemas.android.com/apk/res-auto"
这个是命名空间声明,android studio 这样下;
eclipse 这样写:
xmlns:custom="http://schemas.android.com/apk/com.example.crg.customview1_attributes"
<com.example.crg.customview1_attributes.CustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
custom:text="helloworld"
custom:textcolor="6"
custom:textsize="7"
/>
程序运行结果如下:
06-09 03:12:42.084 1873-1873/com.example.crg.customview1_attributes D/CustomView: >>>>>>str: helloworld
06-09 03:12:42.084 1873-1873/com.example.crg.customview1_attributes D/CustomView: >>>>>>textSize: 7
06-09 03:12:42.084 1873-1873/com.example.crg.customview1_attributes D/CustomView: >>>>>>textColor: 6
我们已经在 自定义View(CostomView)里拿到了 xml里设定的值了,然后就可以,使用这些值,测量,布局,绘制出来了;
public CustomView(Context context, AttributeSet attrs) {
点进去看源码的解释:
A collection of attributes, as found associated with a tag in an XML
* document. Often you will not want to use this interface directly, instead
* passing it to {@link android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
* Resources.Theme.obtainStyledAttributes()}
* which will take care of parsing the attributes for you. In particular,
* the Resources API will convert resource references (attribute values such as
* "@string/my_label" in the original XML) to the desired type
* for you; if you use AttributeSet directly then you will need to manually
* check for resource references
* (with {@link #getAttributeResourceValue(int, int)}) and do the resource
* lookup yourself if needed. Direct use of AttributeSet also prevents the
* application of themes and styles when retrieving attribute values.
public interface AttributeSet {
就是我们自定义 CustomView(Context context, AttributeSet attrs) 在布局文件里 使用的属性的集合;上面说的很清楚,我们使用
android.content.res.Resources.Theme#obtainStyledAttributes
自动为我们解析为特定类型的属性值;
if you use directly then you will need to manually
* check for resource references
如果我们直接使用AttributeSet API 获取属性值,如果是引用类型的值,需要我们自己人为解析特定类型的值@string/my_label。
demo 如下:
<com.example.crg.customview1_attributes.CustomView
android:layout_width="100dp"
android:layout_height="100dp"
custom:text="helloworld"
custom:textcolor="6"
custom:textsize="7"
/>
打印结果:
D/CustomView: >>>>>>>>>>>>>>>>> attrCount: 5
D/CustomView: key: layout_width >>>>>>>>> value: 100.0dip
D/CustomView: key: layout_height >>>>>>>>> value: 100.0dip
D/CustomView: key: text >>>>>>>>> value: helloworld
D/CustomView: key: textsize >>>>>>>>> value: 7
D/CustomView: key: textcolor >>>>>>>>> value: 6
结果一目了然,拿到了布局文件里 传入的5个属性,这5个属性传的都是具体的值,
然后看一个传入引用类型的demo:
<com.example.crg.customview1_attributes.CustomView
android:layout_width="match_parent"
android:layout_height="wrap_content"
custom:text="@string/demo_txt"
custom:textcolor="@color/demo_textcolor"
custom:textsize="@dimen/demo_size"
/>
结果如下:
D/CustomView: >>>>>>>>>>>>>>>>> attrCount: 5
D/CustomView: key: layout_width >>>>>>>>> value: -1
D/CustomView: key: layout_height >>>>>>>>> value: -2
D/CustomView: key: text >>>>>>>>> value: @2131099669
D/CustomView: key: textsize >>>>>>>>> value: @2131230789
D/CustomView: key: textcolor >>>>>>>>> value: @2131427349
这句注释写的很清楚:
if you use AttributeSet directly then you will need to manually
* check for resource references
* (with {@link #getAttributeResourceValue(int, int)}) and do the resource
* lookup yourself if needed.
只需要两步:
int resValue = attrs.getAttributeResourceValue(i, 0);
"value: " + getResources().getString(resValue)
就能拿到 属性值了,
int attrCount = attrs.getAttributeCount();
Log.d(TAG, ">>>>>>>>>>>>>>>>> attrCount: " + attrCount);
for (int i = 0; i < attrCount; i++) {
String key = attrs.getAttributeName(i);
String value = attrs.getAttributeValue(i);
Log.d(TAG, "key: " + key + " >>>>>>>>> " + "value: " + value);
int resValue = attrs.getAttributeResourceValue(i, 0);
if (i == 2) {
Log.d(TAG, "key: " + key + " >>>>>getAttributeResourceValue()>>>> " + "value: " + getResources().getString(resValue));
}
}
结果如下:
D/CustomView: >>>>>>>>>>>>>>>>> attrCount: 5
D/CustomView: key: layout_width >>>>>>>>> value: -1
D/CustomView: key: layout_height >>>>>>>>> value: -2
D/CustomView: key: text >>>>>>>>> value: @2131099669
D/CustomView: key: text >>>>>getAttributeResourceValue()>>>> value: helloworld
D/CustomView: key: textsize >>>>>>>>> value: @2131230789
D/CustomView: key: textcolor >>>>>>>>> value: @2131427349
总结:
CustomView(Context context, AttributeSet attrs) AttributeSet attrs里面包含所有的CustomView布局文件里的属性,若果使用 AttributeSet 解析属性值,布局文件里如果有引用类型的,例如
custom:text="@string/demo_txt"
就需要两步,才能得到 属性值;而:
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomView)
TypedArray 就像注释里写的,自动解析为特定类型的值,简化了我们的解析工作。
例如使用 android:text 属性:
<resources>
<declare-styleable name="CustomView">
<attr name="android:text"/>
<attr name="textsize" format="integer|reference"/>
<attr name="textcolor" format="integer|reference"/>
declare-styleable>
resources>
上面是声明属性,用android 已有的属性,不需要 format
使用如下:直接android:text
<com.example.crg.customview1_attributes.CustomView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/demo_txt"
custom:textcolor="@color/demo_textcolor"
custom:textsize="@dimen/demo_size"
/>
在自定义CustomView 里的构造方法中这样获取:
//拿到第一个自定义属性
String str = typedArray.getString(R.styleable.CustomView_android_text);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
//拿到第一个自定义属性
String str = typedArray.getString(R.styleable.CustomView_android_text);
//拿到第二个自定义属,如果没有就赋值默认值10
int textSize = typedArray.getInteger(R.styleable.CustomView_textsize, 10);
//拿到第三个自定义属,如果没有就赋值默认值5
int textColor = typedArray.getInteger(R.styleable.CustomView_textcolor, 5); //拿到第一个自定义属
public final TypedArray obtainStyledAttributes(
AttributeSet set, @StyleableRes int[] attrs) {
return getTheme().obtainStyledAttributes(set, attrs, 0, 0);
}
实际调用的是:getTheme().obtainStyledAttributes(set, attrs, 0, 0);
最终调用这个方法:
public TypedArray obtainStyledAttributes(AttributeSet set,
@StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes)
次方法总共有四个参数,前两个参数,上面已经熟悉了;下来看第四个参数:defStyleRes
第四个参数:
@param defStyleRes A resource identifier of a style resource that
* supplies default values for the TypedArray,
* used only if defStyleAttr is 0 or can not be found
* in the theme. Can be 0 to not look for defaults.
源码注释的解释:当第三个参数为0,或者在 theme 找不到时,才会为 TypedArray 提供默认值。
在 res/valus/styles.xml 中声明
构造方法中使用:
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomView, 0, R.style.customview_style);
String str = null;
int textSize = 0;
int textColor = 0;
int n = typedArray.getIndexCount();
for (int i = 0; i < n; i++){
switch (typedArray.getIndex(i)){
case R.styleable.CustomView_android_text:
str = typedArray.getString(typedArray.getIndex(i));
break;
case R.styleable.CustomView_textsize:
textSize = typedArray.getInteger(typedArray.getIndex(i), 1);
break;
case R.styleable.CustomView_textcolor:
textColor = typedArray.getInteger(typedArray.getIndex(i), 1);
break;
}
}
Log.d(TAG, ">>>>>>>>>>>>>>>>> str: " + str);
Log.d(TAG, ">>>>>>>>>>>>>>>>> textSize: " + textSize);
Log.d(TAG, ">>>>>>>>>>>>>>>>> textColor: " + textColor);
xml布局文件什么都没声明,如下:
<com.example.crg.customview1_attributes.CustomView
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
运行结果如下:
D/CustomView: >>>>>>>>>>>>>>>>> str: test test
D/CustomView: >>>>>>>>>>>>>>>>> textSize: 10
D/CustomView: >>>>>>>>>>>>>>>>> textColor: 9
结果和我们在第四个参数中传的值一样 R.style.customview_style
当在使用自定义view 没有给定 值时,就会使用第四个参数的默认值。
第三个参数:
@param defStyleAttr:
An attribute in the current theme that contains a
reference to a style resource that supplies
defaults values for the TypedArray. Can be
0 to not look for defaults.
defStyleAttr
当前主题的一个attribute,并且当前主题包含了一个 style resource 的应用, style resource 的应用 为 TypedArray 提供默认值。
实例如下:
<resources>
<declare-styleable name="CustomView">
<attr name="android:text"/>
<attr name="textsize" format="integer|reference"/>
<attr name="textcolor" format="integer|reference"/>
declare-styleable>
<attr name="defStyleAttr_custom" format="reference"/>
resources>
-- Base application theme. -->
<com.example.crg.customview1_attributes.CustomView
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomView, R.attr.defStyleAttr_custom, R.style.customview_style);
String str = null;
int textSize = 0;
int textColor = 0;
int n = typedArray.getIndexCount();
for (int i = 0; i < n; i++){
switch (typedArray.getIndex(i)){
case R.styleable.CustomView_android_text:
str = typedArray.getString(typedArray.getIndex(i));
break;
case R.styleable.CustomView_textsize:
textSize = typedArray.getInteger(typedArray.getIndex(i), 1);
break;
case R.styleable.CustomView_textcolor:
textColor = typedArray.getInteger(typedArray.getIndex(i), 1);
break;
}
}
Log.d(TAG, ">>>>>>>>>>>>>>>>> str: " + str);
Log.d(TAG, ">>>>>>>>>>>>>>>>> textSize: " + textSize);
Log.d(TAG, ">>>>>>>>>>>>>>>>> textColor: " + textColor);
//xml布局文件的值通过Log打出来
//资源回收
typedArray.recycle();
}
D/CustomView: >>>>>>>>>>>>>>>>> str: defStyleAttr defStyleAttr
D/CustomView: >>>>>>>>>>>>>>>>> textSize: 8
D/CustomView: >>>>>>>>>>>>>>>>> textColor: 7
结果一幕了然,当第三个参数不为:0时,第四个参数不起作用。,只有defStyleAttr设置为0或者在当前的theme中没有找到相关属性时,才会去defStyleRes中读取,那么很明显优先级是defStyleAttr更高
那么对于第三个参数呢,实际上用的还是比较多的,比如看系统的Button,EditText,它们都会在构造里面指定第三个参数:
提供一些参数的样式,比如background,textAppearance,textColor等,所以当我们切换不同的主题时,你会发现控件的样式会发生一些变化,就是因为不同的主题,设置了一些不同的style。
那么推演到我们自定义的View,如果你的属性非常多,你也可以去提供默认的style,然后让用户去设置到theme里面即可。