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

HarmonyOS非侵入式事件分發(fā)設(shè)計(jì)

開發(fā) 前端 OpenHarmony
在鴻蒙的Java UI框架中的交互中,是只存在消費(fèi)機(jī)制,并沒有分發(fā)機(jī)制。消費(fèi)事件是從子控件向父控件傳遞,而分發(fā)事件是從父控件向子控件傳遞。

[[418480]]

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

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

https://harmonyos.51cto.com

在鴻蒙的Java UI框架中的交互中,是只存在消費(fèi)機(jī)制,并沒有分發(fā)機(jī)制。消費(fèi)事件是從子控件向父控件傳遞,而分發(fā)事件是從父控件向子控件傳遞。消費(fèi)機(jī)制雖然可以滿足大部分單一化的場(chǎng)景,但是隨著業(yè)務(wù)和UI設(shè)計(jì)的復(fù)雜化,僅靠消費(fèi)機(jī)制是無法滿足實(shí)際需求的。下面簡(jiǎn)單介紹下鴻蒙目前的消費(fèi)機(jī)制流程:

首先自定義一個(gè)CustomContainer和CustomChild,然后都增加TouchEventListener的監(jiān)聽,下面打印出父控件和子控件的onTouchEvent設(shè)置不同返回值時(shí)候的事件消費(fèi)日志:

  1. CustomContainer:true CustomChild:false 
  2. 07-12 10:15:29.785 28923-28923/? W 0006E/seagazer:  com.testbug.widget.CustomContainer # init[Line:33]: onTouchEvent: DOWN 1, MOVE 3, UP 2 
  3. 07-12 10:15:33.103 28923-28923/? D 0006E/seagazer:  com.testbug.widget.CustomChild$1 # onTouchEvent[Line:33]: ------->1 
  4. 07-12 10:15:33.103 28923-28923/? D 0006E/seagazer:  com.testbug.widget.CustomContainer$1 # onTouchEvent[Line:37]: ------->1 
  5. 07-12 10:15:33.652 28923-28923/? D 0006E/seagazer:  com.testbug.widget.CustomContainer$1 # onTouchEvent[Line:37]: ------->3 
  6. 07-12 10:15:34.344 28923-28923/? D 0006E/seagazer:  com.testbug.widget.CustomContainer$1 # onTouchEvent[Line:37]: ------->2 
  7.  
  8. CustomContainer:true CustomChild:true 
  9. 07-12 10:16:02.501 5438-5438/? D 0006E/seagazer:  com.testbug.widget.CustomChild$1 # onTouchEvent[Line:33]: ------->1 
  10. 07-12 10:16:03.050 5438-5438/? D 0006E/seagazer:  com.testbug.widget.CustomChild$1 # onTouchEvent[Line:33]: ------->3 
  11. 07-12 10:16:03.532 5438-5438/? D 0006E/seagazer:  com.testbug.widget.CustomChild$1 # onTouchEvent[Line:33]: ------->3 
  12. 07-12 10:16:03.970 5438-5438/? D 0006E/seagazer:  com.testbug.widget.CustomChild$1 # onTouchEvent[Line:33]: ------->2 
  13.  
  14. CustomContainer:false CustomChild:true 
  15. 07-12 10:16:54.300 5441-5441/ D 0006E/seagazer:  com.testbug.widget.CustomChild$1 # onTouchEvent[Line:33]: ------->1 
  16. 07-12 10:16:54.555 5441-5441/ D 0006E/seagazer:  com.testbug.widget.CustomChild$1 # onTouchEvent[Line:33]: ------->3 
  17. 07-12 10:16:54.881 5441-5441/ D 0006E/seagazer:  com.testbug.widget.CustomChild$1 # onTouchEvent[Line:33]: ------->3 
  18. 07-12 10:16:55.269 5441-5441/ D 0006E/seagazer:  com.testbug.widget.CustomChild$1 # onTouchEvent[Line:33]: ------->2 
  19.  
  20. CustomContainer:false CustomChild:false  
  21. 07-12 10:17:29.362 10847-10847/? D 0006E/seagazer:  com.testbug.widget.CustomChild$1 # onTouchEvent[Line:33]: ------->1 
  22. 07-12 10:17:29.362 10847-10847/? D 0006E/seagazer:  com.testbug.widget.CustomContainer$1 # onTouchEvent[Line:37]: ------->1 

