自定义View画板

1,148 阅读3分钟

「这是我参与2022首次更文挑战的第25天,活动详情查看:2022首次更文挑战

自定义View画板

不知道大家有没有玩过一种涂鸦游戏,手指在屏幕上划来划去,就能产生美丽的图案,那么它是怎么实现的呢? 这篇文章的目的在于了解涂鸦绘画板的基本功能实现。

效果图

1.jpg

基本知识

  • 自定义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);
    }