Android'de Viewpager denetleyicisinin yavaşlama hızı


105

Android'de görüntüleyici adaptörüyle kaydırma hızını yavaşlatmanın bir yolu var mı?


Biliyorsun, bu koda bakıyordum. Neyi yanlış yaptığımı anlayamıyorum.

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

Henüz bir şey değiştirmiyor mu, hiçbir istisna olmuyor mu?



1
child.setNestedScrollingEnabled(false);benim için çalıştı
Pierre

Yanıtlar:


230

Gerçekten mScroller alanını değiştiren (bu harika bir başlangıç) HighFlyer'in koduyla başladım, ancak kaydırma süresini uzatmaya yardımcı olmadı çünkü ViewPager, kaydırma isteğinde bulunurken süreyi mScroller'a açıkça aktarıyor.

ViewPager'ı genişletmek, önemli yöntem (smoothScrollTo) geçersiz kılınamadığından işe yaramadı.

Scroller'ı şu kodla genişleterek bunu düzelttim:

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

Ve bunu şu şekilde kullanarak:

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

Temelde süreyi 5 saniyeye kodladım ve ViewPager'ımın bunu kullanmasını sağladım.

Bu yardımcı olur umarım.


16
Lütfen bana interpolator'ın ne olduğunu söyleyebilir misiniz?
Shashank Degloorkar

10
'Interpolator', animasyonların çalıştırılma hızıdır, örneğin hızlanabilir, yavaşlayabilir ve çok daha fazlasını yapabilir. Enterpolatör olmayan bir kurucu var, bunun işe yaraması gerektiğini düşünüyorum, bu yüzden sadece bu argümanı çağrıdan kaldırmaya çalışın. Bu işe yaramazsa şunu ekleyin: 'Interpolator sInterpolator = new AccelerateInterpolator ()'
marmor

8
Bu durumda DecelerateInterpolator'ın daha iyi çalıştığını buldum, ilk kaydırmanın hızını koruyor ve ardından yavaşça yavaşlıyor.
Quint Stoffers

Merhaba, neden scroller.setFixedDuration(5000);hata verdiğine dair bir fikriniz var mı? "SetFixedDuration (int) yöntemi tanımsız"
yazıyor

6
Proguard kullanıyorsanız bu satırı eklemeyi unutmayın: -keep sınıf android.support.v4. ** {*;} aksi takdirde getDeclaredField ()
GuilhE

61

Kendim yapmak istedim ve bir çözüme ulaştım (ancak yansıma kullanarak). Kabul edilen çözüme benzer, ancak aynı enterpolatörü kullanır ve süreyi yalnızca bir faktöre göre değiştirir. ViewPagerCustomDurationXML'nizde yerine a kullanmanız gerekir ViewPagerve sonra bunu yapabilirsiniz:

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

}

Umarım bu birine yardımcı olur!


3
Bu çözümü, kabul edilen cevaptan daha çok seviyorum çünkü özel ViewPager, mevcut kodda minimum değişikliğe sahip bir drop-in sınıfıdır. Özel kaydırıcıyı, özel ViewPager'ın statik bir iç sınıfı olarak kullanmak, onu daha da kapsüllü hale getirir.
Emanuel Moecklin

1
Proguard kullanıyorsanız bu satırı eklemeyi unutmayın: -keep sınıf android.support.v4. ** {*;} aksi takdirde getDeclaredField ()
GuilhE

Üçlü sayıya güncelleme yapmanızı öneririm, çünkü kimsenin 0 faktörünü geçmesine izin vermek istemezsiniz, bu sizin sürenizi bozar. MScrollFactor> 0'a ilettiğiniz son parametre güncellensin mi? (int) (süre * mScrollFactor): süre
portfoliobuilder

ViewPagerCustomDuration adlı sınıfınızda kaydırma faktörünü ayarlarken boş bir denetim yapmanızı da öneririm. MScroller ile boş bir nesne olarak somutlaştırırsanız, o nesnede kaydırma faktörünü ayarlamaya çalışırken hala boş olmadığını onaylamalısınız.
portfoliobuilder

