ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android 移动端手势交互实战:从触摸事件分发到仿美图秀秀抠图工具

Android 移动端手势交互实战:从触摸事件分发到仿美图秀秀抠图工具 摘要:本文聚焦移动端手势交互十大高频痛点,覆盖 Android/iOS 双平台,从触摸事件分发、多点触控到滑动冲突解决逐一拆解,并附仿美图秀秀抠图工具实战案例与性能优化建议,助你快速上手、落地可用。在移动端开发中,手势交互往往是决定应用“手感”的关键。很多开发者在初期只关注业务逻辑的实现,却忽略了用户手指与屏幕接触那一瞬间的细腻反馈。当用户试图滑动列表时页面却意外缩放,或者在输入框聚焦时软键盘遮挡了关键内容,这些看似微小的体验瑕疵,往往会直接导致用户的流失。特别是在涉及绘图、签名或图像编辑等复杂场景时,如何精准地捕获触摸轨迹、区分点击与长按、以及协调多重手势之间的冲突,成为了技术攻关的难点。解决这些问题不能仅靠堆砌代码,更需要深入理解 Android 或 iOS 底层的事件分发机制。从最基础的监听配置到复杂的多人多点触控处理,每一个环节都需要精细化的设计。例如,在实现手写签名功能时,不仅要记录坐标点,还要考虑滑动的平滑度和断点重连;而在制作类似美图秀秀的抠图工具时,则需要同时处理缩放、旋转和平移多种手势的并发操作,这对事件拦截和消费逻辑提出了极高的要求。目录① 开发环境搭建与事件监听基础配置② 软键盘弹出检测与物理按键响应实现③ 自定义返回键逻辑与拦截处理技巧④ 触摸事件分发机制与手势接管流程⑤ 滑动轨迹跟踪与手写签名功能开发轨迹采集:完整的事件生命周期管理贝塞尔平滑:为什么取中点而不是直接连点动态笔触:根据滑动速度调整画笔粗细数据存储:序列化路径点而非 Bitmap导出透明背景 PNG⑥ 点击长按区分及滑动方向识别方法点击与长按:用计时器 + 位移阈值做互斥双击:基于时间窗口的二次判定滑动方向识别:向量角度映射到 8 方位⑦ 缩放旋转手势判定与多点触控处理双指手势的数学原理:距离与角度完整实现骨架:从 POINTER_DOWN 到 UP锚点保持:手指抬起后不丢状态⑧ 滚动翻页冲突解决与下拉刷新适配冲突根源:谁该拦截这次滑动方案一:基于方向与边界的动态协商方案二:ViewPager2 与下拉刷新的组合方案三:NestedScrolling 机制(推荐)关键细节与避坑⑨ 综合实战:仿美图秀秀抠图工具制作功能拆解:一个抠图工具需要哪些模块核心实现:状态机驱动的手势切换图层管理:为什么不能全量重绘边缘羽化:让抠图更自然关键细节与避坑⑩ 常见交互报错排查与性能优化建议一、事件丢失二、手势不灵敏三、界面卡顿四、内存泄漏附录:完整实战代码(仿美图秀秀抠图工具)1. 布局文件 activity_cutout.xml2. 自定义抠图视图 CutoutView.kt3. 主 Activity 集成4. 自定义返回键:撤销优先总结与参考资料本文将剥离理论化的概念陈述,直接切入实际开发中高频遇到的痛点。我们将沿着一条清晰的技术路径,从环境搭建开始,逐步攻克软键盘适配、物理按键拦截、触摸事件分发等基础关卡,进而深入到滑动轨迹跟踪、手势冲突解决等核心领域。最后,通过一个完整的仿美图秀秀抠图工具实战案例,将前述零散的知识点串联成可落地的解决方案,并分享常见的报错排查思路与性能优化技巧,帮助大家在面对复杂交互需求时能够游刃有余。① 开发环境搭建与事件监听基础配置工欲善其事,必先利其器。在进行复杂的手势开发前,确保开发环境的纯净与配置的正确是第一步。对于 Android 开发者而言,建议在build.gradle中确认最低支持版本(minSdkVersion),因为部分高级手势特性(如多指触控的某些细节)在不同 API 级别下的表现存在差异。通常建议将目标版本设定在较新的稳定版,以利用系统优化的事件分发机制。这里给出一个推荐的基础配置:android{compileSdk34defaultConfig{minSdk23// 覆盖绝大多数机型,且支持多点触控 APItargetSdk34}}基础配置的核心在于监听器的注册与管理。在许多初学者项目中,常见的问题是在Activity或Fragment中过度耦合监听逻辑,导致代码难以维护。更优雅的做法是创建一个独立的GestureManager类,统一接管视图树的触摸事件。在初始化阶段,我们需要实例化GestureDetector和ScaleGestureDetector(针对 Android)或对应的 UIKit 手势识别器(针对 iOS)。下面是一个 Android 侧的监听器注册示例:publicclassGestureManager{privatefinalGestureDetectorgestureDetector;privatefinalScaleGestureDetectorscaleDetector;publicGestureManager(Contextcontext,ViewtargetView){gestureDetector=newGestureDetector(context,newGestureListener());scaleDetector=newScaleGestureDetector(context,newScaleListener());// 统一接管目标视图的触摸事件targetView.setOnTouchListener((v,event)-{gestureDetector.onTouchEvent(event);scaleDetector.onTouchEvent(event);returntrue;// 消费事件,避免事件流中断});}}这里有一个关键的配置细节容易被忽视:视图的focusable和clickable属性。如果一个自定义 View 需要接收触摸事件,它必须明确声明可聚焦,否则在软键盘弹出或焦点转移时,事件可能会丢失。此外,对于需要拦截父容器滑动事件的场景,应在 XML 布局或代码中设置android:clipChildren="false",防止子视图绘制超出边界时被裁剪,影响手势视觉反馈。同时建议在AndroidManifest.xml中为 Activity 声明android:windowSoftInputMode="adjustResize",为后续软键盘适配打好基础。② 软键盘弹出检测与物理按键响应实现软键盘的弹出与收起是移动端最典型的界面状态变化之一,处理不当极易造成布局错乱。传统的做法是监听窗口高度变化,但这在某些全面屏机型或分屏模式下并不稳定。更可靠的方案是利用OnApplyWindowInsetsListener(Android)或键盘通知中心(iOS)来获取精确的键盘高度信息。下面是一个 Android 侧的键盘状态监听示例:publicclassKeyboardUtils{publicinterfaceOnKeyboardVisibilityListener{voidonVisibilityChanged(booleanvisible,intkeyboardHeight);}publicstaticvoidattach(finalViewrootView,finalOnKeyboardVisibilityListenerlistener){rootView.setOnApplyWindowInsetsListener((v,insets)-{intkeyboardHeight=insets.getInsets(WindowInsets.Type.ime()).bottom;booleanvisible=keyboardHeight0;listener.onVisibilityChanged(visible,keyboardHeight);returninsets;});}}当检测到键盘弹出时,首要任务是将当前聚焦的输入框滚动至可视区域。这不仅仅是简单的scrollTo,还需要计算键盘遮挡的具体像素值,并预留一定的安全边距(Padding),避免输入框紧贴键盘边缘。我们可以编写一个工具方法,动态调整根布局的paddingBottom或使用WindowSoftInputMode的adjustResize模式,让系统自动协助布局重排。下面是一个动态调整布局的示例:// 在键盘弹出时,为根布局增加底部 padding,避免输入框被遮挡privatevoidadjustForKeyboard(booleanvisible,intkeyboardHeight){Viewroot=findViewById(R.id.rootLayout);if(visible){// 预留 16dp 安全边距,避免输入框紧贴键盘intsafePadding=(int)(16*getResources().getDisplayMetrics().density);root.setPadding(0,0,0,keyboardHeight+safePadding);}else{root.setPadding(0,0,0,0);}}物理按键的响应则涉及到底层事件拦截。虽然现代触屏设备物理按键较少,但在平板或折叠屏设备上,返回键、菜单键的处理依然重要。重写onKeyDown或dispatchKeyEvent方法是标准流程,但需要注意事件消费的时机。如果在自定义手势处理过程中(如正在绘图),用户按下了返回键,我们可能希望先提示“是否保存草稿”而不是直接退出。此时,需要在事件回调中设置标志位,延迟执行默认的返回逻辑,直到用户确认操作。下面是一个带状态标志位的按键处理示例:privatebooleanisDrawing=false;// 是否处于绘图等需要拦截返回键的状态privatebooleanpendingExit=false;// 是否已弹出确认对话框@OverridepublicbooleandispatchKeyEvent(KeyEventevent){if(event.getKeyCode()==KeyEvent.KEYCODE_BACKevent.getAction()==KeyEvent.ACTION_DOWN){if(isDrawing!pendingExit){// 绘图过程中按返回键,先提示保存草稿pendingExit=true;showSaveDraftDialog();// 用户确认后再真正退出returntrue;// 拦截事件,不执行默认返回}}returnsuper.dispatchKeyEvent(event);}需要特别注意的是,dispatchKeyEvent的拦截优先级高于onKeyDown,适合在需要全局拦截的场景使用;而onKeyDown更适合在单个 View 或 Activity 内部做局部处理。两者结合使用,可以构建出既灵活又稳定的物理按键响应体系。③ 自定义返回键逻辑与拦截处理技巧在复杂交互场景中,默认的返回行为往往无法满足需求。例如,在一个全屏图片预览模式中,用户点击返回键应该退出预览而非关闭整个应用;在绘图模式下,第一次点击返回键应撤销上一步操作,第二次才退出页面。实现这一逻辑的关键在于“拦截”与“分级处理”。我们可以在 Activity 中维护一个栈结构,记录当前的交互状态(如:正常浏览、编辑模式、弹窗显示)。当捕获到返回键事件时,优先检查栈顶状态。如果处于编辑模式,则调用撤销方法并消耗掉该事件(返回 true),阻止系统默认的 finish 操作。下面是一个基于状态栈的分级拦截示例:// 用一个栈记录交互状态,栈顶表示当前所处的模式privatefinalDequeInteractionModemodeStack=newArrayDeque();enumInteractionMode{NORMAL,// 正常浏览EDITING,// 编辑/绘图模式DIALOG// 弹窗显示中}@OverridepublicbooleanonKeyDown(intkeyCode,KeyEventevent){if(keyCode==KeyEvent.KEYCODE_BACKevent.getAction()==KeyEvent.ACTION_DOWN){InteractionModecurrent=modeStack.peek();// 弹窗显示时,返回键优先关闭弹窗if(current==InteractionMode.DIALOG){dismissDialog();modeStack.pop();returntrue;}// 编辑模式下,第一次撤销、第二次才退出if(current==InteractionMode.EDITING){if(drawingView.canUndo()){drawingView.undo();showToast("已撤销上一步");returntrue;// 消耗事件,不执行默认返回}else{// 无法撤销,提示退出showExitConfirmDialog();returntrue;}}// 全屏预览模式下,返回键退出预览而非关闭应用if(current==InteractionMode.NORMALisFullScreenPreview()){exitFullScreenPreview();returntrue;}}returnsuper.onKeyDown(keyCode,event);}这种分层拦截机制不仅提升了用户体验,也避免了因误触导致的数据丢失。值得注意的是,在处理拦截逻辑时,务必保证 UI 线程的流畅性,避免在按键回调中执行耗时操作,以免引起界面卡顿。除了onKeyDown,dispatchKeyEvent提供了更底层的拦截入口,适合在需要全局拦截的场景使用。两者的核心区别在于:dispatchKeyEvent在事件分发到任何 View 之前触发,优先级更高;而onKeyDown在 Activity 内部处理,更适合局部逻辑。下面是一个结合两者优点的完整示例:// 全局拦截:在事件分发前判断是否需要特殊处理@OverridepublicbooleandispatchKeyEvent(KeyEventevent){if(event.getKeyCode()==KeyEvent.KEYCODE_BACKevent.getAction()==KeyEvent.ACTION_DOWN){// 若当前处于绘图状态,优先交给 onKeyDown 的分级逻辑处理if(modeStack.peek()==InteractionMode.EDITING){returnonKeyDown(event.getKeyCode(),event);}}returnsuper.dispatchKeyEvent(event);}最后提醒一个容易被忽略的细节:在 Android 10(API 29)及以上版本,系统引入了预测性返回手势(Predictive Back),建议在Manifest中声明android:enableOnBackInvokedCallback="true",并配合OnBackInvokedDispatcher使用,以便在全面屏手势导航下也能获得一致的返回体验。对于仍在使用传统按键导航的设备,上述onKeyDown/dispatchKeyEvent方案依然完全适用。④ 触摸事件分发机制与手势接管流程触摸事件的分发是移动开发的深水区,理解dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent三者之间的关系至关重要。事件流从 Activity 向下传递到 ViewGroup,再分发到具体的 View。在这个过程中,任何一个节点都有权拦截或消费事件。下面先梳理三者的职责分工:dispatchTouchEvent:事件分发的总入口,负责把事件交给onInterceptTouchEvent判断是否拦截,再决定是否下发给子 View。onInterceptTouchEvent:仅存在于 ViewGroup,用于决定是否拦截事件流。返回true表示拦截,后续事件不再下发给子 View。onTouchEvent:真正处理事件的回调,返回true表示消费事件,返回false则事件会向上回传给父容器。一个典型的完整分发流程如下:手指按下时,事件从 Activity 的dispatchTouchEvent进入,逐层向下传递。每一层 ViewGroup 先调用onInterceptTouchEvent判断是否拦截;若未拦截,则继续下发给子 View 的dispatchTouchEvent。当事件到达最底层的 View 后,由它的onTouchEvent决定是否消费。若子 View 返回false,事件会沿原路向上回传,直到某个父容器消费为止。下面用一个简化示例演示如何自定义 ViewGroup 的拦截逻辑:publicclassGestureViewGroupextendsFrameLayout{publicGestureViewGroup(Contextcontext){super(context);}@OverridepublicbooleanonInterceptTouchEvent(MotionEventev){// 仅在移动事件中判断是否需要拦截if(ev.getActionMasked()==MotionEvent.ACTION_MOVE){// 例如:当子视图是横向滑动控件时,父容器不拦截水平滑动if(isHorizontalSwipe(ev)){returnfalse;// 放行给子视图}// 垂直滑动则拦截,交给父容器处理returntrue;}// DOWN 事件不拦截,保证子视图能收到完整事件流returnsuper.onInterceptTouchEvent(ev);}privatebooleanisHorizontalSwipe(MotionEventev){// 根据历史坐标计算滑动方向floatdx=Math.abs(ev.getHistoricalX(0)-ev.getX());floatdy=Math.abs(ev.getHistoricalY(0)-ev.getY());returndxdy;}}手势接管的核心在于“预判”与“抢占”。当用户手指按下(ACTION_DOWN)时,系统尚不清楚用户意图是点击、滑动还是缩放。此时,父容器通常会暂时拦截事件进行观察。一旦检测到移动距离超过阈值(TouchSlop),父容器应立即通过requestDisallowInterceptTouchEvent(true)告知子视图不要拦截后续事件,或者由父容器直接接管后续的所有 MOVE 和 UP 事件。下面是一个子视图主动抢占事件流的示例:// 在子视图的 onTouchEvent 中,检测到滑动意图后主动抢占事件流@OverridepublicbooleanonTouchEvent(MotionEventevent){switch(event.getActionMasked()){caseMotionEvent.ACTION_DOWN:// 记录初始触摸点downX=event.getX();downY=event.getY();returntrue;// 消费 DOWN,确保后续事件能到达caseMotionEvent.ACTION_MOVE:floatdx=event.getX()-downX;floatdy=event.getY()-downY;// 位移超过 TouchSlop,判定为滑动,禁止父容器拦截if(Math.abs(dx)touchSlop||Math.abs(dy)touchSlop){getParent().requestDisallowInterceptTouchEvent(true);}// 处理滑动逻辑handleSwipe(dx,dy);returntrue;caseMotionEvent.ACTION_UP:caseMotionEvent.ACTION_CANCEL:// 复位,允许父容器恢复拦截getParent().requestDisallowInterceptTouchEvent(false);returntrue;}returnsuper.onTouchEvent(event);}在实际开发中,经常遇到 ScrollView 内部嵌套可滑动图表或地图的情况。如果不做特殊处理,滑动地图时会触发页面的上下滚动。解决方案是在子视图的onTouchEvent中判断滑动方向,如果是水平滑动,则禁止父容器拦截;如果是垂直滑动,则放行。这种动态的拦截策略需要精确计算初始触摸点与当前点的差值,并结合角度判定来实现。下面是一个结合角度判定的完整示例:// 在子视图的 onTouchEvent 中,根据滑动角度动态决定是否抢占事件流@OverridepublicbooleanonTouchEvent(MotionEventevent){switch(event.getActionMasked()){caseMotionEvent.ACTION_DOWN:downX=event.getX();downY=event.getY();returntrue;caseMotionEvent.ACTION_MOVE:floatdx=event.getX()-downX;floatdy=event.getY()-downY;doubleangle=Math.toDegrees(Math.atan2(dy,dx));// 角度接近 0° 或 180° 视为水平滑动,禁止父容器拦截if(Math.abs(angle)45||Math.abs(angle)135){getParent().requestDisallowInterceptTouchEvent(true);}else{// 垂直滑动,放行给父容器处理getParent().requestDisallowInterceptTouchEvent(false);}returntrue;caseMotionEvent.ACTION_UP:caseMotionEvent.ACTION_CANCEL:getParent().requestDisallowInterceptTouchEvent(false);returntrue;}returnsuper.onTouchEvent(event);}最后补充一个新手容易踩的坑:onInterceptTouchEvent一旦对某个事件返回true,后续的 MOVE 和 UP 事件将不再经过该方法,而是直接交给父容器的onTouchEvent处理。因此,如果需要在拦截后恢复子视图的事件接收,必须在ACTION_UP或ACTION_CANCEL时手动复位状态,否则会出现“事件被永久抢占”的诡异现象。这也是很多嵌套滚动冲突难以排查的根源所在。为了更直观地理解上述拦截与抢占机制,下面将常见的四类手势冲突场景整理成对比表,并给出对应的解决方案。表中的技术点均来自前文介绍的requestDisallowInterceptTouchEvent与onInterceptTouchEvent。冲突场景问题原因解决方案ScrollView 嵌套横向滑动地图父容器 ScrollView 默认拦截所有纵向滑动,地图的水平滑动被误判为纵向滚动,导致地图无法平移。在子视图(地图)的onTouchEvent中,通过角度判定识别水平滑动,一旦位移超过 TouchSlop 就调用getParent().requestDisallowInterceptTouchEvent(true)禁止父容器拦截;垂直滑动则放行。ViewPager 嵌套垂直滑动列表ViewPager 拦截水平滑动,列表拦截垂直滑动,两者方向正交时仍可能因边界判断失误互相抢占事件流。在子视图(列表)的onTouchEvent中,检测到垂直滑动意图后调用requestDisallowInterceptTouchEvent(true)抢占事件流;在ACTION_UP/ACTION_CANCEL时复位为false,让 ViewPager 恢复水平翻页能力。绘图模式下父容器抢占事件父容器在onInterceptTouchEvent中对ACTION_MOVE返回true,导致绘图 View 收不到后续事件,笔画中断。父容器在onInterceptTouchEvent中仅当滑动方向与自身滚动方向一致时才拦截;绘图模式下对ACTION_DOWN不拦截,保证子视图收到完整事件流,并在ACTION_UP/ACTION_CANCEL时复位拦截状态。除了手动实现事件分发,Android 与 iOS 都提供了封装好的手势识别器,能大幅降低开发成本。下面将 Android 的GestureDetector、ScaleGestureDetector与 iOS 的UIGestureRecognizer家族在点击、长按、滑动、缩放、旋转等场景下的使用方式、优缺点与适用场景整理成对比表,方便你快速选型。手势场景Android 识别器iOS 识别器使用方式(Android)使用方式(iOS)优点缺点适用场景点击(单击/双击)GestureDetector(onSingleTapUp/onDoubleTap)UITapGestureRecognizer在GestureDetector.SimpleOnGestureListener中重写onSingleTapUp、onDoubleTap,再通过setOnTouchListener把事件喂给gestureDetector.onTouchEvent(event)。UITapGestureRecognizer(target:action:),设置numberOfTapsRequired = 2即可识别双击,添加到视图后自动接管触摸。系统已封装时间窗口与位移阈值,无需手写计时器;双击与单击可自动互斥。自定义时长/位移阈值受限,需继承或手动实现;与滑动同时发生时需额外协调。按钮点击、图片双击放大、列表项轻点等基础交互。长按GestureDetector(onLongPress)UILongPressGestureRecognizer在SimpleOnGestureListener中重写onLongPress,配合setIsLongpressEnabled(true)启用。设置minimumPressDuration(默认 0.5s),在回调中根据state == .began触发长按。系统自动处理长按与滑动的互斥,手指移动超过阈值会自动取消长按。长按触发后若需持续拖动,要自行处理state的.changed/.ended阶段。长按呼出菜单、拖拽排序、图片长按预览等。滑动(拖拽/平移)GestureDetector(onScroll)UIPanGestureRecognizer在onScroll回调中接收distanceX/distanceY,累加即可实现拖拽。在回调中读取translation(in: view),配合state == .changed实时更新位置。系统已处理 TouchSlop 判定,位移超过阈值才回调,避免抖动误触。无法直接识别 8 方位方向,需自行根据velocity/translation计算角度。拖拽控件、滑动解锁、手势密码、列表滚动等。缩放(双指捏合)ScaleGestureDetectorUIPinchGestureRecognizer在onScale回调中读取scaleFactor,累乘到视图的Matrix即可。在回调中读取scale,累乘到CGAffineTransform。系统自动管理双指焦点与缩放比例,无需手写POINTER_DOWN数学计算。默认只提供缩放比例,不提供旋转角度,旋转需另配识别器。图片缩放、地图缩放、画布缩放等。旋转(双指旋转)无内置识别器,需在ScaleGestureDetector基础上自行计算两指连线角度UIRotationGestureRecognizer在onScale回调中额外计算event.getX(0) - event.getX(1)与event.getY(0) - event.getY(1)的atan2角度差。在回调中读取rotation(弧度),转换为角度后应用到视图。iOS 原生支持,开箱即用;Android 需自行实现但可控性更强。Android 无内置旋转识别器,需手动维护角度状态;iOS 的rotation为相对值,需累加。图片旋转、地图旋转、贴纸旋转等。组合手势(缩放+旋转+平移)ScaleGestureDetector+ 手动onTouchEvent状态机多个UIGestureRecognizer叠加 +delegate协调用状态机(NONE/ZOOM/DRAG)区分单指拖拽与双指缩放,在ACTION_POINTER_DOWN时切换模式。同时添加UIPinchGestureRecognizer、UIRotationGestureRecognizer、UIPanGestureRecognizer,在gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:)返回true允许并行识别。iOS 组合手势开箱即用,识别器之间可并行;Android 状态机灵活但需自行处理互斥。Android 组合手势实现复杂,需精细管理POINTER生命周期;iOS 多识别器并行时需注意回调顺序。图像编辑器、地图浏览、AR 场景等需要多指并发操作的场景。选型建议:若只需单一手势(点击、长按、滑动、缩放),优先使用系统识别器,代码量少且稳定;若需组合手势或自定义阈值/方向判定,Android 侧建议在ScaleGestureDetector基础上叠加状态机,iOS 侧则利用多识别器并行 +delegate协调,两者都能在保证手感的同时兼顾可维护性。| 双击与长按同时触发 | 双击的第二次按下与长按计时器同时启动,两者未做互斥,导致一次操作同时触发双击和长按回调。 | 在ACTION_DOWN时先取消长按计时器,再启动双击窗口计时器;一旦发生明显位移(超过 TouchSlop)立即取消长按,转入滑动,确保双击与长按互斥。 |⑤ 滑动轨迹跟踪与手写签名功能开发手写签名功能是事件跟踪的典型应用。其核心原理是将连续的触摸点连接成平滑的曲线。naive 的实现方式是每收到一个ACTION_MOVE就画一条线段,但这在手指快速移动时会出现明显的折角和断点,因为系统上报的采样点频率有限。要获得丝滑的笔触,我们需要从「轨迹采集」和「曲线平滑」两个层面入手。轨迹采集:完整的事件生命周期管理一个健壮的签名视图,必须正确处理ACTION_DOWN、ACTION_MOVE、ACTION_UP和ACTION_CANCEL四个事件,缺一不可。ACTION_CANCEL尤其容易被忽略——当系统因来电、弹窗或父容器抢占而中断事件流时,如果不复位状态,下一次签名会残留上一次的轨迹。下面是一个完整的轨迹采集骨架:publicclassSignatureViewextendsView{privatefinalPathdrawPath=newPath();// 当前正在绘制的路径privatefinalListPathstrokes=newArrayList();// 已完成的笔画privatefloatlastX,lastY;@OverridepublicbooleanonTouchEvent(MotionEventevent){switch(event.getActionMasked()){caseMotionEvent.ACTION_DOWN:// 新笔画开始:重置路径并移动到起点drawPath.reset();drawPath.moveTo(event.getX(),event.getY());lastX=event
返回列表