淺析:Android 嵌套滑動機制(NestedScrolling)

來源:互聯網
上載者:User

標籤:

Google在發布安卓 Lollipop版本之後,為了更好的使用者體驗,Google為Android的滑動機制提供了NestedScrolling特性

NestedScrolling的特性可以體現在哪裡呢?

比如你使用了Toolbar,下面一個ScrollView,向上滾動隱藏Toolbar,向下滾動顯示Toolbar,這裡在邏輯上就是一個NestedScrolling —— 因為你在滾動整個Toolbar在內的View的過程中,又嵌套滾動了裡面的ScrollView。

在這之前,我們知道Android對Touch事件的分發是有自己一套機制的。主要是有是三個函數:

dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。

 

這種分發機制讓行動裝置 App安全檢測平台-愛內測(ineice.com)發現有一個漏洞,據愛內測的CTO介紹:

如果子view獲得處理touch事件機會的時候,父view就再也沒有機會去處理這個touch事件了,直到下一次手指再按下。

 

也就是說,我們在滑動子View的時候,如果子View對這個滑動事件不想要處理的時候,只能拋棄這個touch事件,而不會把這些傳給父view去處理。

 

但是Google新的NestedScrolling機制就很好的解決了這個問題。

我們看看如何?這個NestedScrolling,首先有幾個類(介面)我們需要關注一下

NestedScrollingChild

NestedScrollingParent

NestedScrollingChildHelper

NestedScrollingParentHelper

以上四個類都在support-v4包中提供,Lollipop的View預設實現了幾種方法。

 

實現介面很簡單,這邊我暫時用到了NestedScrollingChild系列的方法(因為Parent是support-design提供的CoordinatorLayout)

    @Override

    publicvoid setNestedScrollingEnabled(boolean enabled) {

        super.setNestedScrollingEnabled(enabled);

        mChildHelper.setNestedScrollingEnabled(enabled);

    }

 

    @Override

    publicboolean isNestedScrollingEnabled() {

        returnmChildHelper.isNestedScrollingEnabled();

    }

    @Override

    publicboolean startNestedScroll(int axes) {

        returnmChildHelper.startNestedScroll(axes);

    }

    @Override

    publicvoid stopNestedScroll() {

        mChildHelper.stopNestedScroll();

    }

    @Override

    publicboolean hasNestedScrollingParent() {

        returnmChildHelper.hasNestedScrollingParent();

    }

    @Override

    publicboolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,int dyUnconsumed, int[] offsetInWindow) {

        return mChildHelper.dispatchNestedScroll(dxConsumed,dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);

    }

    @Override

    publicboolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[]offsetInWindow) {

        returnmChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);

    }

    @Override

    publicboolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed){

        returnmChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);

    }

    @Override

    publicboolean dispatchNestedPreFling(float velocityX, float velocityY) {

        returnmChildHelper.dispatchNestedPreFling(velocityX, velocityY);

    }

對,簡單的話你就這麼實現就好了。

 

這些介面都是我們在需要的時候自己調用的。childHelper幹了些什麼事呢?,看一下startNestedScroll方法

    /**

     *Start a new nested scroll for this view.

     *

     *<p>This is a delegate method. Call it from your {@link android.view.ViewView} subclass

     *method/{@link NestedScrollingChild} interface method with the same signature toimplement

     *the standard policy.</p>

     *

     *@param axes Supported nested scroll axes.

     *             See {@linkNestedScrollingChild#startNestedScroll(int)}.

     *@return true if a cooperating parent view was found and nested scrollingstarted successfully

     */

    publicboolean startNestedScroll(int axes) {

        if (hasNestedScrollingParent()) {

            // Already in progress

            return true;

        }

        if (isNestedScrollingEnabled()) {

            ViewParent p = mView.getParent();

            View child = mView;

            while (p != null) {

                if(ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {

                    mNestedScrollingParent = p;

                    ViewParentCompat.onNestedScrollAccepted(p,child, mView, axes);

                    return true;

                }

                if (p instanceof View) {

                    child = (View) p;

                }

                p = p.getParent();

            }

        }

        return false;

    }

可以看到這裡是幫你實現一些跟NestedScrollingParent互動的一些方法。

 

ViewParentCompat是一個和父view互動的相容類,它會判斷api version,如果在Lollipop以上,就是用view內建的方法,否則判斷是否實現了NestedScrollingParent介面,去調用介面的方法。

 

那麼具體我們怎麼使用這一套機制呢?比如子View這時候我需要通知父view告訴它我有一個嵌套的touch事件需要我們共同處理。那麼針對一個只包含scroll互動,它整個工作流程是這樣的:

 

一、startNestedScroll

首先子view需要開啟整個流程(內部主要是找到合適的能接受nestedScroll的parent),通知父View,我要和你配合處理TouchEvent

 

二、dispatchNestedPreScroll

在子View的onInterceptTouchEvent或者onTouch中(一般在MontionEvent.ACTION_MOVE事件裡),調用該方法通知父View滑動的距離。該方法的第三第四個參數返回父view消費掉的scroll長度和子View的表單位移量。如果這個scroll沒有被消費完,則子view進行處理剩下的一些距離,由於表單進行了移動,如果你記錄了手指最後的位置,需要根據第四個參數offsetInWindow計算位移量,才能保證下一次的touch事件的計算是正確的。

如果父view接受了它的滾動參數,進行了部分消費,則這個函數返回true,否則為false。

這個函數一般在子view處理scroll前調用。

 

三、dispatchNestedScroll

向父view彙報滾動情況,包括子view消費的部分和子view沒有消費的部分。

如果父view接受了它的滾動參數,進行了部分消費,則這個函數返回true,否則為false。

這個函數一般在子view處理scroll後調用。

四、stopNestedScroll

結束整個流程。

整個對應流程是這樣

子view   父view

startNestedScroll onStartNestedScroll、onNestedScrollAccepted

dispatchNestedPreScroll    onNestedPreScroll

dispatchNestedScroll  onNestedScroll

stopNestedScroll onStopNestedScroll

一般是子view發起調用,父view接受回調。

 

我們最需要關注的是dispatchNestedPreScroll中的consumed參數。

    publicboolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[]offsetInWindow) ;

它是一個int型的數組,長度為2,第一個元素是父view消費的x方向的滾動距離;第二個元素是父view消費的y方向的滾動距離,如果這兩個值不為0,則子view需要對滾動的量進行一些修正。正因為有了這個參數,使得我們處理滾動事件的時候,思路更加清晰,不會像以前一樣被一堆的滾動參數搞混。

淺析:Android 嵌套滑動機制(NestedScrolling)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.