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

Android的滑動(dòng)分析以及各種實(shí)現(xiàn)

移動(dòng)開(kāi)發(fā) Android
滑動(dòng)一個(gè)View,本質(zhì)區(qū)別就是移動(dòng)一個(gè)View。改變當(dāng)前View所在的坐標(biāo),原理和動(dòng)畫(huà)相似不斷改變坐標(biāo)位置實(shí)現(xiàn)。實(shí)現(xiàn)View的滑動(dòng)就必須監(jiān)聽(tīng)滑動(dòng)的事件,并且根據(jù)事件傳入的坐標(biāo),動(dòng)態(tài)且不斷改變View的坐標(biāo),從而實(shí)現(xiàn)View跟隨用戶觸摸的滑動(dòng)而滑動(dòng)。

【引自MrXI的博客】一、滑動(dòng)效果的產(chǎn)生

滑動(dòng)一個(gè)View,本質(zhì)區(qū)別就是移動(dòng)一個(gè)View。改變當(dāng)前View所在的坐標(biāo),原理和動(dòng)畫(huà)相似不斷改變坐標(biāo)位置實(shí)現(xiàn)。實(shí)現(xiàn)View的滑動(dòng)就必須監(jiān)聽(tīng)滑動(dòng)的事件,并且根據(jù)事件傳入的坐標(biāo),動(dòng)態(tài)且不斷改變View的坐標(biāo),從而實(shí)現(xiàn)View跟隨用戶觸摸的滑動(dòng)而滑動(dòng)。

(1)、Android的坐標(biāo)系

Android中將屏幕最左上角的頂點(diǎn)作為Android坐標(biāo)系的原點(diǎn),從這個(gè)點(diǎn)向右是X軸正方向,從這個(gè)點(diǎn)向下是Y軸正方向,如下圖:

 

系統(tǒng)提供了getLocationOnScreen(int location[])這樣的方法來(lái)獲取Android坐標(biāo)系中點(diǎn)的位置,即該視圖左上角在Android坐標(biāo)系中的坐標(biāo)。在觸控事件中使用getRawX()、getRawY()方法所獲得的坐標(biāo)同樣是Android坐標(biāo)系中的坐標(biāo)。

(2)、視圖坐標(biāo)系

Android中除了上面所說(shuō)的這種坐標(biāo)系之外,還有一個(gè)視圖坐標(biāo)系,它描述了子視圖在父視圖中的位置關(guān)系。這兩種坐標(biāo)系并不矛盾也不復(fù)雜,他們的作用是相互相成的。與Android坐標(biāo)系類似,視圖坐標(biāo)系同樣是以原點(diǎn)向右為X軸正方向,以原點(diǎn)向下為Y軸正方向,只不過(guò)在視圖坐標(biāo)系中,原點(diǎn)不再是Android坐標(biāo)系中的屏幕最左上角,而是以父視圖左上角為坐標(biāo)原點(diǎn),如下圖: 

 

在觸控事件中,通過(guò)getX()、getY()所獲得的坐標(biāo)系就是視圖坐標(biāo)系中的坐標(biāo)。

(3)、觸控事件——MotionEvent

觸控事件MotionEvent在用戶交互中,占著舉足輕重的地位。首先看看MotionEvent封裝的一些常用事件常量,定義了觸控事件的不同類型。

  1. //單點(diǎn)觸摸按下動(dòng)作 
  2. public static final int ACTION_DOWN             = 0; 
  3.  
  4. //單點(diǎn)觸摸離開(kāi)動(dòng)作 
  5. public static final int ACTION_UP               = 1; 
  6.  
  7. //觸摸點(diǎn)移動(dòng)動(dòng)作 
  8. public static final int ACTION_MOVE             = 2; 
  9.  
  10. //觸摸動(dòng)作取消 
  11. public static final int ACTION_CANCEL           = 3; 
  12.  
  13. //觸摸動(dòng)作超出邊界 
  14. public static final int ACTION_OUTSIDE          = 4; 
  15.  
  16. //多點(diǎn)觸摸按下動(dòng)作 
  17. public static final int ACTION_POINTER_DOWN     = 5; 
  18.  
  19. //多點(diǎn)離開(kāi)動(dòng)作 
  20. public static final int ACTION_POINTER_UP       = 6;  

