运行图
布局的设计
要实现上面的效果需要搞定NestedScrollView和RecycleView的滑动冲突。有人要问RecycleView为何要滑动自动撑大不就好了么?这个问题其实对于有限的资源加载来说是很好的解决方案,但是如果涉及到的是图文结合的并且有大批量的数据的时候就需要用到RecycleView的复用机制,这样就要求RecycleView固定高度。
这里面涉及到的几个参数:
1:整屏高度 screenHeight (用此方法可以省掉Context的传入)
/**
* 获取屏幕高度 px
* @return
*/
public static int getScreenHeightPixels() {
return Resources.getSystem().getDisplayMetrics().heightPixels;
}
2:头部悬浮框高度 floatActionBarHeight
这里直接用View去getHeight()就行,这个方法在使用的时候调用就好,如果一开始就调用有可能拿不到数据,因为页面还没加载完成
3:底部导航高度 bottomBarHeight
这个可以是固定值,看自己的设计是多大的,我的设定是48dp,所以转化后的结果是。
//将设置的db转为屏幕像素
public static int dp2px(int dpVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpVal, Resources.getSystem().getDisplayMetrics());
}
4:悬浮tab的高度 floatTabBarHeight
跟导航的高度一样处理。
在这里RecycleView的高度值我们有了方案。
scrollRecycleHeight=screenHeight-floatActionBarHeight-bottomBarHeight-floatTabBarHeight
设置高度在数据返回回来的时候或者一开始的时候设置都行。
接下来要处理的就是滑动冲突的问题
NestedScrollingChildHelper 提供了一个禁止view滑动的方法,这个类封装在了RecycleView内部,所以RecycleView也可以调用。
public void setNestedScrollingEnabled(boolean enabled) {
if (this.mIsNestedScrollingEnabled) {
ViewCompat.stopNestedScroll(this.mView);
}
this.mIsNestedScrollingEnabled = enabled;
}
说到这里小伙伴可能会说那就简单了,只要NestedScrollView没有滑动到浮层tab的位置的时候都禁止RecyceView的滑动就好了,超出的时候允许其滑动是不是就可以解决了?
事实的情况并不是这样。当我们测试的时候,当我们慢慢滑动NestedScrollView,当屏幕出现RecycleView的时候,这时候NestedScroolView的滑动惯性还在继续,我们此时触摸滑动RecycleView 是可以滑动的。具体原因大家可以查看源码去分析下,这里不做分析。
解决方案
重写NestedScrollView 实现拦截。
public class NestedInsScrollView extends NestedScrollView {
private int limitHeight = 0;
private float startX, startY;
public void setLimitHeight(int limitHeight) {
this.limitHeight = limitHeight;
}
public NestedInsScrollView(@NonNull Context context) {
super(context);
}
public NestedInsScrollView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public NestedInsScrollView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int scrollY = 0;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = ev.getX();
startY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
scrollY = getScrollY();
// 判断是否需要拦截子View的滑动事件
float offsetX = Math.abs(ev.getX() - startX);
float offsetY = Math.abs(ev.getY() - startY);
if (offsetY > offsetX && ev.getY() - startY < 0) {//向上的垂直滑动
if (scrollY < limitHeight) {
return true;
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
break;
}
return super.onInterceptTouchEvent(ev);
}
}
这里面通过重写onInterceptTouchEvent 在ACTION_MOVE事件中判断是否可以滚动,如果不允许RecycleView滚动则事件不往下分发。
这里面需要注意的点:
1:要判断事件是垂直滚动 (因为内部的View还要处理左右滚动的事件)
2:要判断是向上滚动 (因为头部的RecycleView还要处理下拉刷新)
3:最后只有满足垂直向上并且滚动距离达到tab的浮动距离的时候才拦截
homeScrollView.setOnScrollChangeListener(new
NestedScrollView.OnScrollChangeListener() {
@Override
public void onScrollChange(NestedScrollView v, int scrollX, int
scrollY, int oldScrollX, int oldScrollY) {
int limitHeight = ((ViewGroup)
homeScrollView.getChildAt(0)).getChildAt(0).getHeight();
int topHeight = homeHeadTopLayout.getHeight();
homeScrollView.setLimitHeight(limitHeight - topHeight -
DisplayUtils.dp2px(5));
// 例如:
if (scrollY >= limitHeight - topHeight - DisplayUtils.dp2px(5)) {
main_home_services_tab_recycle.setNestedScrollingEnabled(true);
} else {
main_home_services_tab_recycle.setNestedScrollingEnabled(false);
}
}
});
这时候再设置 setNestedScrollingEnabled(true/false);
就可以解决问题了。