1. 程式人生 > >從原始碼角度分析ViewDragHelper

從原始碼角度分析ViewDragHelper

最近群裡的小夥伴都在說ViewDragHelper這玩意,我就感覺好像很牛逼的樣子。然後稍微看了下,不是很難,在此先做個筆記。因為之前他們說scroller的時候,我都不知道是啥。然後今天發現我去年寫的demo中還用到了。原諒我豬一般的記性!!
先來個測試demo的效果圖。
這裡寫圖片描述
下面直接上程式碼:


/**
 * Created by Angel on 2016/11/26.
 */
public class ViewDragHelperLayout extends LinearLayout {

    private ViewDragHelper helper;

    public ViewDragHelperLayout
(Context context) { super(context); inital(); } public ViewDragHelperLayout(Context context, AttributeSet attrs) { super(context, attrs); inital(); } private void inital() { helper = ViewDragHelper.create(this, 1.0f, new ViewDragCallback()); } private
class ViewDragCallback extends ViewDragHelper.Callback { public boolean tryCaptureView(View view, int id) { return true; } public void onViewDragStateChanged(int state) { super.onViewDragStateChanged(state); } public void onViewPositionChanged
(View changedView, int left, int top, int dx, int dy) { super.onViewPositionChanged(changedView, left, top, dx, dy); } public void onViewCaptured(View capturedChild, int activePointerId) { super.onViewCaptured(capturedChild, activePointerId); } public int clampViewPositionHorizontal(View child, int left, int dx) { //讓我們的檢視不越界 int paddingleft = getPaddingLeft(); int view_width = child.getWidth(); int view_left = getWidth() - view_width - paddingleft; int new_left = Math.min(Math.max(paddingleft, left), view_left); return new_left; } public int clampViewPositionVertical(View child, int top, int dy) { return 0; } } public boolean onInterceptTouchEvent(MotionEvent event) { return helper.shouldInterceptTouchEvent(event); } public boolean onTouchEvent(MotionEvent event) { helper.processTouchEvent(event); return true; } }

好了,迴歸整體,從原始碼角度開始分析,接下來要放大招了~~~
ViewDragHelper並不強大,強大的是他有一個回撥函式
我們點開ViewDragHelper的原始碼,找到他的回撥:

 public abstract static class Callback {
        public Callback() {
        }

        public void onViewDragStateChanged(int state) {
        }

        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
        }

        public void onViewCaptured(View capturedChild, int activePointerId) {
        }

        public void onViewReleased(View releasedChild, float xvel, float yvel) {
        }

        public void onEdgeTouched(int edgeFlags, int pointerId) {
        }

        public boolean onEdgeLock(int edgeFlags) {
            return false;
        }

        public void onEdgeDragStarted(int edgeFlags, int pointerId) {
        }

        public int getOrderedChildIndex(int index) {
            return index;
        }

        public int getViewHorizontalDragRange(View child) {
            return 0;
        }

        public int getViewVerticalDragRange(View child) {
            return 0;
        }

        public abstract boolean tryCaptureView(View var1, int var2);

        public int clampViewPositionHorizontal(View child, int left, int dx) {
            return 0;
        }

        public int clampViewPositionVertical(View child, int top, int dy) {
            return 0;
        }
    }

接下來,我們分析一下他們分別是什麼時候才會呼叫的。

onViewDragStateChanged

當ViewDragHelper狀態變更時回撥該方法

onViewPositionChanged

當捕獲view由於拖曳或者設定而發生位置變更時回撥

onViewCaptured

當子view被由於拖曳而被捕獲時回撥的方法.

onViewReleased

手指釋放的時候回撥

onEdgeTouched

當觸控到邊界時回撥。

onEdgeLock

true的時候會鎖住當前的邊界,false則unLock。

onEdgeDragStarted

在邊界拖動時回撥

getOrderedChildIndex

改變同一個座標(x,y)去尋找captureView位置的方法。

getViewHorizontalDragRange
getViewVerticalDragRange

這兩個的返回值大於0時才可捕獲

tryCaptureView

是否捕捉該view的滾動,id代表捕捉某一個view的滾動

clampViewPositionHorizontal

左右滑動時呼叫

clampViewPositionVertical

上下滑動時呼叫

到這邊,應該所有的方法都介紹完了。好了,現在我們來聊聊,ViewDragHelper的方法。
建立一個ViewDragHelper物件

