【问题标题】:Change ViewPager animation duration when sliding programmatically以编程方式滑动时更改 ViewPager 动画持续时间
【发布时间】:2020-04-05 05:47:58
【问题描述】:

我正在使用以下代码更改幻灯片:

viewPager.setCurrentItem(index++, true);

但它变化太快了。有没有办法手动设置动画速度?

【问题讨论】:

  • 不确定您的搜索结果如何,但请查看此处的this answer。这应该正是您所追求的。
  • 实际上这与我的解决方案非常相似,尽管我使用了一个因子而不是绝对持续时间(因为在 ViewPager 中,持续时间可能取决于您滚动浏览的页面数)。

标签: android animation android-viewpager


【解决方案1】:

我一直想做自己并获得了解决方案(但是使用反射)。 我还没有对其进行测试,但它应该可以工作或需要最少的修改。在 Galaxy Nexus JB 4.2.1 上进行了测试。您需要在 XML 中使用 ViewPagerCustomDuration 而不是 ViewPager,然后您可以这样做:

ViewPagerCustomDuration vp = (ViewPagerCustomDuration) findViewById(R.id.myPager);
vp.setScrollDurationFactor(2); // make the animation twice as slow

ViewPagerCustomDuration.java

import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.animation.Interpolator;

import java.lang.reflect.Field;

public class ViewPagerCustomDuration extends ViewPager {

    public ViewPagerCustomDuration(Context context) {
        super(context);
        postInitViewPager();
    }

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

    private ScrollerCustomDuration mScroller = null;

    /**
     * Override the Scroller instance with our own class so we can change the
     * duration
     */
    private void postInitViewPager() {
        try {
            Field scroller = ViewPager.class.getDeclaredField("mScroller");
            scroller.setAccessible(true);
            Field interpolator = ViewPager.class.getDeclaredField("sInterpolator");
            interpolator.setAccessible(true);

            mScroller = new ScrollerCustomDuration(getContext(),
                    (Interpolator) interpolator.get(null));
            scroller.set(this, mScroller);
        } catch (Exception e) {
        }
    }

    /**
     * Set the factor by which the duration will change
     */
    public void setScrollDurationFactor(double scrollFactor) {
        mScroller.setScrollDurationFactor(scrollFactor);
    }

}

ScrollerCustomDuration.java

import android.annotation.SuppressLint;
import android.content.Context;
import android.view.animation.Interpolator;
import android.widget.Scroller;

public class ScrollerCustomDuration extends Scroller {

    private double mScrollFactor = 1;

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

    public ScrollerCustomDuration(Context context, Interpolator interpolator) {
        super(context, interpolator);
    }

    @SuppressLint("NewApi")
    public ScrollerCustomDuration(Context context, Interpolator interpolator, boolean flywheel) {
        super(context, interpolator, flywheel);
    }

    /**
     * Set the factor by which the duration will change
     */
    public void setScrollDurationFactor(double scrollFactor) {
        mScrollFactor = scrollFactor;
    }

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

}

希望这对某人有所帮助!

【讨论】:

  • 您的解决方案是迄今为止我发现的最好的解决方案,但是当行“scroller.set(this, mScroller);”时执行时,出现错误“IllegalArgumentException:字段值无效”。我猜这是因为使用了 ViewPager 子类...有什么解决办法吗?
  • 另外,如何使用 var ViewPagerCustomDuration .mScroller ?它似乎没有链接到任何东西......
  • 我会稍微研究一下。这应该是可能的。此外,mScroller 仅由 ViewPager 在内部使用。
  • 嗯我测试了这个对我有用;我不太清楚你为什么收到IllegalArgumentException
  • 在 setScrollDurationFactor this 上出现空指针错误,当 proguard 启用 android 时
【解决方案2】:

我找到了更好的解决方案,基于 @df778899's answerAndroid ValueAnimator API。它无需反射即可正常工作,并且非常灵活。 也不需要制作自定义 ViewPager 并将其放入 android.support.v4.view 包中。 这是一个例子:

private void animatePagerTransition(final boolean forward) {

    ValueAnimator animator = ValueAnimator.ofInt(0, viewPager.getWidth());
    animator.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) {
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            viewPager.endFakeDrag();
        }

        @Override
        public void onAnimationCancel(Animator animation) {
            viewPager.endFakeDrag();
        }

        @Override
        public void onAnimationRepeat(Animator animation) {
        }
    });

    animator.setInterpolator(new AccelerateInterpolator());
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

        private int oldDragPosition = 0;

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            int dragPosition = (Integer) animation.getAnimatedValue();
            int dragOffset = dragPosition - oldDragPosition;
            oldDragPosition = dragPosition;
            viewPager.fakeDragBy(dragOffset * (forward ? -1 : 1));
        }
    });

    animator.setDuration(AppConstants.PAGER_TRANSITION_DURATION_MS);
    if (viewPager.beginFakeDrag()) {
        animator.start();
    }
}

