自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

HarmonyOS自定義控件之觸摸事件與事件分發(fā)

開發(fā) 前端 OpenHarmony
事件分發(fā)是一套比較重要同時(shí)也比較復(fù)雜的機(jī)制,如果不熟悉這套機(jī)制,那么在遇到稍微復(fù)雜的滑動(dòng)失效問題就會(huì)覺得手足無措。在這里通過打印日志的方式來摸索HarmonyOS上的事件的傳遞機(jī)制。

[[416896]]

想了解更多內(nèi)容,請(qǐng)?jiān)L問:

51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)

https://harmonyos.51cto.com

觸摸事件

如何監(jiān)聽觸摸事件

HarmonyOS中可以通過Listener的方式:

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

注意:setTouchEventListener會(huì)被覆蓋

常用的觸摸事件的類型

這里我們對(duì)比其他主流系統(tǒng)中MotionEvent與HarmonyOS中TouchEvent來方便理解與記憶。

MotionEvent的常用的事件類型與HarmonyOS中的TouchEvent類型基本可以對(duì)應(yīng)起來:

  • MotionEvent.ACTION_CANCEL -> TouchEvent.CANCEL
  • MotionEvent.ACTION_HOVER_ENTER -> TouchEvent.HOVER_POINTER_ENTER
  • MotionEvent.ACTION_HOVER_EXIT -> TouchEvent.HOVER_POINTER_EXIT
  • MotionEvent.ACTION_HOVER_MOVE -> TouchEvent.HOVER_POINTER_MOVE
  • MotionEvent.ACTION_POINTER_DOWN -> TouchEvent.OTHER_POINT_DOWN
  • MotionEvent.ACTION_POINTER_UP -> TouchEvent.OTHER_POINT_UP
  • MotionEvent.ACTION_MOVE -> TouchEvent.POINT_MOVE
  • MotionEvent.ACTION_DOWN -> TouchEvent.PRIMARY_POINT_DOWN
  • MotionEvent.ACTION_UP -> TouchEvent.PRIMARY_POINT_UP

常用的Api

獲取事件類型

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

獲取手指相對(duì)于屏幕的x、y坐標(biāo)

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

獲取手指相對(duì)于父控件的x、y坐標(biāo)

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

getPointerScreenPosition與getPointerPosition的區(qū)別

前者是相對(duì)的屏幕的坐標(biāo),而后者是相對(duì)于父控件的坐標(biāo)。如果在手指滑動(dòng)過程中,對(duì)該控件做了位移,那么getPointerPosition獲取的坐標(biāo)將會(huì)是手指本身坐標(biāo)加上控件的位移量,導(dǎo)致位移異常。

這里建議,如果需要根據(jù)坐標(biāo)來計(jì)算,都使用getPointerScreenPosition比較保險(xiǎn)。

總結(jié)

TouchEvent提供了基礎(chǔ)api,但是沒有MotionEvent內(nèi)一些比較高階的api,比如obtain等。接下來我們來關(guān)注更為重要的事件分發(fā)。

事件分發(fā)

事件分發(fā)是一套比較重要同時(shí)也比較復(fù)雜的機(jī)制,如果不熟悉這套機(jī)制,那么在遇到稍微復(fù)雜的滑動(dòng)失效問題就會(huì)覺得手足無措。在這里通過打印日志的方式來摸索HarmonyOS上的事件的傳遞機(jī)制。

HarmonyOS中事件的傳遞機(jī)制

首先,我們通過打印日志的方式,來摸索觸摸事件是如何在Component中傳遞的。經(jīng)過實(shí)驗(yàn),發(fā)現(xiàn)如下幾條規(guī)律:

  • 事件首先會(huì)傳遞到最底層的目標(biāo)控件,而非頂層的父控件
  • 如果目標(biāo)控件不處理該事件,即onTouchEvent返回false,那么事件冒泡到父控件
  • 如果目標(biāo)控件處理了該事件,即onTouchEvent返回true,那么后續(xù)事件不會(huì)向上冒泡,而是直接被目標(biāo)控件消費(fèi)
  • 如果一個(gè)控件在down事件中,返回了false,那么后續(xù)的事件也不會(huì)被傳遞到該控件中
  • 如果一個(gè)控件接受到了down事件,并返回了true,那么后續(xù)的事件會(huì)直接被傳遞到該控件中,其他控件不會(huì)收到事件