通常情況會(huì)在onTouchEvent(MotionEvent event)方法中通過(guò)event.getAction()方法來(lái)獲取觸控事件的類型,并使用switch-case方法來(lái)進(jìn)行篩選,這個(gè)代碼的模式基本固定:

  1. @Override 
  2. public boolean onTouchEvent(MotionEvent event) { 
  3.     //獲取當(dāng)前輸入點(diǎn)的X、Y坐標(biāo)(視圖坐標(biāo)) 
  4.     int x = (int) event.getX(); 
  5.     int y = (int) event.getY(); 
  6.     switch (event.getAction()) { 
  7.         case MotionEvent.ACTION_DOWN: 
  8.             //處理按下事件 
  9.             break; 
  10.         case MotionEvent.ACTION_MOVE: 
  11.             //處理移動(dòng)事件 
  12.             break; 
  13.         case MotionEvent.ACTION_UP: 
  14.             //處理離開(kāi)事件 
  15.             break; 
  16.     } 
  17.     return true
  18.  

在不涉及多點(diǎn)操作的情況下,通??梢允褂靡陨洗a來(lái)完成觸控事件的監(jiān)聽(tīng)。

在Android中系統(tǒng)提供了非常多的方法來(lái)獲取坐標(biāo)值、相對(duì)距離等。方法豐富固然好,下面對(duì)坐標(biāo)系的API進(jìn)行總結(jié),如下圖:

 

這些方法可以分為如下兩個(gè)類別:

  • View提供的獲取坐標(biāo)方法
    • getTop():獲取到的是View自身的頂邊到其父布局頂邊的距離。
    • getLeft():獲取到的是View自身的左邊到其父布局最左邊的距離。
    • getRight():獲取到的是View自身的右邊到其父布局左邊的距離。
    • getBottom():獲取到的是View自身的底邊到其父布局頂邊的距離。
  • MotionEvent提供的方法
    • getX():獲取點(diǎn)擊事件距離空間左邊的距離,即視圖坐標(biāo)。
    • getY():獲取點(diǎn)擊事件距離控件頂邊的距離,即視圖坐標(biāo)。
    • getRawX():獲取點(diǎn)擊事件距離整個(gè)屏幕左邊的距離,即絕對(duì)坐標(biāo)。
    • getRawY():獲取點(diǎn)擊事件距離整個(gè)屏幕頂邊的距離,即絕對(duì)坐標(biāo)。

二、實(shí)現(xiàn)滑動(dòng)的七種方式

當(dāng)了解Android坐標(biāo)系和觸控事件后,我們?cè)賮?lái)看看如何使用系統(tǒng)提供的API來(lái)實(shí)現(xiàn)動(dòng)態(tài)地修改一個(gè)View坐標(biāo),即實(shí)時(shí)滑動(dòng)效果。而不管采用哪一種方式,其實(shí)現(xiàn)的思想基本是一致的,當(dāng)觸摸View時(shí),系統(tǒng)記下當(dāng)前觸摸點(diǎn)坐標(biāo),當(dāng)手指移動(dòng)時(shí),系統(tǒng)記下移動(dòng)后的觸摸點(diǎn)坐標(biāo),從而獲取到相對(duì)于前一次坐標(biāo)點(diǎn)的偏移量,并通過(guò)偏移量來(lái)修改View的坐標(biāo),這樣不斷重復(fù),實(shí)現(xiàn)滑動(dòng)過(guò)程。

通過(guò)一個(gè)實(shí)例看看Android中該如何實(shí)現(xiàn)滑動(dòng)效果,定義一個(gè)View,處于LinearLayout中,實(shí)現(xiàn)一個(gè)簡(jiǎn)單布局:

  1. <?xml version="1.0" encoding="utf-8"?> 
  2.  
  3. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  4.  
  5. android:layout_width="match_parent" 
  6.  
  7. android:layout_height="match_parent" 
  8.  
  9. android:orientation="vertical"
  10.  
  11. <com.xjf.drawview.DragView1 
  12.  
  13. android:layout_width="100dp" 
  14.  
  15. android:layout_height="100dp" /> 
  16.  
  17. </LinearLayout>  

我們的目的就是讓這個(gè)自定義的View隨著手指在屏幕上的滑動(dòng)而滑動(dòng)。初始化時(shí)顯示效果: 

 

(1)、layout方法

在View繪制時(shí),會(huì)調(diào)用onLayout()方法來(lái)設(shè)置顯示的位置。同樣,可以通過(guò)修改View的left,top,right,bottom四個(gè)屬性來(lái)控制View的坐標(biāo)。與前面提供的模板代碼一樣,在每次回調(diào)onTouchEvent的時(shí)候,我們都來(lái)獲取一下觸摸點(diǎn)的坐標(biāo),代碼如下:

  1. //獲取當(dāng)前輸入點(diǎn)的X、Y坐標(biāo)(視圖坐標(biāo)) 
  2.  
  3. int x = (int) event.getX(); 
  4.  
  5. int y = (int) event.getY();  

接著,在Action_DOWN事件中記錄觸摸點(diǎn)的坐標(biāo),如下:

  1. case MotionEvent.ACTION_DOWN: 
  2.  
  3. // 記錄觸摸點(diǎn)坐標(biāo) 
  4.  
  5. lastX = x; 
  6.  
  7. lastY = y; 
  8.  
  9. break;  

***,可以在Action_MOVE事件中計(jì)算偏移量,并將偏移量作用到Layout方法中,在目前Layout的left,top,right,bottom基礎(chǔ)上,增加計(jì)算出來(lái)的偏移量,代碼如下所示:

  1. case MotionEvent.ACTION_MOVE: 
  2.  
  3. // 計(jì)算偏移量 
  4.  
  5. int offsetX = x - lastX; 
  6.  
  7. int offsetY = y - lastY; 
  8.  
  9. // 在當(dāng)前left、top、right、bottom的基礎(chǔ)上加上偏移量 
  10.  
  11. layout(getLeft() + offsetX, 
  12.  
  13. getTop() + offsetY, 
  14.  
  15. getRight() + offsetX, 
  16.  
  17. getBottom() + offsetY); 
  18.  
  19. break;  

這樣沒(méi)錯(cuò)移動(dòng)后,View都會(huì)調(diào)用Layout方法來(lái)對(duì)自己重新布局,從而達(dá)到移動(dòng)View的效果。

