Android NestedScrolling機(jī)制

一攻冷、概述
這樣一個效果圖投慈,我們思考下如何實現(xiàn)

nestedscrolling.gif

可以看到“Sticky View”滾動到頂部會“固定住”,列表下拉到第一條數(shù)據(jù)“Sticky View”又會一起往下滾動接箫。

有人說,這個不就是View的事件分發(fā)嗎朵诫?

假設(shè)我們按照傳統(tǒng)的事件分發(fā)去理解辛友,我們滑動的是下面的內(nèi)容區(qū)域View,但是滾動的卻是外部的ViewGroup剪返,那么肯定是ViewGroup攔截了子View的事件废累;但是,上面的效果圖脱盲,當(dāng)ViewGroup滑動到一定程度邑滨,子View又開始滑動了,而且中間的過程是沒有間斷的钱反。從正常的事件分發(fā)機(jī)制來講這個是不可能的掖看,因為當(dāng)ViewGroup攔截事件后,是沒辦法再次交還給子View去處理的(除非你手動干預(yù)了事件的分發(fā))面哥,關(guān)于這一點如果有不清楚的同學(xué)哎壳,可以先去了解下Android的事件分發(fā)機(jī)制。

那么有沒有其他方案去解決我們的問題呢幢竹?答案是耳峦,有。

Android在support.v4包中為我們引入兩個重要的接口:

  • NestedScrollingParent
  • NestedScrollingChild

有了上面這兩個類焕毫,我們就可以實現(xiàn)“NestedScrolling(嵌套滾動)”的無縫銜接蹲坷。

二驶乾、實現(xiàn)
上述效果圖,分為三個部分:頂部布局(ImageView)循签,中間的“Sticky View”(TextView)和底部的列表(RecyclerView)级乐。

RecyclerView已經(jīng)實現(xiàn)了NestedScrollingChild接口,所以本文的重點是實現(xiàn)NestedScrollingParent接口县匠。

(1) 布局

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.example.hiphonezhu.nestedscrolling.StickyLayout
        android:id="@+id/stickyNavLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <ImageView
            android:id="@+id/iv"
            android:layout_width="match_parent"
            android:layout_height="100dp"
           android:scaleType="centerCrop"
            android:src="@drawable/bg" />
        <TextView
            android:id="@+id/tv_sticky"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/holo_green_dark"
            android:gravity="center"
            android:paddingBottom="10dp"
            android:paddingTop="10dp"
            android:text="Sticky View"
            android:textColor="@android:color/white" />
        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </com.example.hiphonezhu.nestedscrolling.StickyLayout>
</FrameLayout>

StickyLayout是直接繼承自LinearLayout风科,并且實現(xiàn)了NestedScrollingParent接口。

(2) 實現(xiàn)NestedScrollingParent接口
在具體實現(xiàn)之前乞旦,我們先看下這個接口的幾個方法贼穆。

public interface NestedScrollingParent {
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);
    public void onStopNestedScroll(View target);
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,        int dxUnconsumed, int dyUnconsumed);
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
    public boolean onNestedPreFling(View target, float velocityX, float velocityY);
    public int getNestedScrollAxes();
}

我們需要重點關(guān)注下面幾個方法

  • onStartNestedScroll該方法返回true,代表當(dāng)前ViewGroup能接受內(nèi)部View的滑動參數(shù)(這個內(nèi)部View不一定是直接子View)兰粉,一般情況下建議直接返回true故痊,當(dāng)然你可以根據(jù)nestedScrollAxes:判斷垂直或水平方向才返回true。

  • onNestedPreScroll該方法會傳入內(nèi)部View移動的dx與dy玖姑,當(dāng)前ViewGroup可以消耗掉一定的dx與dy愕秫,然后通過最后一個參數(shù)consumed傳回給子View。例如焰络,當(dāng)前ViewGroup消耗掉一半dx與dy

    scrollBy(dx/2, dy/2);
    consumed[0] = dx/2;
    consumed[1] = dy/2;
    
  • onNestedPreFling你可以捕獲對內(nèi)部View的fling事件戴甩,返回true表示攔截掉內(nèi)部View的事件

我們看下具體的代碼實現(xiàn)(僅是關(guān)鍵代碼):

public class StickyLayout extends LinearLayout implements NestedScrollingParent {
    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes)
    {
        return true;
    }
    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed)
    {
        // dy > 0表示子View向上滑動;

        // 子View向上滑動且父View的偏移量<ImageView高度
        boolean hiddenTop = dy > 0 && getScrollY() < maxScrollY;

        // 子View向下滑動(說明此時父View已經(jīng)往上偏移了)且父View還在屏幕外面, 另外內(nèi)部View不能在垂直方向往下移動了
        /**
         * ViewCompat.canScrollVertically(view, int)
         * 負(fù)數(shù): 頂部是否可以滾動(官方描述: 能否往上滾動, 不太準(zhǔn)確吧~)
         * 正數(shù): 底部是否可以滾動
         */
        boolean showTop = dy < 0 && getScrollY() > 0 && !ViewCompat.canScrollVertically(target, -1);

        if (hiddenTop || showTop)
        {
            scrollBy(0, dy);
            consumed[1] = dy;
        }
    }
    @Override
    public boolean onNestedPreFling(View target, float velocityX, float velocityY)
    {
        if (velocityY > 0 && getScrollY() < maxScrollY) // 向上滑動, 且當(dāng)前View還沒滑到頂
        {
            fling((int) velocityY, maxScrollY);
            return true;
        }
        else if (velocityY < 0 && getScrollY() > 0) // 向下滑動, 且當(dāng)前View部分在屏幕外
        {
            fling((int) velocityY, 0);
            return true;
        }
        return false;
    }
}    
  • onNestedPreScroll中,判斷子View上滑(dy>0)并且StickyLayout滾動到屏幕外的距離(getScrollY())< 最大滾動距離maxScrollY闪彼,則隱藏頂部布局(ImageView)甜孤;同理,如果子View下滑(dy < 0)且StickyLayout還在屏幕外面(getScrollY() > 0)备蚓,同時內(nèi)部View不能在垂直方向往下移動了(可以借助ViewCompat.canScrollVertically來實現(xiàn))课蔬。

