Android系统原生的为我们提供很多的功能强大的基础view控件,但即使如此,很多时候,他们还是不能够满足我们的需求,那么我们就需要通过自定义View来实现自己的view。在Android系统种已经为我们提供了一套很好的机制来实现自定义view,下面我们就自定义view的实现过程做一个简单的介绍。
1.创建自己的view类
通过继承Android的view类或view的子类来创建我们自己的view类。下面我们通过继承ImageButton类实现一个简单的自定义ImageBtn,在这个类种我们会添加3个自定义属性,分别用来定义Button的正常状态,press和focus状态,disable状态的图片资源。类的定义如下,initImageBtn方法用来处理自定义属性,后面添加自定属性后我们在完善。
public class ImageBtn extends ImageButton {
public ImageBtn(Context context) {
super(context);
}
public ImageBtn(Context context, AttributeSet attrs) {
super(context, attrs);
initImageBtn(context, attrs, 0);
}
public ImageBtn(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initImageBtn(context, attrs, defStyleAttr);
}
private void initImageBtn(Context context, AttributeSet attrs, int defStyleAttr){
}
}
2.为自己的view添加自定属性
在Android应用种添加自定义属性,我们只需要在res/values下建立attrs.xml文件,并在里面添加相应的自定义属性即可,此次,我们为ImageBtn 添加如下属性:
上面的attrs.xml文件中,我们为ImageBtn添加了三个自定义属性,分别是imageNormal,imagePressed和imageDisabled;三个属性的格式类型都是integer,此实例中是一个Drewable的资源Id值。
3.在自定义view类种使用自定义属性
上面我们已经为ImageBtn定义了三个自定义属性,下面根据自定义属性来完善我们的ImageBtn类。
定义属性的 format 可以是integer, string, boolean, color, dimension等,具体可以看 ide 的提示(建议使用 Android studio )。
private void initImageBtn(Context context, AttributeSet attrs, int defStyleAttr){
TypedArray typeds = context.obtainStyledAttributes(attrs, R.styleable.ImageBtn, defStyleAttr, 0);
int nCount = typeds.getIndexCount();
Drawable imgNormal = null;
Drawable imgPressed = null;
Drawable imgDiabeled = null;
for (int i = 0; i < nCount; ++i) {
int attr = typeds.getIndex(i);
if (attr == R.styleable.ImageBtn_imageNormal){
imgNormal = typeds.getDrawable(attr);
} else if (attr == R.styleable.ImageBtn_imagePressed){
imgPressed = typeds.getDrawable(attr);
} else if (attr == R.styleable.ImageBtn_imageDisabled){
imgDiabeled = typeds.getDrawable(attr);
}
}
typeds.recycle();
StateListDrawable sd = new StateListDrawable();
if (imgDiabeled != null) {
sd.addState(new int[]{-android.R.attr.state_enabled}, imgDiabeled);
}
if (imgPressed != null){
sd.addState(new int[]{android.R.attr.state_focused}, imgPressed);
sd.addState(new int[]{android.R.attr.state_pressed}, imgPressed);
}
if (imgNormal != null){
sd.addState(new int[]{}, imgNormal);
}
setBackgroundDrawable(sd);
}
通过context.obtainStyledAttributes方法,我们可以获取到ImageBtn的属性(layout文件种定义的属性)在获取到的TypedArray种,我们通过对比其各个item的index值来获取我们的自定义属性。获取到的TypedArray需要显示的调用recycle()。获取到自定属性后,我们通过建立一个StateListDrawable,并把各个状态的ImageDrawable添加到StateListDrawable中,最后调用setBackgroundDrawable进行设置。有没有发现,最后这部分tateListDrawable的处理与我们平时使用ImageButton时位他自定义的Background的Drawable很像呢,是的,他们的实现原理是一样,此处只是换了一个表达形似而已。ImageView等根据触摸状态发生变化的部分大多都是通过一StateListDrawable来管理要显示的Drawable,通过StateListDrawable来控制不同的状态显示不同的状态。
4.重写onMesure
onMesure用来测量view的大小,一般父view执行layout的时候都会通过调用子view的onMesure来测量view需要的位置大小。此实例中直接使用父类的onMesure。
5.重写onDraw
onDraw用来实现view的绘制部分,此实例种使用与父类相同的方式来处理Background的跟随状态变化功能,因此此实例中也是直接使用父类的onDraw。
6.自定义view使用实例
好了,我们的自定义view类ImageBtn写好了,把他加入layout种使用吧。
上面layout中我们加如了imageNormal和imagePressed属性,即定义了按下状态的背景图片和正常状态下的背景图片。运行一下,效果如我们期待。
7.View绘制简介
在Android系统中,与view绘制流程息息相关的三个重要接口是onLayout,onMesure,onDraw。View控件都是放在ViewGroup中的,整个view的树形结构如下:
整个view树的绘制会经历以下过程Layout->Mesure->Draw,在重绘过程种,如果不需要重新layout和重新Mesure则会直接进行Draw。
Layout: 主要是根据子view的大小及布局信息以及ViewGroup自身的特效及属性,将当前View放到父view的合适位置上。
Mesure:为view或view树计算实际所需的位置大小信息
Draw:绘制view,处理绘制业务。