109 votes

Ralentissement de la vitesse du contrôleur Viewpager dans Android

Existe-t-il un moyen de ralentir la vitesse de défilement avec l'adaptateur viewpager dans Android ?


Tu sais, j'ai regardé ce code. Je n'arrive pas à trouver ce que je fais de mal.

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

Cela ne change rien et pourtant aucune exception ne se produit ?

0 votes

1 votes

child.setNestedScrollingEnabled(false); a fonctionné pour moi

236voto

marmor Points 4559

J'ai commencé par le code de HighFlyer qui a effectivement modifié le champ mScroller (ce qui est un bon début) mais n'a pas permis d'étendre la durée du défilement car ViewPager transmet explicitement la durée au mScroller lors de la demande de défilement.

L'extension du ViewPager n'a pas fonctionné car la méthode importante (smoothScrollTo) ne peut pas être surchargée.

J'ai fini par résoudre ce problème en étendant Scroller avec ce code :

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

Et l'utiliser comme ça :

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

J'ai codé en dur la durée à 5 secondes et j'ai fait en sorte que mon ViewPager l'utilise.

J'espère que cela vous aidera.

16 votes

Pouvez-vous me dire ce qu'est sInterpolator ?

10 votes

L'interpolateur est la vitesse à laquelle les animations sont exécutées, c'est-à-dire qu'elles peuvent accélérer, décélérer, et bien plus encore. Il y a un constructeur sans interpolateur, je pense que cela devrait fonctionner, donc essayez simplement de supprimer cet argument de l'appel. Si cela ne fonctionne pas, ajoutez : 'Interpolator sInterpolator = new AccelerateInterpolator()'.

9 votes

J'ai trouvé que DecelerateInterpolator fonctionnait mieux dans ce cas, il maintient la vitesse du glissement initial puis décélère lentement.

61voto

Oleg Vaskevich Points 4216

J'ai voulu le faire moi-même et j'ai trouvé une solution (en utilisant la réflexion, toutefois). Elle est similaire à la solution acceptée mais utilise le même interpolateur et ne modifie la durée qu'en fonction d'un facteur. Vous devez utiliser un ViewPagerCustomDuration dans votre XML au lieu de ViewPager et ensuite tu peux faire ça :

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

}

J'espère que cela aidera quelqu'un !

3 votes

J'aime mieux cette solution que la réponse acceptée parce que le ViewPager personnalisé est une classe "drop-in" avec des changements minimes au code existant. Le fait que le scroller personnalisé soit une classe interne statique du ViewPager personnalisé le rend encore plus encapsulé.

1 votes

Si vous utilisez proguard, n'oubliez pas d'ajouter cette ligne : -keep class Android.support.v4.** {*;} sinon vous obtiendrez un pointeur nul lors de getDeclaredField().

0 votes

Je suggère de mettre à jour vers un ternaire, parce que vous ne voulez pas permettre à quiconque de passer dans un facteur 0, cela brisera votre durée de temps. Mettez à jour le paramètre final que vous passez en mScrollFactor > 0 ? (int) (duration * mScrollFactor) : duration

18voto

HighFlyer Points 684

Comme vous pouvez voir Dans les sources ViewPager, la durée du battement est contrôlée par l'objet mScroller. Dans documantation nous pouvons lire :

La durée du défilement peut être passée dans le constructeur et spécifie le temps maximum que l'animation de défilement doit prendre.

Ainsi, si vous voulez contrôler la vitesse, vous pouvez modifier l'objet mScroller par réflexion.

Tu devrais écrire quelque chose comme ça :

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

0 votes

Je suis un peu confus sur la façon de procéder - je configure le ViewPager dans le fichier XML et je l'ai assigné à un mPager, et setAdapter (mAdapter)... Devrais-je créer une classe qui étend ViewPager ?

13 votes

Vous devriez écrire quelque chose comme ceci : setContentView(R.layout.main) ; mPager = (ViewPager)findViewById(R.id.view_pager) ; Field mScroller = ViewPager.class.getDeclaredField("mScroller") ; mScroller.setAccessible(true) ; mScroller.set(mPager, scroller) ; // initialiser l'objet scroller par vous-même

0 votes

Je viens de poster une autre réponse... Mais ça ne marche pas... Auriez-vous le temps de m'expliquer pourquoi ?

16voto

pawelzieba Points 10393

Ce n'est pas une solution parfaite, on ne peut pas ralentir la vitesse parce que c'est un problème de sécurité. int . Mais pour moi, c'est assez lent et je n'ai pas besoin d'utiliser la réflexion.

Remarquez le paquet où se trouve la classe. smoothScrollTo a la visibilité du paquet.

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

0 votes

Notez que la première ligne : "package Android.support.v4.view ;" est obligatoire et vous devez créer ce package dans votre racine src. Si vous ne le faites pas, votre code ne sera pas compilé.

3 votes

@ElhananMishraky Exactement, c'est ce que je voulais dire avec Notice the package where the class is .

3 votes

Attends, attends, attends, attends... wuuuut ? Vous pouvez utiliser les sdk's package-local fields / methods si vous faites croire à Android Studio que vous utilisez le même paquet ? Suis-je le seul à sentir une certaine sealing violation ?

8voto

df778899 Points 21

Les méthodes fakeDrag de ViewPager semblent fournir une solution alternative.

Par exemple, cette page passera de l'élément 0 à l'élément 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();
      }
    }
  }
}

(Le count * count est juste là pour que la traînée s'accélère au fur et à mesure qu'elle avance)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X