使用NestedScrollView+ViewPager+RecyclerView+SmartRefreshLayout打造酷炫下拉視差效果并解決各種滑動(dòng)沖突

如果你還在為處理滑動(dòng)沖突而發(fā)愁印机,那么你需要靜下心來(lái)看看這邊文章矢腻,如果你能徹底理解這篇文章中使用的技術(shù),那么射赛,一切滑動(dòng)沖突的問(wèn)題解決起來(lái)就輕而易舉了:

先扔一個(gè)最終實(shí)現(xiàn)的效果圖

GIF.gif

先分析下效果圖中實(shí)現(xiàn)的功能點(diǎn)

  • 頂部下拉時(shí)背景圖形成視差效果
  • 上拉時(shí)標(biāo)題欄透明切換顯示
  • 底部實(shí)現(xiàn)TabLayout+ViewPager+Fragment+RecyclerView

復(fù)雜在哪里多柑?整個(gè)布局中使用了SmartRefreshLayout,NestedScrollView,ViewPager,RecyclerView,每一個(gè)都有滑動(dòng)事件,我們平時(shí)只是使用ScrollView+RecyclerView都會(huì)有滑動(dòng)沖突咒劲,更何況這里又四個(gè)會(huì)引起沖突的控件顷蟆!

接下來(lái),我們一步一步實(shí)現(xiàn)這個(gè)效果

1腐魂、布局設(shè)計(jì)分析

-FrameLayout(最外層)
    -ImageView(頭部背景圖)
        -SmartRefreshLayout(頭部刷新控件)
            -JudgeNestedScrollView(自定義的NestedScrollView)
                ...省略中間巴拉巴拉布局
                -Tablayout
                    -ViewPager

2帐偎、功能點(diǎn)實(shí)現(xiàn)說(shuō)明

2.1、下拉時(shí)視差效果的實(shí)現(xiàn)

最外層為FrameLayout,ImageView高度設(shè)置超過(guò)屏幕頂部蛔屹,借助SmartRefreshLayout控件在下拉和松開(kāi)時(shí)做平移處理削樊,背景圖片做了高斯模糊處理

布局代碼:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <ImageView
        android:id="@+id/iv_header"
        android:layout_width="match_parent"
        android:layout_height="670dp"
        android:layout_marginTop="-300dp"
        android:adjustViewBounds="true"
        android:contentDescription="@string/app_name"
        android:scaleType="centerCrop"
        android:src="@drawable/image_home"
        app:layout_collapseMode="parallax" />

    <com.scwang.smartrefresh.layout.SmartRefreshLayout
        android:id="@+id/refreshLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:srlEnablePreviewInEditMode="false">
        ...

下拉刷新時(shí)頭部背景圖片平移代碼:

refreshLayout.setOnMultiPurposeListener(new SimpleMultiPurposeListener() {
            @Override
            public void onHeaderPulling(RefreshHeader header, float percent, int offset, int bottomHeight, int extendHeight) {
                mOffset = offset / 2;
                ivHeader.setTranslationY(mOffset - mScrollY);
            }

            @Override
            public void onHeaderReleasing(RefreshHeader header, float percent, int offset, int bottomHeight, int extendHeight) {
                mOffset = offset / 2;
                ivHeader.setTranslationY(mOffset - mScrollY);
            }
        });

2.2、TabLayout的頂部懸浮效果的實(shí)現(xiàn)

此處使用的是最為簡(jiǎn)單笨拙的方法兔毒,兩個(gè)TabLayout漫贞,一個(gè)固定為屏幕頂部ToolBar下面,并隱藏育叁,另一個(gè)正常繪制在布局中;
計(jì)算ToolBar的高度,根據(jù)NestedScrollView滑動(dòng)的高度(這里的高度指的是跟隨滑動(dòng)的TabLayout的Y坐標(biāo))恰好到ToolBar的高度位置時(shí)顯示隱藏的ToolBar豪嗽;

