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

HarmonyOS自定義控件之速度檢測(cè)VelocityDetector

開(kāi)發(fā) 前端 OpenHarmony
一般在涉及到滾動(dòng)的場(chǎng)景時(shí),我們會(huì)用到速度檢測(cè)。比如列表滑動(dòng)時(shí),我們需要拿到手指抬起時(shí)的瞬時(shí)速度,來(lái)做慣性滾動(dòng)。又比如在滾動(dòng)翻頁(yè)時(shí),我們要根據(jù)手指速度來(lái)判斷是否翻到下一頁(yè)還是繼續(xù)保持當(dāng)頁(yè)。

[[419340]]

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

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

https://harmonyos.51cto.com

一般在涉及到滾動(dòng)的場(chǎng)景時(shí),我們會(huì)用到速度檢測(cè)。比如列表滑動(dòng)時(shí),我們需要拿到手指抬起時(shí)的瞬時(shí)速度,來(lái)做慣性滾動(dòng)。又比如在滾動(dòng)翻頁(yè)時(shí),我們要根據(jù)手指速度來(lái)判斷是否翻到下一頁(yè)還是繼續(xù)保持當(dāng)頁(yè)。

接下來(lái)我們就來(lái)看看HarmonyOS中的VelocityDetector如何使用。

使用方法

VelocityDetector使用起來(lái)還是比較簡(jiǎn)單的,主要是分為以下幾步:

  • 獲取VelocityDetector實(shí)例
  • 為VelocityDetector添加TouchEvent
  • 計(jì)算速度
  • 獲取計(jì)算后的速度
  • 清除已添加的event

獲取實(shí)例

通過(guò)obtainInstance函數(shù)獲取實(shí)例:

  1. VelocityDetector detector = VelocityDetector.obtainInstance(); 

添加TouchEvent

在控件的TouchEventListener內(nèi)調(diào)用addEvent函數(shù):

  1. component.setTouchEventListener(new TouchEventListener() { 
  2.     @Override 
  3.     public boolean onTouchEvent(Component component, TouchEvent ev) { 
  4.         detector.addEvent(ev); 
  5.         return true
  6.     } 
  7. }); 

計(jì)算速度

一般情況下,我們需要在手指抬起時(shí)計(jì)算速度,因?yàn)槲覀冃枰氖鞘种柑鸷蟮乃俣戎?。因此我們可以在TouchEvent.PRIMARY_POINT_UP時(shí)調(diào)用calculateCurrentVelocity函數(shù)來(lái)計(jì)算速度:

  1. static final int MAX_VELOCITY = 10000; 
  2.  
  3. @Override 
  4. public boolean onTouchEvent(Component component, TouchEvent ev) { 
  5.     detector.addEvent(ev); 
  6.     if (ev.getAction() == TouchEvent.PRIMARY_POINT_UP) { 
  7.         detector.calculateCurrentVelocity(1000, MAX_VELOCITY, MAX_VELOCITY); 
  8.     } 
  9.     return true

calculateCurrentVelocity函數(shù)有兩個(gè)重載:

  1. void calculateCurrentVelocity(int units); 
  2. void calculateCurrentVelocity(int units, float maxVxVelocity, float maxVyVelocity) 

其中:

  • units為單位,1代表像素/毫秒,1000代表像素/秒,以此類推。一般情況下我們都傳1000,獲取的速度代表手指每秒移動(dòng)多少像素
  • maxVxVelocity為橫向最大速度為多少,比如慣性滾動(dòng)時(shí),如果我們不希望滾動(dòng)過(guò)快,可以設(shè)置一個(gè)最大速度
  • maxVyVelocity為縱向最大速度為多少,比如慣性滾動(dòng)時(shí),如果我們不希望滾動(dòng)過(guò)快,可以設(shè)置一個(gè)最大速度

獲取速度

在計(jì)算速度之后就能直接獲取速度值了:

  1. float velocityY = detector.getVerticalVelocity(); 
  2. float velocityX = detector.getHorizontalVelocity(); 
  3.  
  4. // 或者獲取速度數(shù)組,下標(biāo)0為橫向速度,下標(biāo)1為縱向速度 
  5. float[] velocity = detector.getVelocity(); 

獲取到的速度可能是正值也可能是負(fù)值,正負(fù)值代表了速度的方向,這個(gè)大家可以通過(guò)日志自行實(shí)驗(yàn)一下。

清除

最后,我們需要清除前面添加的TouchEvent,為新一輪的事件做準(zhǔn)備,避免舊的TouchEvent影響了后續(xù)的速度計(jì)算。這里我們?cè)讷@取到速度后或者CANCEL事件中,就可以調(diào)用clear函數(shù):

  1. if (ev.getAction() == TouchEvent.PRIMARY_POINT_UP) { 
  2.     ... 
  3.     float[] velocity = detector.getVelocity(); 
  4.     ... 
  5.     detector.clear(); 
  6.  
  7. if (ev.getAction() == TouchEvent.CANCEL) { 
  8.      detector.clear(); 

總結(jié)

VelocityDetector目前只能獲取一個(gè)手指的速度,在多點(diǎn)觸控的情況下,暫時(shí)沒(méi)法獲取其他手指的速度。

到此我們就獲取到了手指抬起時(shí)的速度了,至于怎么利用這個(gè)速度,后續(xù)會(huì)在慣性滾動(dòng)相關(guān)的文章中講述。接下來(lái)我們?cè)賮?lái)分析一下VelocityDetector存在什么問(wèn)題。

