這篇我再來寫一個客製化View的應用
客製化視圖(Custom View) ,又有人稱為畫布(Canvas)
反正這種東西喔就像92共識一樣,自己說了算( ・ὢ・ )
Okay那姑且叫他是Canvas,這個東西是所有UI元件的基礎
簡單來說就是提供你一個畫布,然後你想對他畫什麼、做什麼都隨便你用(前提是你要會寫XD)
我這篇部落格之前有寫過一篇"畫儀表盤"的文章,其應用就是這個
-> 碼農日常-『Android studio』簡單地教你實現在Android畫一個弧形儀表盤
那今天的文章我就一樣來應用Canvas,來完成一個塗鴉板
功能如下
Github在此
-> https://github.com/thumbb13555/CanvasPointDrawer
開始(´◑ω◐`)
1. 專案結構&介面
這次的專案基本如下
Main我想應該不用多講,就是我們的主要控制項
而今天的重點是MyCanvasView.java這個檔案
基本上這個檔案可以理解為我們"畫布"的元件本身
而這次要載入的圖片也就是那張名為aaaaa.jpg的迷因圖โ๏∀๏ใ
就是這張啦↓
說真的放這個沒什麼特別的意義啦==''
就是今天我在跟我弟連絡的時候他莫名其妙傳了一張這個給我
我當時正在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
把介面先寫好吧ʕ→ᴥ←ʔ
<?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()
onDraw()
onTouchEvent()
這三個由上而下分別為
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一下全部吧
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
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
最後...
留言列表