 helper.create(forParent, cb);
 helper.create(forParent, sensitivity, cb);

至於有小夥伴對sensitivity這個屬性有疑問,那麼我們點開原始碼來了解下他是幹嘛的。

public static ViewDragHelper create(ViewGroup forParent, float sensitivity, ViewDragHelper.Callback cb) {
        ViewDragHelper helper = create(forParent, cb);
        helper.mTouchSlop = (int)((float)helper.mTouchSlop * (1.0F / sensitivity));
        return helper;
    }

我們傳入的值越大,他的值越小。但mTochSlop是怎麼計算的呢,我們繼續看原始碼:

 private ViewDragHelper(Context context, ViewGroup forParent, ViewDragHelper.Callback cb) {
        if(forParent == null) {
            throw new IllegalArgumentException("Parent view may not be null");
        } else if(cb == null) {
            throw new IllegalArgumentException("Callback may not be null");
        } else {
            this.mParentView = forParent;
            this.mCallback = cb;
            ViewConfiguration vc = ViewConfiguration.get(context);
            float density = context.getResources().getDisplayMetrics().density;
            this.mEdgeSize = (int)(20.0F * density + 0.5F);
            this.mTouchSlop = vc.getScaledTouchSlop();
            this.mMaxVelocity = (float)vc.getScaledMaximumFlingVelocity();
            this.mMinVelocity = (float)vc.getScaledMinimumFlingVelocity();
            this.mScroller = ScrollerCompat.create(context, sInterpolator);
        }
    }

我們定位到了這個:vc.getScaledTouchSlop();這個屬性是用來幹嘛的呢。我查了下。解釋如下:
getScaledTouchSlop是一個距離,表示滑動的時候,手的移動要大於這個距離才開始移動控制元件。如果小於這個距離就不觸發移動控制元件。
現在不要我多說了把。就是你一次最少滑動的距離要大於這個距離,否則,檢視是不會移動了。
好了,現在看我們之前的demo還用到了什麼。

helper.shouldInterceptTouchEvent(event);

繼續看原始碼:

public boolean shouldInterceptTouchEvent(MotionEvent ev) {      
    ...
    ...
    ...
  return mDragState == 1;
}

等於1是什麼鬼 ?找方法啊。經過翻山越嶺我們終於找到了。

public void captureChildView(View childView, int activePointerId) {
        if(childView.getParent() != this.mParentView) {
            throw new IllegalArgumentException("captureChildView: parameter must be a descendant of the ViewDragHelper\'s tracked parent view (" + this.mParentView + ")");
        } else {
            this.mCapturedView = childView;
            this.mActivePointerId = activePointerId;
            this.mCallback.onViewCaptured(childView, activePointerId);
            this.setDragState(1);
        }
    }

哎呀呀,這是什麼吊東西。說白了。就是它的父view是同一個的時候執行。否則直接拋異常咯。
接下來就是:

 helper.processTouchEvent(event);

經過我幾般周折。終於瞭解了一丟丟。這是加工從父view中獲取的觸控事件。這個方法將分發callback回撥事件。父view的觸控事件實現中應該呼叫該方法。說白了,這是處理ontouch事件的~~~
好了。稍微介紹下ViewDragHelper的原理和幾個重點:
1.ViewDragHelper用於監聽整個拖拽事件的開始到結束,過程分三步:休息,開始拖動,結束拖動。
2.ViewDragHelper的拖拽是通過Scoller實現的。
3.ViewDragHelper是有儲存歷史記錄的。例如我從(0,0)滾動到(100,0),那麼下次滾動肯定是從後者繼續滾動,而不是前者了。
4。 有歷史記錄,當然也有清空記錄的功能,ViewDragHelper提供了cancel()方法,類似onTouch的ACTION_UP事件。當拖曳結束,可能系統還認為過程還在,因此就需要提供的cancel()或abort()方法去終止這個過程,同時也自動呼叫clearMotionHistory()方法,置空歷史記錄。確保下次觸控拖曳事件是”新的開始”。
5.ViewDragHelper的拖拽事件是根據父view的最頂層的子view才會響應事件。所以該類提供了public View findTopChildUnder(int x, int y)方法捕獲父view中最頂層的子view物件。
好了。應該沒什麼要說了的把?如果還有什麼疑問,可以提出來,一起討論。