Android 使用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 / ;
ivHeader.setTranslationY(mOffset - mScrollY);
}
@Override
public void onHeaderReleasing(RefreshHeader header, float percent, int offset, int bottomHeight, int extendHeight) {
mOffset = offset / ;
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[];
magicIndicator.getLocationOnScreen(location);
int xPosition = location[];
int yPosition = location[];
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=""
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=""
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();
toolbar.setBackgroundColor();
* 下拉头部刷新时ToolBar渐变隐藏,同样利用SmartRefreshLayout处理
refreshLayout.setOnMultiPurposeListener(new SimpleMultiPurposeListener() {
@Override
public void onHeaderPulling(RefreshHeader header, float percent, int offset, int bottomHeight, int extendHeight) {
toolbar.setAlpha( - Math.min(percent, ));
}
@Override
public void onHeaderReleasing(RefreshHeader header, float percent, int offset, int bottomHeight, int extendHeight) {
toolbar.setAlpha( - Math.min(percent, ));
}
});
* 上下滑动时标题栏渐变显示和隐藏,并切换图标颜色(这里实际上是根据临界点直接更换图片,处理的比较简单)
scrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
int lastScrollY = ;
int h = DensityUtil.dp2px();
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[];
magicIndicator.getLocationOnScreen(location);
int xPosition = location[];
int yPosition = location[];
if (lastScrollY < h) {
scrollY = Math.min(h, scrollY);
mScrollY = scrollY > h ? h : scrollY;
buttonBarLayout.setAlpha(1f * mScrollY / h);
toolbar.setBackgroundColor((( * mScrollY / h) << ) | color);
ivHeader.setTranslationY(mOffset - mScrollY);
}
if (scrollY == ) {
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()+;
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[];
magicIndicator.getLocationOnScreen(location);
int xPosition = location[];
int yPosition = location[];
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;
}
}
至此,完美的解决了所有的问题,当时有些细节这里并没有话费太多的时间去处理,如有任何问题,欢迎各位大佬进行指正
源码:https://github.com/SiberiaDante/MultiScrollDemo
Android 使用NestedScrollView+ViewPager+RecyclerView+SmartRefreshLayout打造酷炫下拉视差效果并解决各种滑动冲突的更多相关文章
- 使用NestedScrollView+ViewPager+RecyclerView+SmartRefreshLayout打造酷炫下拉视差效果并解决各种滑动冲突
使用NestedScrollView+ViewPager+RecyclerView+SmartRefreshLayout打造酷炫下拉视差效果并解决各种冲突 如果你还在为处理滑动冲突而发愁,那么你需要静 ...
- Android仿苹果版QQ下拉刷新实现(二) ——贝塞尔曲线开发"鼻涕"下拉粘连效果
前言 接着上一期Android仿苹果版QQ下拉刷新实现(一) ——打造简单平滑的通用下拉刷新控件 的博客开始,同样,在开始前我们先来看一下目标效果: 下面上一下本章需要实现的效果图: 大家看到这个效果 ...
- 使用SwipeRefreshLayout和RecyclerView实现仿“简书”下拉刷新和上拉载入很多其它
一.概述 本篇博客介绍的是怎样使用SwipeRefreshLayout和RecyclerView实现高仿简书Android端的下拉刷新和上拉载入很多其它的效果. 依据效果图能够发现,本案例实现了例如以 ...
- 纯CSS3写的10个不同的酷炫图片遮罩层效果【转】
这个是纯CSS3实现的的10个不同的酷炫图片遮罩层效果,可以欣赏一下 在线预览 下载地址 实例代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ...
- 纯CSS3写的10个不同的酷炫图片遮罩层效果
这个是纯CSS3实现的的10个不同的酷炫图片遮罩层效果,可以欣赏一下 在线预览 下载地址 实例代码 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1 ...
- Android 学习笔记之AndBase框架学习(六) PullToRefrech 下拉刷新的实现
PS:Struggle for a better future 学习内容: 1.PullToRefrech下拉刷新的实现... 不得不说AndBase这个开源框架确实是非常的强大..把大部分的东西 ...
- 【Android】Anroid5.0+新控件---酷炫标题栏的简单学习
Android5.0+推出的新控件感觉特别酷,最近想模仿大神做个看图App出来,所以先把这些新控件用熟悉了. 新控件的介绍.使用等等网上相应的文章已经特别多了,题主也没那能力去写篇详解出来,本篇随笔记 ...
- android 新闻应用、Xposed模块、酷炫的加载动画、下载模块、九宫格控件等源码
Android精选源码 灵活的ShadowView,可替代CardView使用 基于Tesseract-OCR实现自动扫描识别手机号 Android播放界面仿QQ音乐开源音乐播放器 新闻应用项目采用了 ...
- 三分钟学会用 js + css3 打造酷炫3D相册
之前发过该文,后来不知怎么回事不见了,现在重新发一下. 中秋主题的3D旋转相册 如图,这是通过Javascript和css3来实现的.整个案例只有不到80行代码,我希望通过这个案例,让正处于迷茫期的j ...
随机推荐
- python中re.sub的使用 --解读一段代码
json='{ title: "条纹针织衫+蓬蓬裙套装", picSrc: "http://d05.res.meilishuo.net/pic/_o/15/f5/f2e2 ...
- Docker入门(六):Stacks
这个<Docker入门系列>文档,是根据Docker官网(https://docs.docker.com)的帮助文档大致翻译而成.主要是作为个人学习记录.有错误的地方,Robin欢迎大家指 ...
- thymeleaf控制view的返回格式
package com.ailk.dd1.jike.web.config; import nz.net.ultraq.thymeleaf.LayoutDialect; import org.sprin ...
- Struts2返回JSON数据的具体应用范…
Struts2返回JSON数据的具体应用范例 博客分类: Struts2 Struts2JSON 早在我刚学Struts2之初的时候,就想写一篇文章来阐述Struts2如何返回JSON数据的原理和具 ...
- 51Nod - 1640 天气晴朗的魔法 大+小生成树(最大值最小)/二分
天气晴朗的魔法 这样阴沉的天气持续下去,我们不免担心起他的健康. 51nod魔法学校近日开展了主题为“天气晴朗”的魔法交流活动. N名魔法师按阵法站好,之后选取N - 1条魔法链将所有魔法师的 ...
- Codeforces Round #459 (Div. 2):D. MADMAX(记忆化搜索+博弈论)
题意 在一个有向无环图上,两个人分别从一个点出发,两人轮流从当前点沿着某条边移动,要求经过的边权不小于上一轮对方经过的边权(ASCII码),如果一方不能移动,则判负.两人都采取最优策略,求两人分别从每 ...
- DataGridView DataSource INotifyPropertyChanged 避免闪烁的方法
代码说话: dgvPosition就是需要避免闪烁的DataGridView 主要是加2段代码 1.SetStyle 2.datagridview设置DoubleBuffered属性为True pub ...
- 洛谷P1549 棋盘问题(2)
P1549 棋盘问题(2) 题目描述 在N*N的棋盘上(1≤N≤10),填入1,2,…,N*N共N*N个数,使得任意两个相邻的数之和为素数. 例如:当N=2时,有: 其相邻数的和为素数的有: 1+2, ...
- 洛谷P2513 [HAOI2009]逆序对数列
P2513 [HAOI2009]逆序对数列 题目描述 对于一个数列{ai},如果有i<j且ai>aj,那么我们称ai与aj为一对逆序对数.若对于任意一个由1~n自然数组成的数列,可以很容易 ...
- 洛谷2105 k皇后
P2105 K皇后 题目描述 小Z最近捡到了一个棋盘,他想在棋盘上摆放K个皇后.他想知道在他摆完这K个皇后之后,棋盘上还有多少了格子是不会被攻击到的. (Ps:一个皇后会攻击到这个皇后所在的那一行,那 ...