使用NestedScrollView+ViewPager+RecyclerView+SmartRefreshLayout打造酷炫下拉視差效果並解決各種滑動沖突
阿新 • • 發佈:2017-12-17
magic refresh gen view設置 需要 如果 .cn int bre
使用NestedScrollView+ViewPager+RecyclerView+SmartRefreshLayout打造酷炫下拉視差效果並解決各種沖突
如果你還在為處理滑動沖突而發愁,那麽你需要靜下心來看看這邊文章,如果你能徹底理解這篇文章中使用的技術,那麽,一切滑動沖突的問題解決起來就輕而易舉了:
先扔一個最終實現的效果圖
先分析下效果圖中實現的功能點
- 頂部下拉時背景圖形成視差效果
- 上拉時標題欄透明切換顯示
- 底部實現TabLayout+ViewPager+Fragment+RecyclerView
- NestedScrollView+ViewPager的滑動沖突解決
- NestedScrollView+RecyclerView滑動沖突的解決
復雜在哪裏?整個布局中使用了SmartRefreshLayout,NestedScrollView,ViewPager,RecyclerView,每一個都有滑動事件,我們平時只是使用ScrollView+RecyclerView都會有滑動沖突,更何況這裏有四個會引起沖突的控件一起使用!
接下來,我們一步一步實現這個效果
1、布局設計分析
-FrameLayout(最外層) -ImageView(頭部背景圖) -SmartRefreshLayout(頭部刷新控件) -JudgeNestedScrollView(自定義的NestedScrollView) ...省略中間巴拉巴拉布局 -Tablayout -ViewPager
2、功能點實現說明
2.1、下拉時視差效果的實現
最外層為FrameLayout,ImageView高度設置超過屏幕頂部,借助SmartRefreshLayout控件在下拉和松開時頭部背景圖做平移處理,背景圖片做了高斯模糊處理
布局代碼:
<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"> ...
下拉刷新時頭部背景圖片平移代碼:
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的頂部懸浮效果的實現
此處使用的是最為簡單笨拙的方法,兩個TabLayout,一個固定為屏幕頂部ToolBar下面,並隱藏,另一個正常繪制在布局中;
計算ToolBar的高度,根據NestedScrollView滑動的高度(這裏的高度指的是跟隨滑動的TabLayout的Y坐標)恰好到ToolBar的高度位置時顯示隱藏的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中間標題默認隱藏,使用的ButtonBarLayout包裹ImageView和TextView設置百分比透明,具體處理有兩點:
buttonBarLayout.setAlpha(0);
toolbar.setBackgroundColor(0);
* 下拉頭部刷新時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)); } });
* 上下滑動時標題欄漸變顯示和隱藏,並切換圖標顏色(這裏實際上是根據臨界點直接更換圖片,處理的比較簡單)
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導致ViewPager高度為0的處理
很多人可能認為直接自定義ViewPager,測量子View的高度,讓ViewPager去適應高度即可,其實不然,如果這樣處理的話我們的Viewpager可能就是無限高度,我們在處理完NestedScrollView後,無限高度的ViewPager和RecyclerView又是一個問題,所以我這裏的處理是計算ViewPager所需要的最大高度,即TabLayout在最頂部顯示時到屏幕底部的最大高度為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,後面會有解釋
2.5、NestedScrollView嵌套RecyclerView滑動沖突
NestedScrollView嵌套RecyclerView滑動沖突我們使用事件攔截處理,這裏處理的是NestedScrollView的滑動,首先滑動的時候肯定是需要NestedScrollView的滑動事件,所以我們默認不攔截NestedScrollView的滑動事件,直到TabLayout頂部懸浮的時候,我們攔截NestedScrollView的滑動事件,交給RecyclerView來處理
* 重寫NestedScrollView
public class JudgeNestedScrollView extends NestedScrollView { private boolean isNeedScroll = true; ...省略構造方法 @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_MOVE: return isNeedScroll; } return super.onInterceptTouchEvent(ev); } /* 改方法用來處理NestedScrollView是否攔截滑動事件 */ public void setNeedScroll(boolean isNeedScroll) { this.isNeedScroll = isNeedScroll; } }
這裏默認不攔截NestedScrollView滑動事件,只有當我們TabLayout滑動到頂部時才去攔截,也就是TabLayout顯示隱藏的時候
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); }
至於前面測量ViewPager高度的時候,為什麽會+1處理,這是因為,如果不+1時,剛好是TabLayout要出現的臨界點,也就是ViewPager恰好的高度,但是這個時候又剛好是我們NestedScrollView攔截沒有取消的臨界點,所以,在上滑的時候,TabLayout剛好懸浮頂部時,RecyclerView沒有獲取事件,無法進行滑動,這就是給ViewPager+1處理的理由;
2.5、NestedScrollView嵌套ViewPager滑動沖突2
如果你足夠細心的話,就會發現,當你的TabLayout上滑到一半的時候,再去左右滑動ViewPager是滑動不了的,因為這個時候NestedScrollView依然消費事件,所以我們還需要對NestedScrollView事件進行處理,判斷如果是左右滑動的時候,我們不讓NestedScrollView處理,而是交給子View處理,即ViewPager
public class JudgeNestedScrollView extends NestedScrollView { private boolean isNeedScroll = true; private float xDistance, yDistance, xLast, yLast; ...省略構造方法 @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); } /* 改方法用來處理NestedScrollView是否攔截滑動事件 */ public void setNeedScroll(boolean isNeedScroll) { this.isNeedScroll = isNeedScroll; } }
至此,完美的解決了所有的問題,當時有些細節這裏並沒有話費太多的時間去處理,如有任何問題,歡迎各位大佬進行指正
使用NestedScrollView+ViewPager+RecyclerView+SmartRefreshLayout打造酷炫下拉視差效果並解決各種滑動沖突