上面的代碼中,使用的是getX()、getY()方法來(lái)獲取坐標(biāo)值,即通過(guò)視圖坐標(biāo)來(lái)獲取偏移量。當(dāng)然,同樣可以使用getRawX()、getRawY()來(lái)獲取坐標(biāo),并使用絕對(duì)坐標(biāo)來(lái)計(jì)算偏移量,代碼如下:

  1. // 視圖坐標(biāo)方式 
  2.  
  3. @Override 
  4.  
  5. public boolean onTouchEvent(MotionEvent event) { 
  6.  
  7. int x = (int) event.getRawX(); 
  8.  
  9. int y = (int) event.getRawY(); 
  10.  
  11. switch (event.getAction()) { 
  12.  
  13. case MotionEvent.ACTION_DOWN: 
  14.  
  15. // 記錄觸摸點(diǎn)坐標(biāo) 
  16.  
  17. lastX = x; 
  18.  
  19. lastY = y; 
  20.  
  21. break; 
  22.  
  23. case MotionEvent.ACTION_MOVE: 
  24.  
  25. // 計(jì)算偏移量 
  26.  
  27. int offsetX = x - lastX; 
  28.  
  29. int offsetY = y - lastY; 
  30.  
  31. // 在當(dāng)前lefttop、right、bottom的基礎(chǔ)上加上偏移量 
  32.  
  33. layout(getLeft() + offsetX, 
  34.  
  35. getTop() + offsetY, 
  36.  
  37. getRight() + offsetX, 
  38.  
  39. getBottom() + offsetY); 
  40.  
  41. //重新設(shè)置初始化坐標(biāo) 
  42.  
  43. lastX = x; 
  44.  
  45. lastY = y; 
  46.  
  47. break; 
  48.  
  49.  
  50. return true
  51.  
  52.  

使用絕對(duì)坐標(biāo)系,有一點(diǎn)非常需要注意的地方,就是在每次執(zhí)行完ACTION_MOVE的邏輯后,一定要重新設(shè)置初始化坐標(biāo),這樣才能準(zhǔn)確地獲取偏移量。

(2)、offsetLeftAndRight()與offsetTopAndBottom()

這個(gè)方法相當(dāng)于系統(tǒng)提供的一個(gè)對(duì)左右、上下移動(dòng)的API的封裝。當(dāng)計(jì)算出偏移量后,只需要使用如下代碼就可以完成View的重新布局,效果與使用Layout方法一樣,代碼如下所示:

  1. //同時(shí)對(duì)leftright進(jìn)行偏移 
  2.  
  3. offsetLeftAndRight(offsetX); 
  4.  
  5. //同時(shí)對(duì)top和bottom進(jìn)行偏移 
  6.  
  7. offsetTopAndBottom(offsetY);  

這里的offsetX、offsetY與在layout方法中計(jì)算offset方法一樣。

(3)、LayoutParams

LayoutParams保存了一個(gè)View的布局參數(shù)。因此可以在程序中,通過(guò)改變LayoutParams來(lái)動(dòng)態(tài)地修改一個(gè)布局的位置參數(shù),從而達(dá)到改變View位置的效果。我們可以很方便在程序中使用getLayoutParams()來(lái)獲取一個(gè)View的LayoutParams。當(dāng)然,計(jì)算偏移量的方法與在Layout方法中計(jì)算offset也是一樣。當(dāng)獲取到偏移量之后,就可以通過(guò)setLayoutParams來(lái)改變其LayoutParams,代碼如下:

  1. LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams(); 
  2.  
  3. layoutParams.leftMargin = getLeft() + offsetX; 
  4.  
  5. layoutParams.topMargin = getTop() + offsetY; 
  6.  
  7. setLayoutParams(layoutParams);  

這里getLayoutParams()獲取LayoutParams時(shí),需要根據(jù)View所在View父布局的類型來(lái)設(shè)置不同的類型,比如這里將View放在LinearLayout中,那么就可以使用LinearLayout.LayoutParams。如果在RelativeLayout中,就要使用RelativeLayout.LayoutParams。這一切的前提是你必須要有一個(gè)父布局,不然系統(tǒng)無(wú)法獲取LayoutParams。

在通過(guò)改變LayoutParams來(lái)改變一個(gè)View的位置時(shí),通常改變的是這個(gè)View的Margin屬性,所以除了使用布局的LayoutParams之外,還可以使用ViewGroup.MarginLayoutParams來(lái)實(shí)現(xiàn)這一一個(gè)功能,代碼:

  1. ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams(); 
  2.  
  3. layoutParams.leftMargin = getLeft() + offsetX; 
  4.  
  5. layoutParams.topMargin = getTop() + offsetY; 
  6.  
  7. setLayoutParams(layoutParams);  

我們可以發(fā)現(xiàn),使用ViewGroup.MarginLayoutParams更加的方便,不需要考慮父布局的類型,當(dāng)然它們的本質(zhì)都是一樣。

(4)、scrollTo與scrollBy

在一個(gè)View中,系統(tǒng)提供了scrollTo、scrollBy兩種方式來(lái)改變一個(gè)View的位置。這兩個(gè)方法的區(qū)別非常好理解,與英文中To與By的區(qū)別類似,scrollTo(x,y)表示移動(dòng)到一個(gè)具體的坐標(biāo)點(diǎn)(x,y),而scrollBy(dx,dy)表示移動(dòng)的增量為dx,dy。

與前面幾種方式相同,在獲取偏移量后使用scrollBy來(lái)移動(dòng)View,代碼如下:

  1. int offsetX = x - lastX; 
  2.  
  3. int offsetY = y - lastY; 
  4.  
  5. scrollBy(offsetX, offsetY);  

但是,當(dāng)我們拖動(dòng)View的時(shí)候,你會(huì)發(fā)現(xiàn)View并沒(méi)有移動(dòng),其實(shí)方法沒(méi)錯(cuò),View確實(shí)移動(dòng)了,只是移動(dòng)的并不是我們想要的東西。scrollTo、scrollBy方法移動(dòng)的是View的content,即讓View的內(nèi)容移動(dòng),如果在ViewGroup中使用scrollTo、scrollBy方法,那么移動(dòng)的將是所有子View,如果在View中使用,那么移動(dòng)的將是View的內(nèi)容,例如TextView,content就是它的文本,ImageView,content就是它的drawable對(duì)象。

通過(guò)以上的分析,現(xiàn)在知道為什么不能再View中使用這兩個(gè)方法來(lái)拖動(dòng)這個(gè)View了。那么我們就該View所在的ViewGroup中來(lái)使用scrollBy方法,移動(dòng)它的子View,代碼如下:

  1. ((View) getParent()).scrollBy(offsetX, offsetY); 

但是再次拖動(dòng)View的時(shí)候,你會(huì)發(fā)現(xiàn)View雖然移動(dòng)了,但卻在亂動(dòng),并不是我們想要的跟隨觸摸點(diǎn)的移動(dòng)而移動(dòng)。這里先看一下視圖移動(dòng),不妨這樣想象一下手機(jī)屏幕是一個(gè)中空的蓋板,蓋板下面是一個(gè)巨大的畫(huà)布,也就是我們想要顯示的視圖。當(dāng)把這個(gè)蓋板蓋在畫(huà)布上的某一處時(shí),透過(guò)中間空的矩形,我們看見(jiàn)了手機(jī)屏幕上顯示的視圖,而畫(huà)布上其他地方的視圖,則被蓋板蓋住了無(wú)法看見(jiàn)。我們的視圖與這個(gè)例子非常類似,我們沒(méi)有看見(jiàn)視圖,并不代表它就不存在,有可能只是在屏幕外面而已。當(dāng)調(diào)用scrollBy方法時(shí),可以想象為外面的蓋板在移動(dòng),這么說(shuō)比較抽象。

下圖一中間的矩形相當(dāng)于屏幕,及可視區(qū)域。后面的content就相當(dāng)于畫(huà)布,代表視圖??梢钥吹?,只有視圖的中間部分目前是可視的,其他部分都不可見(jiàn)。在可見(jiàn)區(qū)域中,我們?cè)O(shè)置了一個(gè)Button,它的坐標(biāo)為(20,10)。

下面使用scrollBy方法,將蓋板(屏幕、可視區(qū)域),在水平方向上向X軸正方向(向右)平移20,在豎直方向上向Y軸正方向(下方)平移10,那么平移之后的可視區(qū)域如圖二。 

 

圖一 

 

圖二、移動(dòng)之后的可視區(qū)域

我們發(fā)現(xiàn),雖然設(shè)置scrollBy(20,10),偏移量均為X軸、Y軸正方向上的正數(shù),但是在屏幕的可視區(qū)域內(nèi),Button卻向X軸、Y軸負(fù)方向上移動(dòng)了。這就是因?yàn)閰⒖枷颠x擇的不同,而產(chǎn)生的不同效果。

