Tốc độ chậm của bộ điều khiển Viewpager trong Android


104

Có cách nào để làm chậm tốc độ cuộn với bộ điều hợp viewpager trong Android không?


Bạn biết đấy, tôi đã xem mã này. Tôi không thể tìm ra những gì tôi đồng sai.

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

Nó không thay đổi bất cứ điều gì nhưng không có ngoại lệ xảy ra?



1
child.setNestedScrollingEnabled(false);đã làm việc cho tôi
Pierre

Câu trả lời:


230

Tôi đã bắt đầu với mã của HighFlyer thực sự đã thay đổi trường mScroller (đây là một khởi đầu tuyệt vời) nhưng không giúp kéo dài thời lượng của cuộn vì ViewPager chuyển rõ ràng thời lượng cho mScroller khi yêu cầu cuộn.

Việc mở rộng ViewPager không hoạt động vì không thể ghi đè phương thức quan trọng (SmoothScrollTo).

Tôi đã kết thúc việc sửa lỗi này bằng cách mở rộng Scroller với mã này:

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

Và sử dụng nó như thế này:

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) {
}

Về cơ bản, tôi đã mã hóa thời lượng thành 5 giây và làm cho ViewPager của tôi sử dụng nó.

Hi vọng điêu nay co ich.


16
bạn có thể vui lòng cho tôi biết sInterpolator là gì không ??
Shashank Degloorkar

10
'Bộ nội suy' là tốc độ chạy các hoạt ảnh, ví dụ: chúng có thể tăng tốc, giảm tốc và hơn thế nữa. Có một hàm tạo mà không có bộ nội suy, tôi nghĩ điều đó sẽ hoạt động, vì vậy chỉ cần cố gắng xóa đối số đó khỏi cuộc gọi. Nếu đó không phải làm việc, thêm: 'xen vào sInterpolator = AccelerateInterpolator mới ()'
marmor

8
Tôi thấy DecelerateInterpolator hoạt động tốt hơn trong trường hợp này, nó duy trì tốc độ vuốt ban đầu và sau đó giảm tốc từ từ.
Quint Stoffers

Xin chào, bạn có biết tại sao scroller.setFixedDuration(5000);lại báo lỗi không? Nó nói "Phương pháp setFixedDuration (int) là không xác định"
jaibatrik

6
Nếu bạn đang sử dụng Proguard đừng quên thêm dòng này: -keep lớp android.support.v4 ** {*;} nếu không bạn sẽ nhận được con trỏ null khi getDeclaredField ().
GuilhE

61

Tôi muốn tự mình làm và đã đạt được giải pháp (tuy nhiên, sử dụng sự phản chiếu). Nó tương tự như giải pháp được chấp nhận nhưng sử dụng cùng một bộ nội suy và chỉ thay đổi thời lượng dựa trên một yếu tố. Bạn cần sử dụng một ViewPagerCustomDurationtrong XML của mình thay vì ViewPager, và sau đó bạn có thể làm điều này:

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

}

Hy vọng điều này sẽ giúp ai đó!


3
Tôi thích giải pháp này hơn câu trả lời được chấp nhận bởi vì ViewPager tùy chỉnh là một lớp bổ sung với những thay đổi tối thiểu đối với mã hiện có. Việc có con lăn tùy chỉnh làm lớp bên trong tĩnh của ViewPager tùy chỉnh làm cho nó được đóng gói nhiều hơn.
Emanuel Moecklin

1
Nếu bạn đang sử dụng Proguard đừng quên thêm dòng này: -keep lớp android.support.v4 ** {*;} nếu không bạn sẽ nhận được con trỏ null khi getDeclaredField ().
GuilhE

Tôi khuyên bạn nên cập nhật lên hệ số ba, bởi vì bạn không muốn cho phép bất kỳ ai vượt qua hệ số 0, điều này sẽ phá vỡ thời gian của bạn. Cập nhật tham số cuối cùng mà bạn đang chuyển vào mScrollFactor> 0? (int) (thời lượng * mScrollFactor): thời lượng
portfoliobuilder

Tôi cũng khuyên bạn nên kiểm tra null khi đặt yếu tố cuộn trong lớp của bạn, ViewPagerCustomDuration. Bạn khởi tạo với mScroller dưới dạng một đối tượng null, bạn nên xác nhận rằng nó vẫn không null khi cố gắng đặt hệ số cuộn trên đối tượng đó.
portfoliobuilder