-FrameLayout
    -SmartRefreshLayout
        -Tablayout
        -Viewpager
    -SmartRefreshLayout
    -RelativeLayout
        -Toolbar
        -Tablayout
    -RelativeLayout
-FrameLayout
toolbar.post(new Runnable() {
            @Override
            public void run() {
                toolBarPositionY = toolbar.getHeight();
            }
        });
scrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
            @Override
            public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                int[] location = new int[2];
                magicIndicator.getLocationOnScreen(location);
                int xPosition = location[0];
                int yPosition = location[1];
                if (yPosition < toolBarPositionY) {
                    toolBarTablayout.setVisibility(View.VISIBLE);
                } else {
                    toolBarTablayout.setVisibility(View.GONE);
                }
            }
        });
2.3谴蔑、ToolBar的漸變透明度以及按鈕的切換
        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            style="@style/AppTheme.Toolbar"
            android:layout_marginBottom="0dp"
            android:background="@android:color/transparent"
            app:layout_collapseMode="pin">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center_vertical"
                android:orientation="horizontal">

                <ImageView
                    android:id="@+id/iv_back"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:src="@drawable/back_white" />


                <android.support.v7.widget.ButtonBarLayout
                    android:id="@+id/buttonBarLayout"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:layout_weight="1"
                    android:gravity="center">

                    <de.hdodenhof.circleimageview.CircleImageView
                        android:id="@+id/toolbar_avatar"
                        style="@style/UserTitleAvatar"
                        android:src="@drawable/timg" />

                    <TextView
                        android:id="@+id/toolbar_username"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        android:maxLines="1"
                        android:text="SiberiaDante"
                        android:textColor="@color/mainBlack"
                        android:textSize="@dimen/font_16" />


                </android.support.v7.widget.ButtonBarLayout>

                <ImageView
                    android:id="@+id/iv_menu"
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout_gravity="end"
                    android:src="@drawable/icon_menu_white" />
            </LinearLayout>

        </android.support.v7.widget.Toolbar>

ToolBar中間標(biāo)題默認(rèn)隱藏,使用的ButtonBarLayout包裹ImageView和TextView設(shè)置百分比透明龟梦,具體處理有兩點(diǎn):

 buttonBarLayout.setAlpha(0);
 toolbar.setBackgroundColor(0);
  • 下拉頭部刷新時(shí)ToolBar漸變隱藏,同樣利用SmartRefreshLayout處理
 refreshLayout.setOnMultiPurposeListener(new SimpleMultiPurposeListener() {
            @Override
            public void onHeaderPulling(RefreshHeader header, float percent, int offset, int bottomHeight, int extendHeight) {
                toolbar.setAlpha(1 - Math.min(percent, 1));
            }

            @Override
            public void onHeaderReleasing(RefreshHeader header, float percent, int offset, int bottomHeight, int extendHeight) {
                toolbar.setAlpha(1 - Math.min(percent, 1));
            }
        });

  • 上下滑動(dòng)時(shí)標(biāo)題欄漸變顯示和隱藏隐锭,并切換圖標(biāo)顏色(這里實(shí)際上是根據(jù)臨界點(diǎn)直接更換圖片,處理的比較簡(jiǎn)單)
 scrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
            int lastScrollY = 0;
            int h = DensityUtil.dp2px(170);
            int color = ContextCompat.getColor(getApplicationContext(), R.color.mainWhite) & 0x00ffffff;

            @Override
            public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                int[] location = new int[2];
                magicIndicator.getLocationOnScreen(location);
                int xPosition = location[0];
                int yPosition = location[1];

                if (lastScrollY < h) {
                    scrollY = Math.min(h, scrollY);
                    mScrollY = scrollY > h ? h : scrollY;
                    buttonBarLayout.setAlpha(1f * mScrollY / h);
                    toolbar.setBackgroundColor(((255 * mScrollY / h) << 24) | color);
                    ivHeader.setTranslationY(mOffset - mScrollY);
                }
                if (scrollY == 0) {
                    ivBack.setImageResource(R.drawable.back_white);
                    ivMenu.setImageResource(R.drawable.icon_menu_white);
                } else {
                    ivBack.setImageResource(R.drawable.back_black);
                    ivMenu.setImageResource(R.drawable.icon_menu_black);
                }

                lastScrollY = scrollY;
            }
        });
