【Jetpack Compose进阶学与练】第16课 Compose自定义绘制Canvas;手势;实现简易画板Demo

发布时间:2026/10/9 12:34:20
【Jetpack Compose进阶学与练】第16课 Compose自定义绘制Canvas;手势;实现简易画板Demo 前言前面课程使用现成的系统组件当普通Compose UI无法绘制复杂图形时需要Canvas画布同时配合手势检测接收手指滑动。本节课学习drawBehind修饰器、Canvas组件Path路径pointerInput手势实现简易画板可以手指画线、清空画布。知识点清单drawBehind 后台绘制修饰器 与 Canvas组件的区别Canvas画布基础坐标原点(0,0)是左上角Path路径记录手指滑动路径MutableState保存绘制路径手势pointerInput、detectDragGestures手指滑动简易画板Demo保存多条线条清空画板完整示例源码importandroidx.compose.foundation.Canvasimportandroidx.compose.foundation.gestures.detectDragGesturesimportandroidx.compose.foundation.layout.Arrangementimportandroidx.compose.foundation.layout.Columnimportandroidx.compose.foundation.layout.fillMaxSizeimportandroidx.compose.foundation.layout.fillMaxWidthimportandroidx.compose.foundation.layout.paddingimportandroidx.compose.material3.Buttonimportandroidx.compose.material3.Textimportandroidx.compose.runtime.Composableimportandroidx.compose.runtime.MutableStateimportandroidx.compose.runtime.mutableStateListOfimportandroidx.compose.runtime.rememberimportandroidx.compose.runtime.setValueimportandroidx.compose.runtime.getValueimportandroidx.compose.ui.Modifierimportandroidx.compose.ui.geometry.Offsetimportandroidx.compose.ui.graphics.Colorimportandroidx.compose.ui.graphics.Pathimportandroidx.compose.ui.input.pointer.pointerInputimportandroidx.compose.ui.unit.dp//一条笔迹数据保存一条Path以及画笔颜色dataclassDrawPathItem(valpath:Path,valcolor:Color)ComposablefunSimpleDrawingBoardScreen(){//保存全部笔迹多条路径valpathListremember{mutableStateListOfDrawPathItem()}//当前正在画的这条路径varcurrentPath:Path?remember{null}Column(modifierModifier.fillMaxSize().padding(12.dp)){Button(modifierModifier.fillMaxWidth(),onClick{pathList.clear()currentPathnull}){Text(清空画板)}Canvas(modifierModifier.fillMaxSize().weight(1f).pointerInput(Unit){detectDragGestures(onDragStart{offset-//手指按下新建一条路径移动到按下的坐标currentPathPath().apply{moveTo(offset.x,offset.y)}},onDrag{change,dragAmount-//手指拖动追加线条change.consume()currentPath?.lineTo(change.position.x,change.position.y)},onDragEnd{//手指抬起结束本条笔迹加入列表保存currentPath?.let{p-pathList.add(DrawPathItem(pathp,colorColor.Blue))}currentPathnull})}){//先绘制已经完成保存的笔迹pathList.forEach{item-drawPath(pathitem.path,coloritem.color,strokeWidth4f)}//绘制当前正在拖动的临时笔迹currentPath?.let{drawPath(pathit,colorColor.Blue,strokeWidth4f)}}}}逐行解读drawBehind vs CanvasModifier.drawBehind{} 在现有组件的底层绘制适合给现有UI补充简单图形组件本身仍然可以放置子Compose元素。Canvas{} 专门的画布组件Canvas花括号内部是绘制作用域不能直接写Compose组件只能调用底层绘制APIdrawLine、drawRect、drawPath、drawCircle。画布坐标系左上角是原点 Offset(0,0) X向右增大Y向下增大。Path路径对象moveTo(x,y) 移动画笔到起点不画线lineTo(x,y) 从上一个点连线到当前坐标持续拖动就不断追加lineTo形成连续笔迹。Path是可变对象不要直接放在状态里面本Demo策略抬起手指之后复制保存到stateList。手势 detectDragGesturespointerInput(Unit) 开启手势输入域onDragStart手指初次按下创建新PathmoveTo按下点onDrag手指持续滑动持续调用lineTo追加路径 change.consume() 消费手势事件防止下层组件同时响应触摸onDragEnd手指抬起把这条完成的笔迹加入 mutableStateListOf 列表之后Canvas重组就会绘制这条笔迹。状态保存逻辑mutableStateListOf() 用来存放已经完成的笔迹currentPath 保存正在画还没有结束的临时笔迹当点击清空按钮直接clear整个列表画布就全部擦除。注意内存与重建remember保存笔迹屏幕旋转Activity重建画板上绘制的线条会全部消失如果希望旋转之后保留手绘笔迹需要把笔迹数据上移到ViewModel复杂场景可以序列化为点集合保存。容易踩坑❌ Path是可变对象直接修改存进状态Compose无法感知变化界面不会自动重绘抬起之后把完成的笔迹加入stateList。❌ Canvas绘制代码里面写Compose Text、ButtonCanvas的绘制lambda只能调用底层绘图函数不能放可组合项。❌ 忘记 change.consume() 手势事件向下穿透导致下层控件意外响应点击。❌ 使用remember保存手绘笔迹屏幕旋转绘制内容丢失正式项目笔迹数据迁移到ViewModel。✅ detectDragGestures监听拖拽手势Path构建线条mutableStateList保存已经完成笔迹。多元化习题练习习题1【基础‑改错题】直接把Path对象放在remember可变状态中拖动的时候直接修改同一个Path为什么画布不会实时刷新答案Path属于可变类修改对象内部属性不会触发Compose重组Compose只能感知状态变量本身引用的变更应当新建Path对象修改完成之后加入状态列表。解读Compose依靠引用变化触发重组可变对象内部改动不会被追踪。习题2【基础‑代码修改题】修改画板示例默认画笔颜色改为红色(Color.Red)。答案pathList.add(DrawPathItem(pathp,colorColor.Red))同时临时绘制的那一处也修改colorColor.Red。解读绘制的时候传入color后续可以扩展画笔颜色选择器。习题3【进阶‑补充代码题】简述drawBehind修饰器和独立Canvas组件的适用场景差异。答案drawBehind给已有Compose组件底层补充绘制图形该组件依然可以拥有子元素Canvas独立画布专门用来大面积自定义绘图绘制域内部不能放置Compose子组件适合画板、图表。解读简单装饰用drawBehind大面积绘图优先Canvas。习题4【综合‑逻辑应用题】当前Demo的手绘笔迹使用remember保存屏幕旋转之后线条消失如何解决答案把 mutableStateListOf() 上移至ViewModel当中ViewModel抗Activity重建屏幕旋转笔迹保留如果希望App杀掉进程之后依然保留手绘需要序列化点集合写入Room数据库持久化。解读短期抗旋转放ViewModel长期持久化走数据库。习题5【概念简答题】detectDragGestures里面调用change.consume()作用是什么答案消费本次触摸手势事件阻止触摸事件继续向下传递到下层UI避免底层的组件同时响应点击、滑动。解读画板画布上绘图的时候不希望底层控件接收触摸。本节课知识点总结Canvas画布自定义绘图画布坐标原点位于左上角X向右Y向下区分Canvas组件与drawBehind修饰器。Path路径moveTo设置起点lineTo追加线条Path是可变对象直接修改不会触发重组。pointerInput detectDragGestures监听手指按下、拖动、抬起consume消费触摸事件。使用mutableStateList保存多条已经完成笔迹currentPath临时保存正在绘制的线条实现简易画板、清空功能。remember保存的笔迹屏幕旋转丢失想要保留则迁移ViewModel长期保存笔迹存入Room。下一课预告第17课 Compose动画基础animate*AsState、Transition页面元素显隐动画

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询