Slowing speed of Viewpager controller in android

2019-01-02 17:01发布

Is there any way to slow the scroll speed with the viewpager adaptor in android?


You know, I've been looking at this code. I can't figure out what I'm dong wrong.

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();
} 

It doesn't change anything yet no exceptions occur?

标签: android
10条回答
梦寄多情
2楼-- · 2019-01-02 17:30

I have used

DecelerateInterpolator()

Here is the example:

  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();
            }
查看更多
谁念西风独自凉
3楼-- · 2019-01-02 17:30

Here's an answer using an entirely different approach. Someone might say this has a hacky feel; however, it doesn't use reflection, and I would argue that it will always work.

We find the following code inside 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);

It calculates the duration based on a few things. See anything we can control? mAdapter.getPageWidth. Let's implement ViewPager.OnPageChangeListener in our adapter. We're going to detect when the ViewPager is scrolling, and give a fake value for width. If I want the duration to be k*100, then I will return 1.0/(k-1) for getPageWidth. The following is Kotlin impl of this part of the adapter which turns the duration into 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) {
    }

Don't forget to add the adapter as an OnPageChangedListener.

My particular case can safely assume that the user can't swipe to drag between pages. If you have to support settling after a drag, then you need to do a bit more in your calculation.

One downside is that this depends on that hardcoded 100 base duration value in ViewPager. If that changes, then your durations change with this approach.

查看更多
春风洒进眼中
4楼-- · 2019-01-02 17:30
                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();

I wanted to solve the same issue as all of you and this is my solution for the same problem, but I think this way the solution is more flexible as you can change the duration however you like and also change the interpolation of the values as desired to achieve different visual effects. My solution swipes from page "1" to page "2" , so only in increasing positions, but can easily be changed to go in decreasing positions by doing "animProgress - lastFakeDrag" instead of "lastFakeDrag - animProgress". I think this is the most flexible solution for performing this task.

查看更多
何处买醉
5楼-- · 2019-01-02 17:32

I have found better solution, based on @df778899's answer and the Android ValueAnimator API. It works fine without reflection and is very flexible. Also there is no need for making custom ViewPager and putting it into android.support.v4.view package. Here is an example:

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();
}
查看更多
登录 后发表回答