自定義View系列(8)--越界回彈ScrollView
難度
中等
效果說明
越界回彈的效果不用多說了吧,大家應該都知道, 不知道的看下方效果圖。
效果圖
特性說明
- 支援阻尼係數
- 支援多指觸控
- 支援上拉回彈、下拉回彈
- 支援設定開啟/關閉回彈:ENABLED_ALL、ENABLED_TOP、ENABLED_BOTTOM 、ENABLED_NONE
- 不影響原有手勢的分發處理
- 支援設定最大滑動距離
- 支援設定插值器
實現原理
整體採用offsetTopAndBottom()
+ValueAnimator
實現。
事件分發處理
重寫dispatchTouchEvent(MotionEvent ev)
方法,在ACTION_DOWN
滑動處理
在ACTION_MOVE
中,計算每次滑動的差值diffY
,然後使用offsetTopAndBottom()
進行滑動
手指擡起處理
在ACTION_UP
中,獲取已滑動的距離scrollY
,然後使用ValueAnimator
計算每一幀滑動的距離,最後再次使用offsetTopAndBottom()
進行滑動
多點觸控
多點觸控其實很簡單,都是有套路可尋的,只要單點觸控沒問題,多點觸控其實很好實現,因為雖然是多點觸控,但是實際上只有一個手指處於活躍狀態。
關於dispatchTouchEvent(MotionEvent ev)
方法
在Android的整個事件傳遞體系中,很多人都知道dispatchTouchEvent(MotionEvent ev)
方法是用來分發事件的,分發後的事件如果由自身處理,則需要重寫
onTouchEvent(MotionEvent ev)
進行相關操作,但有時候這種方式很麻煩,特別是在我們繼承已有的Layout時,比如ScrollView
、FrameLayout
等,
因為這些Layout本身就有一些事件的處理機制,如何在不破壞已有的處理機制的基礎上再加上我們自己的處理邏輯,這是一個較為困難的問題。
dispatchTouchEvent(MotionEvent ev)
方法要先於onInterceptTouchEvent(MotionEvent ev)
dispatchTouchEvent(MotionEvent ev)
方法中處理一些攔截邏輯,這比在onInterceptTouchEvent(MotionEvent ev)
方法中處理攔截邏輯有時候會更好。因為在dispatchTouchEvent(MotionEvent ev)
方法中我們可以同時處理事件的攔截以及view的滑動等操作。當然要使用哪一個方法還要視具體情況而定。
在繼承ScrollView時,我們既要保證原有的ScrollView的邏輯不變,還要在此基礎上新增我們自己的邏輯,使用dispatchTouchEvent(MotionEvent ev)
是一個比較好的選擇。
核心程式碼
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int action = ev.getAction() & MotionEvent.ACTION_MASK;
switch (action) {
case MotionEvent.ACTION_DOWN:
if (mAnimator.isStarted()) mAnimator.cancel();
mActivePointerId = ev.getPointerId(0);
mLastY = (int) ev.getY();
canPullDown = isCanPullDown();
canPullUp = isCanPullUp();
break;
case MotionEvent.ACTION_MOVE:
final int y = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
int diffY = y - mLastY;
if ((canPullUp || canPullDown)) {
ViewParent parent = getParent();
if (parent != null) parent.requestDisallowInterceptTouchEvent(true);
move(diffY);
}
mLastY = y;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (canPullDown || canPullUp) {
final int scrollY = mChild.getTop();
mLastFrameValue = scrollY;
mAnimator.setIntValues(scrollY, 0);
mAnimator.start();
}
mActivePointerId = MotionEvent.INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_POINTER_DOWN:
final int downActionIndex = ev.getActionIndex();
mLastY = (int) ev.getY(downActionIndex);
mActivePointerId = ev.getPointerId(downActionIndex);
break;
case MotionEvent.ACTION_POINTER_UP:
final int upActionIndex = ev.getActionIndex();
final int pointerId = ev.getPointerId(upActionIndex);
if (pointerId == mActivePointerId) {
final int newPointerIndex = upActionIndex == 0 ? 1 : 0;
mActivePointerId = ev.getPointerId(newPointerIndex);
}
mLastY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
break;
}
super.dispatchTouchEvent(ev);//分發父view的事件
return true;
}
以上是實現整個效果的核心程式碼,更多詳細處理邏輯請移步至PopularEffect。
PopularEffect是一個彙集各種效果的倉庫,除此之外,還對每種效果的實現方式都做了剖析,並編寫了對應的樣例。雖然倉庫已經建立了快一年,但實際上並沒有精力去維護,現在打算把它維護起來,我只要有時間,都會盡量新增一些效果分析和樣例,雖然只有我一個人維護這個倉庫,註定速度上會很慢(分析效果以及編寫樣例是最為費時費力的),但我相信水滴石穿! 感興趣的可以stat一波(手動滑稽)。