news 2026/10/22 18:25:45

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Jetpack Compose进阶学与练】第16课 Compose自定义绘制Canvas;手势;实现简易画板Demo

前言

前面课程使用现成的系统组件;当普通Compose UI无法绘制复杂图形时,需要Canvas画布;同时配合手势检测,接收手指滑动。
本节课学习drawBehind修饰器、Canvas组件;Path路径;pointerInput手势;实现简易画板,可以手指画线、清空画布。

知识点清单

  1. drawBehind 后台绘制修饰器 与 Canvas组件的区别
  2. Canvas画布基础坐标;原点(0,0)是左上角
  3. Path路径,记录手指滑动路径;MutableState保存绘制路径
  4. 手势pointerInput、detectDragGestures手指滑动;
  5. 简易画板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(){//保存全部笔迹,多条路径valpathList=remember{mutableStateListOf<DrawPathItem>()}//当前正在画的这条路径varcurrentPath:Path?=remember{null}Column(modifier=Modifier.fillMaxSize().padding(12.dp)){Button(modifier=Modifier.fillMaxWidth(),onClick={pathList.clear()currentPath=null}){Text("🗑清空画板")}Canvas(modifier=Modifier.fillMaxSize().weight(1f).pointerInput(Unit){detectDragGestures(onDragStart={offset->//手指按下,新建一条路径,移动到按下的坐标currentPath=Path().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(path=p,color=Color.Blue))}currentPath=null})}){//先绘制已经完成保存的笔迹pathList.forEach{item->drawPath(path=item.path,color=item.color,strokeWidth=4f)}//绘制当前正在拖动的临时笔迹currentPath?.let{drawPath(path=it,color=Color.Blue,strokeWidth=4f)}}}}

逐行解读

  1. drawBehind vs Canvas
  • Modifier.drawBehind{} :在现有组件的底层绘制;适合给现有UI补充简单图形;组件本身仍然可以放置子Compose元素。
  • Canvas{} :专门的画布组件;Canvas花括号内部是绘制作用域,不能直接写Compose组件;只能调用底层绘制API(drawLine、drawRect、drawPath、drawCircle)。
    画布坐标系:左上角是原点 Offset(0,0) ;X向右增大;Y向下增大。
  1. Path路径对象
    moveTo(x,y) :移动画笔到起点(不画线)
    lineTo(x,y) :从上一个点连线到当前坐标;持续拖动就不断追加lineTo,形成连续笔迹。

Path是可变对象!不要直接放在状态里面;本Demo策略:抬起手指之后复制保存到stateList。

  1. 手势 detectDragGestures
    pointerInput(Unit) 开启手势输入域;
  • onDragStart:手指初次按下;创建新Path,moveTo按下点;
  • onDrag:手指持续滑动;持续调用lineTo追加路径; change.consume() 消费手势事件;防止下层组件同时响应触摸;
  • onDragEnd:手指抬起;把这条完成的笔迹加入 mutableStateListOf 列表;之后Canvas重组就会绘制这条笔迹。
  1. 状态保存逻辑
    mutableStateListOf() 用来存放已经完成的笔迹;
    currentPath 保存正在画还没有结束的临时笔迹;
    当点击清空按钮直接clear整个列表,画布就全部擦除。
  2. 注意内存与重建
    remember保存笔迹;屏幕旋转Activity重建画板上绘制的线条会全部消失!
    如果希望旋转之后保留手绘笔迹:需要把笔迹数据上移到ViewModel;复杂场景可以序列化为点集合保存。

容易踩坑

  1. ❌ Path是可变对象;直接修改存进状态,Compose无法感知变化,界面不会自动重绘;抬起之后把完成的笔迹加入stateList。
  2. ❌ Canvas绘制代码里面写Compose Text、Button;Canvas的绘制lambda只能调用底层绘图函数,不能放可组合项。
  3. ❌ 忘记 change.consume() ;手势事件向下穿透,导致下层控件意外响应点击。
  4. ❌ 使用remember保存手绘笔迹;屏幕旋转绘制内容丢失;正式项目笔迹数据迁移到ViewModel。
  5. ✅ detectDragGestures监听拖拽手势;Path构建线条;mutableStateList保存已经完成笔迹。

多元化习题(练习)

习题1【基础‑改错题】

直接把Path对象放在remember可变状态中,拖动的时候直接修改同一个Path,为什么画布不会实时刷新?
答案:
Path属于可变类;修改对象内部属性不会触发Compose重组;Compose只能感知状态变量本身引用的变更;应当新建Path对象,修改完成之后加入状态列表。
解读:
Compose依靠引用变化触发重组,可变对象内部改动不会被追踪。

习题2【基础‑代码修改题】

修改画板示例,默认画笔颜色改为红色(Color.Red)。
答案:

pathList.add(DrawPathItem(path=p,color=Color.Red))

同时临时绘制的那一处也修改color=Color.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;避免底层的组件同时响应点击、滑动。
解读:
画板画布上绘图的时候,不希望底层控件接收触摸。

本节课知识点总结

  1. Canvas画布自定义绘图;画布坐标原点位于左上角;X向右,Y向下;区分Canvas组件与drawBehind修饰器。
  2. Path路径:moveTo设置起点,lineTo追加线条;Path是可变对象;直接修改不会触发重组。
  3. pointerInput + detectDragGestures监听手指按下、拖动、抬起;consume消费触摸事件。
  4. 使用mutableStateList保存多条已经完成笔迹;currentPath临时保存正在绘制的线条;实现简易画板、清空功能。
  5. remember保存的笔迹屏幕旋转丢失;想要保留则迁移ViewModel;长期保存笔迹存入Room。

下一课预告:第17课 Compose动画基础;animate*AsState、Transition;页面元素显隐动画

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 18:25:42

Socket TCP通信实战:从三次握手到端口冲突排障全解析

接触Socket编程TCP的朋友&#xff0c;很多都不是单纯想学理论&#xff0c;而是手里正卡着某个具体问题&#xff1a;写了半天C#上位机收不到PLC数据、ESP01S连不上手机热点、Harbor推镜像一直报“dial tcp connection refused”、或者Windows服务一重启就报“套接字地址只允许使…

作者头像 李华
网站建设 2026/10/22 18:25:41

高分二号遥感图像语义分割实战:从DN值到mIoU的全链路PyTorch指南

简介&#xff1a;本资源是一份面向遥感图像处理研究者、深度学习初学者及地理信息工程实践者的PyTorch语义分割实战教程&#xff0c;聚焦高分遥感图像的地物精细识别问题&#xff0c;覆盖环境监测、城市规划与灾害评估等典型应用场景。资源包共1029个文件&#xff0c;含819张遥…

作者头像 李华
网站建设 2026/10/22 16:02:36

千问办公标准模式:用工程手段突破大模型性能成本速度三角

把三个问题放在台面上&#xff1a;每次调用要花多少钱&#xff0c;第一次响应要等多久&#xff0c;返回答案到底准不准。任何接触过AI办公产品的开发者&#xff0c;一定在这三件事上做过艰难的取舍。选便宜的小模型&#xff0c;答复速度快了&#xff0c;可一旦面对数据分析、长…

作者头像 李华
网站建设 2026/10/21 3:44:52

网上宠物店毕设项目实测:SSM+MySQL从部署到二次开发全流程

简介&#xff1a;采用Java、JSP、SSM框架与MySQL数据库实现的网上宠物店毕业设计项目&#xff0c;面向计算机相关专业学生、需要完成毕设的应届生以及想熟悉SSM整合流程的开发者&#xff0c;可作毕设参考、二次开发或课程项目。压缩包共1539个文件、约99.12MB&#xff0c;以Jav…

作者头像 李华
网站建设 2026/10/15 18:06:17

JavaWeb最小运行单元:从零手搭可调试Servlet环境

简介&#xff1a;这是一份面向Java Web初学者与进阶学习者的实战型源码资源&#xff0c;聚焦MVC分层开发实践&#xff0c;帮助学习者系统掌握从Servlet/JSP基础到Spring、Hibernate等主流框架集成的完整Web应用开发流程。资源压缩包为6.75MB的ZIP格式&#xff0c;共含450个文件…

作者头像 李华
网站建设 2026/10/15 20:34:21

HW蓝队总结怎么写?从告警日志到防守证据链的完整指南

简介&#xff1a;面向HW护网行动、攻防演练与重保工作的防守方人员&#xff0c;提供一份可直接套用的总结模板&#xff0c;以docx格式呈现&#xff0c;压缩包约18KB。文件仅1个&#xff0c;内容按工作概述、阶段性防守安排、防守手段、工作亮点、改进与未来规划、数据分析与图表…

作者头像 李华