因?yàn)椴淮嬖诜职l(fā)和攔截機(jī)制,不論什么情況,down事件永遠(yuǎn)是子控件優(yōu)先觸發(fā),根據(jù)子控件是否消費(fèi)down事件來判斷后續(xù)的move,up事件是否傳遞給它。

何為事件分發(fā)

這里簡(jiǎn)單介紹下事件分發(fā):客戶端的視圖框架一般都是設(shè)計(jì)成樹結(jié)構(gòu),視圖樹會(huì)有根節(jié)點(diǎn)。事件的源頭就是從根節(jié)點(diǎn)開始,一般通過深度遍歷傳遞給各個(gè)子節(jié)點(diǎn),然后根據(jù)各個(gè)子節(jié)點(diǎn)是否攔截,繼續(xù)下發(fā)給各個(gè)子子節(jié)點(diǎn),以此類推。這就是事件分發(fā)模型。事件消費(fèi)模型則是從子節(jié)點(diǎn)開始,根據(jù)該子節(jié)點(diǎn)是否消費(fèi),繼續(xù)把事件回溯給父節(jié)點(diǎn)或者同級(jí)子節(jié)點(diǎn),看其是否消費(fèi)。分發(fā)消費(fèi)機(jī)制可以理解為一種典型的責(zé)任鏈的設(shè)計(jì)模式。

主流的事件分發(fā)設(shè)計(jì)

鴻蒙目前其實(shí)也已經(jīng)存在一些分發(fā)的框架,但是多數(shù)都是屬于侵入式的設(shè)計(jì),需要自定義控件繼承或者實(shí)現(xiàn)其接口,再在onTouchEvent代理其事件。這種方式在絕大部分場(chǎng)景的確可以滿足需求,并且如果是從framework層設(shè)計(jì),這種方式也是最優(yōu)的。畢竟都是通過頂層接口或者抽象類對(duì)外暴露的方式,說白點(diǎn)就是把所有原生控件完全自主可控化,需要外界繼承或?qū)崿F(xiàn)其進(jìn)行統(tǒng)一化的邏輯處理。

何為侵入式以及其缺陷

但是有些開發(fā)場(chǎng)景,里面涉及到第三方提供的控件,第三方提供的控件肯定不會(huì)去實(shí)現(xiàn)我們的頂層接口或抽象類,這種場(chǎng)景就不是太合適了,畢竟我們是從應(yīng)用層的角度去增加一個(gè)分發(fā)機(jī)制。如果我們按照這種方式去設(shè)計(jì),就需要把第三方源碼全部拷貝到我們項(xiàng)目,自行對(duì)其進(jìn)行修改適配我們的規(guī)則,暫且稱之為侵入式設(shè)計(jì)。能夠保證在不需要修改第三方源碼的前提下去實(shí)現(xiàn),稱之為非侵入式設(shè)計(jì)。

舉個(gè)例子,你的項(xiàng)目中用到一個(gè)第三方提供的自定義CustomView組件,它并沒有繼承你的頂層接口,但是它把所有事件都消費(fèi)掉了,因?yàn)樗陨聿⒉粫?huì)考慮太多復(fù)雜的場(chǎng)景,那假設(shè)你需要CustomView插入到一個(gè)自定義的滑動(dòng)列表使用,它都完全消費(fèi)掉了事件,你的自定義滑動(dòng)列表還能處理消費(fèi)事件么?答案是肯定不能的。那有什么辦法可以讓第三方組件不消費(fèi)事件呢,并且讓其加入我們自定義的攔截機(jī)制中呢?可以通過邏輯托管方式。

事件溯源托管

