文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

HarmonyOS自定义控件之触摸事件与事件分发

2024-12-02 23:47

关注

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

触摸事件

如何监听触摸事件

HarmonyOS中可以通过Listener的方式:

  1. setTouchEventListener(new TouchEventListener() { 
  2.     @Override 
  3.     public boolean onTouchEvent(Component component, TouchEvent touchEvent) { 
  4.         return false
  5.     } 
  6. }); 

注意:setTouchEventListener会被覆盖

常用的触摸事件的类型

这里我们对比其他主流系统中MotionEvent与HarmonyOS中TouchEvent来方便理解与记忆。

MotionEvent的常用的事件类型与HarmonyOS中的TouchEvent类型基本可以对应起来:

常用的Api

获取事件类型

  1. touchEvent.getAction() == TouchEvent.PRIMARY_POINT_DOWN 

获取手指相对于屏幕的x、y坐标

  1. touchEvent.getPointerScreenPosition(touchEvent.getIndex()).getX(); 
  2. touchEvent.getPointerScreenPosition(touchEvent.getIndex()).getY(); 

获取手指相对于父控件的x、y坐标

  1. touchEvent.getPointerPosition(touchEvent.getIndex()).getX(); 
  2. touchEvent.getPointerPosition(touchEvent.getIndex()).getY(); 

getPointerScreenPosition与getPointerPosition的区别

前者是相对的屏幕的坐标,而后者是相对于父控件的坐标。如果在手指滑动过程中,对该控件做了位移,那么getPointerPosition获取的坐标将会是手指本身坐标加上控件的位移量,导致位移异常。

这里建议,如果需要根据坐标来计算,都使用getPointerScreenPosition比较保险。

总结

TouchEvent提供了基础api,但是没有MotionEvent内一些比较高阶的api,比如obtain等。接下来我们来关注更为重要的事件分发。

事件分发

事件分发是一套比较重要同时也比较复杂的机制,如果不熟悉这套机制,那么在遇到稍微复杂的滑动失效问题就会觉得手足无措。在这里通过打印日志的方式来摸索HarmonyOS上的事件的传递机制。

HarmonyOS中事件的传递机制

首先,我们通过打印日志的方式,来摸索触摸事件是如何在Component中传递的。经过实验,发现如下几条规律:

HarmonyOS中的事件传递更像是冒泡,而非分发,down事件一旦被某一个控件消费了,那么其他控件将都收不到后续事件了。这样的机制比较难去实现一些复杂的嵌套效果。

比如子控件响应横向滑动,父控件响应垂直滑动这种情况。子控件如果要想收到后续的move事件,只能在down的时候返回true,这样就导致父控件完全收不到触摸事件。子控件如果像要在move时判断滑动方向而down事件返回了false,那么子控件将再也接收不到后续的事件了。

HarmonyOS的事件冒泡比较简单,一旦约定好就再也没有反悔的机会了。那么如何类似其他主流系统一样,从顶层控件分发并且可以拦截事件呢?

这里只提供思路,具体代码可以参考:事件分发

实现事件分发

我们构想中的事件分发应该是这样:事件是首先到顶层的父控件,然后经过dispatchTouchEvent一层层向下分发。ComponentContainer可以通过onInterceptTouchEvent拦截事件,并交给自己的onTouchEvent来处理。如果ComponentContainer不处理事件则继续向下分发,直到最终的Component控件。这样的机制意味着每一层都有机会能拿到事件,那么如何在HarmonyOS中实现呢?

我们可以将事件分发相关的函数与代码,抽取出来,移植到HarmonyOS中,并通过一些手段应用到HarmonyOS的onTouchEvent中。

抽象

HarmonyOS中没有dispatchTouchEvent、onInterceptTouchEvent等函数,如何应用到组件中呢?抽象接口,将事件分发相关的函数抽象成两个接口:

View

  1.  
  2. public interface View { 
  3.      
  4.     boolean dispatchTouchEvent(TouchEvent event); 
  5.  
  6.      
  7.     boolean onTouchEvent(TouchEvent event); 
  8.  
  9.      
  10.     boolean isConsumed(); 

ViewGroup

  1.  
  2. public interface ViewGroup extends View { 
  3.  
  4.      
  5.     void requestDisallowInterceptTouchEvent(boolean disallowIntercept); 
  6.  
  7.      
  8.     boolean onInterceptTouchEvent(TouchEvent ev); 

实现

然后借助两个帮助类,来实现两个接口中的相关函数。将View中事件分发的具体代码封装到ViewHelper中,将ViewGroup中事件分发的具体代码封装到ViewGroupHelper中。

代码参考ViewHelper、ViewGroupHelper

分发

最后借助一个分发帮助类DispatchHelper,来将HarmonyOS中的事件,从顶层开始按照ViewGroupHelper中的dispatchTouchEvent来分发。

DispatchHelper主要做了下面几件事:

代码:

  1.  
  2. public class DispatchHelper { 
  3.  
  4.      
  5.     private static final List nodes = new ArrayList<>(); 
  6.      
  7.     private static final HashMap<Integer, Boolean> records = new HashMap<>(); 
  8.  
  9.      
  10.     private static String lastEvent = ""
  11.     private final static TouchEventCompact compact = new TouchEventCompact(true); 
  12.  
  13.      
  14.     public static boolean dispatch(Component component, TouchEvent touchEvent) { 
  15.         // 过滤由于自下而上的事件冒泡 与 自上而下的事件分发机制而产生的重复分发 
  16.         if (isSameEvent(touchEvent)) { 
  17.             return true
  18.         } 
  19.  
  20.         // 纠正通过getPointerPosition获取的y坐标的偏移 
  21.         compact.correct(touchEvent); 
  22.  
  23.         lastEvent = convertEvent(touchEvent); 
  24.  
  25.         int action = touchEvent.getAction(); 
  26.         if (action == TouchEvent.PRIMARY_POINT_DOWN) { 
  27.             clearNodes(); 
  28.         } 
  29.  
  30.         if (nodes.size() <= 0) createNodes(component); 
  31.         dispatch(nodes.size(), 1, touchEvent); 
  32. //        collectRecords(); 
  33.  
  34. //        boolean result = findRecord(component); 
  35.  
  36.         if (action == TouchEvent.PRIMARY_POINT_UP) { 
  37.             clearNodes(); 
  38.         } 
  39.  
  40.         return true
  41.     } 
  42.  
  43.      
  44.     public static void requestDisallowInterceptTouchEvent(Component component, boolean disallowIntercept) { 
  45.         if (component.getComponentParent() instanceof ViewGroup) { 
  46.             ((ViewGroup) component.getComponentParent()).requestDisallowInterceptTouchEvent(disallowIntercept); 
  47.         } 
  48.     } 
  49.  
  50.      
  51.     public static void postRequestDisallowInterceptTouchEvent(Component component, boolean disallowIntercept) { 
  52.         EventHandler handler = new EventHandler(EventRunner.getMainEventRunner()); 
  53.         handler.postTask(() -> requestDisallowInterceptTouchEvent(component, disallowIntercept)); 
  54.     } 
  55.  
  56.     public static TouchEventCompact getTouchEventCompact() { 
  57.         return compact; 
  58.     } 
  59.  
  60.      
  61.     private static boolean dispatch(int sizeint i, TouchEvent touchEvent) { 
  62.         boolean result = false
  63.         if (size > 0) { 
  64.             Component node = nodes.get(size - i); 
  65.             if (node instanceof ViewGroup) { 
  66.                 ViewGroup group = (ViewGroup) node; 
  67.                 result = group.dispatchTouchEvent(touchEvent); 
  68.             } else if (node instanceof View) { 
  69.                 View view = (View) node; 
  70.                 result = view.dispatchTouchEvent(touchEvent); 
  71.             } else { 
  72.                 if (i < size) { 
  73.                     i++; 
  74.                     result = dispatch(size, i, touchEvent); 
  75.                 } 
  76.             } 
  77.         } 
  78.  
  79.         return result; 
  80.     } 
  81.  
  82.     private static void collectRecords() { 
  83.         records.clear(); 
  84.         for (int i = 0; i < nodes.size(); i++) { 
  85.             records.put(i, ((View) nodes.get(i)).isConsumed()); 
  86.         } 
  87.     } 
  88.  
  89.     private static boolean findRecord(Component component) { 
  90.         int i = nodes.indexOf(component); 
  91.         if (i < 0) return false
  92.         return records.get(i); 
  93.     } 
  94.  
  95.     private static void clearNodes() { 
  96.         nodes.clear(); 
  97.     } 
  98.  
  99.     private static void createNodes(Component component) { 
  100.         if (component instanceof View) nodes.add(component); 
  101.         if (component.getComponentParent() != null) { 
  102.             createNodes((Component) component.getComponentParent()); 
  103.         } 
  104.     } 
  105.  
  106.     private static String convertEvent(TouchEvent event) { 
  107.         String split = ","
  108.         MmiPoint point = event.getPointerScreenPosition(event.getIndex()); 
  109.         return event.getAction() + split + point.getX() + split + 
  110.                 point.getY() + split + event.hashCode(); 
  111.     } 
  112.  
  113.     private static boolean isSameEvent(TouchEvent event) { 
  114.         return lastEvent.equals(convertEvent(event)); 
  115.     } 

使用方式

参考文档

注意事项

  1. 虽然能使用事件分发了,但是由于底层机制的不同,在使用上还是会有一些差别:
  2. 如果根布局或者中间的ComponentContainer实现的是View而非ViewGroup,那么事件将不会继续往下传递。
  3. 视图树中间可以出现断层,即出现未实现View或ViewGroup的控件,事件会跳过并往下传递。
  4. 未实现View或ViewGroup的控件,如果设置了setTouchEventListener,那么事件将在回调返回true后直接被消费,而导致不会被分发。
  5. 如果遇到super.onTouchEvent或者super.onInterceptTouchEvent,需要去父类查看逻辑并移植进来,如果是普通的布局或者控件一般是可以忽略,或者返回false的。
  6. 如果遇到super.dispatchTouchEvent则可以直接使用ViewGroupHelper/ViewHelper的dispatchTouchEvent来替代。
  7. 暂时只支持单点触摸的分发

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

 

来源:鸿蒙社区内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