更新:

刚刚检查了此解决方案是否可用于一次滑动多个页面(例如,是否应在最后一页之后显示第一页)。这是处理指定页数的稍微修改的代码:

private int oldDragPosition = 0;

private void animatePagerTransition(final boolean forward, int pageCount) {
    // if previous animation have not finished we can get exception
    if (pagerAnimation != null) {
        pagerAnimation.cancel();
    }
    pagerAnimation = getPagerTransitionAnimation(forward, pageCount);
    if (viewPager.beginFakeDrag()) {    // checking that started drag correctly
        pagerAnimation.start();
    }
}

private Animator getPagerTransitionAnimation(final boolean forward, int pageCount) {
    ValueAnimator animator = ValueAnimator.ofInt(0, viewPager.getWidth() - 1);
    animator.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) {
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            viewPager.endFakeDrag();
        }

        @Override
        public void onAnimationCancel(Animator animation) {
            viewPager.endFakeDrag();
        }

        @Override
        public void onAnimationRepeat(Animator animation) {
            viewPager.endFakeDrag();
            oldDragPosition = 0;
            viewPager.beginFakeDrag();
        }
    });

    animator.setInterpolator(new AccelerateInterpolator());
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            int dragPosition = (Integer) animation.getAnimatedValue();
            int dragOffset = dragPosition - oldDragPosition;
            oldDragPosition = dragPosition;
            viewPager.fakeDragBy(dragOffset * (forward ? -1 : 1));
        }
    });

    animator.setDuration(AppConstants.PAGER_TRANSITION_DURATION_MS / pageCount); // remove divider if you want to make each transition have the same speed as single page transition
    animator.setRepeatCount(pageCount);

    return animator;
}

【讨论】:

  • 所以它会假拖动?似乎是一个聪明的方法来实现它,但是如果用户在这个操作过程中开始拖动会发生什么?另外,如果我想让它滚动到第一页怎么办(因为当它到达最后一页时,我希望能够让它再次到达第一页)?
  • @androiddeveloper,我已经将此解决方案与 NonSwipeableViewPager (stackoverflow.com/a/9650884/2923816) 一起使用。所以我在假拖动期间没有用户交互问题。但是如果有人需要保持滑动手势,可以通过覆盖 ViewPager 并在用户触摸 ViewPager 时控制动画来实现。我没有尝试过,但我认为这是可能的并且应该可以正常工作。对于循环寻呼机,您应该调整动画代码,但调整取决于循环的具体实现。
  • 关于用户交互,好的(因为动画没那么慢,我不介意)。关于滚动到第一页,我不明白。假设只有 3 页,而我在最后一页,如何使用您设置的自定义持续时间使其滚动到第一页?使用动画师的重复属性会有帮助吗?
  • @androiddeveloper 是的,您可以使用重复属性。如果您想在与通常的页面转换相同的时间返回到第一页,您还应该更改此返回动画的转换持续时间。
  • 我不明白。您能否修改代码以显示如何执行此操作?也许代替“前进”有一个整数,表示要走多少页(如果为负,则向后走)?
【解决方案3】:
 public class PresentationViewPager extends ViewPager {

    public static final int DEFAULT_SCROLL_DURATION = 250;
    public static final int PRESENTATION_MODE_SCROLL_DURATION = 1000;

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

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

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

    public class OwnScroller extends Scroller {

        private int durationScrollMillis = 1;

        public OwnScroller(Context context, int durationScroll) {
            super(context, new DecelerateInterpolator());
            this.durationScrollMillis = durationScroll;
        }

        @Override
        public void startScroll(int startX, int startY, int dx, int dy, int duration) {
            super.startScroll(startX, startY, dx, dy, durationScrollMillis);
        }
    }
}

