【问题标题】:ViewPager setCurrentItem(pageId, true) does NOT smoothscrollViewPager setCurrentItem(pageId, true) 不平滑滚动
【发布时间】:2020-06-23 07:30:01
【问题描述】:

我在 SDK 4.03、Samsung Infuse Android 2.2、Android 4 支持库上编译,并在我的应用程序中使用 ViewPager,实际滑动工作正常,但是当我这样做时

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

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

【问题讨论】:

  • 你的意思是support library by Android Compatibility Library 4
  • 是的,我会纠正的。谢谢
  • 您是在调用 setAdapter() 方法后立即调用 setCurrentItem 吗?如果是,那么您应该等到视图寻呼机正在实例化的所有视图都准备好。否则效果不好。
  • @JanMuller 你如何等到所有视图都准备好?

标签: android android-layout


【解决方案1】:

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

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*/);
        }
    }
}

【讨论】:

  • 唯一的问题就是移动的时候再刷一下就会停止,用户体验很差
  • 在发布版本中它进入最后一个屏幕
  • Pro-guard 影响此查看器
  • 有什么建议吗?
  • 如果您使用此解决方法和 ProGuard,您需要将其添加到您的 ProGuard 配置中:-keepclassmembers class android.support.v4.view.ViewPager { private android.widget.Scroller mScroller; }(复制自stackoverflow.com/questions/12009180/…
【解决方案2】:

这就是我所做的。我通过将我自己的自定义子类放在同一个包中来覆盖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);
    }
}

效果很好,如果您愿意,您可以计算并提供仅为 1 的实际速度 ISO。

【讨论】:

  • 我忘了说,如果它的实际弹射比速度非零,所以要区分实际弹射和`viewPager.setCurrentItem(id, true);`使用这个检查。
  • ViewPager类中没有smoothScrollTo(int,int,int)方法
  • 有! (如果您将 MyViewPager 放入 android.support.v4.view)
  • 这行得通。唯一需要注意的是,您必须将其放入 android.support.v4.view 包中才能覆盖包私有方法 smoothScrollTo
  • 这对我来说编译得很好,但似乎没有效果,滚动仍然几乎是即时的。
【解决方案3】:

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

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

希望这会有所帮助。

【讨论】:

  • 这个答案帮助我 +1 解决了它仅适用于某些布局的情况
  • 这似乎不起作用。我仍然看到视图立即出现,而不是被滚动到。
【解决方案4】:

我知道这个帖子已经很老了,但这是谷歌搜索结果中的佼佼者之一。我一直在反复讨论如何解决这个问题。上述解决方案都没有帮助我。但是,我确实找到了适合我的解决方案。

我的设置目前看起来像一个 viewpager 中的列表视图。当您单击其中一个视图时,它会创建一个新页面并滚动到该页面。之前很爽快,不过好像是因为我打电话的原因

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() 是我项目中的一个函数。它可能很有用,但很容易被滥用。
【解决方案5】:

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

该类覆盖setCurrentItem(int item, boolean smoothScroll) 并使用反射来尽可能保持此方法的原始性。关键是速度不为0。 你只需要用这个MyViewPager 类替换你当前的ViewPager 并像往常一样使用它:

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();
        }
    }
}

【讨论】:

    【解决方案6】:

    正如其他人后来发现的那样,这是一个错误,我们必须延迟发布,但它可以很简单:

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

    遗憾的是,您必须尝试第一批错误的答案才能解决这个问题...

    【讨论】:

      【解决方案7】:

      ViewPager 从第 5 版到第 9 版更改了很多。其中一些更改可能与您的问题有关:

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

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

      【讨论】:

      • 我仍然对 android-support-v4-rev11 有同样的问题
      • 你只是这样猜测和回答,你回答错了......多年后它仍然无法在最新版本中工作,所以显然需要其他一些解决方案......
      【解决方案8】:

      我遇到了同样的问题,但今天我找到了一个简单的解决方案。也许它会帮助你。 首先,假设我们有一个填满整个屏幕的 ViewPager。为了在页面之间切换,我创建了自己的带有标签的自定义视图并将其放在 ViewPager 上。单击选项卡应该使用 setCurrentItem(item, true) 平滑地滚动到 ViewPager 中的相应页面 - 但它会立即滚动,没有平滑!然后我尝试在 ViewPager + 回调上添加一个简单的按钮(不是自定义的):

      @Override
      public void onClick(View v) {
         viewPager.setCurrentItem(item, true);
      }
      

      在那之后,平滑的滚动开始工作了。因此,解决方案非常简单:在 Button 类内部,内部 boolean onTouch(...) 侦听器返回 true,因此它总是消耗触摸事件!然后,当我在内部布尔 onTouch(...) 侦听器中将“return false”替换为“return true”时,平滑滚动开始适用于我的自定义选项卡视图。

      希望我的成功故事可以帮助到你。

      【讨论】:

      • 这不是解决方案。最多是一个变通办法。但它不起作用。
      • 这不是一个解决方案,它甚至没有提供所需的滚动效果。
      【解决方案9】:

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

      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 实现

      【讨论】:

        【解决方案10】:

        在浪费了我一整天之后,我找到了一个解决方案,将 offscreenPageLimit 设置为 total no。页面。

        请关注https://stackoverflow.com/a/54800154/9097612

        【讨论】:

          【解决方案11】:

          注意这个变量mFirstLayout - viewpager 回调onAttachedToWindow(如recyclerview)时会设置为true,所以不会smoothScroll。您应该覆盖 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();
                      }
                  }
          

          【讨论】:

            【解决方案12】:

            下面的代码将暂停页面片刻(500 毫秒)然后滑动到下一个。

            import androidx.viewpager.widget.ViewPager;
            
            
            new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        viewPager.setCurrentItem(index, true);
                    }
                }, 500);
            

            【讨论】:

              【解决方案13】:

              试试这个代码:

              viewPager.postDelayed(new Runnable()
              {
                  @Override
                  public void run()
                  {
                      viewPager.setCurrentItem(id, true);
                  }
              }, 100);
              

              【讨论】:

              • 这不会使滚动平滑,它会延迟执行
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2013-05-05
              • 1970-01-01
              • 1970-01-01
              • 2017-01-15
              • 1970-01-01
              • 2012-12-22
              • 1970-01-01
              相关资源
              最近更新 更多