通過(guò)上面的分析可以發(fā)現(xiàn),如果講scrollBy中的參數(shù)dx和dy設(shè)置為正數(shù),那么content講向坐標(biāo)軸負(fù)方向移動(dòng),如果將scrollBy中的參數(shù)dx和dy設(shè)置為負(fù)數(shù),那么content將向坐標(biāo)軸正方向移動(dòng),因此回到前面的例子,要實(shí)現(xiàn)跟隨著手指移動(dòng)而滑動(dòng)的效果,就必須將偏移量改為負(fù)值,代碼如下:

  1. int offsetX = x - lastX; 
  2.  
  3. int offsetY = y - lastY; 
  4.  
  5. ((View) getParent()).scrollBy(-offsetX, -offsetY);  

現(xiàn)在在運(yùn)行一次發(fā)現(xiàn)和前面幾種方式效果相同了,類似地使用絕對(duì)坐標(biāo)時(shí),也可以通過(guò)使用scrollTo發(fā)方法來(lái)實(shí)現(xiàn)這一效果。

(5)、Scroller

前面提到了scrollBy、scrollTo方法,就不得不再來(lái)說(shuō)一說(shuō)Scroller類。Scroller類與scrollBy、scrollTo方法十分相似。什么區(qū)別?先看例子,如果要完成這樣一個(gè)效果;通過(guò)點(diǎn)擊按鈕,讓一個(gè)ViewGroup的子View向右移動(dòng)100個(gè)像素。問(wèn)題看起來(lái)很簡(jiǎn)單,只要在按鈕的點(diǎn)擊事件中使用前面的scrollBy方法設(shè)置下偏移量就可以了嗎?確實(shí)這樣可以讓一個(gè)子ViewGroup中的子View平移,但是不管使用scrollBy還是scrollTo方法,子view的平移都是瞬間發(fā)生的,在事件執(zhí)行的時(shí)候平移就已經(jīng)完成了,這樣的效果會(huì)讓人感覺(jué)非常突然,Google建議使用自然的過(guò)度動(dòng)畫(huà)來(lái)實(shí)現(xiàn)移動(dòng)效果。因此Scroller類就這樣誕生了,通過(guò)Scroller類可以實(shí)現(xiàn)平滑移動(dòng)的效果,而不是瞬間就完成移動(dòng)。

Scroller類的實(shí)現(xiàn)原理,其實(shí)它與前面使用的scrollTo和scrollBy方法來(lái)實(shí)現(xiàn)子View跟隨手指移動(dòng)的原理基本類似,雖然scrollBy芳芳法是讓子View瞬間從某點(diǎn)移動(dòng)到另一個(gè)點(diǎn),但是由于在ACTION_MOVE事件中不斷獲取手指移動(dòng)的微小的偏移量,這樣就將一段距離劃分成了N個(gè)非常小的偏移量。雖然每個(gè)偏移量里面,通過(guò)scrollBy方法進(jìn)行了瞬間移動(dòng),但是在整體上卻可以獲得一個(gè)平滑移動(dòng)的效果。這個(gè)原理與動(dòng)畫(huà)的實(shí)現(xiàn)原理也是基本類似的,它們都是利用了人眼的視覺(jué)暫留特性。

下面我們使用Scroller類實(shí)現(xiàn)平滑移動(dòng),在這個(gè)實(shí)例中,同樣讓子View跟隨手指的滑動(dòng)而滑動(dòng),但是在手指離開(kāi)屏蔽時(shí),讓子View平滑的移動(dòng)到初始化位置,即屏幕左上角。使用Scroller類需要如下三個(gè)步驟:

  • 初始化Scroller

首先通過(guò)它的構(gòu)造方法來(lái)創(chuàng)建一個(gè)Scroller對(duì)象,代碼如下所示:

  1. // 初始化Scroller 
  2. mScroller = new Scroller(context);  
  • 重寫(xiě)computerScroller方法,實(shí)現(xiàn)模擬滑動(dòng)

