「这是我参与2022首次更文挑战的第25天,活动详情查看:2022首次更文挑战」
自定义View画板
不知道大家有没有玩过一种涂鸦游戏,手指在屏幕上划来划去,就能产生美丽的图案,那么它是怎么实现的呢? 这篇文章的目的在于了解涂鸦绘画板的基本功能实现。
效果图
基本知识
- 自定义View
自定义View需要集成View,然后实现构造方法,细心的你会发现自定义View有四个构造方法。
public DrawView(Context context) {
super(context);
}
public DrawView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public DrawView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public DrawView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
这些构造方法有什么不同呢? 第一个构造方法,是动态创建视图中调用的,没有传递属性之类的。 第二个构造方法是在布局文件中写入时调用的,带属性值,在自定义View中自定义的属性,在这里进行解析。 第三个第四个构造方法,由我们主动调用,一般情况下用不到。
- onSizeChanged什么时候响应
onSizeChanged() 在控件大小发生改变时调用,在新加载视图时,会首先调用一次,所以一般在这里初始化,也可以在这里获取空间的宽和高。
- Path 作用
Path类将多种复合路径(多个轮廓,如直线段、二次曲线、立方曲线)封装在其内部的几何路径。
画板实现
1. 自定义View
在自定义View中初始化画笔工作
public DrawView(Context context) {
super(context);
initPaint();
}
public DrawView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initPaint();
}
public DrawView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaint();
}
public DrawView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initPaint();
}
在onSizeChanged方法中,进行初始化背景
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
initBackground();
Log.d(TAG, "onSizeChanged");
}
2. 初始化画笔 和 背景
/**
* 初始化画笔
*/
public void initPaint() {
Log.d(TAG, "initPaint");
mPaint.setDither(true);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
setPaintColor(DEFAULT_PAINT_COLOR);
setPaintWidth(DEFAULT_PAINT_WIDTH);
}
/**
* 初始化白色画板背景
*/
private void initBackground() {
if (mBitmap != null && !mBitmap.isRecycled()) {
mBitmap.recycle();
mBitmap = null;
}
int width = getWidth();
int height = getHeight();
if (width == 0 || height == 0) return;
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
if (mCanvas == null) {
mCanvas = new Canvas(mBitmap);
} else {
mCanvas.setBitmap(mBitmap);
}
mCanvas.drawColor(Color.WHITE);
}
3. 监听触摸事件
监听触摸事件,不同的事件进行不同的操作。
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchDown(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touchMove(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touchUp(x, y);
invalidate();
break;
}
return true;
}
/**
* 触摸事件按下
*
* @param x
* @param y
*/
private void touchDown(float x, float y) {
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
}
/**
* 触摸移动
*
* @param x
* @param y
*/
private void touchMove(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
/**
* 触摸事件结束
*
* @param x
* @param y
*/
private void touchUp(float x, float y) {
mPath.lineTo(mX, mY);
mCanvas.drawPath(mPath, mPaint);
mPath.reset();
}
4. 在Canvas中进行绘画
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//先将已保存在位图中的轨迹绘制到背景
if (mBitmap == null) return;
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
//载绘制新的轨迹
canvas.drawPath(mPath, mPaint);
}