AndroidでのViewpagerコントローラーの速度の低下


104

Androidのviewpagerアダプターでスクロール速度を遅くする方法はありますか?


私はこのコードを見てきました。私は何が間違っているのか理解できません。

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

それは何も変更しませんが、例外は発生しませんか?



1
child.setNestedScrollingEnabled(false);私のために働いた
ピエール

回答:


230

私はHighFlyerのコードから始めましたが、実際にはmScrollerフィールドを変更しました(これは素晴らしいスタートです)が、スクロールを要求するとViewPagerがmScrollerに継続時間を明示的に渡すため、スクロールの継続時間を延長できませんでした。

重要なメソッド(smoothScrollTo)はオーバーライドできないため、ViewPagerの拡張は機能しませんでした。

私はこのコードで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で使用するようにしました。

お役に立てれば。


16
sInterpolatorとは何ですか?
Shashank Degloorkar

10
「補間」は、アニメーションが実行される速度です。たとえば、加速、減速などが可能です。インターポレーターのないコンストラクターがあります。それはうまくいくと思いますので、呼び出しからその引数を削除してみてください。それが機能しない場合は、次を追加します。 'Interpolator sInterpolator = new AccelerateInterpolator()'
marmor

8
この場合、DecelerateInterpolatorの方がうまく機能することがわかりました。最初のスワイプの速度を維持し、その後ゆっくり減速します。
Quint Stoffers

こんにちは、なぜscroller.setFixedDuration(5000);エラーが出るのか考えていますか?「メソッドsetFixedDuration(int)は未定義です」
jaibatrik

6
。;ときgetDeclaredField()そうでなければ、NULLポインタを取得します-keepクラスandroid.support.v4 ** {*}:あなたはProGuardのこの行を追加することを忘れないでください使用している場合
GuilhE

61

私は自分でやりたかったのですが、(リフレクションを使用して)解決策を達成しました。これは、承認されたソリューションに似ていますが、同じ補間を使用し、係数に基づいて期間のみを変更します。の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));
    }

}

これが誰かを助けることを願っています!


3
カスタムViewPagerは既存のコードへの変更を最小限に抑えたドロップインクラスであるため、このソリューションは受け入れられた回答よりも優れています。カスタムスクローラーをカスタムViewPagerの静的内部クラスとして持つと、さらにカプセル化されます。
エマニュエルモエクリン2014

1
。;ときgetDeclaredField()そうでなければ、NULLポインタを取得します-keepクラスandroid.support.v4 ** {*}:あなたはProGuardのこの行を追加することを忘れないでください使用している場合
GuilhE

3進数に更新することをお勧めします。誰にも0ファクターを渡すことを許可したくないので、これはあなたの期間を壊します。mScrollFactor> 0に渡す最後のパラメーターを更新しますか?(int)(duration * mScrollFactor):期間
ポートフォリオ

また、ViewPagerCustomDurationクラスでスクロールファクターを設定するときにnullチェックを実行することをお勧めします。mScrollerをnullオブジェクトとしてインスタンス化します。そのオブジェクトにスクロール係数を設定しようとするとき、それがnullでないことを確認する必要があります。
ポートフォリオ

@portfoliobuilderは有効なコメントですが、SOのスニペットよりもIMOが不透明なライブラリに適しているより防御的なプログラミングスタイルを好むようです。スクロール係数を<= 0に設定することと、インスタンス化されていないビューでsetScrollDurationFactorを呼び出すことは、ほとんどのAndroid開発者がランタイムチェックなしで実行することを回避できるはずです。
Oleg Vaskevich

43

@ df778899の回答Android ValueAnimator APIに基づいて、より良い解決策を見つけました。それは反射なしでうまく機能し、非常に柔軟です。また、カスタム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();
}

7
これは過小評価されていると思います。それはを使用するstartFakeDragendFakeDrag、より利用可能であるよりも、行うためであることを意味しているViewPagerボックスのうち。これは私にとってはうまく機能していて、まさに私が探していたものです
user3280133

1
それは素晴らしい解決策です、ありがとう。ただし、1つの小さな拡張機能:パディング付きのviewPagerで機能させるには、最初の行を...にする必要がありますValueValueator.ofInt(0、viewPager.getWidth()-(forward?viewPager.getPaddingLeft():viewPager.getPaddingRight() ));
DmitryO。

1
これは、反射を無視し、上記のソリューションのようなシーリング違反を回避するため、これまでで最も簡単で効率的で信頼できるソリューションでした
Ryan

