- 提供一個畫板,讓使用者可以通過手指在上面畫畫。
- 可以將畫好的內容存到相簿當中。
Components
- View
- Canvas
- File
- Bitmap
在 Canvas 上畫圖
Paint 對象
Paint 對象用來定義畫筆的風格,比如顏色、粗細等等,比如
- Paint.style – 繪製模式
- paint.setColor(int Color) – 顏色
- Paint.strokeWidth – 線條寬度
- Paint.isAntiAlias – 抗鋸齒開關
1 2 3 4 5 6 7 8 |
private fun setupPaint() { paint = Paint() paint.setColor(Color.GREEN) paint.strokeWidth = 5f paint.style = Paint.Style.STROKE paint.strokeCap = Paint.Cap.ROUND paint.isAntiAlias = true } |
可以到官方文件上查更多有關 Canvas 和 Paint 的資料
PaintBoard
我們建立一個 View 來讓使用者可以在上面畫畫。
Canvas 雖然是一個畫布,但實際上繪畫過程中所產生的內容都是存在 Canvas 的 bitmap 屬性中。
所以我們這裡初始化了一個 bitmap 放到 Canvas 中,在之後也會使用這個 bitmap 來將畫好的圖片存到相簿中。
(如果對 ARGB_8888 有疑慮可以參考之前的文章)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
private var paint: Paint private var bitmap: Bitmap private var mCanvas: Canvas private var startX:Float = 0f private var startY:Float = 0f init { // bitmap val width = Resources.getSystem().displayMetrics.widthPixels bitmap = Bitmap.createBitmap(width, 800, Bitmap.Config.ARGB_8888) // Canvas mCanvas = Canvas(bitmap) mCanvas.drawColor(Color.GRAY) // Paint paint = Paint() paint.setColor(Color.BLACK) paint.setStrokeWidth(10f) } |
手勢處理
當使用者按下時紀錄 startX / startY 移動的過程中不斷把下一個點和前一個點連成線
並將新的位置更新到 startX / startY 上。
最後通過 invalidate() 方法通知調用 onDraw() 方法更新畫面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
override fun onDraw(canvas: Canvas?) { super.onDraw(canvas) canvas!!.drawBitmap(bitmap, 0f, 0f, paint) } override fun onTouchEvent(event: MotionEvent): Boolean { when(event.action){ MotionEvent.ACTION_DOWN -> { startX = event.x startY = event.y } MotionEvent.ACTION_MOVE -> { val stopX = event.x val stopY = event.y mCanvas.drawLine(startX, startY, stopX, stopY, paint) startX = event.x startY = event.y // call onDraw invalidate() } } return true } |
saveBitmap
1 2 3 4 |
fun saveBitmap(stream: OutputStream) { bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream) } |
在 layout/activity_main.xml 中,引入 PaintBoard
1 2 3 4 5 6 7 |
<devdon.com.painter.PaintBoard android:id="@+id/layout_paint_board" android:layout_width="match_parent" android:layout_height="300dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> |
BoardActivity
在 AndroidManifest.xml 中,加入存圖片的權限請求
1 |
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> |
判斷使用者是否同意保存圖片,如果尚未允許則跳出請求
1 2 3 4 5 6 7 8 |
private fun checkWritable():Boolean { if(ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),0) return false } else { return true } } |
當使用者點下 SAVE 的時候,將圖片存到主要儲存卡上
而圖片的檔名根據當下的時間產生一個檔名(如果是同一個檔名會覆蓋)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
private val saveClickHandler = View.OnClickListener { view -> if(checkWritable()){ try { val fileName = (System.currentTimeMillis() / 1000).toString() + ".jpg" val file = File(Environment.getExternalStorageDirectory(), fileName) val stream = FileOutputStream(file) paintBoard.saveBitmap(stream) stream.close() val intent = Intent() intent.setAction(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) intent.setData(Uri.fromFile(Environment.getExternalStorageDirectory())) sendBroadcast(intent) Toast.makeText(this, "Save Success", Toast.LENGTH_SHORT).show() } catch(e:Exception) { println(e) Toast.makeText(this, "Save Failed", Toast.LENGTH_SHORT).show() } } } |
在相簿裡面就會看到我們剛才畫好的內容
筆記
- 研究:如何通過 Code 把 BaseBoard 放到 Activity 中,而不是通過 Layout(類似於 iOS 的 AddSubView)
- TODO:畫板重置的實踐
- API:後來聽朋友說,文件讀寫的功能如果要支援 API 24 以上的設備就需要使用 FileProvider 而不能直接 Uri.fromFile 不然會跳出 FileUriExposedException.
參考
- 官方文件 – Canvas
- 官方文件 – Paint
- 可以到 Github 上看對應的 Source Code