概述
自定义控件常见于系统控件不能满足业务需求时,自定义控件的一般流程:
attrs.xml-->onMeasure()-->onLayout(ViewGroup)-->onDraw()-->onTouchEvent()-->onInterceptTouchEvent(ViewGroup);
其中带有ViewGroup的是自定义ViewGroup需要用到的方法.
一般自定义控件包括:
1. 扩展系统自带控件
2. 组合系统控件
3. 通过集成View或ViewGroup来实现新的控件
自定义属性
自定义属性,是可以通过xml来定义的一些控件特性,之前关于自定义属性的总结Android 自定义属性解析,主要步骤:
需要在values目录下声明相关属性,在自定义控件的构造方法中获取这些属性,应用到自定义控件中.同时可以提供set/get
方法来设置相关属性.
onMeasure
onMeasure测量.测量的值是由两部分决定的[Mode和Size],Mode和Size都封装到了一个叫做MeasureSpec的类中;有measure()
方法回调.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//模式,分为三种MeasureSpec.EXACTLY,MeasureSpec.AT_MOST,MeasureSpec.UNSPECIFIED
int mode = MeasureSpec.getMode(widthMeasureSpec);
//父控件建议的大小,需要根据不同模式来实际取值
int size = MeasureSpec.getSize(widthMeasureSpec);
//...
setMeasuredDimension(width,height);
}
注意:
requestLayout();
触发重新测量onMeasure()
onLayout
onLayout 决定了ViewGroup中子View的显示位置. 有layout()回调
View中提供了空实现的onLayout,但自定义View一般不需要管.
ViewGroup中的onLayout是抽象的,即必须实现.
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE){
continue; //如果child为GONE,则不需要布局
}
int left = caculateChildLeft();//计算ChildView左上角x坐标
int top = caculateChildTop();//计算ChildView左上角y坐标
child.layout(left,top,left+childWidth,top+childHeight);//childWidth 和 childHeight 是child的宽和高
}
}
注意:
1. 尽可能将onMeasure中的一些与Measure无关的操作移动到onLayout中.
2. 因为onMeasure在一次自定义控件流程中,可能会执行多次,而onLayout只执行一次.
3. requestLayout();
会触发重新布局onLayout()
onDraw
在onDraw()只负责View内容的绘制,具体可以看下面view.draw()方法.其有draw()
回调.
//view#draw();
@CallSuper
public void draw(Canvas canvas) {
//...
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
//...
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
}
扩展阅读 自定义 View 必备-Draw 源码分析及其实践
讲解Canvas中的一些重要方法
注意:
1. 可以通过Canvas的Api来进行绘制和变换操作,通过save()和restore()来保存于恢2. 复canvas的状态.
3. 如果是自定义ViewGroup一般我们也不需要管onDraw这个方法,draw由子控件自己完成
4. postInvalidate();
和invalidate();
会触发重新绘制onDraw().
onTouchEvent
控件的事件处理,如果不需要处理事件,则不需要复写.可操作如下常量.
- MotionEvent.ACTION_DOWN: 按下的时候,做一些初始化,赋值操作.
- MotionEvent.ACTION_MOVE: 移动的事件
- MotionEvent.ACTION_UP: 手指抬起时,做一些释放资源,重置变量的操作
- MotionEvent.ACTION_CANCEL:手势释放操作,释放资源,重置变量
- MotionEvent.ACTION_POINTER_DOWN: 多点触控操作,需要借助event.getActionIndex(),getPointerId(actionIndex)等一系列方法
- MotionEvent.ACTION_POINTER_UP:多点触控非最后一个点被释放时执行,这里需要注意的是activePointer才会起作用
//多☞触控下的处理
private void onSecondaryPointerUp(MotionEvent ev) {
//...
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
// TODO: Make this decision more intelligent.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
//...
mActivePointerId = ev.getPointerId(newPointerIndex);
}
}
// 通知父控件不要拦截事件
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
onInterceptTouchEvent
ViewGroup的方法,表示是否拦截事件,其中传递的参数也是MotionEvent,
同样包含上面那些常量,我们可以在不同时机调用不同的方法来完成逻辑
这里包含了事件分发的处理.
扩展阅读 图解 Android 事件分发机制
onSizeChanged
组件大小改变时回调,一般在改变控件内容,或者子控件个数的时候,会触发
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//...
}
onFinishInflate
在xml被inflate出来后加载的方法.
@Override protected void onFinishInflate() {
if (!this.isInEditMode()) {
//...
}
super.onFinishInflate();
}
Canvans与Paint
Paint ,画笔,Canvans,画布.canvas通过paint将view绘制到界面上.
可以使用translate,rotate,scale,skew等来实现canvas的变换
Canvans相关api
canvas.drawLine(float startX, float startY, float stopX, float stopY, Paint paint);
canvas.drawRect(float left, float top, float right, float bottom, Paint paint);
canvas.drawCircle(float cx, float cy, float radius, Paint paint);
canvas.drawText(String text, float x, float y, Paint paint);
canvas.drawBitmap(Bitmap bitmap, float left, float top, Paint paint);
Paint 相关API
//锯齿效果
setAntiAlias(boolean aa);
//设置画笔的颜色
setColor(@ColorInt int color);
//设置画笔的A、R、G、B值
setARGB(int a, int r, int g, int b);
//设置颜色过滤器
setColorFilter(ColorFilter filter);
//防抖动
setDither(boolean dither);
//设置画笔的风格(空心或实心),枚举参数
setStyle(Style style);
//设置空心边框的宽度
setStrokeWidth(float width);
//获取画笔的颜色
getColor();
//设置阴影
setShadowLayer(float radius, float dx, float dy, int shadowColor);
更多关于Paint的介绍和与Matrix结合使用的实例见
Paint、Canvas、Matrix使用讲解(一、Paint)
其他
Configuration: 述设备的配置信息(locale,scaling,..):官网Configuration介绍
ViewConfiguration: 包含了设置UI的超时、大小和距离 de 方法和标准的常量用来,官网ViewConfiguration介绍
扩展阅读: Viewconfiguration和configuration类使用
手势操作GestureDetector: Android - Gestures Tutorial
ViewDragHelper:事件处理,Android应用ViewDragHelper详解及部分源码浅析
VelocityTracker: 触摸速率跟踪 官网介绍Tracking Movement
Scroller: 平滑滚动帮助类 Android应用开发Scroller详解及源码浅析
onSaveInstanceState和onRestoreInstanceState: 存储和恢复自定义控件的状态,参考onSaveInstanceState & onRestoreInstanceState