這篇我再來寫一個客製化View的應用

客製化視圖(Custom View) ,又有人稱為畫布(Canvas)

反正這種東西喔就像92共識一樣,自己說了算( ・ὢ・ )

 

Okay那姑且叫他是Canvas,這個東西是所有UI元件的基礎

簡單來說就是提供你一個畫布,然後你想對他畫什麼、做什麼都隨便你用(前提是你要會寫XD)

我這篇部落格之前有寫過一篇"畫儀表盤"的文章,其應用就是這個

-> 碼農日常-『Android studio』簡單地教你實現在Android畫一個弧形儀表盤

 

那今天的文章我就一樣來應用Canvas,來完成一個塗鴉板

功能如下

 

Github在此

-> https://github.com/thumbb13555/CanvasPointDrawer

 

開始(´◑ω◐`)

 


 

1. 專案結構&介面

 

這次的專案基本如下

 

截圖 2020-12-19 下午11.50.01

 

Main我想應該不用多講,就是我們的主要控制項

而今天的重點是MyCanvasView.java這個檔案

基本上這個檔案可以理解為我們"畫布"的元件本身

 

而這次要載入的圖片也就是那張名為aaaaa.jpg的迷因圖โ๏∀๏ใ

就是這張啦↓

aaaaa

 

說真的放這個沒什麼特別的意義啦==''

就是今天我在跟我弟連絡的時候他莫名其妙傳了一張這個給我

我當時正在Coding, 所以就直接放上來了XDDD

這個圖檔也可以從上面下載噢(・ω・)b

 

那麼,我們先來把MyCanvasView.java創起來

創好這個檔案後,可以先輸入以下內容

 

public class MyCanvasView extends View {

    Context context;
    private Paint   mBitmapPaint;
    private Bitmap mBitmap,background;
    private Canvas  mCanvas;
    //畫筆
    private Path mPath;

    private Paint circlePaint,mPaint;
    private Path circlePath;
    //暫存使用者手指的X,Y座標
    private float mX, mY;
    private static final float TOUCH_TOLERANCE = 4;


    public MyCanvasView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        this.context = context;

        mPath = new Path();
        mBitmapPaint = new Paint(Paint.DITHER_FLAG);
        //畫點選畫面時顯示的圈圈
        circlePaint = new Paint();
        circlePath = new Path();
        circlePaint.setAntiAlias(true);
        circlePaint.setColor(Color.BLACK);
        circlePaint.setStyle(Paint.Style.STROKE);
        circlePaint.setStrokeJoin(Paint.Join.MITER);
        circlePaint.setStrokeWidth(4f);

        //繪製線條
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setColor(Color.GREEN);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeWidth(12);

        background = BitmapFactory.decodeResource(getResources(),R.drawable.aaaaa);

    }

 

這個地方我還沒把別的東西寫上去

目前就先把一些可以先設定好的東西先設定起來

再來就是切記⚠️

粉底白字的public記得要加入喔!!

 

再來來到activity_main.xml

把介面先寫好吧ʕ→ᴥ←ʔ

 

activity_main.xml

Screenshot_1608393846

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:background="@color/colorAccent"
    >

    <com.jetec.canvaspointdrawer.MyCanvasView
        android:id="@+id/myCanvasView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@android:color/white"
        app:layout_constraintBottom_toTopOf="@+id/guideline"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.8" />

    <Button
        android:id="@+id/button_Clear"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="16dp"
        android:text="清除"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button_Red" />

    <Button
        android:id="@+id/button_Blue"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:text="藍色"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline" />

    <Button
        android:id="@+id/button_Red"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="紅色"
        app:layout_constraintEnd_toStartOf="@+id/button_Blue"
        app:layout_constraintStart_toEndOf="@+id/button_Green"
        app:layout_constraintTop_toBottomOf="@+id/myCanvasView" />

    <Button
        android:id="@+id/button_Green"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:text="綠色"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline" />


</androidx.constraintlayout.widget.ConstraintLayout>

 

整體來說並沒有什麼要特別在意的地方

唯一要注意的事就是粉底白字的部分

這個部分就是剛才我們用MyCanvasView.java寫出來的喔!

這時候可以先執行一次看看,那張迷因圖應該就會出現囉:D

 


 

2. 撰寫MyCanvasView.java

 

首先請先加入三個複寫,分別是

onSizeChanged()

截圖 2020-12-20 上午12.12.41

 

onDraw()

截圖 2020-12-20 上午12.13.10

 

onTouchEvent()

截圖 2020-12-20 上午12.13.58

 

 

這三個由上而下分別為

 

1. 設定畫面尺寸

2. 畫內容

3. 回傳使用者螢幕觸碰事件

 

那麼接下來,我就直接PO相應的內容了

 

public class MyCanvasView extends View {

    Context context;
    private Paint   mBitmapPaint;
    private Bitmap mBitmap,background;
    private Canvas  mCanvas;
    //畫筆
    private Path mPath;

    private Paint circlePaint,mPaint;
    private Path circlePath;
    //暫存使用者手指的X,Y座標
    private float mX, mY;
    private static final float TOUCH_TOLERANCE = 4;


    public MyCanvasView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        this.context = context;

        mPath = new Path();
        mBitmapPaint = new Paint(Paint.DITHER_FLAG);
        //畫點選畫面時顯示的圈圈
        circlePaint = new Paint();
        circlePath = new Path();
        circlePaint.setAntiAlias(true);
        circlePaint.setColor(Color.BLACK);
        circlePaint.setStyle(Paint.Style.STROKE);
        circlePaint.setStrokeJoin(Paint.Join.MITER);
        circlePaint.setStrokeWidth(4f);

        //繪製線條
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setColor(Color.GREEN);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeWidth(12);

        background = BitmapFactory.decodeResource(getResources(),R.drawable.aaaaa);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //初始化空畫布
        mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mBitmap);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //不要背景圖的話,請從這邊刪
        @SuppressLint("DrawAllocation")
        Bitmap res = Bitmap.createScaledBitmap(background
                ,getWidth(),getHeight(),true);
        canvas.drawBitmap(res,0,0,mBitmapPaint);
        //到這邊

        //取得上一個動作所畫過的內容
        canvas.drawBitmap( mBitmap, 0, 0, mBitmapPaint);
        //依據移動路徑畫線
        canvas.drawPath( mPath,  mPaint);
        //畫圓圈圈
        canvas.drawPath( circlePath,  circlePaint);
    }

    /**覆寫:偵測使用者觸碰螢幕的事件*/
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX(),y = event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                touch_start(x, y);
                break;
            case MotionEvent.ACTION_MOVE:
                touch_move(x, y);
                break;
            case MotionEvent.ACTION_UP:
                touch_up();
                break;
        }
        invalidate();
        return true;

    }
    /**觸碰到螢幕時,取得手指的X,Y座標;並順便設定為線的起點*/
    private void touch_start(float x, float y) {
        mPath.reset();
        mPath.moveTo(x, y);
        mX = x;
        mY = y;
    }
    /**在螢幕上滑動時,不斷刷新移動路徑*/
    private void touch_move(float x, float y) {
        //取得目前位置與前一點位置的X距離
        float dx = Math.abs(x - mX);
        //取得目前位置與前一點位置的Y距離
        float dy = Math.abs(y - mY);
        //判斷此兩點距離是否有大於預設的最小值,有才把他畫進去
        if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
            //畫貝爾茲曲線
            mPath.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
            //更新上一點X座標
            mX = x;
            //更新上一點Y座標
            mY = y;

            //消滅上一點時的小圈圈位置
            circlePath.reset();
            //更新小圈圈的位置
            circlePath.addCircle(mX, mY, 30, Path.Direction.CW);
        }
    }
    /**當使用者放開時,把位置設為終點*/
    private void touch_up() {
        mPath.lineTo(mX, mY);
        circlePath.reset();
        mCanvas.drawPath(mPath,  mPaint);
        mPath.reset();
    }
    
}

 

詳細的介紹我的註解都寫了,還有不懂的話可以留言問喔:D

 

再來我們要設置外部的接口,也就是給MainActivity.java用的"方法"

我這次撰寫的分別是

 

1. 清除畫面上所有塗鴉的內容

2. 設置畫筆顏色

3. 設置背景圖片(本案中沒有用到)

 

而分別對應以下

/**清除所有畫筆內容*/
public void clear(){
    setDrawingCacheEnabled(false);
    onSizeChanged(getWidth(),getHeight(),getWidth(),getHeight());
    invalidate();
    setDrawingCacheEnabled(true);

}
/**設置接口從外部設置畫筆顏色*/
public void setColor(int color){
    mPaint.setColor(color);
}
/**設置接口從外部設置背景圖片*/
public void setBackground(Bitmap bitmap){
    //BitmapFactory.decodeResource(getResources(),R.drawable.aaaaa);
    background = bitmap;
    invalidate();
}

 

最後,PO一下全部吧

 

MyCanvasView.java

public class MyCanvasView extends View {

    Context context;
    private Paint   mBitmapPaint;
    private Bitmap mBitmap,background;
    private Canvas  mCanvas;
    //畫筆
    private Path mPath;

    private Paint circlePaint,mPaint;
    private Path circlePath;
    //暫存使用者手指的X,Y座標
    private float mX, mY;
    private static final float TOUCH_TOLERANCE = 4;


    public MyCanvasView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        this.context = context;

        mPath = new Path();
        mBitmapPaint = new Paint(Paint.DITHER_FLAG);
        //畫點選畫面時顯示的圈圈
        circlePaint = new Paint();
        circlePath = new Path();
        circlePaint.setAntiAlias(true);
        circlePaint.setColor(Color.BLACK);
        circlePaint.setStyle(Paint.Style.STROKE);
        circlePaint.setStrokeJoin(Paint.Join.MITER);
        circlePaint.setStrokeWidth(4f);

        //繪製線條
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setColor(Color.GREEN);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeWidth(12);

        background = BitmapFactory.decodeResource(getResources(),R.drawable.aaaaa);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //初始化空畫布
        mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mBitmap);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //不要背景圖的話,請從這邊刪
        @SuppressLint("DrawAllocation")
        Bitmap res = Bitmap.createScaledBitmap(background
                ,getWidth(),getHeight(),true);
        canvas.drawBitmap(res,0,0,mBitmapPaint);
        //到這邊

        //取得上一個動作所畫過的內容
        canvas.drawBitmap( mBitmap, 0, 0, mBitmapPaint);
        //依據移動路徑畫線
        canvas.drawPath( mPath,  mPaint);
        //畫圓圈圈
        canvas.drawPath( circlePath,  circlePaint);
    }

    /**覆寫:偵測使用者觸碰螢幕的事件*/
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX(),y = event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                touch_start(x, y);
                break;
            case MotionEvent.ACTION_MOVE:
                touch_move(x, y);
                break;
            case MotionEvent.ACTION_UP:
                touch_up();
                break;
        }
        invalidate();
        return true;

    }
    /**觸碰到螢幕時,取得手指的X,Y座標;並順便設定為線的起點*/
    private void touch_start(float x, float y) {
        mPath.reset();
        mPath.moveTo(x, y);
        mX = x;
        mY = y;
    }
    /**在螢幕上滑動時,不斷刷新移動路徑*/
    private void touch_move(float x, float y) {
        //取得目前位置與前一點位置的X距離
        float dx = Math.abs(x - mX);
        //取得目前位置與前一點位置的Y距離
        float dy = Math.abs(y - mY);
        //判斷此兩點距離是否有大於預設的最小值,有才把他畫進去
        if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
            //畫貝爾茲曲線
            mPath.quadTo(mX, mY, (x + mX)/2, (y + mY)/2);
            //更新上一點X座標
            mX = x;
            //更新上一點Y座標
            mY = y;

            //消滅上一點時的小圈圈位置
            circlePath.reset();
            //更新小圈圈的位置
            circlePath.addCircle(mX, mY, 30, Path.Direction.CW);
        }
    }
    /**當使用者放開時,把位置設為終點*/
    private void touch_up() {
        mPath.lineTo(mX, mY);
        circlePath.reset();
        mCanvas.drawPath(mPath,  mPaint);
        mPath.reset();
    }
    /**清除所有畫筆內容*/
    public void clear(){
        setDrawingCacheEnabled(false);
        onSizeChanged(getWidth(),getHeight(),getWidth(),getHeight());
        invalidate();
        setDrawingCacheEnabled(true);

    }
    /**設置接口從外部設置畫筆顏色*/
    public void setColor(int color){
        mPaint.setColor(color);
    }
    /**設置接口從外部設置背景圖片*/
    public void setBackground(Bitmap bitmap){
        //BitmapFactory.decodeResource(getResources(),R.drawable.aaaaa);
        background = bitmap;
        invalidate();
    }
}

 


 

3. 撰寫MainActivity.java

 

這裡完全沒難度,直接PO

 

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btClear,btRed,btBlue,btGreen;
        MyCanvasView canvasView = findViewById(R.id.myCanvasView);
        btClear = findViewById(R.id.button_Clear);
        btGreen = findViewById(R.id.button_Green);
        btBlue = findViewById(R.id.button_Blue);
        btRed = findViewById(R.id.button_Red);

        btClear.setOnClickListener(v->{ canvasView.clear(); });
        btGreen.setOnClickListener(v -> {canvasView.setColor(Color.GREEN);});
        btBlue.setOnClickListener(v -> {canvasView.setColor(Color.BLUE);});
        btRed.setOnClickListener(v -> {canvasView.setColor(Color.RED);});

    }
}

 


 

啊...其實我以為會是長篇大論的,結果一下子就寫完了...囧rz

這篇其實我剛以往不一樣,這篇算是寫得非常久

我以往寫程式網誌時,大部分都是程式寫很快,網誌寫很久

但這次正好相反,我的程式寫得很久,網誌反而寫得很快

甚至我都還一直努力回想我今天下午到底是哪裡卡住了= ="""

 

好吧,雖然我自認為有點虎頭蛇尾,但是寫完了就是寫完了,就別再廢話了XD

最後...

TK

arrow
arrow

    碼農日常 發表在 痞客邦 留言(17) 人氣()