使用以下类对ViewPager2中的recycleView进行包裹即可
解决的是如果子view可以水平滚动,则将水平滚动优先交给子类处理。
NestedScrollableHost 是入口
使用策略模式封装了两个类
DisableNestedScrollableHost
和
EnableNestedScrollableHost
两者的区别是
水平滚动到达子类的端点的时候,是否将事件交给父类。
DisableNestedScrollableHost是全部自己处理
EnableNestedScrollableHost是交给父类处理,因此可以嵌套滚动。
两种策略,通过xml的属性来确定使用哪一种。
注意:
DisableNestedScrollableHost
EnableNestedScrollableHost
的构造函数都是包级作用域的。为了避免被误用。
三个类需要声明在一个包内。
NestedScrollableHost 入口类
package com.trs.v6.news.ui.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.trs.news.R;
/**
* <pre>
* 用于解决ViewPager2嵌套产生的滑动冲突
* 可以通过在xml中声明属性,来控制是否开启嵌套滚动。
* 如果关闭嵌套滚动。那么在子控件滑动到断点时,事件不会传递给父控件。
* <code>
* app:nestedScrollingEnabled="true"
* </code>
*
* Layout to wrap a scrollable component inside a ViewPager2. Provided as a solution to the problem
* where pages of ViewPager2 have nested scrollable elements that scroll in the same direction as
* ViewPager2. The scrollable element needs to be the immediate and only child of this host layout.
* <p>
* This solution has limitations when using multiple levels of nested scrollable elements
* (e.g. a horizontal RecyclerView in a vertical RecyclerView in a horizontal ViewPager2).
* </pre>
*/
public class NestedScrollableHost extends FrameLayout {
ViewGroup proxyView;
private boolean nestedScrollable ;
public NestedScrollableHost(@NonNull Context context) {
this(context,null);
}
public NestedScrollableHost(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray array = context.obtainStyledAttributes(attrs, new int[]{R.attr.nestedScrollingEnabled});
nestedScrollable = array.getBoolean(0, true);
//此处使用策略模式,将可以嵌套滚动和不能嵌套滚动的逻辑封装进两个view
//可以降低相关代码的逻辑复杂性。
proxyView=nestedScrollable?new EnableNestedScrollableHost(context):new DisableNestedScrollableHost(context);
addView(proxyView,ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);
array.recycle();
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if(child!=proxyView){
proxyView.addView(child,index,params);
return;
}
super.addView(child, index, params);
}
}
DisableNestedScrollableHost
package com.trs.v6.news.ui.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager2.widget.ViewPager2;
import com.trs.news.R;
/**
* <pre>
* 将左右的滑动事件全部传递给子view。上下滚动事件交由父类处理。
*</pre>
*/
public class DisableNestedScrollableHost extends FrameLayout {
private ViewPager2 parentViewPager;
private int touchSlop = 0;
private float initialX = 0f;
private float initialY = 0f;
/**
* 将构造方法声明为包内级别,防止大家误用
* @param context
*/
DisableNestedScrollableHost(@NonNull Context context) {
super(context);
init(context);
}
private void init(Context context) {
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
View v = (View) getParent();
while (v != null && !(v instanceof ViewPager2)) {
v = (View) v.getParent();
}
parentViewPager = (ViewPager2) v;
getViewTreeObserver().removeOnPreDrawListener(this);
return false;
}
});
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
handleInterceptTouchEvent(ev);
return false;
}
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
boolean isHorizontal = View.SCROLL_AXIS_HORIZONTAL == nestedScrollAxes;
if (isHorizontal) {
//如果不支持嵌套滚动 而且是横向滚动就拦截
return true;
}
return super.onStartNestedScroll(child, target, nestedScrollAxes);
}
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(target, dxConsumed, dyConsumed, 0, dyUnconsumed);
}
private boolean handleInterceptTouchEvent(MotionEvent e) {
if (parentViewPager == null) return false;
int orientation = parentViewPager.getOrientation();
if (e.getAction() == MotionEvent.ACTION_DOWN) {
initialX = e.getX();
initialY = e.getY();
getParent().requestDisallowInterceptTouchEvent(true);
} else if (e.getAction() == MotionEvent.ACTION_MOVE) {
float dx = e.getX() - initialX;
float dy = e.getY() - initialY;
boolean isVpHorizontal = orientation == ViewPager2.ORIENTATION_HORIZONTAL;
float scaledDx = Math.abs(dx);
float scaledDy = Math.abs(dy);
if (scaledDx > touchSlop || scaledDy > touchSlop) {
if (isVpHorizontal == (scaledDy > scaledDx)) {
// Gesture is perpendicular, allow all parents to intercept
getParent().requestDisallowInterceptTouchEvent(false);
} else {
getParent().requestDisallowInterceptTouchEvent(true);
}
}
}
return false;
}
}
EnableNestedScrollableHost
package com.trs.v6.news.ui.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager2.widget.ViewPager2;
import com.trs.news.R;
/**
* <pre>
* 允许子类左右滚动,左右滚动到端点以后,继续滚动,会将事件交给父类处理
* 上下滚动的事件直接交给父类处理
* </pre>
*/
public class EnableNestedScrollableHost extends FrameLayout {
private ViewPager2 parentViewPager;
private int touchSlop = 0;
private float initialX = 0f;
private float initialY = 0f;
EnableNestedScrollableHost(@NonNull Context context) {
super(context);
init(context);
}
private void init(Context context) {
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
View v = (View) getParent();
while (v != null && !(v instanceof ViewPager2)) {
v = (View) v.getParent();
}
parentViewPager = (ViewPager2) v;
getViewTreeObserver().removeOnPreDrawListener(this);
return false;
}
});
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return handleInterceptTouchEvent(ev);
}
private boolean canChildScroll(int orientation, float delta) {
int direction = (int) -delta;
View child = getChildAt(0);
if (orientation == 0) {
return child.canScrollHorizontally(direction);
} else if (orientation == 1) {
return child.canScrollVertically(direction);
} else {
throw new IllegalArgumentException();
}
}
private boolean handleInterceptTouchEvent(MotionEvent e) {
if (parentViewPager == null) return false;
int orientation = parentViewPager.getOrientation();
if (e.getAction() == MotionEvent.ACTION_DOWN) {
initialX = e.getX();
initialY = e.getY();
getParent().requestDisallowInterceptTouchEvent(true);
} else if (e.getAction() == MotionEvent.ACTION_MOVE) {
float dx = e.getX() - initialX;
float dy = e.getY() - initialY;
boolean isVpHorizontal = orientation == ViewPager2.ORIENTATION_HORIZONTAL;
// assuming ViewPager2 touch-slop is 2x touch-slop of child
float scaledDx = Math.abs(dx) * (isVpHorizontal ? .5f : 1f);
float scaledDy = Math.abs(dy) * (isVpHorizontal ? 1f : .5f);
if (scaledDx > touchSlop || scaledDy > touchSlop) {
if (isVpHorizontal == (scaledDy > scaledDx)) {
// Gesture is perpendicular, allow all parents to intercept
getParent().requestDisallowInterceptTouchEvent(false);
} else {
// Gesture is parallel, query child if movement in that direction is possible
if (canChildScroll(orientation, isVpHorizontal ? dx : dy)) {
// Child can scroll, disallow all parents to intercept
getParent().requestDisallowInterceptTouchEvent(true);
} else {
// Child cannot scroll, allow all parents to intercept
getParent().requestDisallowInterceptTouchEvent(false);
//直接拦截子view的事件,这样事件就会使用嵌套滚动的机制交给父view处理
return true;
}
}
}
}
return false;
}
}
属性
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<!--用于在NestedScrollableHost和HorizontalScrollViewParent中使用-->
<!--用于判断是否支持嵌套滚动,所谓的嵌套滚动,定义为在和父控件同方向上,子控件达到端点后,是否将滑动事件交由父控件继续处理-->
<attr name="nestedScrollingEnabled" tools:ignore="MissingDefaultResource" tools:override="true"/>
</resources>
使用方法
<com.trs.v6.news.ui.view.NestedScrollableHost
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.trs.v6.news.ui.view.NestedScrollableHost>
或者包裹RecycleView
<com.trs.v6.news.ui.view.NestedScrollableHost
android:layout_width="match_parent"
android:layout_height="90dp"
android:clipChildren="false"
android:clipToPadding="false"
android:layout_below="@id/tag_layout">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:paddingStart="13dp"
android:clipChildren="false"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_gravity="center_vertical"
android:layout_height="wrap_content"
/>
</com.trs.v6.news.ui.view.NestedScrollableHost>