2.4、NestedScrollView嵌套ViewPager導(dǎo)致ViewPager高度為0的處理

很多人可能認(rèn)為直接自定義ViewPager计贰,測(cè)量子View的高度钦睡,讓ViewPager去適應(yīng)高度即可,其實(shí)不然躁倒,如果這樣處理的話(huà)我們的Viewpager可能就是無(wú)限高度荞怒,我們?cè)谔幚硗闚estedScrollView后洒琢,無(wú)限高度的ViewPager和RecyclerView又是一個(gè)問(wèn)題,所以我這里的處理是計(jì)算ViewPager所需要的最大高度挣输,即TabLayout在最頂部顯示時(shí)到屏幕底部的最大高度為ViewPager高度

 toolbar.post(new Runnable() {
            @Override
            public void run() {
                toolBarPositionY = toolbar.getHeight();
                ViewGroup.LayoutParams params = viewPager.getLayoutParams();
                params.height = SDScreenUtil.getScreenHeight() - toolBarPositionY - tablayout.getHeight()+1;
                viewPager.setLayoutParams(params);
            }
        });

這里為什么要+1纬凤,后面會(huì)有解釋

2.5、NestedScrollView嵌套R(shí)ecyclerView滑動(dòng)沖突

NestedScrollView嵌套R(shí)ecyclerView滑動(dòng)沖突我們使用事件攔截處理撩嚼,這里處理的是NestedScrollView的滑動(dòng)停士,首先滑動(dòng)的時(shí)候肯定是需要NestedScrollView的滑動(dòng)事件,所以我們默認(rèn)不攔截NestedScrollView的滑動(dòng)事件完丽,直到TabLayout頂部懸浮的時(shí)候恋技,我們攔截NestedScrollView的滑動(dòng)事件,交給RecyclerView來(lái)處理

  • 重寫(xiě)NestedScrollView
public class JudgeNestedScrollView extends NestedScrollView {
    private boolean isNeedScroll = true;
    ...省略構(gòu)造方法
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_MOVE:
                return isNeedScroll;
        }
        return super.onInterceptTouchEvent(ev);
    }

    /*
    改方法用來(lái)處理NestedScrollView是否攔截滑動(dòng)事件
     */
    public void setNeedScroll(boolean isNeedScroll) {
        this.isNeedScroll = isNeedScroll;
    }
}

這里默認(rèn)不攔截NestedScrollView滑動(dòng)事件逻族,只有當(dāng)我們TabLayout滑動(dòng)到頂部時(shí)才去攔截,也就是TabLayout顯示隱藏的時(shí)候

scrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
            @Override
            public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                int[] location = new int[2];
                magicIndicator.getLocationOnScreen(location);
                int xPosition = location[0];
                int yPosition = location[1];
                if (yPosition < toolBarPositionY) {
                    tablayout.setVisibility(View.VISIBLE);
                    scrollView.setNeedScroll(false);
                } else {
                    tablayout.setVisibility(View.GONE);
                    scrollView.setNeedScroll(true);
                }

至于前面測(cè)量ViewPager高度的時(shí)候蜻底,為什么會(huì)+1處理,這是因?yàn)槠噶郏绻?1時(shí)薄辅,剛好是TabLayout要出現(xiàn)的臨界點(diǎn),也就是ViewPager恰好的高度抠璃,但是這個(gè)時(shí)候又剛好是我們NestedScrollView攔截沒(méi)有取消的臨界點(diǎn)站楚,所以,在上滑的時(shí)候搏嗡,TabLayout剛好懸浮頂部時(shí)窿春,RecyclerView沒(méi)有獲取事件,無(wú)法進(jìn)行滑動(dòng)采盒,這就是給ViewPager+1處理的理由旧乞;