HarmonyOS中的事件傳遞更像是冒泡,而非分發(fā),down事件一旦被某一個(gè)控件消費(fèi)了,那么其他控件將都收不到后續(xù)事件了。這樣的機(jī)制比較難去實(shí)現(xiàn)一些復(fù)雜的嵌套效果。

比如子控件響應(yīng)橫向滑動(dòng),父控件響應(yīng)垂直滑動(dòng)這種情況。子控件如果要想收到后續(xù)的move事件,只能在down的時(shí)候返回true,這樣就導(dǎo)致父控件完全收不到觸摸事件。子控件如果像要在move時(shí)判斷滑動(dòng)方向而down事件返回了false,那么子控件將再也接收不到后續(xù)的事件了。

HarmonyOS的事件冒泡比較簡(jiǎn)單,一旦約定好就再也沒有反悔的機(jī)會(huì)了。那么如何類似其他主流系統(tǒng)一樣,從頂層控件分發(fā)并且可以攔截事件呢?

這里只提供思路,具體代碼可以參考:事件分發(fā)

實(shí)現(xiàn)事件分發(fā)

我們構(gòu)想中的事件分發(fā)應(yīng)該是這樣:事件是首先到頂層的父控件,然后經(jīng)過dispatchTouchEvent一層層向下分發(fā)。ComponentContainer可以通過onInterceptTouchEvent攔截事件,并交給自己的onTouchEvent來處理。如果ComponentContainer不處理事件則繼續(xù)向下分發(fā),直到最終的Component控件。這樣的機(jī)制意味著每一層都有機(jī)會(huì)能拿到事件,那么如何在HarmonyOS中實(shí)現(xiàn)呢?

我們可以將事件分發(fā)相關(guān)的函數(shù)與代碼,抽取出來,移植到HarmonyOS中,并通過一些手段應(yīng)用到HarmonyOS的onTouchEvent中。

抽象

HarmonyOS中沒有dispatchTouchEvent、onInterceptTouchEvent等函數(shù),如何應(yīng)用到組件中呢?抽象接口,將事件分發(fā)相關(guān)的函數(shù)抽象成兩個(gè)接口:

View

  1. /** 
  2.  * 事件分發(fā)基礎(chǔ)接口,需要分發(fā)并處理事件的Component需實(shí)現(xiàn)此接口 
  3.  */ 
  4. public interface View { 
  5.     /** 
  6.      * 傳遞屏幕的觸摸事件到目標(biāo)控件或自己消費(fèi) 
  7.      * 
  8.      * @param event 被傳遞的觸摸事件 
  9.      * @return 如果事件被自己消費(fèi),返回true,否則返回false 
  10.      */ 
  11.     boolean dispatchTouchEvent(TouchEvent event); 
  12.  
  13.     /** 
  14.      * 處理觸摸事件的方法 
  15.      * @param event 待消費(fèi)的事件 
  16.      * @return 是否消費(fèi)了事件 
  17.      */ 
  18.     boolean onTouchEvent(TouchEvent event); 
  19.  
  20.     /** 
  21.      * 事件是否被自己消費(fèi)了,該結(jié)果只能獲取一次,獲取后將重置為false 
  22.      * @return 是否消費(fèi)了事件 
  23.      */ 
  24.     boolean isConsumed(); 

ViewGroup

  1. /** 
  2.  * 包含子控件的事件分發(fā)接口,需要攔截或分發(fā)事件的ComponentContainer需實(shí)現(xiàn)此接口 
  3.  */ 
  4. public interface ViewGroup extends View { 
  5.  
  6.     /** 
  7.      * 當(dāng)子控件不想父控件通過{@link #onInterceptTouchEvent(TouchEvent)}攔截事件時(shí),調(diào)用此方法 
  8.      * @param disallowIntercept 如果子控件不想父控件攔截事件,傳遞true 
  9.      */ 
  10.     void requestDisallowInterceptTouchEvent(boolean disallowIntercept); 
  11.  
  12.     /** 
  13.      * 當(dāng)需要攔截所有觸摸事件時(shí),實(shí)現(xiàn)此方法。 
  14.      * 注意:如果需要在后續(xù)再攔截事件,則down事件不要返回true,不然子控件會(huì)由于事件冒泡機(jī)制而收不到down之后的事件。 
  15.      * 
  16.      * 當(dāng)此方法返回true,事件會(huì)傳遞到onTouchEvent()方法中,如果在onTouchEvent中返回true后, 
  17.      * 后續(xù)的事件將會(huì)持續(xù)到控件的onTouchEvent()方法中,并且不會(huì)再傳遞到onInterceptTouchEvent()中。 
  18.      * 
  19.      * 當(dāng)此方法返回false,事件會(huì)首先被傳遞onInterceptTouchEvent()中,然后再到子控件的onTouchEvent()中。 
  20.      * 一旦此方法返回了true,子控件將會(huì)收到最后一次CANCEL事件,并且事件也不會(huì)再傳遞到onInterceptTouchEvent這里, 
  21.      * 而是直接傳遞到自己的onTouchEvent中。 
  22.      * 
  23.      * @param ev 被傳遞下來的觸摸事件 
  24.      * @return 當(dāng)需要攔截子控件的觸摸事件時(shí),返回true,這時(shí)事件會(huì)傳遞到{@link View#onTouchEvent(TouchEvent)}中。 
  25.      * 子控件將收到CANCEL事件,并且后續(xù)事件不會(huì)再傳遞到該控件中。 
  26.      */ 
  27.     boolean onInterceptTouchEvent(TouchEvent ev); 

實(shí)現(xiàn)

然后借助兩個(gè)幫助類,來實(shí)現(xiàn)兩個(gè)接口中的相關(guān)函數(shù)。將View中事件分發(fā)的具體代碼封裝到ViewHelper中,將ViewGroup中事件分發(fā)的具體代碼封裝到ViewGroupHelper中。

代碼參考ViewHelper、ViewGroupHelper

分發(fā)

最后借助一個(gè)分發(fā)幫助類DispatchHelper,來將HarmonyOS中的事件,從頂層開始按照ViewGroupHelper中的dispatchTouchEvent來分發(fā)。

DispatchHelper主要做了下面幾件事:

  • 緩存當(dāng)次事件中,視圖樹內(nèi)所有實(shí)現(xiàn)了View、ViewGroup接口的控件
  • 從最頂層的控件開始,調(diào)用其dispatchTouchEvent函數(shù)
  • 過濾掉由于事件冒泡,而傳遞過來的可能的重復(fù)事件

代碼:

  1. /** 
  2.  * 事件分發(fā)幫助類,輔助{@link View}與{@link ViewGroup}分發(fā)事件。 
  3.  * 
  4.  * 在{@link Component.TouchEventListener#onTouchEvent(Component, TouchEvent)} 
  5.  * 調(diào)用{@link #dispatch(Component, TouchEvent)}來分發(fā)事件。 
  6.  */ 
  7. public class DispatchHelper { 
  8.  
  9.     /** 暫存所有的實(shí)現(xiàn)了View接口的控件 **/ 
  10.     private static final List<Component> nodes = new ArrayList<>(); 
  11.     /** 暫存每次事件的處理結(jié)果 **/ 
  12.     private static final HashMap<Integer, Boolean> records = new HashMap<>(); 
  13.  
  14.     /** 暫存上次事件,用于過濾由于自下而上的事件冒泡與自上而下的事件分發(fā)機(jī)制而產(chǎn)生的多次分發(fā) **/ 
  15.     private static String lastEvent = ""
  16.     private final static TouchEventCompact compact = new TouchEventCompact(true); 
  17.  
  18.     /** 
  19.      * 在{@link Component.TouchEventListener#onTouchEvent(Component, TouchEvent)}中調(diào)用此函數(shù)來分發(fā)事件。 
  20.      * @param component 需分發(fā)事件的控件 
  21.      * @param touchEvent 需分發(fā)的事件 
  22.      * @return 事件處理的結(jié)果 
  23.      */ 
  24.     public static boolean dispatch(Component component, TouchEvent touchEvent) { 
  25.         // 過濾由于自下而上的事件冒泡 與 自上而下的事件分發(fā)機(jī)制而產(chǎn)生的重復(fù)分發(fā) 
  26.         if (isSameEvent(touchEvent)) { 
  27.             return true
  28.         } 
  29.  
  30.         // 糾正通過getPointerPosition獲取的y坐標(biāo)的偏移 
  31.         compact.correct(touchEvent); 
  32.  
  33.         lastEvent = convertEvent(touchEvent); 
  34.  
  35.         int action = touchEvent.getAction(); 
  36.         if (action == TouchEvent.PRIMARY_POINT_DOWN) { 
  37.             clearNodes(); 
  38.         } 
  39.  
  40.         if (nodes.size() <= 0) createNodes(component); 
  41.         dispatch(nodes.size(), 1, touchEvent); 
  42. //        collectRecords(); 
  43.  
  44. //        boolean result = findRecord(component); 
  45.  
  46.         if (action == TouchEvent.PRIMARY_POINT_UP) { 
  47.             clearNodes(); 
  48.         } 
  49.  
  50.         return true
  51.     } 
  52.  
  53.     /** 
  54.      * 當(dāng)子控件不想父控件攔截事件時(shí),調(diào)用此方法 
  55.      * 
  56.      * @param component 不想事件被攔截的控件 
  57.      * @param disallowIntercept true為不攔截 
  58.      */ 
  59.     public static void requestDisallowInterceptTouchEvent(Component component, boolean disallowIntercept) { 
  60.         if (component.getComponentParent() instanceof ViewGroup) { 
  61.             ((ViewGroup) component.getComponentParent()).requestDisallowInterceptTouchEvent(disallowIntercept); 
  62.         } 
  63.     } 
  64.  
  65.     /** 
  66.      * 當(dāng)子控件不想父控件攔截事件時(shí),在{@link EventHandler#postTask(Runnable)}中調(diào)用此方法 
  67.      * 
  68.      * @param component 不想事件被攔截的控件 
  69.      * @param disallowIntercept true為不攔截 
  70.      */ 
  71.     public static void postRequestDisallowInterceptTouchEvent(Component component, boolean disallowIntercept) { 
  72.         EventHandler handler = new EventHandler(EventRunner.getMainEventRunner()); 
  73.         handler.postTask(() -> requestDisallowInterceptTouchEvent(component, disallowIntercept)); 
  74.     } 
  75.  
  76.     public static TouchEventCompact getTouchEventCompact() { 
  77.         return compact; 
  78.     } 
  79.  
  80.     /** 
  81.      * 自頂?shù)较碌氖录职l(fā),如果最上層的父控件沒有實(shí)現(xiàn){@link ViewGroup},則找尋下一個(gè)實(shí)現(xiàn)了{(lán)@link ViewGroup}的控件來分發(fā)事件。 
  82.      * 
  83.      * @param size {@link #nodes}的size 
  84.      * @param i 尋找實(shí)現(xiàn)了{(lán)@link ViewGroup}的控件的次數(shù),初始為1,自增 
  85.      * @param touchEvent 傳遞的事件 
  86.      * @return 事件分發(fā)的結(jié)果 
  87.      */ 
  88.     private static boolean dispatch(int sizeint i, TouchEvent touchEvent) { 
  89.         boolean result = false
  90.         if (size > 0) { 
  91.             Component node = nodes.get(size - i); 
  92.             if (node instanceof ViewGroup) { 
  93.                 ViewGroup group = (ViewGroup) node; 
  94.                 result = group.dispatchTouchEvent(touchEvent); 
  95.             } else if (node instanceof View) { 
  96.                 View view = (View) node; 
  97.                 result = view.dispatchTouchEvent(touchEvent); 
  98.             } else { 
  99.                 if (i < size) { 
  100.                     i++; 
  101.                     result = dispatch(size, i, touchEvent); 
  102.                 } 
  103.             } 
  104.         } 
  105.  
  106.         return result; 
  107.     } 
  108.  
  109.     private static void collectRecords() { 
  110.         records.clear(); 
  111.         for (int i = 0; i < nodes.size(); i++) { 
  112.             records.put(i, ((View) nodes.get(i)).isConsumed()); 
  113.         } 
  114.     } 
  115.  
  116.     private static boolean findRecord(Component component) { 
  117.         int i = nodes.indexOf(component); 
  118.         if (i < 0) return false
  119.         return records.get(i); 
  120.     } 
  121.  
  122.     private static void clearNodes() { 
  123.         nodes.clear(); 
  124.     } 
  125.  
  126.     private static void createNodes(Component component) { 
  127.         if (component instanceof View) nodes.add(component); 
  128.         if (component.getComponentParent() != null) { 
  129.             createNodes((Component) component.getComponentParent()); 
  130.         } 
  131.     } 
  132.  
  133.     private static String convertEvent(TouchEvent event) { 
  134.         String split = ","
  135.         MmiPoint point = event.getPointerScreenPosition(event.getIndex()); 
  136.         return event.getAction() + split + point.getX() + split + 
  137.                 point.getY() + split + event.hashCode(); 
  138.     } 
  139.  
  140.     private static boolean isSameEvent(TouchEvent event) { 
  141.         return lastEvent.equals(convertEvent(event)); 
  142.     } 

使用方式

參考文檔

注意事項(xiàng)

  1. 雖然能使用事件分發(fā)了,但是由于底層機(jī)制的不同,在使用上還是會(huì)有一些差別:
  2. 如果根布局或者中間的ComponentContainer實(shí)現(xiàn)的是View而非ViewGroup,那么事件將不會(huì)繼續(xù)往下傳遞。
  3. 視圖樹中間可以出現(xiàn)斷層,即出現(xiàn)未實(shí)現(xiàn)View或ViewGroup的控件,事件會(huì)跳過并往下傳遞。
  4. 未實(shí)現(xiàn)View或ViewGroup的控件,如果設(shè)置了setTouchEventListener,那么事件將在回調(diào)返回true后直接被消費(fèi),而導(dǎo)致不會(huì)被分發(fā)。
  5. 如果遇到super.onTouchEvent或者super.onInterceptTouchEvent,需要去父類查看邏輯并移植進(jìn)來,如果是普通的布局或者控件一般是可以忽略,或者返回false的。
  6. 如果遇到super.dispatchTouchEvent則可以直接使用ViewGroupHelper/ViewHelper的dispatchTouchEvent來替代。
  7. 暫時(shí)只支持單點(diǎn)觸摸的分發(fā)

想了解更多內(nèi)容,請(qǐng)?jiān)L問:

51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)

https://harmonyos.51cto.com

 

責(zé)任編輯:jianghua 來源: 鴻蒙社區(qū)
相關(guān)推薦

2021-08-16 14:45:38

鴻蒙HarmonyOS應(yīng)用

2009-08-04 09:56:46

C#事件處理自定義事件

2021-02-18 08:19:21

Vue自定義Vue 3.0

2022-05-07 10:22:32

JavaScript自定義前端

2013-04-22 15:40:00

Android開發(fā)觸摸事件與點(diǎn)擊事件區(qū)別

2009-09-03 15:46:57

C#自定義事件

2010-08-13 11:34:54

Flex自定義事件

2021-08-25 10:14:51

鴻蒙HarmonyOS應(yīng)用

2009-08-04 13:53:58

C#委托類C#事件

2013-05-14 11:08:23

AIR Android觸摸事件鼠標(biāo)事件

2013-04-15 15:22:06

2010-08-12 09:45:33

jQuery自定義事件

2009-08-04 12:56:51

C#自定義事件

2021-09-06 14:58:23

鴻蒙HarmonyOS應(yīng)用

2015-02-11 17:49:35

Android源碼自定義控件

2009-08-04 12:40:34

c#自定義事件

2010-08-06 10:24:56

Flex事件分發(fā)

2023-03-10 16:40:21

Frameworkinput觸摸事件

2011-08-03 17:32:17

IOS UIScrollVi touch

2021-09-02 10:00:42

鴻蒙HarmonyOS應(yīng)用
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)