如何禁止ViewPager在一个方向上滑动

35

我想让用户在ViewPager中只能从右向左滑动,因此一旦他通过了某一页,就无法回到该页。如何做到这一点?

我尝试了这个解决方案:

public class CustomViewPager extends ViewPager {

float lastX = 0;

boolean lockScroll = false;

public CustomViewPager(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public CustomViewPager(Context context) {
    super(context);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {

    switch (ev.getAction()) {
    case MotionEvent.ACTION_DOWN:
        lastX = ev.getX();
        lockScroll = false;
        return super.onTouchEvent(ev);
    case MotionEvent.ACTION_MOVE:

        if (lastX > ev.getX()) {
            lockScroll = false;
        } else {
            lockScroll = true;
        }

        lastX = ev.getX();
        break;
    }

    lastX = ev.getX();

    if(lockScroll) {
        return false;
    } else {
        return super.onTouchEvent(ev);
    }
}
}
但它仍然允许我向另一个方向滑动得不太好。

1
可能是One side ViewPager swiping only的重复问题。 - C--
1
@MartínMarconcini,你能展示一下你所说的代码片段吗?我应该在Activity还是ViewPager中添加这个监听器? - Emil Adz
1
@SubinSebastian,那里提出的解决方案是修改ViewPager类并将其整个引入您的项目。我正在寻找更简单的方法。 - Emil Adz
不,如果你想停止它的滚动,你必须子类化自己的ViewPager,并避免它向左移动...我需要再想一想。这可能不太容易。 - Martin Marconcini
1
今天得到了完全相同的任务。通过重写ViewPager.canScroll方法并在应禁用滚动时返回true来解决了问题。你可以在这里找到解决方案 - http://pastebin.com/sCcFu0Yn。它有一些问题,有时仍然允许滚动,但对我来说完全合适。如果有帮助,请告诉我,我会发布答案。 - Vladimir Mironov
显示剩余2条评论
4个回答

78

你错过的还有一个事件:onInterceptTouchEvent。它必须包含与onTouchEvent相同的逻辑。

我的完整解决方案基于这个答案。它将允许您在任何时候启用/禁用任何方向上的分页。

1. 创建枚举

 public enum SwipeDirection {
    ALL, LEFT, RIGHT, NONE ;
}

2.扩展ViewPager(使用Java)

public class CustomViewPager extends ViewPager {

    private float initialXValue;
    private SwipeDirection direction;

    public CustomViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.direction = SwipeDirection.ALL;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (this.isSwipeAllowed(event)) {
            return super.onTouchEvent(event);
        }

        return false;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (this.isSwipeAllowed(event)) {
            return super.onInterceptTouchEvent(event);
        }

        return false;
    }

    private boolean isSwipeAllowed(MotionEvent event) {
        if(this.direction == SwipeDirection.ALL) return true;

        if(direction == SwipeDirection.NONE )//disable any swipe
            return false;

        if(event.getAction()==MotionEvent.ACTION_DOWN) {
            initialXValue = event.getX();
            return true;
        }

        if (event.action === MotionEvent.ACTION_MOVE) {
            val diffX = event.x - initialXValue
            if (diffX > 0 && direction === SwipeDirection.RIGHT) {
                // swipe from left to right detected
                return false
            } else if (diffX < 0 && direction === SwipeDirection.LEFT) {
                // swipe from right to left detected
                return false
            }
        }

        return true;
    }

    public void setAllowedSwipeDirection(SwipeDirection direction) {
        this.direction = direction;
    }
}

2.扩展ViewPager(使用Kotlin)

enum class SwipeDirection {
    ALL, LEFT, RIGHT, NONE
}

class SingleDirectionViewPager @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet?
) : ViewPager(context, attrs) {

    private var initialXValue = 0f
    private var direction: SwipeDirection = SwipeDirection.ALL

    override fun onTouchEvent(event: MotionEvent): Boolean =
        if (isSwipeAllowed(event)) {
            super.onTouchEvent(event)
        } else {
            false
        }

    override fun onInterceptTouchEvent(event: MotionEvent): Boolean =
        if (isSwipeAllowed(event)) {
            super.onInterceptTouchEvent(event)
        } else {
            false
        }

    private fun isSwipeAllowed(event: MotionEvent): Boolean {
        if (direction == SwipeDirection.ALL) {
            return true
        }

        if (direction == SwipeDirection.NONE) {
            return false
        }

        if (event.action == MotionEvent.ACTION_DOWN) {
            initialXValue = event.x
            return true
        }

        if (event.action == MotionEvent.ACTION_MOVE) {
            val diffX = event.x - initialXValue

            if (diffX > 0 && direction === SwipeDirection.RIGHT) {
                // swipe from left to right detected
                return false
            } else if (diffX < 0 && direction === SwipeDirection.LEFT) {
                // swipe from right to left detected
                return false
            }
        }

        return true
    }

    fun setAllowedSwipeDirection(direction: SwipeDirection) {
        this.direction = direction
    }
}

3.在布局中使用您的viewPager

 <package_name.customviewpager 
     android:id="@+id/customViewPager" 
     android:layout_height="match_parent" 
     android:layout_width="match_parent" />

4.在代码中启用任意滑动方向。 默认情况下为所有(向右和向左)

mViewPager.setAllowedSwipeDirection(SwipeDirection.RIGHT);

