1. 程式人生 > >自定義View系列(8)--越界回彈ScrollView

自定義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時,比如ScrollViewFrameLayout等,
因為這些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一波(手動滑稽)。