@portfoliobuilder geçerli yorumlar, ancak IMO'nun SO'daki bir parçacığa göre opak kitaplıklar için daha uygun olduğu daha savunmacı bir programlama stilini tercih ediyor gibi görünüyorsunuz. Kaydırma faktörünü <= 0 olarak ayarlamak ve örneklenmemiş bir görünümde setScrollDurationFactor'ı çağırmak, çoğu Android geliştiricisinin çalışma zamanı kontrolleri olmadan yapmaktan kaçınması gereken bir şeydir.
Oleg Vaskevich

43

@ Df778899'un cevabına ve Android ValueAnimator API'sine dayalı olarak daha iyi bir çözüm buldum . Yansıma olmadan iyi çalışır ve çok esnektir. Ayrıca özel ViewPager oluşturmaya ve onu android.support.v4.view paketine koymaya gerek yoktur. İşte bir örnek:

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
Bunun küçümsenmediğini düşünüyorum. Bu yararlanır startFakeDragve endFakeDragdaha mevcuttur daha yapmak için olması gerekiyordu hangi ViewPagerkutunun out. Bu benim için harika çalışıyor ve tam olarak aradığım şey buydu
user3280133

1
Bu harika bir çözüm, teşekkürler. Yine de küçük bir geliştirme: dolgulu bir viewPager için çalışmasını sağlamak için, ilk satır şu olmalıdır ... ValueAnimator.ofInt (0, viewPager.getWidth () - (forward? ViewPager.getPaddingLeft (): viewPager.getPaddingRight () ));
DmitryO.

1
Bu, yansımayı göz ardı ettiği ve yukarıdaki çözümler gibi ihlalleri ortadan kaldırdığı için açık arayla en basit / etkili / sorumlu çözümdü
Ryan

2
@lobzic, animatePagerTransition(final boolean forwardyönteminizi tam olarak nereye çağırıyorsunuz ? Kafam karışan şey bu. ViewPager.OnPageChangeListenerArayüz yöntemlerinden birinde mi çağırırsınız ? Veya başka bir yerde?
Sakiboy

Çirkin düşünce hileleri yok. Kolayca yapılandırılabilir. Çok güzel bir cevap.
Oren

18

Eğer gibi bakın ViewPager kaynaklarında, süresi mScroller nesnesi tarafından kontrol edilen kaçamaktı. Gelen Dokümantasyon Okuduğumuz edilebilir:

Yapıcıda kaydırma süresi geçirilebilir ve kaydırma animasyonunun alması gereken maksimum süreyi belirtir

Bu nedenle, hızı kontrol etmek istiyorsanız, mScroller nesnesini yansıma yoluyla değiştirebilirsiniz.

Bunun gibi bir şey yazmalısın:

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 

Bunun nasıl yapılacağı konusunda biraz kafam karıştı - ViewPager'ı XML dosyasında kuruyorum ve onu bir mPager'e ve setAdapter (mAdapter) 'a atadım ... ViewPager'ı genişleten bir sınıf oluşturmalı mıyım?
Alex Kelly

12
Şöyle bir şey yazmalısınız: setContentView (R.layout.main); mPager = (ViewPager) findViewById (R.id.view_pager); Alan mScroller = ViewPager.class.getDeclaredField ("mScroller"); mScroller.setAccessible (true); mScroller.set (mPager, kaydıraç); // kaydırma nesnesini kendiniz
başlatın

Sadece bir cevap daha gönderdim ... İşe yaramıyor ... Neden olmasın diye bana yol gösterecek vaktin var mı?
Alex Kelly

Kaynaklara daha dikkatli baktıktan sonra: mScroller.startScroll (sx, sy, dx, dy) 'i bulun; ViewPager kaynağında ve Scroller gerçekleştirmeye bakın. startScroll, DEFAULT_DURATION parametresiyle diğer startScroll'u çağırır. Statik son alan için başka bir değer oluşturmak imkansızdır, bu nedenle bir fırsat kaldı: smoothScrollTo of ViewPager'ı geçersiz kılın
HighFlyer

16

Bu mükemmel bir çözüm değil, hızı yavaşlatamazsınız çünkü bir int. Ama benim için yeterince yavaş ve yansımayı kullanmak zorunda değilim.

Sınıfın olduğu pakete dikkat edin. smoothScrollTopaket görünürlüğüne sahiptir.

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

İlk satırın "paket android.support.v4.view;" olduğuna dikkat edin. zorunludur ve src kökünüzde böyle bir paket oluşturmalısınız. Bunu yapmazsanız, kodunuz derlenmez.
Elhanan Mishraky

3
@ElhananMishraky tam olarak, demek istediğim buydu Notice the package where the class is.
pawelzieba

3
Bekle, bekle, bekle, bekle ... wuuuut? Android Studio'nun aynı paketi kullandığınızı düşünmesini sağlarsanız, sdk'leri package-local fields/ kullanabilirsiniz. methodsBiraz koklayan tek kişi ben miyim sealing violation?
Bartek Lipinski

Bu gerçekten güzel bir çözüm, ancak ViewPager Scroller tarafından kullanılan Interpolator bu durumda iyi davranmadığı için benim için de işe yaramadı. Bu yüzden, mScroller'ı elde etmek için yansımayı kullanarak çözümünüzü diğerleriyle birleştirdim. Bu şekilde, kullanıcı sayfalandırdığında orijinal Kaydırıcıyı ve setCurrentItem () ile sayfaları programlı olarak değiştirdiğimde özel Kaydırıcıyı kullanabilirim.
rolgalan

8

ViewPager'daki fakeDrag yöntemleri alternatif bir çözüm sunuyor gibi görünüyor.

Örneğin, bu öğe 0'dan 1'e kadar sayfa olacaktır:

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 * countSadece sürüklemeyi hızlandırmak için orada)