@portfoliobuilder nhận xét hợp lệ, mặc dù có vẻ như bạn thích phong cách lập trình phòng thủ hơn, đó là IMO phù hợp với các thư viện không rõ ràng hơn là một đoạn mã trên SO. Đặt hệ số cuộn thành <= 0 cũng như gọi setScrollDurationFactor trên chế độ xem không khởi tạo là điều mà hầu hết các nhà phát triển Android có thể tránh làm mà không cần kiểm tra thời gian chạy.
Oleg Vaskevich

43

Tôi đã tìm ra giải pháp tốt hơn, dựa trên câu trả lời của @ df778899API ValueAnimator của Android . Nó hoạt động tốt mà không cần phản chiếu và rất linh hoạt. Ngoài ra, không cần tạo ViewPager tùy chỉnh và đưa nó vào gói android.support.v4.view. Đây là một ví dụ:

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

7
Tôi nghĩ rằng điều này được đánh giá thấp. Nó tận dụng startFakeDragendFakeDragcó nghĩa là để làm nhiều hơn những gì có sẵn trong ViewPagerhộp. Đây hoạt động tuyệt vời đối với tôi và là chính xác những gì tôi đang tìm kiếm
user3280133

1
Đó là một giải pháp tuyệt vời, cảm ơn bạn. Tuy nhiên, một cải tiến nhỏ: để làm cho nó hoạt động cho một viewPager có đệm, dòng đầu tiên phải là ... ValueAnimator.ofInt (0, viewPager.getWidth () - (forward? ViewPager.getPaddingLeft (): viewPager.getPaddingRight () ));
DmitryO.

1
điều này là bởi đến nay các đơn giản nhất / effcient / giải pháp chịu trách nhiệm vì nó không quan tâm đến suy nghĩ và tránh vi phạm niêm phong như các giải pháp trên
Ryan

2
@lobzic, Chính xác thì bạn gọi animatePagerTransition(final boolean forwardphương thức của mình ở đâu? Đó là điều tôi bối rối. Bạn có gọi nó bằng một trong các ViewPager.OnPageChangeListenerphương thức giao diện không? Hay ở đâu đó khác?
Sakiboy

Không có hack phản chiếu xấu xí. Dễ dàng cấu hình. Một câu trả lời rất tốt.
Oren

18

Như bạn có thể thấy trong các nguồn ViewPager, thời gian chuyển động được điều khiển bởi đối tượng mScroller. Trong documantation, chúng ta có thể đọc:

Thời lượng của cuộn có thể được chuyển trong hàm tạo và chỉ định thời gian tối đa mà hoạt ảnh cuộn sẽ có

Vì vậy, nếu bạn muốn kiểm soát tốc độ, bạn có thể thay đổi đối tượng mScroller thông qua phản xạ.

Bạn nên viết một cái gì đó như thế này:

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 

Tôi hơi bối rối về cách thực hiện việc này - Tôi đang thiết lập ViewPager trong tệp XML và tôi đã gán nó cho mPager và setAdapter (mAdapter) ... Tôi có nên tạo một lớp mở rộng ViewPager không?
Alex Kelly

12
Bạn nên viết một cái gì đó như sau: setContentView (R.layout.main); mPager = (ViewPager) findViewById (R.id.view_pager); Trường mScroller = ViewPager.class.getDeclaredField ("mScroller"); mScroller.setAccessible (true); mScroller.set (mPager, cuộn); // đối tượng cuộn khởi tạo bởi chính mình
HighFlyer

Tôi chỉ đăng thêm một câu trả lời nữa ... Nó không hoạt động ... Bạn có thể dành thời gian cho tôi hướng dẫn về lý do tại sao không?
Alex Kelly,

Sau khi xem xét cẩn thận hơn các nguồn: tìm mScroller.startScroll (sx, sy, dx, dy); trong nguồn ViewPager và xem xét hiện thực Scroller. startScroll gọi startScroll khác với DEFAULT_DURATION paramater. Nó không thể thực hiện một giá trị cho trường thức tĩnh, vì vậy một cơ hội còn lại - ghi đè smoothScrollTo của ViewPager
HighFlyer

16

Đây không phải là giải pháp hoàn hảo, bạn không thể làm cho tốc độ chậm hơn vì nó là một int. Nhưng đối với tôi, nó đủ chậm và tôi không cần phải sử dụng phản xạ.

Chú ý đến gói mà lớp ở đó. smoothScrollTocó khả năng hiển thị gói.

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

Lưu ý rằng dòng đầu tiên: "package android.support.v4.view;" là bắt buộc và bạn nên tạo gói như vậy tại gốc src của mình. Nếu bạn không làm điều đó, mã của bạn sẽ không được biên dịch.
Elhanan Mishraky

3
@ElhananMishraky chính xác, đó là ý của tôi Notice the package where the class is.
pawelzieba

3
Đợi đã, đợi đã, đợi đã ... wuuuut? Bạn có thể sử dụng sdk package-local fields/ methods, nếu bạn chỉ làm cho Android Studio nghĩ rằng bạn đang sử dụng cùng một gói? Tôi là người duy nhất ngửi thấy mùi sealing violation?
Bartek Lipinski

Đây là một giải pháp thực sự tốt, nhưng nó không hoạt động với tôi vì Bộ nội suy được sử dụng bởi ViewPager Scroller không hoạt động tốt trong trường hợp này. Vì vậy, tôi đã kết hợp giải pháp của bạn với những giải pháp khác bằng cách sử dụng phản chiếu để có được mScroller. Bằng cách này, tôi có thể sử dụng Scroller ban đầu khi người dùng phân trang và Scroller tùy chỉnh của tôi khi tôi thay đổi các trang theo lập trình với setCurrentItem ().
rolgalan

8

Các phương thức fakeDrag trên ViewPager dường như cung cấp một giải pháp thay thế.

Ví dụ, điều này sẽ trang từ mục 0 đến 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 * countChỉ ở đó để làm cho tốc độ kéo tăng lên khi nó di chuyển)