問(wèn)題

首先我們來(lái)了解一下VelocityDetector的基本原理:

我們通過(guò)addEvent將TouchEvent傳遞給VelocityDetector,然后通過(guò)calculateCurrentVelocity來(lái)計(jì)算速度,在這個(gè)過(guò)程中,VelocityDetector基本上就是通過(guò)TouchEvent拿到手指的坐標(biāo),然后通過(guò)移動(dòng)距離以及時(shí)間來(lái)計(jì)算速度。當(dāng)然內(nèi)部算法遠(yuǎn)比說(shuō)的復(fù)雜,但是我們只需要記住一個(gè)關(guān)鍵變量即可:移動(dòng)距離。

TouchEvent有兩個(gè)函數(shù)可以拿到手指坐標(biāo)來(lái)計(jì)算距離:getPointerPosition與getPointerScreenPosition。VelocityDetector究竟用的哪一個(gè)呢?我們可以通過(guò)如下代碼來(lái)實(shí)驗(yàn):

  1. @Override 
  2.     public boolean onTouchEvent(Component component, TouchEvent ev) { 
  3.         detector.addEvent(cloneEvent(ev)); 
  4.          
  5.         return true
  6.     } 
  7.  
  8.     private TouchEvent cloneEvent(TouchEvent event) { 
  9.         return new TouchEvent() { 
  10.  
  11.             @Override 
  12.             public int getIndex() { 
  13.                 System.out.println(TAG + "getIndex"); 
  14.                 return event.getIndex(); 
  15.             } 
  16.  
  17.             @Override 
  18.             public MmiPoint getPointerPosition(int i) { 
  19.                 System.out.println(TAG + "getPointerPosition"); 
  20.                 return event.getPointerPosition(i); 
  21.             } 
  22.  
  23.             @Override 
  24.             public MmiPoint getPointerScreenPosition(int i) { 
  25.                 System.out.println(TAG + "getPointerScreenPosition"); 
  26.                 return event.getPointerScreenPosition(i); 
  27.             } 
  28.  
  29.             ...... 
  30.         }; 
  31.     } 

在手指移動(dòng)過(guò)程中,日志如下:

  1. 08-04 17:14:09.296 24871-24871/com.ryan.ohos.parallaxlayout I System.out:  ParallaxLayout TouchEvent: getIndex 
  2. 08-04 17:14:09.296 24871-24871/com.ryan.ohos.parallaxlayout I System.out:  ParallaxLayout TouchEvent: getPointerPosition 
  3. 08-04 17:14:09.297 24871-24871/com.ryan.ohos.parallaxlayout I System.out:  ParallaxLayout TouchEvent: getIndex 
  4. 08-04 17:14:09.297 24871-24871/com.ryan.ohos.parallaxlayout I System.out:  ParallaxLayout TouchEvent: getPointerPosition 
  5. 08-04 17:14:09.469 24871-24871/com.ryan.ohos.parallaxlayout I System.out:  ParallaxLayout TouchEvent: getIndex 
  6. .... 

答案很明顯,VelocityDetector使用的是getPointerPosition。getPointerPosition獲取的坐標(biāo)是相對(duì)于父控件的,而不是屏幕的左上角,那么根據(jù)getPointerPosition的描述我們有理由猜測(cè):

當(dāng)被監(jiān)聽(tīng)的控件,在手指移動(dòng)過(guò)程中,不斷的改變自己的位置,那么通過(guò)getPointerPosition獲取的手指坐標(biāo)會(huì)加上控件的位移量,導(dǎo)致滑動(dòng)距離計(jì)算偏離預(yù)期。

