Android 自定义View 实例_ 画图

本文档介绍如何通过自定义View在Android中实现一个简单的画图应用。核心概念包括Canvas、Bitmap、Paint和Clip,详细讲解了onDraw()、onSizeChanged()、Path()和onTouchEvent()方法的使用。在onSizeChanged()中创建并初始化Bitmap和Canvas,onDraw()用于绘制Bitmap和边框。在onTouchEvent()中记录触摸事件,根据ACTION_DOWN、ACTION_MOVE和ACTION_UP更新画布内容。同时,文章提到了全屏显示的问题及解决方案,并提供了日志分析,展示不同阶段Canvas对象的变化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

实现一个简单的画图APP,通过自定义View 实现

相关官网文档参考:https://blog.youkuaiyun.com/whjk20/article/details/115666165

目录

1. Canvas基本概念 (Canvas/Bitmap/Paint/Clip)

onDraw(Canvas canvas) 

onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int)

Path()

onTouchEvent() 

3. 日志分析

4. 结论:


1. Canvas基本概念 (Canvas/Bitmap/Paint/Clip)

  • Canvas(画布,帆布)  Paint(涂料)
  • 官方文档介绍:

https://developer.android.com/reference/android/graphics/Canvas.html
The Canvas class holds the "draw" calls. To draw something, you need 4 basic components:
 A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap),
a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).

Bitmap 保存像素点Canvas 定义在屏幕画的形状Paint 定义画的颜色、风格、fund?,  clip定义哪部分是可见

主要方法

onDraw(Canvas canvas) 

在画布上绘制,需要缓存所画的内容,以提升性能 (缓存canvas , bitmap);  调用invalidate() 则会回调方法以更新界面。

onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int)

屏幕分辩率变化时回调,在这里创建基于新尺寸的Bitmap 以及 Canvas 已经一些初始化

Path()

画的轨迹 : https://developer.android.com/reference/kotlin/android/graphics/Path.html

onTouchEvent() 

记录触摸的x, y坐标,响应按下(ACTION_DOWN) / 移动(ACTION_MOVE)/抬起 (ACTION_UP) 操作

2. 问题
(1) 无法全屏(仍有状态栏statusbar) myCanvasView.systemUiVisibility = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

 

package com.example.minipaint

import android.content.Context
import android.graphics.*
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import androidx.core.content.res.ResourcesCompat
import kotlin.math.abs

private const val STROKE_WIDTH = 12f // has to be float

class MyCanvasView(context: Context) : View(context) {

    private lateinit var extraCanvas: Canvas
    private lateinit var extraBitmap: Bitmap

    private val backgroundColor = ResourcesCompat.getColor(resources, R.color.colorBackground, null)
    private val drawColor = ResourcesCompat.getColor(resources, R.color.colorPaint, null)

    private var motionTouchEventX = 0f
    private var motionTouchEventY = 0f

    private var currentX = 0f
    private var currentY = 0f

    //阈值:大于这个值才认为是滑动,进而画出轨迹
    private val touchTolerance = ViewConfiguration.get(context).scaledTouchSlop

    //显示一个边框, 实际上超出这个边界也是可以画,仅仅是显示边框而已
    private lateinit var frame: Rect

    // Set up the paint with which to draw. 画笔的样式
    private val paint = Paint().apply {
        color = drawColor
        // Smooths out edges of what is drawn without affecting shape.
        isAntiAlias = true
        // Dithering affects how colors with higher-precision than the device are down-sampled.
        isDither = true
        style = Paint.Style.STROKE // default: FILL
        strokeJoin = Paint.Join.ROUND // default: MITER
        strokeCap = Paint.Cap.ROUND // default: BUTT
        strokeWidth = STROKE_WIDTH // default: Hairline-width (really thin)
    }

    private var path = Path() //轨迹

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        // 因为每次大小变化,都需要创建新的bitmap, 但是旧的也要回收,否则内存泄露
        if (::extraBitmap.isInitialized) extraBitmap.recycle()
        extraBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
        extraCanvas = Canvas(extraBitmap)
        extraCanvas.drawColor(backgroundColor)

