Скорость замедления контроллера Viewpager в андроиде
Есть ли способ замедлить скорость прокрутки с помощью адаптера 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) не может быть переопределен.
Я решил исправить это, расширив 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 использовать ее.
Надеюсь это поможет.
Я хотел сделать сам и достиг решения (используя рефлексию, однако). Это похоже на принятое решение, но использует тот же интерполятор и изменяет продолжительность только на основе коэффициента. Вам нужно использовать 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 и API-интерфейсе Android ValueAnimator. Он отлично работает без отражения и очень гибкий. Также нет необходимости создавать собственный 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. В документации мы можем прочитать:
Длительность прокрутки может быть передана в конструкторе и указывает максимальное время, которое анимация прокрутки должна занять
Итак, если вы хотите контролировать скорость, вы можете изменить объект 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
Это не идеальное решение, вы не можете замедлить скорость, потому что это 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);
}
}
Методы 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();
}
На основе принятого решения я создал класс kotlin с расширением для просмотра пейджера. Наслаждайтесь!:)
class ViewPageScroller : Scroller {
var fixedDuration = 1500 //time to scroll in milliseconds
constructor(context: Context) : super(context)
constructor(context: Context, interpolator: Interpolator) : super(context, interpolator)
constructor(context: Context, interpolator: Interpolator, flywheel: Boolean) : super(context, interpolator, flywheel)
override fun startScroll(startX: Int, startY: Int, dx: Int, dy: Int, duration: Int) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, fixedDuration)
}
override fun startScroll(startX: Int, startY: Int, dx: Int, dy: Int) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, fixedDuration)
}
}
fun ViewPager.setViewPageScroller(viewPageScroller: ViewPageScroller) {
try {
val mScroller: Field = ViewPager::class.java.getDeclaredField("mScroller")
mScroller.isAccessible = true
mScroller.set(this, viewPageScroller)
} catch (e: NoSuchFieldException) {
} catch (e: IllegalArgumentException) {
} catch (e: IllegalAccessException) {
}
}
Вот ответ, использующий совершенно другой подход. Кто-то может сказать, что это имеет хакерское чувство; однако, это не использует отражение, и я бы сказал, что это всегда будет работать.
Мы находим следующий код внутри 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". Я думаю, что это наиболее гибкое решение для выполнения этой задачи.