下面我們來(lái)實(shí)驗(yàn)一下。在父布局中,子控件監(jiān)聽(tīng)觸摸事件,通過(guò)getPointerPosition獲取手指坐標(biāo)并計(jì)算MOVE與DOWN中坐標(biāo)的差,并使用setComponentPosition與坐標(biāo)差改變子控件的位置。

然后我們打印getPointerPosition獲取的y坐標(biāo),getPointerScreenPosition獲取的y坐標(biāo),以及移動(dòng)距離,代碼如下:

  1. Component child = getComponentAt(1); 
  2. child.setTouchEventListener(this); 
  3.    int top = child.getTop(); 
  4.  
  5. @Override 
  6.    public boolean onTouchEvent(Component component, TouchEvent ev) { 
  7.        float y = getY(ev); 
  8.        float screenY = getScreenY(ev); 
  9.        switch (ev.getAction()) { 
  10.            case TouchEvent.PRIMARY_POINT_DOWN: 
  11.                downY = y; 
  12.                downScreenY = screenY; 
  13.                break; 
  14.  
  15.            case TouchEvent.POINT_MOVE: 
  16.                float deltaY = y - downY; 
  17.                float deltaScreenY = screenY - downScreenY; 
  18.                System.out.println(TAG  + "y: " + y + " screenY: " + screenY + ", deltaY: " + deltaY + " deltaScreenY" + deltaScreenY); 
  19.                moveChildren((int) deltaY); 
  20.                break; 
  21.        } 
  22.  
  23.        return true
  24.    } 
  25.  
  26. private void moveChildren(int deltaY) { 
  27.        child.setComponentPosition(0, top + deltaY, child.getWidth(), top + deltaY + child.getHeight()); 
  28.    } 

日志如下:

  1. y: 1206.0348, screenY: 1905.0348, deltaY: -13.782349, deltaScreenY: -13.782349  
  2. y: 1095.856, screenY: 1781.856, deltaY: -123.96118, deltaScreenY: -136.96118  
  3. y: 1204.7794, screenY: 1780.7794, deltaY: -15.03772, deltaScreenY: -138.03772  
  4. y: 1041.5786, screenY: 1725.5786, deltaY: -178.23853, deltaScreenY: -193.23853  
  5. y: 1094.1056, screenY: 1615.1056, deltaY: -125.71155, deltaScreenY: -303.71155  
  6. y: 972.12244, screenY: 1546.1224, deltaY: -247.6947, deltaScreenY: -372.6947  
  7. y: 1066.4863, screenY: 1518.4863, deltaY: -153.33081, deltaScreenY: -400.3308  
  8. y: 917.2855, screenY: 1463.2855, deltaY: -302.53162, deltaScreenY: -455.53162  
  9. y: 1024.8671, screenY: 1421.8671, deltaY: -194.95007, deltaScreenY: -496.95007  
  10. y: 875.4486, screenY: 1380.4486, deltaY: -344.36853, deltaScreenY: -538.3685  
  11. y: 941.0178, screenY: 1296.0178, deltaY: -278.79932, deltaScreenY: -622.7993  
  12. y: 821.4109, screenY: 1242.4109, deltaY: -398.40625, deltaScreenY: -676.40625  
  13. y: 883.01855, screenY: 1184.0186, deltaY: -336.79858, deltaScreenY: -734.7986  
  14. y: 817.2832, screenY: 1180.2832, deltaY: -402.53394, deltaScreenY: -738.53394  
  15. y: 834.93787, screenY: 1131.9379, deltaY: -384.87927, deltaScreenY: -786.8793  

可以發(fā)現(xiàn)通過(guò)getPointerPosition計(jì)算出來(lái)deltaY是忽大忽小而不是線性增加的,并且與getPointerScreenPosition計(jì)算的deltaScreenY對(duì)比可以發(fā)現(xiàn),deltaY等于deltaScreenY減去上一次的deltaY。也就證明了:通過(guò)getPointerPosition獲取的手指坐標(biāo)會(huì)加上該控件的位移量。

那么這對(duì)VelocityDetector有什么影響呢?VelocityDetector計(jì)算速度有一個(gè)重要的因素就是距離,在這種情況下距離忽大忽小,就會(huì)導(dǎo)致速度計(jì)算出來(lái)的值會(huì)小于正常速度,甚至于正負(fù)值都不太一樣。

