Замедление скорости контроллера Viewpager в android
есть ли способ замедлить скорость прокрутки с помощью адаптера viewpager в android?
вы знаете, я смотрел на этот код. Я не могу понять, что я делаю неправильно.
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();
}
Это ничего не меняет, но никаких исключений не происходит?
9 ответов:
Я начал с кода HighFlyer, который действительно изменил поле mScroller (что является отличным началом), но не помог продлить продолжительность прокрутки, потому что ViewPager явно передает продолжительность в mScroller при запросе прокрутки.
расширение ViewPager не работает, поскольку важный метод (smoothScrollTo) не может быть переопределен.
Я закончил тем, что исправил это, расширив скроллер с помощью этого кода:
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 использовать его.
надеюсь, что это помогает.
Я хотел сделать сам и достиг решения (используя отражение, однако). Это похоже на принятое решение, но использует тот же интерполятор и только изменяет длительность на основе фактора. Вы должны использовать
ViewPagerCustomDurationв вашем XML вместо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. Он отлично работает без отражения и очень гибкий. Также нет необходимости делать пользовательский ViewPager и помещать его в android.поддержка.v4.просмотр пакета. Вот пример:
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(); }
Это не идеальное решение, вы не можете сделать скорость медленнее, потому что это
int. Но для меня это достаточно медленно, и мне не нужно использовать отражение.обратите внимание на пакет, где находится класс.
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, продолжительность броска контролируется объектом mScroller. В документацию мы можем читать:
продолжительность прокрутки может быть передана в конструкторе и указывает максимальное время, которое должна занять анимация прокрутки
Итак, если вы хотите контролировать скорость, вы можете изменить объект 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
методы fakeDrag на ViewPager, похоже, предоставляют альтернативное решение.
например, это будет страница от пункта 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(); } } } }(The
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(); }
вот ответ с использованием совершенно другого подхода. Кто-то может сказать, что это имеет хаки чувство; однако, он не использует отражение, и я бы сказал, что это всегда будет работать.
мы находим следующий код внутри
ViewPager.smoothScrollTo: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, тогда я вернусь1.0/(k-1)наgetPageWidth. Ниже приведен 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.
в моем конкретном случае можно с уверенностью предположить, что пользователь не может перетащить между страницами. Если вы должны поддерживать урегулирование после перетаскивания, то вам нужно сделать немного больше ваши расчеты.
один недостаток заключается в том, что это зависит от того, что жестко
100значение базовой длительности в ViewPager. Если это изменится, то ваша продолжительность изменится с этим подходом.
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();Я хотел решить ту же проблему, что и все вы, и это мое решение для той же проблемы, но я думаю, что таким образом решение является более гибким, поскольку вы можете изменить продолжительность, как вам нравится, а также изменить интерполяцию значений по желанию для достижения различных визуальных эффектов. Мое решение пролистывает со страницы " 1 "на страницу "2", поэтому только в увеличивающихся позициях, но может быть легко изменено, чтобы перейти в уменьшающиеся позиции, выполнив "animProgress-lastFakeDrag" вместо "lastFakeDrag-animProgress". Я думаю, что это самое гибкое решение для выполнения этой задачи.
Comments