4

tôi đã sử dụng

DecelerateInterpolator ()

Đây là ví dụ:

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

2

Dựa trên giải pháp được chấp nhận, tôi đã tạo lớp kotlin với phần mở rộng cho xem máy nhắn tin. Thưởng thức! :)

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) {
    }

}

1

Đây là một câu trả lời sử dụng một cách tiếp cận hoàn toàn khác. Ai đó có thể nói rằng điều này có một cảm giác khó hiểu; tuy nhiên, nó không sử dụng phản xạ, và tôi cho rằng nó sẽ luôn hoạt động.

Chúng tôi tìm thấy mã sau bên trong 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);

Nó tính toán thời lượng dựa trên một số thứ. Xem bất cứ điều gì chúng tôi có thể kiểm soát? mAdapter.getPageWidth. Hãy triển khai ViewPager.OnPageChangeListener trong bộ điều hợp của chúng tôi . Chúng tôi sẽ phát hiện khi nào ViewPager đang cuộn và đưa ra giá trị giả cho chiều rộng . Nếu tôi muốn thời gian tới được k*100, sau đó tôi sẽ trở lại 1.0/(k-1)cho getPageWidth. Sau đây là Kotlin impl của phần này của bộ điều hợp, biến thời lượng thành 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) {
    }

Đừng quên thêm bộ điều hợp làm OnPageChangedListener.

Trường hợp cụ thể của tôi có thể giả định một cách an toàn rằng người dùng không thể vuốt để kéo giữa các trang. Nếu bạn phải hỗ trợ giải quyết sau khi kéo, thì bạn cần phải thực hiện nhiều hơn một chút trong tính toán của mình.

Một nhược điểm là điều này phụ thuộc vào 100giá trị thời lượng cơ sở được mã hóa cứng đó trong ViewPager. Nếu điều đó thay đổi, thì thời lượng của bạn sẽ thay đổi theo cách tiếp cận này.


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

Tôi muốn giải quyết vấn đề tương tự như tất cả các bạn và đây là giải pháp của tôi cho cùng một vấn đề, nhưng tôi nghĩ cách giải quyết này linh hoạt hơn vì bạn có thể thay đổi thời lượng theo ý muốn và cũng có thể thay đổi nội suy của các giá trị như mong muốn để đạt được các hiệu ứng hình ảnh khác nhau. Giải pháp của tôi vuốt từ trang "1" sang trang "2", vì vậy chỉ ở các vị trí tăng dần, nhưng có thể dễ dàng thay đổi để chuyển sang các vị trí giảm dần bằng cách thực hiện "animProgress - lastFakeDrag" thay vì "lastFakeDrag - animProgress". Tôi nghĩ đây là giải pháp linh hoạt nhất để thực hiện nhiệm vụ này.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.