【讨论】:

    【解决方案4】:

    更好的解决方案是通过在支持包中创建类来简单地访问私有字段。 EDIT 这绑定到MAX_SETTLE_DURATION 600ms,由ViewPagerclass 设置。

    package android.support.v4.view;
    
    import android.content.Context;
    import android.util.AttributeSet;
    
    public class SlowViewPager extends ViewPager {
    
        // The speed of the scroll used by setCurrentItem()
        private static final int VELOCITY = 200;
    
        public SlowViewPager(Context context) {
            super(context);
        }
    
        public SlowViewPager(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
            setCurrentItemInternal(item, smoothScroll, always, VELOCITY);
        }
    
    }
    

    当然,您可以添加一个自定义属性,以便可以通过 XML 进行设置。

    【讨论】:

    • 谢谢,这似乎确实有效,但是即使将 VELOCITY 设置为 1 滚动仍然很快恕我直言,我希望我可以进一步减慢它。
    • @HaggleLad 是的,不幸的是,我的方法被绑定到 600 毫秒的最大动画持续时间。如果您需要更长的时间,则需要使用不同的方法。我会更新我的答案来说明这一点。
    • setCurrentItemInternal 不是私有方法吗?
    • @MartyMiller 是的,但是如果支持库是您项目的一部分,您可以在它的包中创建一个类。
    • 编辑支持库不是一种有效的方式,如果我决定升级它,这段代码将无法工作,而且在我个人的情况下,我没有支持库的源代码(并且不会编辑它如果我有)
    【解决方案5】:

    这是我在Librera Reader中使用的代码

    public class MyViewPager extends ViewPager {
    
      public MyViewPager(Context context, AttributeSet attrs) {
            super(context, attrs);
            initMyScroller();
        }
    
        private void initMyScroller() {
            try {
                Class<?> viewpager = ViewPager.class;
                Field scroller = viewpager.getDeclaredField("mScroller");
                scroller.setAccessible(true);
                scroller.set(this, new MyScroller(getContext())); // my liner scroller
    
                Field mFlingDistance = viewpager.getDeclaredField("mFlingDistance");
                mFlingDistance.setAccessible(true);
                mFlingDistance.set(this, Dips.DP_10);//10 dip
    
                Field mMinimumVelocity = viewpager.getDeclaredField("mMinimumVelocity");
                mMinimumVelocity.setAccessible(true);
                mMinimumVelocity.set(this, 0); //0 velocity
    
            } catch (Exception e) {
                LOG.e(e);
            }
    
        }
    
        public class MyScroller extends Scroller {
            public MyScroller(Context context) {
                super(context, new LinearInterpolator()); // my LinearInterpolator
            }
    
            @Override
            public void startScroll(int startX, int startY, int dx, int dy, int duration) {
                super.startScroll(startX, startY, dx, dy, 175);//175 duration
            }
        }
    
     }
    

    【讨论】:

      【解决方案6】:

      我使用 Cicero Moura 的版本制作了一个在 Android 10 中仍然可以完美运行的 Kotlin 类。

      import android.content.Context
      import android.util.AttributeSet
      import android.view.MotionEvent
      import android.view.animation.DecelerateInterpolator
      import android.widget.Scroller
      import androidx.viewpager.widget.ViewPager
      
      class CustomViewPager(context: Context, attrs: AttributeSet) :
              ViewPager(context, attrs) {
      
      
          private companion object {
              const val DEFAULT_SPEED = 1000
          }
      
          init {
              setScrollerSpeed(DEFAULT_SPEED)
          }
      
          var scrollDuration = DEFAULT_SPEED
              set(millis) {
                  setScrollerSpeed(millis)
              }
      
          private fun setScrollerSpeed(millis: Int) {
              try {
                  ViewPager::class.java.getDeclaredField("mScroller")
                          .apply {
                              isAccessible = true
                              set(this@CustomViewPager, OwnScroller(millis))
                          }
      
              } catch (e: Exception) {
                  e.printStackTrace()
              }
          }
      
          inner class OwnScroller(private val durationScrollMillis: Int) : Scroller(context, AccelerateDecelerateInterpolator()) {
              override fun startScroll(startX: Int, startY: Int, dx: Int, dy: Int, duration: Int) {
                  super.startScroll(startX, startY, dx, dy, durationScrollMillis)
              }
          }
      }
      

      从活动类初始化:

      viewPager.apply {
          scrollDuration = 2000
          adapter = pagerAdapter
      }
      

      【讨论】:

        【解决方案7】:

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

        为了保持恒定长度 ViewPager 滚动流畅,setOffScreenLimit(page.length) 会将所有视图保留在内存中。但是,这对于任何涉及调用 View.requestLayout 函数的动画(例如,任何涉及更改边距或边界的动画)都会造成问题。这使它们变得非常慢(根据 Romain Guy 的说法),因为内存中的所有视图也将失效。所以我尝试了几种不同的方法来使事情变得顺利,但是重写 requestLayout 和其他无效方法会导致许多其他问题。

        一个很好的折衷方案是动态修改屏幕外限制,以便页面之间的大部分滚动将非常流畅,同时通过删除用户时的视图来确保所有页面内动画都流畅。当您只有 1 或 2 个视图必须使其他视图脱离内存时,这非常有效。

        ***当没有任何解决方案有效时使用此选项,因为通过设置偏移限制,您将同时加载所有片段

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-01-18
          • 2019-12-21
          • 1970-01-01
          • 2012-12-22
          • 2021-10-19
          相关资源
          最近更新 更多