從原始碼角度分析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物件。
好了。應該沒什麼要說了的把?如果還有什麼疑問,可以提出來,一起討論。