下面我們需要重寫(xiě)computerScroller()芳芳法,它是使用Scroller類的核心,系統(tǒng)在繪制View的時(shí)候會(huì)在draw()方法中調(diào)用該方法。這個(gè)方法實(shí)際就是使用的scrollTo方法。再結(jié)合Scroller對(duì)象,幫助獲取到當(dāng)前滾動(dòng)值。我們可以通過(guò)不斷地瞬間移動(dòng)一個(gè)小的距離來(lái)實(shí)現(xiàn)整體上的平滑移動(dòng)效果。代碼如下:

  1. @Override 
  2. public void computeScroll() { 
  3.     super.computeScroll(); 
  4.     // 判斷Scroller是否執(zhí)行完畢 
  5.     if (mScroller.computeScrollOffset()) { 
  6.         ((View) getParent()).scrollTo( 
  7.                 mScroller.getCurrX(), 
  8.                 mScroller.getCurrY()); 
  9.         // 通過(guò)重繪來(lái)不斷調(diào)用computeScroll 
  10.         invalidate(); 
  11.     } 
  12.  

Scroller類提供了computeScrollOffset()方法來(lái)判斷是否完成了整個(gè)滑動(dòng),同時(shí)也提供了getCurrX()、getCurrY()方法來(lái)獲得當(dāng)前的滑動(dòng)坐標(biāo)。在上面的代碼中,唯一需要注意的是invalidate()方法,因?yàn)橹荒茉赾omputeScroller()方法中獲取模擬過(guò)程中的scrollX和scrollY坐標(biāo)。但computeScroll()方法是不會(huì)自動(dòng)調(diào)用的,只能通過(guò)invalidate()->draw()->computeScroll()來(lái)間接調(diào)用compuetScroll()方法,所以需要在compuetScroll()方法中調(diào)用invaliDate()方法,實(shí)現(xiàn)循環(huán)獲取scrollX和scrollY的目的。而當(dāng)模擬過(guò)程結(jié)束后,scroller.compuetScrollOffset()方法會(huì)返回false,而中斷循環(huán),完成平滑移動(dòng)過(guò)程。

  • startScroll開(kāi)啟模擬過(guò)程

我們?cè)谛枰褂闷交苿?dòng)的事件中,使用Scroller類的startScroll()方法來(lái)開(kāi)啟平滑移動(dòng)過(guò)程。startScroll()方法具有兩個(gè)重載方法。

  1. public void startScroll(int startX, int startY, int dx, int dy)  
  1. public void startScroll(int startX, int startY, int dx, int dy, int duration) 

可以看到它們的區(qū)別就是一個(gè)具有指定的支持時(shí)長(zhǎng),而另一個(gè)沒(méi)有。很好理解,與在動(dòng)畫(huà)中設(shè)置duration和使用默認(rèn)的顯示時(shí)長(zhǎng)是一個(gè)道理。其他四個(gè)坐標(biāo),則與他們的命名含義相同,就是起始坐標(biāo)與偏移量。在獲取坐標(biāo)時(shí),通??梢允褂胓etScrollX()和getScrollY()方法來(lái)獲取父視圖中content所滑動(dòng)到的點(diǎn)的坐標(biāo),需要注意的是這個(gè)值的正負(fù),它與在scrollBy、scrollTo中講解的情況是一樣的。

根據(jù)以上三步,就可以使用Scroller類實(shí)現(xiàn)平滑移動(dòng),在構(gòu)造方法中初始化Scroller對(duì)象,重寫(xiě)View的computerScroll()方法,***監(jiān)聽(tīng)手指離開(kāi)屏蔽的事件,并在該事件中調(diào)用startScroll()方法完成平滑移動(dòng)。監(jiān)聽(tīng)手指離開(kāi)屏幕的事件,只需要在onTouchEvent中增加一個(gè)ACTION_UP監(jiān)聽(tīng)選項(xiàng)即可,代碼如下所示:

  1. case MotionEvent.ACTION_UP: 
  2.     // 手指離開(kāi)時(shí),執(zhí)行滑動(dòng)過(guò)程 
  3.     View viewGroup = ((View) getParent()); 
  4.     mScroller.startScroll( 
  5.             viewGroup.getScrollX(), 
  6.             viewGroup.getScrollY(), 
  7.             -viewGroup.getScrollX(), 
  8.             -viewGroup.getScrollY()); 
  9.     invalidate(); 
  10.     break;  

在startScroll()方法中我們獲取子View移動(dòng)的距離-getScrollX()、getScrollY(),并將偏移量設(shè)置為其相反數(shù),從而將子View滑動(dòng)到原位置。這里的invalidate()方法是用來(lái)通知View進(jìn)行重繪,調(diào)用computeScroll()的模擬過(guò)程。當(dāng)然,也可以給startScroll()方法增加一個(gè)duration的參數(shù)來(lái)設(shè)置滑動(dòng)的持續(xù)時(shí)長(zhǎng)。

(6)、屬性動(dòng)畫(huà)

屬性動(dòng)畫(huà)請(qǐng)參見(jiàn)我的另一篇:Android全套動(dòng)畫(huà)使用技巧

(7)、ViewDragHelper

Google在其support庫(kù)中為我們提供了DrawerLayout和SlidingPaneLayout兩個(gè)布局來(lái)幫助開(kāi)發(fā)者實(shí)現(xiàn)側(cè)邊欄滑動(dòng)的效果。這兩個(gè)新的布局方便我們創(chuàng)建自己的滑動(dòng)布局界面,在這兩個(gè)強(qiáng)大布局背后有一個(gè)功能強(qiáng)大的類——ViewDragHelper。通過(guò)ViewDragHelper,基本可以實(shí)現(xiàn)各種不同的滑動(dòng)、拖放需求,因此這個(gè)方法也是各種滑動(dòng)解決方案中的終結(jié)絕招。

