使用简单代码在Android Jetpack Compose中开发绘图应用

使用简单代码在Android Jetpack Compose中开发绘图应用
作者 | Elye
译者 | 核子可乐
策划 | 田晓旭

使用 Jetpack Compose 触控功能在 Canvas 上画出图形。

如果大家有意学习 Android,不妨先从妙趣横生的绘图应用起步。在今天的文章中,我们将共同了解如何使用最新 Android Jetpack Compose 开发一款绘图应用。

使用简单代码在Android Jetpack Compose中开发绘图应用

设置 Jetpack Compose 的先决条件

目前 Jetpack Compose 仍处于 Alpha 测试阶段,因此大家必须下载 Android Studio 4.2(Canary 版)并完成以下设置才能使用。

在 Jetpack Compose 中绘图

绘图应用的开发流程非常简单,只需要三步:

  1. Canvas 绘图画布

  2. 触控检测(按压与触控移动)

  3. 根据触控检测绘制路径

设置 Canvas
与传统 Android 开发有所不同,这一次我们不再使用布局。因此,我们不需要构建自定义视图并将其绘制到 Canvas 之上。
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
         Canvas(modifier = Modifier.fillMaxSize()) {
            // Drawing happens here
        }
    }
}

在这里,我们只需要通过 fillMaxSize() 设置 Modifier,确保其占用应用程序中的整个空间。

触控检测
要检测触控,我们通常需要在 Android 中的自定义视图内覆盖 onTouchEvent 函数。
override fun onTouchEvent(event: MotionEvent?): Boolean {
    when (event?.action) {
        MotionEvent.ACTION_DOWN -> { }
        MotionEvent.ACTION_MOVE -> { }
        MotionEvent.ACTION_UP -> { }
        else -> return false
    }
    invalidate()
    return true
}
在 Jetpack Compose 当中,我们使用 pointerInteropFilter 修饰符以检测触摸操作。
Canvas(modifier = Modifier
        .fillMaxSize()
        .pointerInteropFilter {
            when (it.action) {
                MotionEvent.ACTION_DOWN -> { }
                MotionEvent.ACTION_MOVE -> { }
                MotionEvent.ACTION_UP -> { }
                else -> false
            }
            true
        }
)

从以上代码来看,二者其实非常相似,唯一的区别在于后者不再需要 invalidate。Jetpack Compose 会通过更改部分状态值通知所需图形。

下面,我们具体聊聊传统 Android 与 Jetpack Compose 之间的工作方式差异。

基于触控的路径绘制

如下图所示,检测触控与绘制的位置有所不同。

使用简单代码在Android Jetpack Compose中开发绘图应用

因此,为了触发绘图,我们需要使用 mutableState 值,其行为类似于传统开发中的 invalidate。此外,我们还需要 path 以存储所有坐标。
private val action: MutableState<Any?> = mutableStateOf(null)
private val path = Path()
接下来,在检测到图形之后,我们可以更新 action 与 path,具体如下所示。
when (it.action) {
    MotionEvent.ACTION_DOWN -> {
        action.value = it
        path.moveTo(it.x, it.y)
    }
    MotionEvent.ACTION_MOVE -> {
        action.value = it
        path.lineTo(it.x, it.y)
    }
    else -> false
}
在 action 更新完成之后,只要能够访问 action、绘图即被触发,具体如下所示。
{
    action.value?.let {
        drawPath(
                path = path,
                color = Color.Green,
                alpha = 1f,
                style = Stroke(10f))

    }
}
完整代码
没错,只需要不到 50 行代码,我们就拥有了一款由 Jetpack Compose 开发而成的 Android 绘图应用。
private val action: MutableState<Any?> = mutableStateOf(null)
private val path = Path()

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContent {
        Canvas(modifier = Modifier
                .fillMaxSize()
                .pointerInteropFilter {
                    when (it.action) {
                        MotionEvent.ACTION_DOWN -> {
                            action.value = it
                            path.moveTo(it.x, it.y)
                        }
                        MotionEvent.ACTION_MOVE -> {
                            action.value = it
                            path.lineTo(it.x, it.y)
                        }
                        else -> false
                    }
                    true
                }
        ) {
            action.value?.let {
                drawPath(
                        path = path,
                        color = Color.Green,
                        alpha = 1f,
                        style = Stroke(10f))

            }
        }
    }
}
    警告    

如果您是一位经验丰富的开发者,也许会好奇我们能否直接在绘图函数内设置 path 坐标,由此代替通过 action 发送该坐标。相应代码如下所示:

使用简单代码在Android Jetpack Compose中开发绘图应用

没问题,这在技术上完全可行。

但根据我的经验,一旦触发后续 action,则某些 action 更新有可能无法正确被发送至绘图函数(特别是在 ACTION_DOWN 之后由 ACTION_MOVE 触发 action 的情况下,此时由 ACTION_DOWN 发出的 action 将会丢失)。

不知道这是功能层面的限制,还是受到 alpha 版本的影响,具体情况仍然有待观察。

因此,为了实现正确的操作效果并获取完整路径信息,请在触控检测函数中设置 path 以避免丢失问题。

延伸阅读

https://elye-project.medium.com/code-simple-android-jetpack-compose-drawing-app-886d1146ad20

使用简单代码在Android Jetpack Compose中开发绘图应用

发表评论

邮箱地址不会被公开。 必填项已用*标注

相关