自定义UI控件
系统是如何定义UI控件的?
1. 首先我们在布局文件中定义了一个布局
我们自定义的布局(其中声明了命名空间为xmlns后面的内容)
<View
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/view"
android:visibility="visible"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
View的属性其实是在attrs.xml中声明的
<?xml version="1.0" encoding="utf-8"?>
<resources>
......
<declare-styleable name="View">
<attr name="id" format="reference" />
<attr name="visibility">
<!-- Visible on screen; the default value. -->
<enum name="visible" value="0" />
<!-- Not displayed, but taken into account during layout (space is left for it). -->
<enum name="invisible" value="1" />
<!-- Completely hidden, as if the view had not been added. -->
<enum name="gone" value="2" />
</attr>
</declare-styleable>
......
</resources>
2. 接着,让我们看看View类是如何获取我们在布局中定义的属性的值
在View.java的构造方法中是这样的
public View(Context context, AttributeSet attrs, int defStyle) {
this(context);
TypedArray a = context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.View, defStyle, 0);
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
switch (attr) {
......
case com.android.internal.R.styleable.View_background:
background = a.getDrawable(attr);
break;
case com.android.internal.R.styleable.View_id:
mID = a.getResourceId(attr, NO_ID);
break;
case com.android.internal.R.styleable.View_visibility:
final int visibility = a.getInt(attr, 0);
if (visibility != 0) {
viewFlagValues |= VISIBILITY_FLAGS[visibility];
viewFlagMasks |= VISIBILITY_MASK;
}
......
}
break;
}
a.recycle();
}
涉及的一些类
TypedArray
它是这样的一种数据结构,它保有了通过Resources.obtainStyledAttributes
或者Resources.obtainAttributes
检索到的值的数组,调用完毕务必要调用recycle
方法以便重用。它既包含资源、资源id,还包含资源的值。里面大量用到TypedValue
的方法来进行资源类型的判定和值的获取
TypedValue
它持有资源的值,具有对资源类型判定和值的获取方法
//int转为float的方法
complexToFloat(int data)
//转换各种尺寸值成像素值
public static float applyDimension(int unit, float value,
DisplayMetrics metrics)
{
switch (unit) {
case COMPLEX_UNIT_PX:
return value;
case COMPLEX_UNIT_DIP:
return value * metrics.density;
case COMPLEX_UNIT_SP:
return value * metrics.scaledDensity;
case COMPLEX_UNIT_PT:
return value * metrics.xdpi * (1.0f/72);
case COMPLEX_UNIT_IN:
return value * metrics.xdpi;
case COMPLEX_UNIT_MM:
return value * metrics.xdpi * (1.0f/25.4f);
}
return 0;
}
Theme
是Resource的一个public final类,是资源属性值的集合。通常和TypedArray连用来解析属性值
其中最常用的一个方法是obtainStyledAttributes
,用来获取属性集中指定命名空间的属性
Resource
提供对应用资源的存取方法
//获取当前屏幕的度量,再通过DisplayMetrics获取比例密度
getDisplayMetrics()
//获取AssetManager
getAssets()
//获取指定资源id的颜色
getColor(int id)
//获取指定资源id的尺寸值(dp/sp)
getDimension(int id)
//获取指定资源id的可绘制资源
getDrawable(int id)
//获取指定资源id的字符串数组
getStringArray(int id)
//获取属性集中指定命名空间的属性,封装成TypedArray
obtainAttributes(AttributeSet set, int[] attrs)
R
提供id,以便对res文件夹的资源进行引用
包括attr、dimen、drawable、id、layout、menu、string、style、styleable
玩一把,自定义控件
1. 在res/values/attrs.xml中声明自定义属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyView">
<attr name="paddingStyle">
<enum name="small" value="0" />
<enum name="normal" value="1" />
<enum name="large" value="2" />
</attr>
</declare-styleable>
</resources>
2. 写自定义控件类,继承自View并重写构造方法
package com.example.test;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;
public class MyView extends TextView {
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
3. 在布局中引用自定义的控件并给自定义的属性赋值(注意:要加命名空间)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:lshare="http://schemas.android.com/apk/res/com.example.test"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.example.test.MyView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Small"
lshare:paddingStyle="small" />
<com.example.test.MyView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Normal"
lshare:paddingStyle="normal" />
<com.example.test.MyView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Large"
lshare:paddingStyle="large" />
</LinearLayout>
4. 在自定义控件的构造方法中获取自定义属性的值
- 从大属性集中获取自定义属性集(包含属性值)
- 遍历属性集获取属性id
- 判断id所属再获取对应的值
- 归还检索的属性集(为了之后再次使用)
package com.example.test;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;
public class MyView extends TextView {
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
// android:layout_height="48dp"
this.setHeight((int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 48, context.getResources()
.getDisplayMetrics()));
// android:layout_width="match_parent"
this.setWidth(context.getResources().getDisplayMetrics().widthPixels);
// android:background="#330000ff"
this.setBackgroundColor(Color.parseColor("#330000ff"));
// obtainStyledAttributes其实是Theme的一个方法,Context里调用了它的方法
// 传入的参数attrs是属性值的集合
// 传入的参数R.styleable.ChangeColorIconWithText是要获取的属性的id数组
// 返回的TypedArray是属性值的集合,要求在调用后,执行recycle方法
TypedArray a = context
.obtainStyledAttributes(attrs, R.styleable.MyView);
// 获取属性的总数
int count = a.getIndexCount();
// 使用for+switch循环判断属性名
// 再通过TypedArray的getDrawable、getColor、getString
// 或getDimension获取对应的属性值
for (int i = 0; i < count; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.MyView_paddingStyle:
int paddingStyle = a.getInt(attr, 1);
switch (paddingStyle) {
case 0:// small
this.setPadding(4, 4, 4, 4);
break;
case 1:// normal
this.setPadding(8, 8, 8, 8);
break;
case 2:// large
this.setPadding(16, 16, 16, 16);
break;
}
break;
}
}
//回收利用属性
a.recycle();
}
}
运行结果:
代码打包下载: