自定义View主要分为两大部分:
第一部分是view的绘制:
大体步骤
1.测量大小
2.放置位置
3绘制
从view 说起一套完整的自定义view流程:
1.onMeasure: 重要的类 Measuresprc 类 MODE 和SIZE 当测量属性的参数是WRAP_CONTEXT 时这些参数将会起作用
测量主要和父容器和自身的Layoutparms有关。
继承自view测量时当属性参数是WRAP_context时 需要重写onMeasure() 通过Measuresprc类的Mode和size,判断自行填写大小(覆写父类setMeasuredDimension(,))
//2.onLayout: 这个方法会确定被放置的位置, 获取父布局layoutparams 或者layout()方法 这个方法不是必须重写的
3.onDraw : 就是绘制了
4.onTouchEvent: 回调事件
示例代码(一个按钮代码):
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mSwitchBackground != null) {
int width = mSwitchBackground.getWidth();
int height = mSwitchBackground.getHeight();
setMeasuredDimension(width, height);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
这里选择的大小是图片的大小!
ondraw方法,和onTouchEvent方法是自定义view的核心与关键:
@Override
protected void onDraw(Canvas canvas) {
// 绘制背景的显示
if (mSwitchBackground != null) {
int left = 0;
int top = 0;
canvas.drawBitmap(mSwitchBackground, left, top, mPaint);
}
if (mSwitchSlide == null) {
return;
}
int slideWidth = mSwitchSlide.getWidth();// 滑块的宽度
int switchWidth = mSwitchBackground.getWidth();
switch (mState) {
case STATE_DOWN:
case STATE_MOVE:
// 当按下的时候
if (!isOpened) {
// 如果滑块是关闭的
// 点击滑块的左侧,滑块不动
if (mCurrentX < slideWidth / 2f) {
// 绘制在左侧
canvas.drawBitmap(mSwitchSlide, 0, 0, mPaint);
} else {
// 点击滑块的右侧,滑块的中线和按下的x坐标对齐
float left = mCurrentX - slideWidth / 2f;
float maxLeft = switchWidth - slideWidth;
if (left > maxLeft) {
left = maxLeft;
}
canvas.drawBitmap(mSwitchSlide, left, 0, mPaint);
}
} else {
// 如果滑块是打开的
float middle = switchWidth - slideWidth / 2f;
if (mCurrentX > middle) {
// 绘制为打开
canvas.drawBitmap(mSwitchSlide, switchWidth - slideWidth,
0, mPaint);
} else {
float left = mCurrentX - slideWidth / 2f;
if (left < 0) {
left = 0;
}
canvas.drawBitmap(mSwitchSlide, left, 0, mPaint);
}
}
break;
case STATE_UP:
case STATE_NONE:
if (!isOpened) {
canvas.drawBitmap(mSwitchSlide, 0, 0, mPaint);
} else {
canvas.drawBitmap(mSwitchSlide, switchWidth - slideWidth, 0,
mPaint);
}
break;
default:
break;
}
}
事件方法通知重绘时,做了标记处理:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mState = STATE_DOWN;
mCurrentX = event.getX();
invalidate();// 触发刷新-->主线程调用
// postInvalidate();// 触发刷新--->子线程中调用
break;
case MotionEvent.ACTION_MOVE:
mState = STATE_MOVE;
mCurrentX = event.getX();
invalidate();
break;
case MotionEvent.ACTION_UP:
mState = STATE_UP;
mCurrentX = event.getX();
// 判断状态改变--> isOpened
int switchWidth = mSwitchBackground.getWidth();
if (switchWidth / 2f > mCurrentX && isOpened) {
// 关闭状态
isOpened = false;
if (mListener != null) {
mListener.onSwitchChanged(isOpened);
}
} else if (switchWidth / 2f <= mCurrentX && !isOpened) {
isOpened = true;
if (mListener != null) {
mListener.onSwitchChanged(isOpened);
}
}
invalidate();
break;
default:
break;
}
// 消费touch事件
return true;
}
注意通知重绘方法!
从viewGroup:
1.onMeasure 和 onLayout 其实没有真正的实现内容只不过是对下面的子view 进行了计算和位置的摆放
2.onMeasure : 会回调子类的方法
而继承自ViewGroup类测量时。当属则需要通测量过子View大小从而重新决定自己的大小。(通常宽高可能是子view的和)。注意:只能获取的是测量值Measureheight.
放置
3.onLayout: viewGroup放置则先onLayout()方法对其中的子布局调用Layout()进行依次放置(getcount(),和getchildAt()获取子控件)
4.onDraw: 在viewGroup中这个方法如果不用 绘制背景颜色可能都不会使用。
示例代码:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int count = getChildCount();
measureChildren(widthMeasureSpec, heightMeasureSpec);
int widthMeasureSpeccode = MeasureSpec.getMode(widthMeasureSpec);
int widthMeasureSpecsize = MeasureSpec.getSize(widthMeasureSpec);
int heightMeasureSpeccode = MeasureSpec.getMode(heightMeasureSpec);
int heightMeasureSpecsize = MeasureSpec.getSize(heightMeasureSpec);
if (count == 0) {
setMeasuredDimension(0, 0);
} else if (widthMeasureSpeccode == MeasureSpec.AT_MOST && heightMeasureSpeccode == MeasureSpec.AT_MOST) {
final View childview0 = getChildAt(0);
//只能获取到测量值
int childheigt = childview0.getMeasuredHeight();
int childwidth = childview0.getMeasuredWidth();
setMeasuredDimension(childwidth*count,childheigt);
}else if(widthMeasureSpeccode == MeasureSpec.AT_MOST){
View childview =getChildAt(0);
int measurewidth=childview.getMeasuredWidth()*count;
setMeasuredDimension(measurewidth,heightMeasureSpecsize);
}else if(heightMeasureSpeccode == MeasureSpec.AT_MOST){
View childview =getChildAt(0);
int measuredHeight=childview.getMeasuredHeight();
setMeasuredDimension(widthMeasureSpecsize,measuredHeight);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int clidrencount = getChildCount();
int childrenleft = 0;
View childview1=getChildAt(0);
int chileviewheight1=childview1.getMeasuredHeight();
int chileviewwidth1=childview1.getMeasuredWidth();
for (int c = 0; c < clidrencount; c++) {
View childrenView = getChildAt(c);
childrenView.layout(childrenleft, 0, childrenleft + chileviewwidth1,chileviewheight1);
childrenleft = childrenleft + chileviewwidth1;
}
}
//需要特别注意的是,需要调用每一个子view的layout方法,这里我们直接给他位置放好!
ondraw()方法中参提供了一个Canvas,这个Canvas系统提供了许多绘制API,例如drawPoint,drawline,drawPath,drawBitmap,drawshape等
同时提供了方法,这些方法为坐标点绘制计算提供方便,rotate()和traslate()分别对坐标原点进行角度转换和平移。Save,和restore对图层保存和合并,另外对图层的管理通过栈的方式。两个方法,saveLayer(),saveLayerALpha(),restoreTocount(),restore(),对图层操作后决定了后面的操作是在哪个图层上。
viewGroup注意:
viewGroup可用addview在构造函数中添加子控件或者布局。要注意的是子控件的点击事件可以通过新建一个接口的,在viewGroup中回调接口方法,同时给出一个方法暴露接口。这样调用者自己就可以在用viewGroup的暴露的接口实现自己的逻辑了。