背景
目前市面上大多数App首页加推荐效果基本实现方式为:RecyclerView + ViewPager + RecyclerView。其中面临的最主要问题就是RecyclerView同方向滑动冲突解决。
效果
前言
滑动冲突解决方案目前最流行的方式NestedScrollingParent和NestedScrollingChild,大家有不了解的可以手动google一下相关知识。
而且最主要的是RecyclerView已经内部实现了NestedScrollingChild。故本文通过最外层RecyclerView实现NestedScrollingParent2接口解决嵌套滑动问题。
备注:对于NestedScrollingParent2和NestedScrollingParent的区别大家自定google一下
实现
1.首先要做的是使得touch事件可以传递到子RcyclerView中去。
通过RecyclerView现有的接口即可实现,主要通过requestDisallowInterceptTouchEvent实现touch事件的穿透。
package com.android.app.sample;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
/**
* Intercept touch event of RecyclerView binded
*/
public class SwipeItemTouchListener implements RecyclerView.OnItemTouchListener {
public SwipeItemTouchListener() {
}
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
//判断当前事件坐标点下是否包含RecyclerView
recyclerView.requestDisallowInterceptTouchEvent(findScrollableChildViewUnder(recyclerView, motionEvent) != null);
return false;
}
@Override
public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean b) {
}
private View findScrollableChildViewUnder(RecyclerView recyclerView, MotionEvent event) {
if (recyclerView == null) {
return null;
}
final int x = (int) event.getX();
final int y = (int) event.getY();
View child = recyclerView.findChildViewUnder(x, y);
if (findCanScrollView(child) != null) {
return child;
}
return null;
}
private View findCanScrollView(View v) {
if (v instanceof ViewGroup) {
ViewGroup target = (ViewGroup) v;
if (target instanceof RecyclerView
&& target.getVisibility() == View.VISIBLE) {
return target;
} else {
for (int i = 0; i < target.getChildCount(); ++i) {
View view = findCanScrollView(target.getChildAt(i));
if (view != null) {
return view;
}
}
return null;
}
} else {
return null;
}
}
}
2.自定义RecyclerView实现NestedScrollingParent2接口处理child的嵌套滚动
大多数接口通过NestedScrollingParentHelper进行实现相对简单。核心逻辑在onNestedPreScroll中处理消费时机及距离。
package com.android.app.sample.widget;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.NestedScrollingParent2;
import android.support.v4.view.NestedScrollingParentHelper;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
public class NestedRecyclerView extends RecyclerView implements NestedScrollingParent2 {
private static final String TAG = NestedRecyclerView.class.getSimpleName();
private NestedScrollingParentHelper mParentHelper;
public NestedRecyclerView(@NonNull Context context) {
super(context);
init();
}
public NestedRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public NestedRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
mParentHelper = new NestedScrollingParentHelper(this);
}
@Override
public boolean onStartNestedScroll(@NonNull View view, @NonNull View target, int axes, int type) {
Log.d(TAG, "onStartNestedScroll axes = " + axes);
return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
@Override
public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes, int type) {
Log.d(TAG, "onNestedScrollAccepted: ");
mParentHelper.onNestedScrollAccepted(child, target, axes, type);
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, type);
}
@Override
public void onStopNestedScroll(@NonNull View target, int type) {
Log.d(TAG, "onStopNestedScroll: ");
mParentHelper.onStopNestedScroll(target, type);
stopNestedScroll(type);
}
@Override
public void onStopNestedScroll(@NonNull View target) {
onStopNestedScroll(target, ViewCompat.TYPE_TOUCH);
}
@Override
public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
Log.d(TAG, "onNestedScroll dxConsumed = " + dxConsumed + ",, dyConsumed = " + dyConsumed + ",,dxUnconsumed = " + dxUnconsumed + ",,dyUnconsumed" + dyUnconsumed);
}
@Override
public void onNestedPreScroll(@NonNull View target, int dx, int dy, @Nullable int[] consumed, int type) {
// dy > 0 向上活动 dy < 0 向下滑动
boolean consum = false;
if (dy > 0){
consum = canScrollVertically(dy) || !target.canScrollVertically(dy);
} else {
consum = !target.canScrollVertically(dy);
}
if (consum){
scrollBy(0, dy);
if (consumed != null) {
consumed[1] = dy;
}
}
Log.d(TAG, "onNestedPreScroll:::dy::" + dy + ",,consumed[1]::" + (consumed != null ? consumed[1] : ""));
}
@Override
public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, ViewCompat.TYPE_TOUCH);
}
@Override
public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed) {
onNestedPreScroll(target, dx, dy, consumed, ViewCompat.TYPE_TOUCH);
}
@Override
public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY,
boolean consumed) {
return false;
}
@Override
public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) {
return false;
}
@Override
public int getNestedScrollAxes() {
return mParentHelper.getNestedScrollAxes();
}
}