在當(dāng)前鴻蒙提供的消費(fèi)機(jī)制中,我們要想自定義父控件能夠接受到事件,子控件必須保證不能消費(fèi)事件。因此,我們必須將子控件的消費(fèi)邏輯暫時(shí)屏蔽(或者onTouchEvent中返回false),這樣,我們就能將所有事件一級(jí)級(jí)的回溯到頂層父控件:

  1. private final WeakHashMap<Component, Component.TouchEventListener> observers = new WeakHashMap<>(); 
  2.   ... 
  3.   // 遍歷所有子控件,如果子控件有自己的touch事件處理邏輯,加入緩存列表,并重置子控件的touch監(jiān)聽 
  4.   // 這樣,所有子控件的touch事件處理邏輯都被托管至緩存列表,實(shí)際上所有子控件并不消費(fèi)事件,事件消費(fèi)回到了頂層控件,也就是我們所說的事件源 
  5.   for (int i = 0; i < childCount; i++) { 
  6.       Component child = rootComponent.getComponentAt(i); 
  7.       Component.TouchEventListener childListener = child.getTouchEventListener(); 
  8.       if (childListener != null) { 
  9.           observers.put(child, childListener); 
  10.           child.setTouchEventListener(null); 
  11.        
  12.   } 

通過上面的邏輯,我們把所有子控件的事件處理都托管到一個(gè)緩存列表,并且重置子控件的事件監(jiān)聽,這樣一來,事件就會(huì)溯源到了我們頂層控件,而一般情況下頂層控件都是屬于布局容器,因此我們就只需要處理好該容器的事件流程:

  1. private Component touchTarget = null
  2.  
  3. @Override 
  4. public boolean onTouchEvent(Component component, TouchEvent touchEvent) { 
  5.     int action = touchEvent.getAction(); 
  6.     boolean isIntercepted = false
  7.     // down事件,判斷當(dāng)前是否需要攔截 
  8.     if (action == TouchEvent.PRIMARY_POINT_DOWN) { 
  9.         touchTarget = null
  10.         isIntercepted = interceptTouchEvent(component, touchEvent); 
  11.     } 
  12.     if (isIntercepted) { 
  13.         // 攔截的話,自己處理掉 
  14.         return processTouchEvent(component, touchEvent); 
  15.     } else { 
  16.         if (action == TouchEvent.PRIMARY_POINT_DOWN) { 
  17.             // down事件,查找touch目標(biāo)子控件 
  18.             // 當(dāng)前控件為布局容器時(shí),遍歷子控件查找,符合目標(biāo)如果需要消費(fèi)down事件,則后續(xù)事件都交給其處理 
  19.             if (component instanceof ComponentContainer) { 
  20.                 ComponentContainer root = (ComponentContainer) component; 
  21.                 int childCount = root.getChildCount(); 
  22.                 for (int i = childCount - 1; i >= 0; i--) { 
  23.                     Component child = root.getComponentAt(i); 
  24.                     if (isTouchInTarget(child, touchEvent)) { 
  25.                         Component.TouchEventListener listener = observers.get(child); 
  26.                         if (listener != null) { 
  27.                             boolean handled = listener.onTouchEvent(child, touchEvent); 
  28.                             if (handled) { 
  29.                                 touchTarget = child; 
  30.                                 return true
  31.                             } 
  32.                         } 
  33.                     } 
  34.                 } 
  35.             } else { 
  36.                 if (isTouchInTarget(component, touchEvent)) { 
  37.                     Component.TouchEventListener listener = observers.get(component); 
  38.                     if (listener != null) { 
  39.                         boolean handled = listener.onTouchEvent(component, touchEvent); 
  40.                         if (handled) { 
  41.                             touchTarget = component; 
  42.                             return true
  43.                         } 
  44.                     } 
  45.                 } 
  46.             } 
  47.         } 
  48.     } 
  49.     // 沒有找到touch目標(biāo)子控件,自己處理 
  50.     if (touchTarget == null) { 
  51.         return processTouchEvent(component, touchEvent); 
  52.     } 
  53.     // 如果touchTarget不為null,說明down事件時(shí)候已經(jīng)找到了需要消費(fèi)的目標(biāo)控件,直接將其余事件交給它處理 
  54.     Component.TouchEventListener listener = observers.get(touchTarget); 
  55.     if (listener != null) { 
  56.         return listener.onTouchEvent(touchTarget, touchEvent); 
  57.     } 
  58.     // 上述條件都不符合,自己處理 
  59.     return processTouchEvent(component, touchEvent); 

這樣一來,自定義控件對(duì)事件的監(jiān)聽回調(diào)的onTouchEvent邏輯就被托管了,具體是否會(huì)執(zhí)行該消費(fèi)邏輯,不再由系統(tǒng)進(jìn)行處理,而是由我們的ExTouchListener根據(jù)布局容器是否攔截,以及子控件是否消費(fèi)共同進(jìn)行決策。下面列列舉一個(gè)demo,里面有2個(gè)自定義控件,一個(gè)自定義父布局包裹一個(gè)自定義子控件:

<ExTouchListener.java>

  1. public abstract class ExTouchListener implements Component.TouchEventListener, Component.LayoutRefreshedListener { 
  2.     private final WeakHashMap<Component, Component.TouchEventListener> observers = new WeakHashMap<>(); 
  3.     private final ComponentContainer rootComponent; 
  4.     private Component touchTarget = null
  5.  
  6.     public ExTouchListener(ComponentContainer root) { 
  7.         this.rootComponent = root; 
  8.         this.rootComponent.setLayoutRefreshedListener(this); 
  9.     } 
  10.  
  11.     @Override 
  12.     public void onRefreshed(Component component) { 
  13.         int childCount = rootComponent.getChildCount(); 
  14.         if (childCount != observers.size()) { 
  15.             for (int i = 0; i < childCount; i++) { 
  16.                 Component child = rootComponent.getComponentAt(i); 
  17.                 Component.TouchEventListener childListener = child.getTouchEventListener(); 
  18.                 if (childListener != null) { 
  19.                     observers.put(child, childListener); 
  20.                     child.setTouchEventListener(null); 
  21.                 } 
  22.             } 
  23.         } 
  24.     } 
  25.  
  26.     @Override 
  27.     public boolean onTouchEvent(Component component, TouchEvent touchEvent) { 
  28.         int action = touchEvent.getAction(); 
  29.         boolean isIntercepted = false
  30.         if (action == TouchEvent.PRIMARY_POINT_DOWN) { 
  31.             touchTarget = null
  32.             isIntercepted = interceptTouchEvent(component, touchEvent); 
  33.         } 
  34.         if (isIntercepted) { 
  35.             // intercepted 
  36.             return processTouchEvent(component, touchEvent); 
  37.         } else { 
  38.             if (action == TouchEvent.PRIMARY_POINT_DOWN) { 
  39.                 // down, find touch target 
  40.                 if (component instanceof ComponentContainer) { 
  41.                     ComponentContainer root = (ComponentContainer) component; 
  42.                     int childCount = root.getChildCount(); 
  43.                     for (int i = childCount - 1; i >= 0; i--) { 
  44.                         Component child = root.getComponentAt(i); 
  45.                         if (isTouchInTarget(child, touchEvent)) { 
  46.                             Component.TouchEventListener listener = observers.get(child); 
  47.                             if (listener != null) { 
  48.                                 boolean handled = listener.onTouchEvent(child, touchEvent); 
  49.                                 if (handled) { 
  50.                                     touchTarget = child; 
  51.                                     return true
  52.                                 } 
  53.                             } 
  54.                         } 
  55.                     } 
  56.                 } else { 
  57.                     if (isTouchInTarget(component, touchEvent)) { 
  58.                         Component.TouchEventListener listener = observers.get(component); 
  59.                         if (listener != null) { 
  60.                             boolean handled = listener.onTouchEvent(component, touchEvent); 
  61.                             if (handled) { 
  62.                                 touchTarget = component; 
  63.                                 return true
  64.                             } 
  65.                         } 
  66.                     } 
  67.                 } 
  68.             } 
  69.         } 
  70.         // not find touch target, handle self 
  71.         if (touchTarget == null) { 
  72.             return processTouchEvent(component, touchEvent); 
  73.         } 
  74.         // move, up ... 
  75.         Component.TouchEventListener listener = observers.get(touchTarget); 
  76.         if (listener != null) { 
  77.             return listener.onTouchEvent(touchTarget, touchEvent); 
  78.         } 
  79.         return processTouchEvent(component, touchEvent); 
  80.     } 
  81.  
  82.     public abstract boolean interceptTouchEvent(Component component, TouchEvent touchEvent); 
  83.  
  84.     public abstract boolean processTouchEvent(Component component, TouchEvent touchEvent); 
  85.  
  86.     private boolean isTouchInTarget(Component target, TouchEvent touchEvent) { 
  87.         MmiPoint pointer = touchEvent.getPointerScreenPosition(touchEvent.getIndex()); 
  88.         float touchX = pointer.getX(); 
  89.         float touchY = pointer.getY(); 
  90.         int[] location = target.getLocationOnScreen(); 
  91.         int targetX = location[0]; 
  92.         int targetY = location[1]; 
  93.         int targetWidth = target.getWidth(); 
  94.         int targetHeight = target.getHeight(); 
  95.         boolean result = touchX >= targetX && touchX <= targetX + targetWidth && touchY >= targetY && touchY <= targetY + targetHeight; 
  96.         return result; 
  97.     } 

<CustomContainer.java>

  1. public class CustomContainer extends DirectionalLayout { 
  2.     public CustomContainer(Context context, AttrSet attrSet) { 
  3.         super(context, attrSet); 
  4.         setTouchEventListener(new ExTouchListener(this) { 
  5.  
  6.             @Override 
  7.             public boolean interceptTouchEvent(Component component, TouchEvent touchEvent) { 
  8.                 return false
  9.             } 
  10.  
  11.             @Override 
  12.             public boolean processTouchEvent(Component component, TouchEvent touchEvent) { 
  13.                 switch (touchEvent.getAction()) { 
  14.                     case TouchEvent.PRIMARY_POINT_DOWN: 
  15.                         Logger2.w("--->down"); 
  16.                         return true
  17.                     case TouchEvent.POINT_MOVE: 
  18.                         Logger2.w("--->move"); 
  19.                         return true
  20.                     case TouchEvent.PRIMARY_POINT_UP: 
  21.                         Logger2.w("--->up"); 
  22.                         return true
  23.                 } 
  24.                 return true
  25.             } 
  26.         }); 
  27.     } 

<CustomComponent.java>

  1. public class CustomComponent extends Text { 
  2.     public CustomComponent(Context context, AttrSet attrSet) { 
  3.         super(context, attrSet); 
  4.         setTouchEventListener(new TouchEventListener() { 
  5.             @Override 
  6.             public boolean onTouchEvent(Component component, TouchEvent touchEvent) { 
  7.                 switch (touchEvent.getAction()) { 
  8.                     case TouchEvent.PRIMARY_POINT_DOWN: 
  9.                         Logger2.e( "--->down"); 
  10.                         return true
  11.                     case TouchEvent.POINT_MOVE: 
  12.                         Logger2.e( "--->move"); 
  13.                         return true
  14.                     case TouchEvent.PRIMARY_POINT_UP: 
  15.                         Logger2.e( "--->up"); 
  16.                         return true
  17.                 } 
  18.                 return false
  19.             } 
  20.         }); 
  21.     } 

下面看下3種常見場(chǎng)景的處理打印日志(上面已經(jīng)貼出全部源碼,可以復(fù)制進(jìn)自己的項(xiàng)目運(yùn)行):

  1. // 父控件不攔截,子控件down事件不消費(fèi),父控件的processTouchEvent進(jìn)行處理 
  2. CustomContainer interceptTouchEvent:false  CustomComponent onTouchEvent down:false 
  3. 08-02 14:42:53.754 17396-17396/? E 0006E/seagazer:  com.example.touch.CustomComponent$1 # onTouchEvent[Line:35]: --->down 
  4. 08-02 14:42:53.754 17396-17396/? D 0006E/seagazer:  com.example.touch.CustomContainer$1 # processTouchEvent[Line:42]: --->down 
  5. 08-02 14:42:53.824 17396-17396/? D 0006E/seagazer:  com.example.touch.CustomContainer$1 # processTouchEvent[Line:48]: --->up 
  6.  
  7. // 父控件不攔截,子控件down事件消費(fèi),子控件onTouchEvent處理 
  8. CustomContainer interceptTouchEvent:false  CustomComponent onTouchEvent down:true 
  9. 08-02 14:43:29.132 17661-17661/com.example.touch E 0006E/seagazer:  com.example.touch.CustomComponent$1 # onTouchEvent[Line:35]: --->down 
  10. 08-02 14:43:29.218 17661-17661/com.example.touch E 0006E/seagazer:  com.example.touch.CustomComponent$1 # onTouchEvent[Line:41]: --->up 
  11.  
  12. // 父控件攔截,父控件的processTouchEvent進(jìn)行處理 
  13. 08-02 14:42:13.409 13918-13918/? W 0006E/seagazer:  com.example.touch.CustomContainer$1 # processTouchEvent[Line:41]: --->down 
  14. 08-02 14:42:13.533 13918-13918/? W 0006E/seagazer:  com.example.touch.CustomContainer$1 # processTouchEvent[Line:47]: --->up 

結(jié)語

通過上面的事件托管、事件溯源再傳遞,就已經(jīng)能夠?qū)崿F(xiàn)簡(jiǎn)單的分發(fā)攔截機(jī)制,并且兼容第三方庫(kù)的控件。當(dāng)然,這里主要是提供一種設(shè)計(jì)的簡(jiǎn)化模型,包括disptach機(jī)制,touchTarget的復(fù)用,nestScroll機(jī)制本文都沒考慮,如果本質(zhì)上能夠理解透徹事件的分發(fā)機(jī)制,在此基礎(chǔ)上進(jìn)行擴(kuò)展也不是什么難事。但是回歸當(dāng)下,從個(gè)人角度去評(píng)判,這類理應(yīng)該由系統(tǒng)提供的機(jī)制,畢竟應(yīng)用層更多的精力應(yīng)該放在業(yè)務(wù)的實(shí)現(xiàn),用戶界面交互,應(yīng)用性能方面,而不是把一些框架層機(jī)制自己去實(shí)現(xiàn)一遍。

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

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

https://harmonyos.51cto.com

 

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

2020-04-10 10:36:20

網(wǎng)絡(luò)通信框架

2024-02-21 15:30:56

2023-07-08 00:12:26

框架結(jié)構(gòu)組件

2010-08-06 10:24:56

Flex事件分發(fā)

2023-10-08 08:23:44

Android事件邏輯

2021-07-27 06:51:53

Istio 微服務(wù)Service Mes

2019-07-15 08:43:23

開源技術(shù) 工具

2016-12-08 10:19:18

Android事件分發(fā)機(jī)制

2021-08-11 14:29:20

鴻蒙HarmonyOS應(yīng)用

2023-12-25 15:40:37

數(shù)據(jù)治理大數(shù)據(jù)GenAI

2012-05-29 10:44:17

WebApp

2018-06-29 13:24:48

沙箱容器解決方案

2021-12-26 23:34:00

微服務(wù)Istio壓縮

2017-06-14 10:12:19

SophixAndroid熱修復(fù)

2010-08-06 10:03:42

Flex事件

2017-03-14 13:51:23

AndroidView事件分發(fā)和處理

2022-06-02 10:35:20

架構(gòu)驅(qū)動(dòng)

2017-02-21 12:20:20

Android事件分發(fā)機(jī)制實(shí)例解析

2024-07-01 08:27:05

KeyAndroid按鍵事件

2021-08-17 13:41:11

AndroidView事件
點(diǎn)贊
收藏

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