ViewCompat.canScrollVertically(view, int) ,第二個int類型參數(shù)
負(fù)數(shù): 頂部是否可以往下滾動
正數(shù): 底部是否可以往上滾動

官方描述:“Negative to check scrolling up, positive to check scrolling down”郊尝,我覺得有誤人子弟的嫌疑二跋。

  • onNestedPreFling中,如果向上滑動(velocityY > 0)且ImageView沒有完全隱藏(getScrollY() < maxScrollY)流昏,則使用fling方法扎即,“嘗試”(因為滑動距離取決于初始速度)將ImageView完全隱藏;同理况凉,如果向下滑動(velocityY < 0)且ImageView部分在屏幕外(getScrollY() > 0)谚鄙,則使用fling方法,“嘗試”(因為滑動距離取決于初始速度)將ImageView完全顯示刁绒。

對于fling方法闷营,我們使用OverScroller的fling方法,另外邊界檢測,重寫了scrollTo方法:

public void fling(int velocityY, int maxY)
{    
    mScroller.fling(0, getScrollY(), 0, velocityY, 0, 0, 0, maxY);          
    invalidate();
}

@Override
public void scrollTo(int x, int y)
{
    if (y < 0) // 不允許向下滑動
    {
        y = 0;
    }
    if (y > maxScrollY) // 防止向上滑動距離大于最大滑動距離
    {
        y = maxScrollY;
    }
    if (y != getScrollY())
    {
        super.scrollTo(x, y);
    }
}
@Override
public void computeScroll()
{
    if (mScroller.computeScrollOffset())
    {
        scrollTo(0, mScroller.getCurrY());
        invalidate();
    }      
}

到這里,大家發(fā)現(xiàn)其實NestedScrolling機(jī)制其實并不復(fù)雜:

在滑動的時候,內(nèi)部View會把滑動的距離(dx與dy)傳入給NestedScrollingParent俩功,NestedScrollingParent可以決定對其是否消耗,消耗的值通過consumed[]再傳回給子View规哲。

三、寫在最后
由于本文的效果ImageView和Sticky View(TextView)與“狀態(tài)欄”有融合的效果诽表,所以具體源碼會比這個略微復(fù)雜些~

主要思路是:

布局中有一個一模一樣的Sticky View(TextView)唉锌,通過隱藏和顯示它來達(dá)到最終的效果,如果你有更好的想法可以聯(lián)系我竿奏。

具體請參考源碼:NestedScrolling

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末袄简,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子议双,更是在濱河造成了極大的恐慌痘番,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件平痰,死亡現(xiàn)場離奇詭異,居然都是意外死亡伍纫,警方通過查閱死者的電腦和手機(jī)宗雇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來莹规,“玉大人赔蒲,你說我怎么就攤上這事×际” “怎么了舞虱?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長母市。 經(jīng)常有香客問我矾兜,道長,這世上最難降的妖魔是什么患久? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任椅寺,我火速辦了婚禮,結(jié)果婚禮上蒋失,老公的妹妹穿的比我還像新娘返帕。我一直安慰自己,他們只是感情好篙挽,可當(dāng)我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布荆萤。 她就那樣靜靜地躺著,像睡著了一般铣卡。 火紅的嫁衣襯著肌膚如雪链韭。 梳的紋絲不亂的頭發(fā)上邑闲,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天,我揣著相機(jī)與錄音梧油,去河邊找鬼苫耸。 笑死,一個胖子當(dāng)著我的面吹牛儡陨,可吹牛的內(nèi)容都是我干的褪子。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼骗村,長吁一口氣:“原來是場噩夢啊……” “哼嫌褪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起胚股,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤笼痛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后琅拌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缨伊,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年进宝,在試婚紗的時候發(fā)現(xiàn)自己被綠了刻坊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡党晋,死狀恐怖谭胚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情未玻,我是刑警寧澤灾而,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站扳剿,受9級特大地震影響旁趟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜舞终,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一轻庆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧敛劝,春花似錦余爆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春桩砰,著一層夾襖步出監(jiān)牢的瞬間拓春,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工亚隅, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留硼莽,地道東北人。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓煮纵,卻偏偏與公主長得像懂鸵,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子行疏,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 仿點單模式匆光,兩個listview 作者簡介 原創(chuàng)微信公眾號郭霖 WeChat ID: guolin_blog 本篇...
    木木00閱讀 531評論 0 7
  • 概述 NestedScrolling的機(jī)制其實是建立在事件分發(fā)的基礎(chǔ)上的,所以要理解NestedScrolling...
    st0rm23閱讀 711評論 0 2
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,144評論 25 707
  • 簡介: 提供一個讓有限的窗口變成一個大數(shù)據(jù)集的靈活視圖酿联。 術(shù)語表: Adapter:RecyclerView的子類...
    酷泡泡閱讀 5,165評論 0 16
  • 來到大學(xué)應(yīng)該做什么呢终息?這四年該如何度過呢? 金秋九月贞让,剛?cè)雽W(xué)的我與大多數(shù)同學(xué)一樣周崭,懷揣著夢想,充...
    SinclairY閱讀 182評論 0 0