下面演示一個(gè)使用ViewDragHelper創(chuàng)建一個(gè)QQ側(cè)邊欄滑動(dòng)的布局,如圖: 

 

圖三 

 

圖四

  • 初始化ViewDragHelper

首先需要初始化ViewDragHelper,ViewDragHelper通常定義在一個(gè)ViewGroup的內(nèi)部,通過(guò)靜態(tài)工廠方法進(jìn)行初始化,代碼如下:

  1. mViewDragHelper = ViewDragHelper.create(this, callback); 

***個(gè)參數(shù)監(jiān)聽(tīng)的View,通常需要一個(gè)ViewGroup,即parentView;第二個(gè)參數(shù)是一個(gè)Callback回調(diào),這個(gè)回調(diào)就是整個(gè)ViewDragHelper的邏輯核心。

  • 攔截事件

重寫(xiě)攔截事件,將事件傳遞給ViewDragHelper進(jìn)行處理;

  1. @Override 
  2.  
  3. public boolean onInterceptTouchEvent(MotionEvent ev) { 
  4.  
  5. return mViewDragHelper.shouldInterceptTouchEvent(ev); 
  6.  
  7.  
  8. @Override 
  9.  
  10. public boolean onTouchEvent(MotionEvent event) { 
  11.  
  12. //將觸摸事件傳遞給ViewDragHelper,此操作必不可少 
  13.  
  14. mViewDragHelper.processTouchEvent(event); 
  15.  
  16. return true
  17.  
  18.  
  • 處理computeScroll()

使用ViewDragHelper同樣需要重寫(xiě)computeScroll()方法,因?yàn)閂iewDragHelper內(nèi)部也是通過(guò)Scroller來(lái)實(shí)現(xiàn)平滑移動(dòng)的。

  1. @Override 
  2. public void computeScroll() { 
  3.     if (mViewDragHelper.continueSettling(true)) { 
  4.         ViewCompat.postInvalidateOnAnimation(this); 
  5.     } 
  6.  
  • 處理回調(diào)Callback

創(chuàng)建一個(gè)ViewDragHelper.Callback

  1. private ViewDragHelper.Callback getCallback = new ViewDragHelper.Callback() { 
  2.     @Override 
  3.     public boolean tryCaptureView(View child, int pointerId) { 
  4.         return false
  5.     } 
  6. };  

as自動(dòng)重寫(xiě)tryCaptureView()方法,通過(guò)這個(gè)方法可以指定在創(chuàng)建ViewDragHelper時(shí),參數(shù)parentView中的哪一個(gè)子Vieww可以被移動(dòng),例如我們?cè)谶@個(gè)實(shí)例中自定義一個(gè)ViewGroup,里面定義了兩個(gè)子View——Menu View和MainView,如下代碼:

  1. // 何時(shí)開(kāi)始檢測(cè)觸摸事件 
  2. @Override 
  3. public boolean tryCaptureView(View child, int pointerId) { 
  4.     //如果當(dāng)前觸摸的child是mMainView時(shí)開(kāi)始檢測(cè) 
  5.     return mMainView == child; 
  6.  

具體垂直滑動(dòng)方法clampViewPositionVertical()和水平滑動(dòng)方法clampViewPositionHorizontal()。實(shí)現(xiàn)滑動(dòng)這個(gè)兩個(gè)方法必須寫(xiě),默認(rèn)返回值是0,即不發(fā)生滑動(dòng),當(dāng)然如果只重寫(xiě)clampViewPositionVertical()或clampViewPositionHorizontal()中的一個(gè),那么就只會(huì)實(shí)現(xiàn)該方向上的滑動(dòng)效果。

  1. // 處理垂直滑動(dòng) 
  2. @Override 
  3. public int clampViewPositionVertical(View child, int topint dy) { 
  4.     return top
  5.  
  6. // 處理水平滑動(dòng) 
  7. @Override 
  8. public int clampViewPositionHorizontal(View child, int leftint dx) { 
  9.     return left
  10.  

clampViewPositionVertical(View child, int top, int dy)中的參數(shù)top,代表在垂直方向上child移動(dòng)的距離,dy則表示比較前一次的增量。clampViewPositionHorizontal(View child, int left, int dx)也是類似的含義,通常情況下只需要返回top和left即可,但需要更加精確地計(jì)算padding等屬性的時(shí)候,就需要對(duì)left進(jìn)行一些處理,并返回合適大小的值。

通過(guò)重寫(xiě)上面的三個(gè)方法,就可以實(shí)現(xiàn)基本的滑動(dòng)效果。當(dāng)用手拖動(dòng)MainView的時(shí)候,它就可有跟隨手指的滑動(dòng)而滑動(dòng)了,代碼:

  1. private ViewDragHelper.Callback callback =  new ViewDragHelper.Callback() { 
  2.  
  3.             // 何時(shí)開(kāi)始檢測(cè)觸摸事件 
  4.             @Override 
  5.             public boolean tryCaptureView(View child, int pointerId) { 
  6.                 //如果當(dāng)前觸摸的child是mMainView時(shí)開(kāi)始檢測(cè) 
  7.                 return mMainView == child; 
  8.             } 
  9.   
  10.  
  11.             // 處理垂直滑動(dòng) 
  12.             @Override 
  13.             public int clampViewPositionVertical(View child, int topint dy) { 
  14.                 return 0; 
  15.             } 
  16.  
  17.             // 處理水平滑動(dòng) 
  18.             @Override 
  19.             public int clampViewPositionHorizontal(View child, int leftint dx) { 
  20.                 return left
  21.             } 
  22.  
  23.              
  24.         };  

在前面的Scroller中講解時(shí)實(shí)現(xiàn)一個(gè)效果——手指離開(kāi)屏幕后,View滑動(dòng)回到初始位置?,F(xiàn)在使用ViewDragHelper實(shí)現(xiàn),在ViewDragHelper.Callback中,系統(tǒng)提供了這樣的方法——onViewReleased(),通過(guò)重寫(xiě)這個(gè)方法,可以非常簡(jiǎn)單地實(shí)現(xiàn)當(dāng)手指離開(kāi)屏幕后實(shí)現(xiàn)的操作。這個(gè)方法內(nèi)部是使用Scroller類實(shí)現(xiàn)的,這也是前面重寫(xiě)computeScroll()方法的原因。

  1. @Override 
  2. public void onViewReleased(View releasedChild, float xvel, float yvel) { 
  3.     super.onViewReleased(releasedChild, xvel, yvel); 
  4.     //手指抬起后緩慢移動(dòng)到指定位置 
  5.     if (mMainView.getLeft() < 500) { 
  6.         //關(guān)閉菜單 
  7.         //等同于Scroll的startScroll方法 
  8.         mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0); 
  9.         ViewCompat.postInvalidateOnAnimation(DragViewGroup.this); 
  10.     } else { 
  11.         //打開(kāi)菜單 
  12.         mViewDragHelper.smoothSlideViewTo(mMainView,300,0); 
  13.         ViewCompat.postInvalidateOnAnimation(DragViewGroup.this); 
  14.     } 
  15.  

設(shè)置讓MainView移動(dòng)后左邊距小于500像素的時(shí)候,就使用smoothSlideViewTo()方法來(lái)講MainView還原到初始狀態(tài),即坐標(biāo)(0,0),左邊距大于500則將MainView移動(dòng)到(300,0)坐標(biāo),即顯示MainView。 

  1. //ViewDragHelper 
  2.  
  3. mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0); 
  4.  
  5. ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);   
  1. //Scroller 
  2.  
  3. mScroller.startScroll(x,y,dx,dy); 
  4.  
  5. invalidate();  

滑動(dòng)的時(shí)候,在自定義ViewGroup的onFinishInflate()方法中,按照順序?qū)⒆覸iew分別定義成MenuView和MainView,并在onSizeChanged方法中獲得View的寬度。如果需要根據(jù)View的寬度來(lái)處理滑動(dòng)后的效果,就可以使用這個(gè)值判斷。

  1. /*** 
  2.  * 加載完布局文件后調(diào)用 
  3.  */ 
  4. @Override 
  5. protected void onFinishInflate() { 
  6.     super.onFinishInflate(); 
  7.     mMenuView = getChildAt(0); 
  8.     mMainView = getChildAt(1); 
  9.  
  10. @Override 
  11. protected void onSizeChanged(int w, int h, int oldw, int oldh) { 
  12.     super.onSizeChanged(w, h, oldw, oldh); 
  13.     mWidth = mMenuView.getMeasuredWidth(); 
  14.  

***,整個(gè)通過(guò)ViewDragHelper實(shí)現(xiàn)QQ側(cè)滑功能代碼:

  1. package com.xjf.drawview; 
  2.  
  3. import android.content.Context; 
  4. import android.support.v4.view.ViewCompat; 
  5. import android.support.v4.widget.ViewDragHelper; 
  6. import android.util.AttributeSet; 
  7. import android.view.MotionEvent; 
  8. import android.view.View
  9. import android.widget.FrameLayout; 
  10.  
  11. public class DragViewGroup extends FrameLayout { 
  12.  
  13.     private ViewDragHelper mViewDragHelper; 
  14.     private View mMenuView, mMainView; 
  15.     private int mWidth; 
  16.  
  17.     public DragViewGroup(Context context) { 
  18.         super(context); 
  19.         initView(); 
  20.     } 
  21.  
  22.     public DragViewGroup(Context context, AttributeSet attrs) { 
  23.         super(context, attrs); 
  24.         initView(); 
  25.     } 
  26.  
  27.     public DragViewGroup(Context context, 
  28.                          AttributeSet attrs, int defStyleAttr) { 
  29.         super(context, attrs, defStyleAttr); 
  30.         initView(); 
  31.     } 
  32.  
  33.     /*** 
  34.      * 加載完布局文件后調(diào)用 
  35.      */ 
  36.     @Override 
  37.     protected void onFinishInflate() { 
  38.         super.onFinishInflate(); 
  39.         mMenuView = getChildAt(0); 
  40.         mMainView = getChildAt(1); 
  41.     } 
  42.  
  43.     @Override 
  44.     protected void onSizeChanged(int w, int h, int oldw, int oldh) { 
  45.         super.onSizeChanged(w, h, oldw, oldh); 
  46.         mWidth = mMenuView.getMeasuredWidth(); 
  47.     } 
  48.  
  49.     @Override 
  50.     public boolean onInterceptTouchEvent(MotionEvent ev) { 
  51.         return mViewDragHelper.shouldInterceptTouchEvent(ev); 
  52.     } 
  53.  
  54.     @Override 
  55.     public boolean onTouchEvent(MotionEvent event) { 
  56.         //將觸摸事件傳遞給ViewDragHelper,此操作必不可少 
  57.         mViewDragHelper.processTouchEvent(event); 
  58.         return true
  59.     } 
  60.  
  61.     private void initView() { 
  62.         mViewDragHelper = ViewDragHelper.create(this, callback); 
  63.     } 
  64.  
  65.     private ViewDragHelper.Callback callback = 
  66.             new ViewDragHelper.Callback() { 
  67.  
  68.                 // 何時(shí)開(kāi)始檢測(cè)觸摸事件 
  69.                 @Override 
  70.                 public boolean tryCaptureView(View child, int pointerId) { 
  71.                     //如果當(dāng)前觸摸的child是mMainView時(shí)開(kāi)始檢測(cè) 
  72.                     return mMainView == child; 
  73.                 } 
  74.  
  75.                 // 觸摸到View后回調(diào) 
  76.                 @Override 
  77.                 public void onViewCaptured(View capturedChild, 
  78.                                            int activePointerId) { 
  79.                     super.onViewCaptured(capturedChild, activePointerId); 
  80.                 } 
  81.  
  82.                 // 當(dāng)拖拽狀態(tài)改變,比如idle,dragging 
  83.                 @Override 
  84.                 public void onViewDragStateChanged(int state) { 
  85.                     super.onViewDragStateChanged(state); 
  86.                 } 
  87.  
  88.                 // 當(dāng)位置改變的時(shí)候調(diào)用,常用與滑動(dòng)時(shí)更改scale等 
  89.                 @Override 
  90.                 public void onViewPositionChanged(View changedView, 
  91.                                                   int leftint topint dx, int dy) { 
  92.                     super.onViewPositionChanged(changedView, lefttop, dx, dy); 
  93.                 } 
  94.  
  95.                 // 處理垂直滑動(dòng) 
  96.                 @Override 
  97.                 public int clampViewPositionVertical(View child, int topint dy) { 
  98.                     return 0; 
  99.                 } 
  100.  
  101.                 // 處理水平滑動(dòng) 
  102.                 @Override 
  103.                 public int clampViewPositionHorizontal(View child, int leftint dx) { 
  104.                     return left
  105.                 } 
  106.  
  107.                 // 拖動(dòng)結(jié)束后調(diào)用 
  108.                 @Override 
  109.                 public void onViewReleased(View releasedChild, float xvel, float yvel) { 
  110.                     super.onViewReleased(releasedChild, xvel, yvel); 
  111.                     //手指抬起后緩慢移動(dòng)到指定位置 
  112.                     if (mMainView.getLeft() < 500) { 
  113.                         //關(guān)閉菜單 
  114.                         //相當(dāng)于Scroller的startScroll方法 
  115.                         mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0); 
  116.                         ViewCompat.postInvalidateOnAnimation(DragViewGroup.this); 
  117.                     } else { 
  118.                         //打開(kāi)菜單 
  119.                         mViewDragHelper.smoothSlideViewTo(mMainView, 300, 0); 
  120.                         ViewCompat.postInvalidateOnAnimation(DragViewGroup.this); 
  121.                     } 
  122.                 } 
  123.             }; 
  124.  
  125.     @Override 
  126.     public void computeScroll() { 
  127.         if (mViewDragHelper.continueSettling(true)) { 
  128.             ViewCompat.postInvalidateOnAnimation(this); 
  129.         } 
  130.     } 
  131.  

除此之外,ViewDragHelper很多強(qiáng)大的功能還沒(méi)得到展示,在ViewDragHelper.Callback中,系統(tǒng)定義了大量的監(jiān)聽(tīng)事件來(lái)幫助我們處理各種事件,如下:

  • onViewCaptured()這個(gè)事件在用戶觸摸到View后回調(diào)
  • onViewDragStateChanged()這個(gè)事件在拖拽狀態(tài)改變時(shí)回調(diào),比如idle,dragging等狀態(tài)

STATE_IDLE:View當(dāng)前沒(méi)有被拖拽也沒(méi)執(zhí)行動(dòng)畫(huà),只是安靜地待在原地

STATE_DRAGGING:View當(dāng)前正在被拖動(dòng),由于用戶輸入或模擬用戶輸入導(dǎo)致View位置正在改變

STATE_SETTLING:View當(dāng)前正被安頓到指定位置,由fling手勢(shì)或預(yù)定義的非交互動(dòng)作觸發(fā)

  • onViewPositionChanged()//view在拖動(dòng)過(guò)程坐標(biāo)發(fā)生變化時(shí)會(huì)調(diào)用此方法,包括兩個(gè)時(shí)間段:手動(dòng)拖動(dòng)和自動(dòng)滾動(dòng)。

三、開(kāi)源代碼庫(kù)

***再分享一個(gè)自己積攢很久的代碼庫(kù),只有你想不到,沒(méi)有用不到的,歡迎star

https://github.com/xijiufu

由于github服務(wù)器在美國(guó),有時(shí)訪問(wèn)很慢,還提供了開(kāi)源中國(guó)地址庫(kù),2個(gè)倉(cāng)庫(kù)代碼均同步更新:

http://git.oschina.net/xijiufu 

責(zé)任編輯:龐桂玉 來(lái)源: MrXI的博客
相關(guān)推薦

2019-12-01 22:34:42

提權(quán)web安全漏洞

2012-06-14 15:49:59

Slider

2009-06-12 11:32:49

Java游戲

2013-08-07 10:16:43

Android內(nèi)存泄漏

2010-07-27 15:39:32

telnet smtp

2023-03-17 08:00:34

OpenCVCvType錯(cuò)誤

2009-07-20 18:01:38

Oracle JDBC

2014-12-31 16:48:43

Touch touchevent多點(diǎn)觸摸

2017-05-11 21:30:01

Android動(dòng)態(tài)代理ServiceHook

2015-10-20 15:54:16

android源碼滑動(dòng)關(guān)閉

2010-02-03 10:17:29

C++繼承方式

2024-01-29 08:28:01

Spring事務(wù)失效

2009-03-24 08:35:57

AndroidGoogle移動(dòng)os

2009-05-30 09:24:24

AndroidGoogle移動(dòng)OS

2023-08-01 08:27:15

Java I/ONIO

2010-10-09 17:11:16

病毒分析

2010-01-28 14:57:36

Android滑動(dòng)手勢(shì)

2012-07-31 09:02:49

Apworks

2018-07-20 09:16:04

鏈?zhǔn)?/a>存儲(chǔ)結(jié)構(gòu)

2021-08-06 11:01:23

大數(shù)據(jù)數(shù)據(jù)分析技術(shù)
點(diǎn)贊
收藏

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