相关视频:
关于view的绘制流程的文章:
相关文章:
Android自定义控件三部曲文章索引(启舰大神)
自定义View基础 - 最易懂的自定义View原理系列(1)
自定义View Measure过程 - 最易懂的自定义View原理系列(2)
自定义View Layout过程 - 最易懂的自定义View原理系列(3)
自定义View Draw过程- 最易懂的自定义View原理系列(4)
一、为什么要自定义控件?
- 特定的显示风格
- 处理特有的用户交互
- 优化我们的布局
- 封装
二、如何自定义控件?
- (一)、自定义属性的声明与获取;
- (二)、测量onMeasure;
- (三)、布局onLayout(ViewGroup)
- (四)、绘制onDraw;
- (五)、onTouchEvent;
- (六)、onInterceptTouchEvent(ViewGroup);
(二)、测量onMeasure;
1、测量模式有三种:EXACTLY、AT_MOST、UNSPECIFIED
EXACTLY:我们设置一个明确的值,比如一个TextView,我们宽高都设置为100dp或者match_parent。
AT_MOST:至多不能超过某个值,一般当我们给一个view设置wrap_content时,有两点值得注意:
首先,它的内容就是自适应,其次,它的测量模式就是AT_MOST,表示它的最大值不能超过父控件;
UNSPECIFIED:没有限制,view想要多大就可以,一般用于ListView和ScrollView中。在ScrollView中,它的子view
高度就没有限制,想要多高就有多高;
2、MeasureSpec:可以理解为一个辅助类,封装了Mode和Size
private int measureHeight(int heightMeasureSpec) {
int result = 0;
int mode = View.MeasureSpec.getMode(heightMeasureSpec);
int size = View.MeasureSpec.getSize(heightMeasureSpec);
if (mode == View.MeasureSpec.EXACTLY) {
result = size;
} else {
//计算自身需要的高度
result = getNeedHeight() + getPaddingTop() + getPaddingBottom();
//最大值不能超过size的大小
if (mode == View.MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
return result;
}
private int getPaddingBottom() {
return 0;
}
private int getPaddingTop() {
return 0;
}
private int getNeedHeight() {
return 0;
}
3、setMeasureDimension(result)
4、requestLayout
(三)、布局onLayout(ViewGroup)
如果自定义的是View,不用考虑这个步骤。
1、决定子View的位置;
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt();
if (child.getVisibility() == GONE) {
}
//计算childView layout的左上角x坐标
left = caculateChildLeft();
//计算childView layout的左上角y坐标
top = caculateChildTop();
int cWidth = 0;
int cHeight = 0;
child.layout(left, top, left + cWidth, top + cHeight);
}
}
private int caculateChildLeft() {
return 0;
}
private int caculateChildTop() {
return 0;
}
private View getChildAt() {
return null;
}
private int getChildCount() {
return 0;
}
2、尽可能将onMeasure中的一些操作移动到此方法中;
3、requestLayout()
(四)、绘制onDraw
- 1、绘制内容区域:onDraw主要是绘制内容区域,像背景的话,系统已经绘制过了,无需考虑
- 2、invalidate(),postInvalidata()
- 3、Canvas.drawXXX
- 4、translate、rotate、scale、skew
- 5、save()、restore()
(五)、onTouchEvent
float mLastMotionY = 0;
float mActivePointerId = 0;
float pointerId = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
final int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
//进行一些初始化、赋值等的操作
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
//如果需要进行速度判断
int initialVelocity = (int) velocityTracker.getYVelocity(1);
//释放各种资源、重置变量
break;
case MotionEvent.ACTION_CANCEL:
//释放各种资源、重置变量
break;
case MotionEvent.ACTION_POINTER_DOWN:
//如果支持多指,在此设置activePointer
final int index = event.getActionIndex();
mLastMotionY = event.getY(index);
mActivePointerId = event.getPointerId(index);
break;
case MotionEvent.ACTION_POINTER_UP:
//如果支持的是多指且抬起的是activePointer,则重新选择一个手指为活跃的手指
if (pointerId == mActivePointerId) {
int pointIndex = 0;
final int newPointerIndex = pointIndex == 0 ? 1 : 0;
mLastMotionY = event.getY(newPointerIndex);
mActivePointerId = event.getPointerId(newPointerIndex);
if (velocityTracker != null) {
velocityTracker.clear();
}
}
break;
}
return true;
}
(六)、onInterceptTouchEvent(ViewGroup)
private int mActivePointerId = 0;
public static final int INVALID_POINTER = 1;
float mLastMotionY = 0;
private int mTouchSlop = 2;
private boolean mIsBeginDragged = false;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_MOVE:
final int acitivePointerId = mActivePointerId;
if (acitivePointerId == INVALID_POINTER) {
break;
}
final int pointerIndex = ev.findPointerIndex(acitivePointerId);
final int y = (int) ev.getY(pointerIndex);
final int yDiff = (int) Math.abs(y - mLastMotionY);
if (yDiff > mTouchSlop) {
mIsBeginDragged = true;
mLastMotionY = y;
}
break;
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_CANCEL:
break;
}
return mIsBeginDragged;
}