        // Calculate a rectangular frame around the picture.
        val inset = 40
        frame = Rect(inset, inset, width - inset, height - inset) // todo 为何左边没有设置,都会有框??
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawBitmap(extraBitmap, 0f, 0f, null)

        // Draw a frame around the canvas.
        canvas.drawRect(frame, paint)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        motionTouchEventX = event.x
        motionTouchEventY = event.y

        when (event.action) {
            MotionEvent.ACTION_DOWN -> touchStart()
            MotionEvent.ACTION_MOVE -> touchMove()
            MotionEvent.ACTION_UP -> touchUp()

        }
        return true
    }

    private fun touchStart() {
        path.reset()
        path.moveTo(motionTouchEventX, motionTouchEventY)
        currentX = motionTouchEventX
        currentY = motionTouchEventY
    }

    private fun touchMove() {
        val dx = abs(motionTouchEventX - currentX)
        val dy = abs(motionTouchEventY - currentY)
        if (dx >= touchTolerance || dy >= touchTolerance) {
            // QuadTo() adds a quadratic bezier from the last point,
            // approaching control point (x1,y1), and ending at (x2,y2).
            path.quadTo(
                currentX,
                currentY,
                (motionTouchEventX + currentX) / 2,
                (motionTouchEventY + currentY) / 2
            ) //低阶法计算数值积分 todo
            currentX = motionTouchEventX
            currentY = motionTouchEventY
            extraCanvas.drawPath(path, paint)
        }
        invalidate()
    }

    private fun touchUp() {
        // Reset the path so it doesn't get drawn again.
        path.reset()
    }
}

其中,

    <color name="colorBackground">#FFFF5500</color>
    <color name="colorPaint">#FFFFEB3B</color>
    <string name="canvasContentDescription">Mini Paint is a simple line drawing app.
   Drag your fingers to draw. Rotate the phone to clear.</string>

extraCanvas 缓存画布, 如何绘制(draw)到当前视图(View)上??   

----> 通过Bitmap,  即不断的往Bitmap中添加内容(使用一个缓存的extraCanvas),然后在View.onDraw 时,使用Canvas 绘制出来

仅仅是在onSizeChanged 时调用 extraCanvas.drawColor,  响应触摸时,调用extraCanvas.drawPath(path, paint)

那么在  onDraw() 时调用  canvas.drawBitmap(extraBitmap, 0f,0f, null) 这个有什么用???  ---> 如果没有,则无背景颜色

 

3. 日志分析

//第一次启动进入,此时的缓存extraCanvas
04-13 09:48:55.759 I MyCanvasView:          onSizeChanged extraCanvas=android.graphics.Canvas@981f35b 
//此时的onDraw回调的canvas
04-13 09:48:55.782 I MyCanvasView:          onDraw Call canvas=android.graphics.RecordingCanvas@7dc87f8 

//之后触摸屏幕后,extraCanvas.drawPath()完成后,刷新界面
//此时回调onDraw 传进来的canvas,  既不是缓存canvas, 也不是第一次回调onDraw的canvas (但是后面触摸后回调onDraw,传入的是同一个Canvas对象)
04-13 09:49:05.992 I MyCanvasView:          onDraw Call canvas=android.graphics.RecordingCanvas@6ec4b0e 
04-13 09:49:06.026 I MyCanvasView:          onDraw Call canvas=android.graphics.RecordingCanvas@6ec4b0e 


4. 结论:

1. 需要一个保存像素的位图 (缓存的Bitmap)
2. 在回调onDraw(canvas:Canvas) 时,通过这个canvas 画出这个位图 (Canvas.drawBitmap), 同时可以其它 drawXXX 画形状操作
3. 画笔(Paint) 可以装饰画的属性(颜色、风格等)
4. 创建的缓存Canvas(通过一个缓存Bitmap创建), 只要有绘制内容,在调用 View 的invalidate()时,就回执行步骤2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值