自定義View畫板

語言: CN / TW / HK

「這是我參與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); }