Android中的Viewpager控制器的速度变慢
有没有什么办法来减慢在android中的viewpager适配器的滚动速度?
你知道,我一直在看这个代码。 我搞不清楚我的东西是错的。
try{ Field mScroller = mPager.getClass().getDeclaredField("mScroller"); mScroller.setAccessible(true); Scroller scroll = new Scroller(cxt); Field scrollDuration = scroll.getClass().getDeclaredField("mDuration"); scrollDuration.setAccessible(true); scrollDuration.set(scroll, 1000); mScroller.set(mPager, scroll); }catch (Exception e){ Toast.makeText(cxt, "something happened", Toast.LENGTH_LONG).show(); }
它不会改变任何东西,但没有例外发生?
我已经开始使用HighFlyer的代码,它确实改变了mScroller字段(这是一个很好的开始),但是没有帮助延长滚动的持续时间,因为ViewPager在请求滚动时明确地将持续时间传递给mScroller。
扩展ViewPager不起作用的重要方法(smoothScrollTo)不能被覆盖。
我最终通过使用以下代码扩展Scroller来解决这个问题:
public class FixedSpeedScroller extends Scroller { private int mDuration = 5000; public FixedSpeedScroller(Context context) { super(context); } public FixedSpeedScroller(Context context, Interpolator interpolator) { super(context, interpolator); } public FixedSpeedScroller(Context context, Interpolator interpolator, boolean flywheel) { super(context, interpolator, flywheel); } @Override public void startScroll(int startX, int startY, int dx, int dy, int duration) { // Ignore received duration, use fixed one instead super.startScroll(startX, startY, dx, dy, mDuration); } @Override public void startScroll(int startX, int startY, int dx, int dy) { // Ignore received duration, use fixed one instead super.startScroll(startX, startY, dx, dy, mDuration); } }
像这样使用它:
try { Field mScroller; mScroller = ViewPager.class.getDeclaredField("mScroller"); mScroller.setAccessible(true); FixedSpeedScroller scroller = new FixedSpeedScroller(mPager.getContext(), sInterpolator); // scroller.setFixedDuration(5000); mScroller.set(mPager, scroller); } catch (NoSuchFieldException e) { } catch (IllegalArgumentException e) { } catch (IllegalAccessException e) { }
我基本上硬编码的持续时间为5秒,并使我的ViewPager使用它。
希望这可以帮助。
我想做自己,并已经达到了一个解决scheme(然而,使用反思)。 它与公认的解决scheme类似,但是使用相同的插入器,并且仅基于一个因素改变持续时间。 您需要在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 { Class<?> viewpager = ViewPager.class; Field scroller = viewpager.getDeclaredField("mScroller"); scroller.setAccessible(true); Field interpolator = viewpager.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)); } }
希望这可以帮助别人!
基于@ df778899的回答和Android ValueAnimator API ,我find了更好的解决scheme。 它工作正常,没有reflection,非常灵活。 也没有必要制作自定义ViewPager并将其放入android.support.v4.view包中。 这里是一个例子:
private void animatePagerTransition(final boolean forward) { ValueAnimator animator = ValueAnimator.ofInt(0, viewPager.getWidth() - ( forward ? viewPager.getPaddingLeft() : viewPager.getPaddingRight() )); 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); viewPager.beginFakeDrag(); animator.start(); }
正如你可以在ViewPager中看到的 ,由mScroller对象控制的持续时间。 在文献中,我们可能会读到:
滚动的持续时间可以在构造函数中传递,并指定滚动animation应该采用的最大时间
所以,如果你想控制速度,你可以通过reflection来改变mScroller对象。
你应该写这样的东西:
setContentView(R.layout.main); mPager = (ViewPager)findViewById(R.id.view_pager); Field mScroller = ViewPager.class.getDeclaredField("mScroller"); mScroller.setAccessible(true); mScroller.set(mPager, scroller); // initialize scroller object by yourself
这不是完美的解决scheme,你不能使速度变慢,因为它是一个int
。 但是对我而言,速度足够慢,我不必使用reflection。
注意class级在哪里。 smoothScrollTo
具有包可见性。
package android.support.v4.view; import android.content.Context; import android.util.AttributeSet; public class SmoothViewPager extends ViewPager { private int mVelocity = 1; public SmoothViewPager(Context context) { super(context); } public SmoothViewPager(Context context, AttributeSet attrs) { super(context, attrs); } @Override void smoothScrollTo(int x, int y, int velocity) { //ignore passed velocity, use one defined here super.smoothScrollTo(x, y, mVelocity); } }
ViewPager上的fakeDrag方法似乎提供了另一种解决scheme。
例如,这将从项目0到页面1:
rootView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { ViewPager pager = (ViewPager) getActivity().findViewById(R.id.pager); //pager.setCurrentItem(1, true); pager.beginFakeDrag(); Handler handler = new Handler(); handler.post(new PageTurner(handler, pager)); } }); private static class PageTurner implements Runnable { private final Handler handler; private final ViewPager pager; private int count = 0; private PageTurner(Handler handler, ViewPager pager) { this.handler = handler; this.pager = pager; } @Override public void run() { if (pager.isFakeDragging()) { if (count < 20) { count++; pager.fakeDragBy(-count * count); handler.postDelayed(this, 20); } else { pager.endFakeDrag(); } } } }
( count * count
只是为了使拖动速度加快)
我用过了
DecelerateInterpolator()
这是一个例子:
mViewPager = (ViewPager) findViewById(R.id.container); mViewPager.setAdapter(mSectionsPagerAdapter); Field mScroller = null; try { mScroller = ViewPager.class.getDeclaredField("mScroller"); mScroller.setAccessible(true); Scroller scroller = new Scroller(this, new DecelerateInterpolator()); mScroller.set(mViewPager, scroller); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); }
这是一个使用完全不同的方法的答案。 有人可能会说这有一个黑客的感觉; 但是,它不使用reflection,我会争辩说,它将始终工作。
我们在ViewPager.smoothScrollTo
find以下代码:
if (velocity > 0) { duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); } else { final float pageWidth = width * mAdapter.getPageWidth(mCurItem); final float pageDelta = (float) Math.abs(dx) / (pageWidth + mPageMargin); duration = (int) ((pageDelta + 1) * 100); } duration = Math.min(duration, MAX_SETTLE_DURATION);
它基于几件事来计算持续时间。 看到什么我们可以控制? mAdapter.getPageWidth
。 让我们在我们的适配器中实现ViewPager.OnPageChangeListener。 我们将检测ViewPager何时滚动,并给出宽度的假值 。 如果我想持续时间是k*100
,那么我将为getPageWidth
返回1.0/(k-1)
。 以下是适配器的这部分的Kotlin impl将持续时间变成400:
var scrolling = false override fun getPageWidth(position: Int): Float { return if (scrolling) 0.333f else 1f } // OnPageChangeListener to detect scroll state override fun onPageScrollStateChanged(state: Int) { scrolling = state == ViewPager.SCROLL_STATE_SETTLING } override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { } override fun onPageSelected(position: Int) { }
不要忘记将适配器添加为OnPageChangedListener。
我个人的情况可以安全地假设用户不能在页面之间滑动拖动。 如果在拖动之后必须支持解决,那么在计算中需要多做一些。
一个缺点是,这取决于ViewPager中硬编码的100
基本持续时间值。 如果这种变化,那么你的持续时间会随着这种方式而改变。
binding.vpTour.beginFakeDrag(); lastFakeDrag = 0; ValueAnimator va = ValueAnimator.ofInt(0, binding.vpTour.getWidth()); va.setDuration(1000); va.addUpdateListener(animation -> { if (binding.vpTour.isFakeDragging()) { int animProgress = (Integer) animation.getAnimatedValue(); binding.vpTour.fakeDragBy(lastFakeDrag - animProgress); lastFakeDrag = animProgress; } }); va.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); if (binding.vpTour.isFakeDragging()) { binding.vpTour.endFakeDrag(); } } }); va.start();
我想解决所有的问题,这是我解决同一个问题的方法,但我认为这样解决scheme更加灵活,因为您可以根据需要更改持续时间,并根据需要更改值的插值实现不同的视觉效果。 我的解决scheme从页面“1”滑动到页面“2”,所以只能增加位置,但可以通过执行“animProgress – lastFakeDrag”而不是“lastFakeDrag – animProgress”来轻松地进行减小位置。 我认为这是执行这个任务的最灵活的解决scheme。