使用我提供的示例,错误仍然存在,但是分页器将永远不允许用户返回到旧页面,当然您可以向一侧滑动,然后向另一侧滑动以查看旧页面,但是分页器将永远不会切换回旧页面,并将弹跳到选择的新页面。因此,实际上,您无法向后滑动到旧页面。 - JamisonMan111

2
package com.contacts_app.jamison.contacts__proprivacy4;

import android.content.Context;
import android.content.res.Resources;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

public class ViewPager_Settings extends ViewPager
{
private final String TAG = ViewPager_Settings.class.getSimpleName();
public float startX;

public ViewPager_Settings(Context context, AttributeSet attrs) {
    super(context, attrs);
}

 ////////////////////////////////////////////////////////////////////////////////////////////////
public static int dpTOpx(double dp)
{
    return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
}
public static int pxTOdp(double px)
{
    return (int) (px / Resources.getSystem().getDisplayMetrics().density);
}
////////////////////////////////////////////////////////////////////////////////////////////////

/*****DispatchTouchEvent for the View Pager to intercept and block swipes Right*****/
@Override
public boolean dispatchTouchEvent(MotionEvent ev)
{
    final int actionMasked = ev.getActionMasked() & MotionEvent.ACTION_MASK;
    //int movement_limit = pxTOdp(50);
    switch (actionMasked)
    {
        case (MotionEvent.ACTION_DOWN):
        {
            startX = ev.getX();
            Log.i(TAG, "startX: " + startX);

            /*Should always be this below*/
            return super.dispatchTouchEvent(ev);
        }
        case (MotionEvent.ACTION_MOVE):
        {
            Log.i(TAG, "ev.getX() - startX:" + (ev.getX() - startX));

            /*Switching directional changes would be a matter of flipping the  "<" sign in the line below.*/
            if (ev.getX() - startX > 0)
            {
                /*The result is that the ViewPager will not swipe from left*/
                ev.setAction(MotionEvent.ACTION_CANCEL);;
            }

            /*Should always be this below*/
            super.dispatchTouchEvent(ev);
        }
        /**The ACTION_UP case statement is only needed if you don't want to pass down the touch event 
        * to buttons that may receive the click after the swipe is blocked.*/
        /*case (MotionEvent.ACTION_UP):
        {
            //Log.i(TAG, "movement_limit: " + movement_limit);

            //(-50) may need to be changed to something more broader in scope to accompany all screen densities
            if ( (ev.getX() - startX) < (-50) )
            {
                ev.setAction(MotionEvent.ACTION_CANCEL);
            }

            //Should always be this below
            super.dispatchTouchEvent(ev);
        }*/
    }
    /*Should always be this below*/
    return super.dispatchTouchEvent(ev);
}
 ////////////////////////////////////////////////////////////////////////////////////////////////

}/*****END OF FILE*****/

不要忘记在顶部更改行,以放置您的应用程序的包名称。此外,如果你想调整代码,大多数(如果不是全部)的注释都会提供代码正在做什么的见解。


最简单的方法之一。 - AKJ

0

像这样定义您的适配器

public class MyFragmentStatePagerAdapter extends FragmentStatePagerAdapter {

    private final int totalPages = 10;
    private int currentPage = 0;

    public MyFragmentStatePagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        // Use whatever logic you want here to
        // to select a fragment based on
        // currentPage instead of position

        if (currentPage % 2 == 0) {
            return new Fragment1();
        } else {
            return new Fragment2();
        }
    }

    @Override
    public int getCount() {
        return currentPage == totalPages ? 1 : 2;
    }

    @Override
    public int getItemPosition(Object object){
        return PagerAdapter.POSITION_NONE;
    }

    public void nextPage() {
        currentPage++;
        notifyDataSetChanged();
    }
}

在使用视图翻页器的片段中,请执行以下操作。
@Override
public void onPageSelected(int arg0) {
    if (arg0 > 0) {
        pagerAdapter.nextPage();
        pager.setCurrentItem(0, false);
    }
}

请注意,在此处扩展FragmentStatePagerAdapter是一个错误,因为它假定其状态管理的项目是恒定的。最好扩展PagerAdapter,并适应来自FragmentStatePagerAdapter的代码以正确管理动态Fragment项的保存和恢复。 - corsair992
@corsair992,你能解释一下这个代码会破坏什么或者会导致什么不良行为吗? - mpkuth
这就是为什么我重写了 getItemPosition 方法,始终返回 POSITION_NONE。它告诉适配器应该重新创建所有内容,而不是假设没有任何变化。这个 看起来也同意我的想法,并且提供了一个更好的方法来实现覆盖,如果性能是一个问题的话。 - mpkuth
是的,我现在只是把两个东西放在一起。因此,如果适配器中使用的任何片段依赖于保存的实例状态,它将导致崩溃。当我测试这个时,我没有看到崩溃(诚然只是简单地测试了一下),所以你是否必须明确地在其中一个片段中使用保存的实例状态才能看到问题,还是我只是幸运? - mpkuth
如果View以一种安全的方式实现了状态恢复,那么它可能不会导致崩溃。然而,在滑动时,您肯定会将错误的状态恢复到Fragment中。您可以在博客文章中看到一些示例。实际上是否应用状态取决于Fragment结构和状态的相似程度。 - corsair992
显示剩余3条评论

-1
尝试添加(与onTouchEvent中相同的逻辑)
@Override
public boolean onInterceptTouchEvent(MotionEvent arg0) {
    // allow/ not allow swiping to switch between pages
    return !lockScroll ;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接