總結(jié)一下:當(dāng)一個(gè)控件在該控件的觸摸事件內(nèi),改變了自己相對(duì)于父控件的位置,那么通過(guò)VelocityDetector獲取的速度就會(huì)出現(xiàn)誤差。能影響控件位置的函數(shù)有setTop(在實(shí)驗(yàn)中setTop未能改變控件的位置,還不確定是為什么)、setContentPosition、setComponentPosition,甚至還包括setTranslationY、setTranslationX。并且如果在該控件的觸摸事件內(nèi),父控件改變了位置,也會(huì)產(chǎn)生此問(wèn)題。

在這種情況下,觸摸事件內(nèi)計(jì)算距離的問(wèn)題好解決,不使用getPointerPosition直接使用getPointerScreenPosition即可。但是VelocityDetector的問(wèn)題如何解決呢?兩個(gè)辦法:代理法與偏移法。

代理法

通過(guò)一個(gè)TouchEventProxy,內(nèi)部維護(hù)一個(gè)TouchEvent,并將其getPointerPosition實(shí)現(xiàn)轉(zhuǎn)發(fā)至TouchEvent的getPointerScreenPosition中。

  1. public class TouchEventProxy extends TouchEvent { 
  2.  
  3.     private TouchEvent event; 
  4.  
  5.     public void setEvent(TouchEvent event) { 
  6.         this.event = event; 
  7.     } 
  8.  
  9.     @Override 
  10.     public int getAction() { 
  11.         return event.getAction(); 
  12.     } 
  13.  
  14.     @Override 
  15.     public int getIndex() { 
  16.         return event.getIndex(); 
  17.     } 
  18.  
  19.     @Override 
  20.     public long getStartTime() { 
  21.         return event.getStartTime(); 
  22.     } 
  23.  
  24.     @Override 
  25.     public int getPhase() { 
  26.         return event.getPhase(); 
  27.     } 
  28.  
  29.     @Override 
  30.     public MmiPoint getPointerPosition(int i) { 
  31.         // 轉(zhuǎn)發(fā)至getPointerScreenPosition 
  32.         return event.getPointerScreenPosition(i); 
  33.     } 
  34.  
  35.     @Override 
  36.     public void setScreenOffset(float v, float v1) { 
  37.         event.setScreenOffset(v, v1); 
  38.     } 
  39.  
  40.     @Override 
  41.     public MmiPoint getPointerScreenPosition(int i) { 
  42.         return event.getPointerScreenPosition(i); 
  43.     } 
  44.  
  45.     @Override 
  46.     public int getPointerCount() { 
  47.         return event.getPointerCount(); 
  48.     } 
  49.  
  50.     @Override 
  51.     public int getPointerId(int i) { 
  52.         return event.getPointerId(i); 
  53.     } 
  54.  
  55.     @Override 
  56.     public float getForce(int i) { 
  57.         return event.getForce(i); 
  58.     } 
  59.  
  60.     @Override 
  61.     public float getRadius(int i) { 
  62.         return event.getRadius(i); 
  63.     } 
  64.  
  65.     @Override 
  66.     public int getSourceDevice() { 
  67.         return event.getSourceDevice(); 
  68.     } 
  69.  
  70.     @Override 
  71.     public String getDeviceId() { 
  72.         return event.getDeviceId(); 
  73.     } 
  74.  
  75.     @Override 
  76.     public int getInputDeviceId() { 
  77.         return event.getInputDeviceId(); 
  78.     } 
  79.  
  80.     @Override 
  81.     public long getOccurredTime() { 
  82.         return event.getOccurredTime(); 
  83.     } 

 使用起來(lái)也很簡(jiǎn)單:

  1. TouchEventProxy proxy = new TouchEventProxy(); 
  2.  
  3.     @Override 
  4.     public boolean onTouchEvent(Component component, TouchEvent ev) { 
  5.         proxy.setEvent(ev); 
  6.         detector.addEvent(proxy); 
  7.         ...... 
  8.         return true
  9.     } 

位移法

通過(guò)反射TouchEvent發(fā)現(xiàn),其內(nèi)部含有能設(shè)置偏移量的函數(shù),該函數(shù)會(huì)影響getPointerPosition的值。那么我們就可以在觸摸事件內(nèi),對(duì)比getPointerPosition與getPointerScreenPosition的差,并通過(guò)函數(shù)設(shè)置偏移,強(qiáng)制使坐標(biāo)同步。這里只提供位移法的可行并驗(yàn)證過(guò)的思路,代碼大家可以自行嘗試。

對(duì)比

既然有方法可以修復(fù)速度的問(wèn)題,那么我們就可以對(duì)比修復(fù)前與修復(fù)后的速度,到底有多少差距。我們定義兩個(gè)VelocityDetector實(shí)例,一個(gè)add代理,一個(gè)add原始的event,然后同時(shí)獲取速度來(lái)看看:

  1.   VelocityDetector detector1 = VelocityDetector.obtainInstance(); 
  2.    VelocityDetector detector2 = VelocityDetector.obtainInstance(); 
  3. TouchEventProxy proxy = new TouchEventProxy(); 
  4.  
  5. @Override 
  6.    public boolean onTouchEvent(Component component, TouchEvent ev) { 
  7.        proxy.setEvent(ev); 
  8.  
  9.        detector1.addEvent(ev); 
  10.        detector2.addEvent(proxy); 
  11.         
  12.        float y = getY(ev); 
  13.        switch (ev.getAction()) { 
  14.            case TouchEvent.PRIMARY_POINT_DOWN: 
  15.                downY = y; 
  16.                break; 
  17.  
  18.            case TouchEvent.POINT_MOVE: 
  19.                float deltaY = y - downY; 
  20.                moveChildren((int) deltaY); 
  21.                break; 
  22.  
  23.            case TouchEvent.PRIMARY_POINT_UP: 
  24.                detector1.calculateCurrentVelocity(1000); 
  25.                detector2.calculateCurrentVelocity(1000); 
  26.                System.out.println(TAG + "detector1: " + detector1.getVerticalVelocity() + ", detector2: " + detector2.getVerticalVelocity()); 
  27.  
  28.                detector1.clear(); 
  29.                detector2.clear(); 
  30.                break; 
  31.        } 
  32.  
  33.        return true
  34.    } 

快速上滑:

  1. 08-05 09:36:29.004 1846-1846/? I System.out:  ParallaxLayout TouchEvent: detector1: -5332.0, detector2: -9285. 

慢一點(diǎn)上滑:

  1. 08-05 09:35:39.065 1846-1846/? I System.out: ParallaxLayout TouchEvent: detector1: -1003.0, detector2: -3560.0 

先慢速最后快速上滑:

  1. 08-05 09:37:04.066 1846-1846/? I System.out: ParallaxLayout TouchEvent: detector1: -4176.0, detector2: -4491.0 

快速下滑:

  1. 08-05 09:39:44.785 1846-1846/? I System.out: ParallaxLayout TouchEvent: detector1: 1955.0, detector2: 6660.0 

慢速下滑:

  1. 08-05 09:40:32.813 1846-1846/? I System.out: ParallaxLayout TouchEvent: detector1: 907.0, detector2: 3835.0 

先慢速最后快速下滑:

  1. 08-05 09:39:15.739 1846-1846/? I System.out: ParallaxLayout TouchEvent: detector1: -784.0, detector2: 1937.0 

總結(jié)

可以發(fā)現(xiàn)上滑過(guò)程采樣越少(慢速突然變快的情況)兩個(gè)速度越接近,但是在下滑過(guò)程中,如果速度比較慢甚至?xí)玫揭粋€(gè)方向相反的速度。

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

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

