内部带有水平滚动视图的 ViewPager2
ViewPager2 with horizontal scrollView inside
我为我的项目实现了新的 ViewPager。
viewPager2 包含片段列表
private class ViewPagerAdapter extends FragmentStateAdapter {
private ArrayList<Integer> classifiedIds;
ViewPagerAdapter(@NonNull Fragment fragment, final ArrayList<Integer> classifiedIds) {
super(fragment);
this.classifiedIds = classifiedIds;
}
@NonNull
@Override
public Fragment createFragment(int position) {
return DetailsFragment.newInstance(classifiedIds.get(position));
}
@Override
public int getItemCount() {
return classifiedIds.size();
}
}
在片段中我有一个水平的 recyclerView
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false);
recyclerViewPicture.setLayoutManager(layoutManager);
问题是当我尝试滚动 recyclerview 时,viewPager 触摸并切换到下一个片段
当我使用旧的 ViewPager 时,我没有遇到这个问题
致电ViewGroup#onInterceptTouchEvent(MotionEvent).
我找到了一个解决方案,这是一个已知错误,您可以在此处看到https://issuetracker.google.com/issues/123006042也许他们会在下一次更新中解决它
感谢 TakeInfos 和 link
中的示例项目
recyclerViewPicture.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
int lastX = 0;
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = (int) e.getX();
break;
case MotionEvent.ACTION_MOVE:
boolean isScrollingRight = e.getX() < lastX;
if ((isScrollingRight && ((LinearLayoutManager) recyclerViewPicture.getLayoutManager()).findLastCompletelyVisibleItemPosition() == recyclerViewPicture.getAdapter().getItemCount() - 1) ||
(!isScrollingRight && ((LinearLayoutManager) recyclerViewPicture.getLayoutManager()).findFirstCompletelyVisibleItemPosition() == 0)) {
viewPager.setUserInputEnabled(true);
} else {
viewPager.setUserInputEnabled(false);
}
break;
case MotionEvent.ACTION_UP:
lastX = 0;
viewPager.setUserInputEnabled(true);
break;
}
return false;
}
@Override
public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
});
我正在检查用户是向右滚动还是向左滚动。如果用户到达 recyclerView 的末尾或开始,我将启用或禁用视图寻呼机上的滑动
我遇到了同样的问题:使用 AndroidX,一个 ViewPager2(水平方向)在其页面之一内有一个 RecyclerView(水平方向)。
我找到的有效解决方案来自 Google issueTracker。这是我的 Java 翻译的 Kotlin class:
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager2.widget.ViewPager2;
// from https://issuetracker.google.com/issues/123006042#comment21
/**
* 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.
*
* 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).
*/
public class NestedScrollableHost extends FrameLayout {
private int touchSlop = 0;
private float initialX = 0.0f;
private float initialY = 0.0f;
private ViewPager2 parentViewPager() {
View v = (View)this.getParent();
while( v != null && !(v instanceof ViewPager2) )
v = (View)v.getParent();
return (ViewPager2)v;
}
private View child() { return (this.getChildCount() > 0 ? this.getChildAt(0) : null); }
private void init() {
this.touchSlop = ViewConfiguration.get(this.getContext()).getScaledTouchSlop();
}
public NestedScrollableHost(@NonNull Context context) {
super(context);
this.init();
}
public NestedScrollableHost(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
this.init();
}
public NestedScrollableHost(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.init();
}
public NestedScrollableHost(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
this.init();
}
private boolean canChildScroll(int orientation, Float delta) {
int direction = (int)(Math.signum(-delta));
View child = this.child();
if( child == null )
return false;
if( orientation == 0 )
return child.canScrollHorizontally(direction);
if( orientation == 1 )
return child.canScrollVertically(direction);
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
this.handleInterceptTouchEvent(ev);
return super.onInterceptTouchEvent(ev);
}
private void handleInterceptTouchEvent(MotionEvent ev) {
ViewPager2 vp = this.parentViewPager();
if( vp == null )
return;
int orientation = vp.getOrientation();
// Early return if child can't scroll in same direction as parent
if( !this.canChildScroll(orientation, -1.0f) && !this.canChildScroll(orientation, 1.0f) )
return;
if( ev.getAction() == MotionEvent.ACTION_DOWN ) {
this.initialX = ev.getX();
this.initialY = ev.getY();
this.getParent().requestDisallowInterceptTouchEvent(true);
}
else if( ev.getAction() == MotionEvent.ACTION_MOVE ) {
float dx = ev.getX() - this.initialX;
float dy = ev.getY() - this.initialY;
boolean isVpHorizontal = (orientation == ViewPager2.ORIENTATION_HORIZONTAL);
// assuming ViewPager2 touch-slop is 2x touch-slop of child
float scaleDx = Math.abs(dx) * (isVpHorizontal ? 0.5f : 1.0f);
float scaleDy = Math.abs(dy) * (isVpHorizontal ? 1.0f : 0.5f);
if( scaleDx > this.touchSlop || scaleDy > this.touchSlop ) {
if( isVpHorizontal == (scaleDy > scaleDx) ) {
// Gesture is perpendicular, allow all parents to intercept
this.getParent().requestDisallowInterceptTouchEvent(false);
}
else {
// Gesture is parallel, query child if movement in that direction is possible
if( this.canChildScroll(orientation, (isVpHorizontal ? dx : dy)) ) {
this.getParent().requestDisallowInterceptTouchEvent(true);
}
else {
// Child cannot scroll, allow all parents to intercept
this.getParent().requestDisallowInterceptTouchEvent(false);
}
}
}
}
}
}
然后,只需将嵌套的 RecyclerView 嵌入到 NestedScrollableHost 容器中即可:
<mywishlist.sdk.Base.NestedScrollableHost
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/photos"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/photolist_collection_background"
android:orientation="horizontal">
</androidx.recyclerview.widget.RecyclerView>
</mywishlist.sdk.Base.NestedScrollableHost>
它解决了我在嵌套的 RecyclerView 和它的宿主 ViewPager2 之间的滚动冲突。
我为我的项目实现了新的 ViewPager。 viewPager2 包含片段列表
private class ViewPagerAdapter extends FragmentStateAdapter {
private ArrayList<Integer> classifiedIds;
ViewPagerAdapter(@NonNull Fragment fragment, final ArrayList<Integer> classifiedIds) {
super(fragment);
this.classifiedIds = classifiedIds;
}
@NonNull
@Override
public Fragment createFragment(int position) {
return DetailsFragment.newInstance(classifiedIds.get(position));
}
@Override
public int getItemCount() {
return classifiedIds.size();
}
}
在片段中我有一个水平的 recyclerView
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false);
recyclerViewPicture.setLayoutManager(layoutManager);
问题是当我尝试滚动 recyclerview 时,viewPager 触摸并切换到下一个片段
当我使用旧的 ViewPager 时,我没有遇到这个问题
致电ViewGroup#onInterceptTouchEvent(MotionEvent).
我找到了一个解决方案,这是一个已知错误,您可以在此处看到https://issuetracker.google.com/issues/123006042也许他们会在下一次更新中解决它
感谢 TakeInfos 和 link
中的示例项目 recyclerViewPicture.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
int lastX = 0;
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = (int) e.getX();
break;
case MotionEvent.ACTION_MOVE:
boolean isScrollingRight = e.getX() < lastX;
if ((isScrollingRight && ((LinearLayoutManager) recyclerViewPicture.getLayoutManager()).findLastCompletelyVisibleItemPosition() == recyclerViewPicture.getAdapter().getItemCount() - 1) ||
(!isScrollingRight && ((LinearLayoutManager) recyclerViewPicture.getLayoutManager()).findFirstCompletelyVisibleItemPosition() == 0)) {
viewPager.setUserInputEnabled(true);
} else {
viewPager.setUserInputEnabled(false);
}
break;
case MotionEvent.ACTION_UP:
lastX = 0;
viewPager.setUserInputEnabled(true);
break;
}
return false;
}
@Override
public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
});
我正在检查用户是向右滚动还是向左滚动。如果用户到达 recyclerView 的末尾或开始,我将启用或禁用视图寻呼机上的滑动
我遇到了同样的问题:使用 AndroidX,一个 ViewPager2(水平方向)在其页面之一内有一个 RecyclerView(水平方向)。
我找到的有效解决方案来自 Google issueTracker。这是我的 Java 翻译的 Kotlin class:
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager2.widget.ViewPager2;
// from https://issuetracker.google.com/issues/123006042#comment21
/**
* 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.
*
* 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).
*/
public class NestedScrollableHost extends FrameLayout {
private int touchSlop = 0;
private float initialX = 0.0f;
private float initialY = 0.0f;
private ViewPager2 parentViewPager() {
View v = (View)this.getParent();
while( v != null && !(v instanceof ViewPager2) )
v = (View)v.getParent();
return (ViewPager2)v;
}
private View child() { return (this.getChildCount() > 0 ? this.getChildAt(0) : null); }
private void init() {
this.touchSlop = ViewConfiguration.get(this.getContext()).getScaledTouchSlop();
}
public NestedScrollableHost(@NonNull Context context) {
super(context);
this.init();
}
public NestedScrollableHost(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
this.init();
}
public NestedScrollableHost(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.init();
}
public NestedScrollableHost(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
this.init();
}
private boolean canChildScroll(int orientation, Float delta) {
int direction = (int)(Math.signum(-delta));
View child = this.child();
if( child == null )
return false;
if( orientation == 0 )
return child.canScrollHorizontally(direction);
if( orientation == 1 )
return child.canScrollVertically(direction);
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
this.handleInterceptTouchEvent(ev);
return super.onInterceptTouchEvent(ev);
}
private void handleInterceptTouchEvent(MotionEvent ev) {
ViewPager2 vp = this.parentViewPager();
if( vp == null )
return;
int orientation = vp.getOrientation();
// Early return if child can't scroll in same direction as parent
if( !this.canChildScroll(orientation, -1.0f) && !this.canChildScroll(orientation, 1.0f) )
return;
if( ev.getAction() == MotionEvent.ACTION_DOWN ) {
this.initialX = ev.getX();
this.initialY = ev.getY();
this.getParent().requestDisallowInterceptTouchEvent(true);
}
else if( ev.getAction() == MotionEvent.ACTION_MOVE ) {
float dx = ev.getX() - this.initialX;
float dy = ev.getY() - this.initialY;
boolean isVpHorizontal = (orientation == ViewPager2.ORIENTATION_HORIZONTAL);
// assuming ViewPager2 touch-slop is 2x touch-slop of child
float scaleDx = Math.abs(dx) * (isVpHorizontal ? 0.5f : 1.0f);
float scaleDy = Math.abs(dy) * (isVpHorizontal ? 1.0f : 0.5f);
if( scaleDx > this.touchSlop || scaleDy > this.touchSlop ) {
if( isVpHorizontal == (scaleDy > scaleDx) ) {
// Gesture is perpendicular, allow all parents to intercept
this.getParent().requestDisallowInterceptTouchEvent(false);
}
else {
// Gesture is parallel, query child if movement in that direction is possible
if( this.canChildScroll(orientation, (isVpHorizontal ? dx : dy)) ) {
this.getParent().requestDisallowInterceptTouchEvent(true);
}
else {
// Child cannot scroll, allow all parents to intercept
this.getParent().requestDisallowInterceptTouchEvent(false);
}
}
}
}
}
}
然后,只需将嵌套的 RecyclerView 嵌入到 NestedScrollableHost 容器中即可:
<mywishlist.sdk.Base.NestedScrollableHost
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/photos"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/photolist_collection_background"
android:orientation="horizontal">
</androidx.recyclerview.widget.RecyclerView>
</mywishlist.sdk.Base.NestedScrollableHost>
它解决了我在嵌套的 RecyclerView 和它的宿主 ViewPager2 之间的滚动冲突。