Android自定义View深度剖析:从入门到精通
一、基础知识
1.1 View的生命周期
public class LifecycleView extends View {
public LifecycleView(Context context) {
super(context);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
// View被添加到Window时调用
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 测量View大小
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// 确定View位置
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制View
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
// View从Window中移除时调用
}
}
1.2 MeasureSpec详解
public class MeasureSpecUtil {
public static String getModeName(int mode) {
switch (mode) {
case MeasureSpec.EXACTLY:
return "EXACTLY";
case MeasureSpec.AT_MOST:
return "AT_MOST";
case MeasureSpec.UNSPECIFIED:
return "UNSPECIFIED";
default:
return "UNKNOWN";
}
}
public static void printMeasureSpec(int measureSpec) {
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
Log.d("MeasureSpec", "mode: " + getModeName(mode) + ", size: " + size);
}
}
二、基础绘制
2.1 画笔设置
public class PaintExample extends View {
private Paint mPaint;
public PaintExample(Context context) {
super(context);
initPaint();
}
private void initPaint() {
mPaint = new Paint();
// 抗锯齿
mPaint.setAntiAlias(true);
// 防抖动
mPaint.setDither(true);
// 设置颜色
mPaint.setColor(Color.RED);
// 设置样式:填充/描边/填充且描边
mPaint.setStyle(Paint.Style.FILL);
// 设置线宽
mPaint.setStrokeWidth(5);
// 设置线帽
mPaint.setStrokeCap(Paint.Cap.ROUND);
// 设置字体大小
mPaint.setTextSize(50);
}
}
2.2 基本图形绘制
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 1. 绘制直线
canvas.drawLine(0, 0, 100, 100, mPaint);
// 2. 绘制矩形
RectF rect = new RectF(100, 100, 200, 200);
canvas.drawRect(rect, mPaint);
// 3. 绘制圆形
canvas.drawCircle(300, 300, 50, mPaint);
// 4. 绘制圆角矩形
RectF roundRect = new RectF(400, 400, 500, 500);
canvas.drawRoundRect(roundRect, 20, 20, mPaint);
// 5. 绘制路径
Path path = new Path();
path.moveTo(0, 0);
path.lineTo(100, 100);
path.quadTo(150, 0, 200, 100);
canvas.drawPath(path, mPaint);
// 6. 绘制文字
canvas.drawText("Hello Custom View", 100, 600, mPaint);
}
三、高级绘制
3.1 Canvas变换
@Override
protected void onDraw(Canvas canvas) {
// 1. 平移
canvas.translate(100, 100);
// 2. 旋转
canvas.rotate(45);
// 3. 缩放
canvas.scale(0.5f, 0.5f);
// 4. 错切
canvas.skew(0.5f, 0.5f);
// 保存和恢复画布状态
canvas.save();
// 执行变换操作
canvas.restore();
// 保存图层
int layer = canvas.saveLayer(0, 0, getWidth(), getHeight(), null);
// 绘制操作
canvas.restoreToCount(layer);
}
3.2 绘制顺序控制
@Override
protected void onDraw(Canvas canvas) {
// 1. 使用clipRect裁剪画布
canvas.clipRect(0, 0, 200, 200);
// 2. 使用clipPath裁剪复杂区域
Path clipPath = new Path();
clipPath.addCircle(100, 100, 50, Path.Direction.CW);
canvas.clipPath(clipPath);
// 3. 设置图层混合模式
Paint paint = new Paint();
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
}
四、触摸事件处理
4.1 基本触摸事件
public class TouchView extends View {
private float lastX;
private float lastY;
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
float deltaX = x - lastX;
float deltaY = y - lastY;
// 处理移动
offsetLeftAndRight((int)deltaX);
offsetTopAndBottom((int)deltaY);
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_UP:
// 处理抬起
break;
}
return true;
}
}
4.2 手势检测
public class GestureView extends View {
private GestureDetector mGestureDetector;
public GestureView(Context context) {
super(context);
initGestureDetector();
}
private void initGestureDetector() {
mGestureDetector = new GestureDetector(getContext(),
new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDoubleTap(MotionEvent e) {
// 处理双击
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
// 处理滑动
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX, float velocityY) {
// 处理快速滑动
return true;
}
});
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
}
五、性能优化
5.1 绘制优化
public class OptimizedView extends View {
private Paint mPaint;
private Path mPath;
private Region mRegion;
// 1. 避免在onDraw中创建对象
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPath = new Path();
mRegion = new Region();
}
@Override
protected void onDraw(Canvas canvas) {
// 2. 判断是否需要绘制
if (!isVisible()) {
return;
}
// 3. 使用硬件加速
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
setLayerType(LAYER_TYPE_HARDWARE, null);
}
// 4. 减少不必要的绘制
canvas.clipRect(getScrollX(), getScrollY(),
getScrollX() + getWidth(), getScrollY() + getHeight());
}
}
5.2 测量优化
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 1. 获取宽高的测量模式和大小
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// 2. 根据不同模式计算实际大小
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
width = calculateDesiredWidth();
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(width, widthSize);
}
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
height = calculateDesiredHeight();
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(height, heightSize);
}
}
// 3. 保存测量结果
setMeasuredDimension(width, height);
}
六、实战案例
6.1 自定义进度条
public class CircleProgressBar extends View {
private Paint mPaint;
private RectF mRectF;
private float mProgress;
private int mColor;
public CircleProgressBar(Context context) {
super(context);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(10);
mRectF = new RectF();
mColor = Color.BLUE;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mRectF.set(10, 10, w - 10, h - 10);
}
@Override
protected void onDraw(Canvas canvas) {
// 绘制背景圆环
mPaint.setColor(Color.GRAY);
canvas.drawArc(mRectF, 0, 360, false, mPaint);
// 绘制进度
mPaint.setColor(mColor);
canvas.drawArc(mRectF, -90, mProgress * 360, false, mPaint);
}
public void setProgress(float progress) {
mProgress = progress;
invalidate();
}
}
6.2 自定义折线图
public class LineChartView extends View {
private List<Point> mPoints;
private Paint mPaint;
private Path mPath;
private void drawLine(Canvas canvas) {
if (mPoints.size() < 2) {
return;
}
mPath.reset();
Point firstPoint = mPoints.get(0);
mPath.moveTo(firstPoint.x, firstPoint.y);
for (int i = 1; i < mPoints.size(); i++) {
Point point = mPoints.get(i);
mPath.lineTo(point.x, point.y);
}
canvas.drawPath(mPath, mPaint);
}
private void drawPoints(Canvas canvas) {
for (Point point : mPoints) {
canvas.drawCircle(point.x, point.y, 5, mPaint);
}
}
}
七、注意事项
- 控制绘制范围
@Override
protected void onDraw(Canvas canvas) {
// 只绘制可见区域
Rect bounds = canvas.getClipBounds();
if (!bounds.intersects(left, top, right, bottom)) {
return;
}
}
- 处理配置变化
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
// 保存状态
return ss;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
// 恢复状态
}
总结
自定义View的开发需要注意:
- 理解View的生命周期和测量规则
- 掌握Canvas的绘制方法
- 正确处理触摸事件
- 注意性能优化
- 做好状态保存
通过合理的设计和优化,可以创建出性能优秀、体验良好的自定义View。