Замедление скорости контроллера 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();
}


Это ничего не меняет, но никаких исключений не происходит?

304   9  

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

    Ничего не найдено.