使用 Jetpack Compose 触控功能在 Canvas 上画出图形。
如果大家有意学习 Android,不妨先从妙趣横生的绘图应用起步。在今天的文章中,我们将共同了解如何使用最新 Android Jetpack Compose 开发一款绘图应用。
设置 Jetpack Compose 的先决条件
目前 Jetpack Compose 仍处于 Alpha 测试阶段,因此大家必须下载 Android Studio 4.2(Canary 版)并完成以下设置才能使用。
绘图应用的开发流程非常简单,只需要三步:
与传统 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 之间的工作方式差异。
如下图所示,检测触控与绘制的位置有所不同。
因此,为了触发绘图,我们需要使用 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 发送该坐标。相应代码如下所示:
没问题,这在技术上完全可行。
但根据我的经验,一旦触发后续 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
领取专属 10元无门槛券
私享最新 技术干货