ViewPager setCurrentItem(pageId, true) 无法平滑滚动。

51

我正在使用SDK 4.03、三星Infuse Android 2.2和Android 4支持库进行编译,并在我的应用程序中使用ViewPager,实际的滑动效果很好,但当我执行

viewPager.setCurrentItem(id); // , or
viewPager.setCurrentItem(id, true);  

它不会平稳滚动,而是立即切换视图。尽管文档清楚地说明了将第二个参数设置为true的目的,但是这是怎么回事?


你是指 Android Compatibility Library 4 中的 support library 吗? - iTurki
2
你是否在调用setAdapter()方法后立即调用了setCurrentItem?如果是的话,那么你应该等待所有视图都准备好,才能正常工作。否则它将无法正常工作。 - Jan Muller
4
@JanMuller 你如何等待直到所有视图都准备好? - Flimm
请查看此链接:http://jerryjobsguo.blogspot.com/2014/06/how-to-change-viewpager-scroll.html - Sumit Shukla
13个回答

43

我通过创建一个MyViewPager类,并使用反射覆盖ViewPager.mScroller来解决了这个问题。

public class MyViewPager extends ViewPager {

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

    private void setMyScroller() {
        try {
            Class<?> viewpager = ViewPager.class;
            Field scroller = viewpager.getDeclaredField("mScroller");
            scroller.setAccessible(true);
            scroller.set(this, new MyScroller(getContext()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public class MyScroller extends Scroller {
        public MyScroller(Context context) {
            super(context, new DecelerateInterpolator());
        }

        @Override
        public void startScroll(int startX, int startY, int dx, int dy, int duration) {
            super.startScroll(startX, startY, dx, dy, 1000 /*1 secs*/);
        }
    }
}

7
唯一的问题是如果您在移动过程中再次滑动,它将停止,并且用户体验会变得糟糕。 - LiangWang
在发布版本中,它会跳转到最后一个屏幕。 - Kalanidhi
Pro-guard 影响了这个 ViewPager。 - Kalanidhi
有什么建议吗? - Kalanidhi
如果您使用此解决方法和ProGuard,则需要将以下内容添加到ProGuard配置中:-keepclassmembers class android.support.v4.view.ViewPager { private android.widget.Scroller mScroller; }(摘自https://dev59.com/dWct5IYBdhLWcg3wjuAj#15549865) - Marc Van Daele

17

我在一个处理程序中调用了setCurrentItem函数,对我来说它运行得很好。

new Handler().post(new Runnable() {
        @Override
        public void run() {
            myViewPager.setCurrentItem(1, true);
        }
    });

希望这能有所帮助。


这个答案帮助了我解决一个问题,对于某些布局它之前无法正常工作。 - voytez
1
这似乎不起作用。我仍然看到视图立即出现,而不是滚动到视图。 - VIN

16

这是我的做法。我通过将自定义的子类放在同一个包中,覆盖了ViewPager中的包私有方法smoothScrollTo。它被传递了一个零值,这导致了捕捉行为而不是平滑滚动。

package android.support.v4.view;

import android.content.Context;
import android.util.AttributeSet;

public class MyViewPager extends ViewPager {

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

    public MyViewPager(Context context, AttributeSet attr) {
        super(context, attr);
    }

    void smoothScrollTo(int x, int y, int velocity) {
        super.smoothScrollTo(x, y, 1);
    }
}

它非常有效,如果您想的话,可以计算并提供实际速度ISO,只需为1。


我忘了提到,如果是实际的fling,则速度为非零,因此要区分实际的fling和viewPager.setCurrentItem(id, true);,请使用此检查。 - tegbird
2
ViewPager类中没有smoothScrollTo(int,int,int)方法。 - heyjii
2
有的!(如果你把你的MyViewPager放在android.support.v4.view中) - Marc Van Daele
3
这段代码在我的机器上可以编译成功,但似乎没有任何作用,滚动仍然几乎瞬间完成。 - user291701
@Epicality,你可以像将其他类放入包中一样来放置它。因此,在这种情况下,你需要将它放在android/support/v4/而不是com/example/app/中,并且在你的类的包行中写上package android.support.v4; - Mikael Ohlson
显示剩余2条评论

6
我知道这个帖子很老了,但它是谷歌搜索结果中排名靠前的之一。我已经反复思考如何解决这个问题很长一段时间了。上面的解决方案都没有对我有所帮助。然而,我找到了一个适合我的解决方案。
目前我的设置是在viewpager内部放置了一个listview。当您单击其中一个视图时,它会创建一个新页面并滚动到该页面。这在之前非常迅速,但现在似乎不是这样,因为我调用了...
mViewPager.setCurrentItem(intIndex, true);

从我的OnClickEvent内部调用ViewPager似乎有些问题,因此我编写了此函数。它创建一个在UI线程上运行runnable的新线程。这个runnable会告诉ViewPager滚动到某个特定的项。

public static void scrollToIndex(int index) {

    final int intIndex = index;

    //We're going to make another thread to separate ourselves from whatever
    //thread we are in 
    Thread sepThread = new Thread(new Runnable(){

        public void run()
        {
            //Then, inside that thread, run a runnable on the ui thread.
            //MyActivity.getContext() is a static function that returns the 
            //context of the activity. It's useful in a pinch.
            ((Activity)MyActivity.getContext()).runOnUiThread(new Runnable(){

                @Override
                public void run() {

                    if (mSlidingTabLayout != null)
                    {
                        //I'm using a tabstrip with this as well, make sure
                        //the tabstrip is updated, or else it won't scroll
                        //correctly
                        if(mSlidingTabLayout.getTabStripChildCount() <= intIndex)
                        mSlidingTabLayout.updateTabStrip();

                        //Inside this thread is where you call setCurrentItem
                        mViewPager.setCurrentItem(intIndex, true);

                    }

                }

            });

         }
    });


    sepThread.start();


}

我希望这篇文章能够帮到你。祝你好运!

编辑:重新审视我的回答,我相信你只需要在UI线程上运行它,它应该可以工作。但请注意,我还没有测试过。


啊,是的,getContext() 是我项目中的一个函数。它可能很有用,但容易被滥用。 - Grant Oberhauser

2

我创建了这个类,因为我对上述解决方案并不完全满意。

该类重写了setCurrentItem(int item, boolean smoothScroll)方法,并使用反射尽可能保持此方法的原始性。关键在于速度不为0。

您只需将当前的ViewPager替换为此MyViewPager类,并像通常一样使用它:

import android.content.Context;
import android.util.AttributeSet;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import androidx.viewpager.widget.ViewPager;

public class MyViewPager extends ViewPager {

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

    /**
     * Set the currently selected page. If the ViewPager has already been through its first
     * layout with its current adapter there will be a smooth animated transition between
     * the current item and the specified item.
     *
     * @param item         Item index to select
     * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
     */
    @Override
    public void setCurrentItem(int item, boolean smoothScroll) {
        final Class<?> viewpager = ViewPager.class;
        int velocity = 1;

        try {
            Field mPopulatePending = viewpager.getDeclaredField("mPopulatePending");
            mPopulatePending.setAccessible(true);
            mPopulatePending.set(this, false);

            Method setCurrentItemInternal = viewpager.getDeclaredMethod("setCurrentItemInternal", int.class, boolean.class, boolean.class, int.class);
            setCurrentItemInternal.setAccessible(true);
            setCurrentItemInternal.invoke(this, item, smoothScroll, false, velocity);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

1

后来其他人发现,这是一个bug,我们必须延迟发布它,但解决方法可能非常简单:

pager.post {
    pager.setCurrentItem(1, true)
}

不幸的是,您必须先尝试一堆错误的答案才能到达这个...


0
请注意这个变量mFirstLayout - 当viewpager回调onAttachedToWindow(例如在recyclerview上),它将被设置为true,因此不会平滑滚动。您应该重写onAttachedToWindow来控制mFirstLayout变量。类似于这样的代码:

        @Override
        protected void onAttachedToWindow() {
            super.onAttachedToWindow();
            try {
                Field mFirstLayout = ViewPager.class.getDeclaredField("mFirstLayout");
                mFirstLayout.setAccessible(true);
                mFirstLayout.set(this, false);
                getAdapter().notifyDataSetChanged();
                setCurrentItem(getCurrentItem());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

0

对于那些使用Xamarin的人(尽管这种方法也适用于Java),我建议使用上面答案中基于Android Support Library v7 AppCompat 19.1.0的下一步方法:

public void ScrollSmooth(int i)
{
    var jClass = JNIEnv.GetObjectClass(_pager.Handle);
    var jMethod = JNIEnv.GetMethodID(jClass, "setCurrentItemInternal", "(IZZI)V");
    JNIEnv.CallVoidMethod (_pager.Handle, jMethod, new JValue (i), new JValue (true), new JValue (false), new JValue (1));
}

它依赖于http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/support/v4/view/ViewPager.java中的ViewPager实现。


0

ViewPager从版本5到9进行了大量的更改。以下是一些可能与您的问题相关的更改:

  • 修复了ViewPager中用户界面行为的错误(版本5和6)
  • 修复了ViewPager的许多错误(版本9)

考虑到您的代码应该可以正常工作,我最好的猜测是您的支持库不是最新的。如果不是,请尝试更新库。


我在使用android-support-v4-rev11时仍然遇到了相同的问题。 - Zsolt Safrany
你只是这样猜测并回答,结果回答错误了...即使过了几年,它在最新版本中仍然对我不起作用,所以显然需要其他解决方案... - Renetik

0

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