如果你還在為處理滑動(dòng)沖突而發(fā)愁印机,那么你需要靜下心來(lái)看看這邊文章矢腻,如果你能徹底理解這篇文章中使用的技術(shù),那么射赛,一切滑動(dòng)沖突的問(wèn)題解決起來(lái)就輕而易舉了:
先扔一個(gè)最終實(shí)現(xiàn)的效果圖
先分析下效果圖中實(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支持 _