https://harmonyos.51cto.com

 

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

2021-08-16 14:45:38

鴻蒙HarmonyOS應(yīng)用

2021-09-06 14:58:23

鴻蒙HarmonyOS應(yīng)用

2015-02-11 17:49:35

Android源碼自定義控件

2021-09-02 10:00:42

鴻蒙HarmonyOS應(yīng)用

2021-08-11 14:29:20

鴻蒙HarmonyOS應(yīng)用

2009-06-08 20:13:36

Eclipse自定義控

2009-08-06 09:18:01

ASP.NET自定義控ASP.NET控件開(kāi)發(fā)

2013-04-19 10:14:24

2009-07-31 10:23:09

ASP.NET源碼DateTimePic

2017-02-17 09:37:12

Android自定義控件方法總結(jié)

2011-08-18 09:44:33

iPhone SDK儀表控件UIDialView

2009-08-06 17:52:45

ASP.NET控件開(kāi)發(fā)自定義控件

2021-10-26 10:07:02

鴻蒙HarmonyOS應(yīng)用

2022-06-30 14:02:07

鴻蒙開(kāi)發(fā)消息彈窗組件

2022-07-15 16:45:35

slider滑塊組件鴻蒙

2022-06-20 15:43:45

switch開(kāi)關(guān)鴻蒙

2009-09-03 13:34:03

.NET自定義控件

2009-08-03 13:34:06

自定義C#控件

2009-08-03 13:39:46

C#自定義用戶控件

2014-09-24 11:42:46

AndroidButton
點(diǎn)贊
收藏

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