2
@lobzic、正確にどこでanimatePagerTransition(final boolean forwardメソッドを呼び出しますか?それは私が混乱しているものです。ViewPager.OnPageChangeListenerインターフェースメソッドの1つで呼び出しますか?またはどこかに?
Sakiboy 2017年

醜い反射ハックはありません。簡単に設定できます。非常に良い答えです。
オーレン

18

ViewPagerソースで確認できるように、mScrollerオブジェクトによって制御されるフリングの期間。でdocumantation我々は読むことができます:

スクロールの継続時間はコンストラクターで渡すことができ、スクロールアニメーションにかかる最大時間を指定します

したがって、速度を制御したい場合は、リフレクションを介して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 

これを行う方法について少し混乱しています。XMLファイルでViewPagerを設定し、それをmPagerに割り当て、setAdapter(mAdapter)... ViewPagerを拡張するクラスを作成する必要がありますか?
Alex Kelly、

12
次のように書く必要があります:setContentView(R.layout.main); mPager =(ViewPager)findViewById(R.id.view_pager); フィールドmScroller = ViewPager.class.getDeclaredField( "mScroller"); mScroller.setAccessible(true); mScroller.set(mPager、scroller); //自分でスクローラーオブジェクトを初期化する
HighFlyer

もう1つ回答を投稿しました...機能しません...なぜ機能しないのかを説明する時間がありますか?
Alex Kelly

ソースをより注意深く調べた後、mScroller.startScroll(sx、sy、dx、dy);を見つけます。ViewPagerソースで、スクローラー実現を見てみましょう。startScrollは、DEFAULT_DURATIONパラメータを使用して他のstartScrollを呼び出します。静的な最終フィールドに別の値を作成することは不可能であるため、1つの機会が残っています-ViewPagerのsmoothScrollToをオーバーライドします
HighFlyer

16

これは完全な解決策ではありません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);
    }
}

最初の行に注意してください: "package android.support.v4.view;" は必須であり、srcルートでそのようなパッケージを作成する必要があります。そうしないと、コードはコンパイルされません。
Elhanan Mishraky 2014年

3
@ElhananMishraky正確には、それが私が意味したことNotice the package where the class isです。
pawelzieba 2014年

3
待って、待って、待って、待って... wuuuut?Android Studioに同じパッケージを使用していると思わせる場合は、sdk package-local fields/ を使用できmethodsますか?臭いがするのは私だけsealing violationですか?
Bartek Lipinski 2015年

これは本当に良い解決策ですが、ViewPager Scrollerで使用されるInterpolatorがこの場合うまく動作しないため、私にとってもうまくいきませんでした。そこで、mScrollerを取得するためにリフレクションを使用して他のソリューションと組み合わせました。このようにして、ユーザーがページネーションを行うときに元のScrollerを使用し、setCurrentItem()を使用してプログラムでページを変更するときにカスタムScrollerを使用できます。
rolgalan 2017

8

ViewPagerのfakeDragメソッドは代替ソリューションを提供するようです。

たとえば、これはアイテム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();
      }
    }
  }
}

count * countドラッグの速度を上げるためにそこにあります)


4

利用した

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

2

受け入れられたソリューションに基づいて、ビューページャーの拡張機能を備えた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) {
    }

}

1

これは、まったく異なるアプローチを使用した答えです。誰かがこれはハック感があると言うかもしれません。ただし、リフレクションを使用していないため、常に機能すると主張します。

内部には次のコードがあり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がスクロールしていることを検出し、widthに偽の値を指定します。私は期間になりたい場合はk*100、その後、私が返されます1.0/(k-1)ためgetPageWidth。以下は、期間を400に変換するアダプターのこの部分のKotlin実装です。

    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として追加することを忘れないでください。

私の特定のケースでは、ユーザーがスワイプしてページ間をドラッグできないと安全に想定できます。ドラッグ後の整定をサポートする必要がある場合は、計算をもう少し行う必要があります。

1つの欠点は、これ100がViewPager でハードコードされた基本期間の値に依存することです。それが変わると、このアプローチで期間が変わります。


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

私はあなたと同じ問題を解決したかったのですが、これは同じ問題に対する私の解決策ですが、この方法は、好きなだけ期間を変更でき、必要に応じて値の補間も変更できるので、この方法の方がより柔軟だと思いますさまざまな視覚効果を実現します。私のソリューションは、ページ「1」からページ「2」にスワイプするため、増加する位置でのみですが、「lastFakeDrag-animProgress」の代わりに「animProgress-lastFakeDrag」を実行することで、減少する位置に移動するように簡単に変更できます。これは、このタスクを実行するための最も柔軟なソリューションだと思います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.