2.5、NestedScrollView嵌套ViewPager滑動(dòng)沖突2

如果你足夠細(xì)心的話(huà)磅氨,就會(huì)發(fā)現(xiàn)尺栖,當(dāng)你的TabLayout上滑到一半的時(shí)候,再去左右滑動(dòng)ViewPager是滑動(dòng)不了的烦租,因?yàn)檫@個(gè)時(shí)候NestedScrollView依然消費(fèi)事件决瞳,所以我們還需要對(duì)NestedScrollView事件進(jìn)行處理,判斷如果是左右滑動(dòng)的時(shí)候左权,我們不讓NestedScrollView處理,而是交給子View處理痴颊,即ViewPager

public class JudgeNestedScrollView extends NestedScrollView {
    private boolean isNeedScroll = true;
    private float xDistance, yDistance, xLast, yLast;
    ...省略構(gòu)造方法
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                xDistance = yDistance = 0f;
                xLast = ev.getX();
                yLast = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                final float curX = ev.getX();
                final float curY = ev.getY();
                xDistance += Math.abs(curX - xLast);
                yDistance += Math.abs(curY - yLast);
                xLast = curX;
                yLast = curY;
                if (xDistance > yDistance) {
                    return false;
                }
                return isNeedScroll;

        }
        return super.onInterceptTouchEvent(ev);
    }

    /*
    改方法用來(lái)處理NestedScrollView是否攔截滑動(dòng)事件
     */
    public void setNeedScroll(boolean isNeedScroll) {
        this.isNeedScroll = isNeedScroll;
    }
}

至此赏迟,完美的解決了所有的問(wèn)題,當(dāng)時(shí)有些細(xì)節(jié)這里并沒(méi)有話(huà)費(fèi)太多的時(shí)間去處理蠢棱,如有任何問(wèn)題锌杀,歡迎各位大佬進(jìn)行指正
Demo地址:https://github.com/SiberiaDante/MultiScrollDemo
整理這個(gè)demo也整理的我快吐了甩栈,歡迎大佬start支持 _

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市糕再,隨后出現(xiàn)的幾起案子量没,更是在濱河造成了極大的恐慌,老刑警劉巖突想,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件殴蹄,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡猾担,警方通過(guò)查閱死者的電腦和手機(jī)袭灯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)绑嘹,“玉大人稽荧,你說(shuō)我怎么就攤上這事」ひ福” “怎么了姨丈?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)擅腰。 經(jīng)常有香客問(wèn)我蟋恬,道長(zhǎng),這世上最難降的妖魔是什么惕鼓? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任筋现,我火速辦了婚禮,結(jié)果婚禮上箱歧,老公的妹妹穿的比我還像新娘矾飞。我一直安慰自己,他們只是感情好呀邢,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布洒沦。 她就那樣靜靜地躺著,像睡著了一般价淌。 火紅的嫁衣襯著肌膚如雪申眼。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天蝉衣,我揣著相機(jī)與錄音括尸,去河邊找鬼。 笑死病毡,一個(gè)胖子當(dāng)著我的面吹牛濒翻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼有送,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼淌喻!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起雀摘,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤裸删,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后阵赠,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體涯塔,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年豌注,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了伤塌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡轧铁,死狀恐怖每聪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情齿风,我是刑警寧澤药薯,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站救斑,受9級(jí)特大地震影響童本,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜脸候,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一穷娱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧运沦,春花似錦泵额、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至烈掠,卻和暖如春羞秤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背左敌。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工瘾蛋, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人矫限。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓瘦黑,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子幸斥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353