4

Kullandım

DecelerateInterpolator ()

İşte örnek:

  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

Kabul edilen çözüme dayanarak, çağrı cihazı görüntüleme uzantısına sahip kotlin sınıfı oluşturdum. Zevk almak! :)

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

İşte tamamen farklı bir yaklaşım kullanan bir cevap. Birisi bunun huysuz bir his olduğunu söyleyebilir; ancak, yansıma kullanmaz ve her zaman işe yarayacağını iddia ediyorum.

Aşağıdaki kodu içinde buluyoruz 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);

Süreyi birkaç şeye göre hesaplar. Kontrol edebileceğimiz bir şey görüyor musun? mAdapter.getPageWidth. ViewPager.OnPageChangeListener'ı bağdaştırıcımıza uygulayalım . ViewPager'ın ne zaman kaydırdığını tespit edeceğiz ve genişlik için sahte bir değer vereceğiz . Sürenin olmasını istiyorsam k*100geri döneceğim1.0/(k-1) için getPageWidth. Aşağıdakiler, adaptörün süreyi 400'e çeviren bu kısmının Kotlin uygulamasıdır:

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

Bağdaştırıcıyı OnPageChangedListener olarak eklemeyi unutmayın.

Benim özel durumum, kullanıcının sayfalar arasında sürüklemek için kaydıramayacağını güvenle varsayabilir. Bir sürüklemeden sonra oturmayı desteklemeniz gerekiyorsa, hesaplamanızda biraz daha fazlasını yapmanız gerekir.

Bir dezavantajı, bunun 100ViewPager'daki kodlanmış temel süre değerine bağlı olmasıdır . Bu değişirse, bu yaklaşımla süreleriniz değişir.


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

Hepinizle aynı sorunu çözmek istedim ve bu aynı problem için benim çözümüm, ancak süreyi istediğiniz gibi değiştirebileceğiniz ve ayrıca değerlerin enterpolasyonunu istediğiniz gibi değiştirebileceğiniz için bu şekilde çözümün daha esnek olduğunu düşünüyorum. farklı görsel efektler elde etmek için. Çözümüm "1" sayfasından "2" sayfasına kaydırıyor, bu nedenle yalnızca artan konumlarda, ancak "lastFakeDrag - animProgress" yerine "animProgress - lastFakeDrag" yaparak azalan konumlara gitmek için kolayca değiştirilebilir. Bu görevi yerine getirmek için en esnek çözümün bu olduğunu düşünüyorum.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.