標籤:
oschina用戶端滑動菜單的View的布局使用了可以拖拽的ScrollView,類檔案為CustomerScrollView。
1 我們需要分析下為什麼要用ScrollView?用過的其實很容易理解避免其內部的子View的布局較大,在較小裝置上無法完全顯示。
2實現可拖拽的效果,只是從使用者體驗角度去考慮的,接下來我們詳細分析下其自訂的ScrollView。
2.1拖拽的目標是ScrollView內的菜單的布局View,所以在CustomerScrollView內的onFinishInflate()函數中首先通過getChildAt(0)來擷取菜單布局的View,這就是第一步的目標是擷取要拖拽的對象。onFinishInflate()載入完view,這裡的View指的就是源碼中<include layout="@layout/fragment_navigation_drawer_items"/>載入的布局檔案。
2.2拖拽的過程實際上是一個“按下-移動-抬起”的過程,因此要重寫onTouchEvent(MotionEvent ev),其中移動過程實際上是將菜單view按照移動的方向和距離,怎麼實現這個功能呢?源碼中最關鍵的就是這行代碼
inner.layout(inner.getLeft(), yy, inner.getRight(),inner.getBottom() - deltaY);這個方法四個參數都是inner相對其父控制項ScrollView的座標原點而言的。不是很瞭解的,可以專門查查座標的相關知識。
2.3當手指抬起也就是MotionEvent.ACTION_UP事件發生時,將拖拽後的view恢複移動到原來位置,移動過程附加了一個動畫,由於移動實際上是位置發生了變化,因此用到了TranslateAnimation,因為是上下拖拽,所以X的起始和終止座標都是0,Y的起始和終止座標至於為什麼那麼寫,相信看完部落格應該就會明白了。那麼問題來了,要自動移動回去,那麼觸發的時機在MotionEvent.ACTION_UP中,原來的位置怎麼儲存,因為移動時需要左上右下四個參數,因此在CustomerScrollView中我們看到了這樣一個變數private Rect normal = new Rect();通過
normal.set(inner.getLeft(), inner.getTop(),inner.getRight(), inner.getBottom());方法記錄菜單view的初始化位置。
2.4在源碼中這個函數也值得我們去分析下:
//是否需要移動
public boolean isNeedMove() {
int offset = inner.getMeasuredHeight() - getHeight();
int scrollY = getScrollY();
if (scrollY == 0 || scrollY == offset) {
return true;
}
return false;
}
經過仔細揣摩發現scrollY == 0這個條件實際上是滾動到了最頂部的時候,而scrollY == offset是滾動到最底部的時候,兩個條件滿足其中一個都可以實現拖拽的效果。int offset = inner.getMeasuredHeight() - getHeight();相當於本身的身高減去實際能看到的身高就等於沒有看到的身高部分。
2.5為什麼源碼中需要isNeedAnimation()這個函數呢?因為恢複到原來位置也用到了inner.layout(normal.left, normal.top, normal.right, normal.bottom);,因此normal首先必須要有四個參數值。而這個normal只有滿足2.4中的條件後才有值的。
2.6為什麼在拖拽發生又恢複到原來位置後,要把這個normal.setEmpty();置空呢?它的意圖是什嗎?仔細想來,發現這個normal的set左上右下四個值時,是在滿足2.4兩種條件之一就會有具體值的。因此這個normal就會有兩種不同的Rect.頂部的時候左上右下四個值分別為(0,0,實際菜單的寬度240dp,菜單的實際測量高度)而滾動到最底部的時候左上右下四個值分別為(0,負的【菜單的實際高度減去螢幕的高度】,實際菜單的寬度240dp,螢幕的高度),因此需要清空。
開源中國 OsChina Android 用戶端源碼分析(3)可以拖拽的ScrollView