web-dev-qa-db-fra.com

HorizontalScrollView: défilement automatique jusqu'à la fin lorsque de nouvelles vues sont ajoutées?

J'ai un HorizontalScrollView contenant un LinearLayout. À l'écran, j'ai un bouton qui va ajouter de nouvelles vues à LinearLayout au moment de l'exécution, et j'aimerais que la vue de défilement défile jusqu'à la fin de la liste lorsqu'une nouvelle vue est ajoutée.

J'ai presque le faire fonctionner - sauf qu'il défile toujours une vue avant la dernière. On dirait qu'il défile sans calculer d'abord l'inclusion de la nouvelle vue.

Dans mon application, j'utilise un objet View personnalisé, mais j'ai créé une petite application de test utilisant ImageView et présentant le même symptôme. J'ai essayé diverses choses comme requestLayout () à la fois dans Layout et dans ScrollView, j'ai essayé scrollTo (Integer.MAX_VALUE) et il a fait défiler l'écran vers le bas. :) Suis-je en train de violer un problème de thread d'interface utilisateur?

  • Meule

======

    public class Main extends Activity {
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);

             Button b = (Button) findViewById(R.id.addButton);
             b.setOnClickListener(new AddListener());

             add();
        }

        private void add() {
             LinearLayout l = (LinearLayout) findViewById(R.id.list);
             HorizontalScrollView s = 
                 (HorizontalScrollView) findViewById(R.id.scroller);

             ImageView i = new ImageView(getApplicationContext());
             i.setImageResource(Android.R.drawable.btn_star_big_on);
             l.addView(i);

             s.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
        }

        private class AddListener implements View.OnClickListener {
             @Override
             public void onClick(View v) {
                 add();
             }
        }
    }

Layout XML:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
        Android:orientation="vertical"
        Android:layout_width="fill_parent"
        Android:layout_height="fill_parent">

        <HorizontalScrollView
            Android:id="@+id/scroller"
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:scrollbarSize="50px">
            <LinearLayout
                Android:id="@+id/list"
                Android:layout_width="wrap_content"
                Android:layout_height="wrap_content"
                Android:padding="4px"/>
        </HorizontalScrollView>

        <LinearLayout
            Android:layout_width="fill_parent"
            Android:layout_height="fill_parent"            
            Android:layout_gravity="center">
            <Button
                Android:id="@+id/addButton"
                Android:layout_width="wrap_content"
                Android:layout_height="wrap_content"
                Android:layout_gravity="center"
                Android:paddingLeft="80px"
                Android:paddingRight="80px"
                Android:paddingTop="40px"
                Android:paddingBottom="40px"
                Android:text="Add"/>
        </LinearLayout>

    </LinearLayout>
49
Rick Barkhouse

Je pense qu'il y a un problème de timing. La mise en page n'est pas terminée lorsqu'une vue est ajoutée. Il est demandé et fait peu de temps après. Lorsque vous appelez fullScroll immédiatement après avoir ajouté la vue, la largeur de la représentation linéaire n'a pas eu la possibilité de se développer.

Essayez de remplacer:

s.fullScroll(HorizontalScrollView.FOCUS_RIGHT);

avec:

s.postDelayed(new Runnable() {
    public void run() {
        s.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
    }
}, 100L);

Ce court délai devrait laisser au système suffisamment de temps pour s’installer.

P.S. Il peut suffire de simplement retarder le défilement après l'itération actuelle de la boucle de l'interface utilisateur. Je n'ai pas testé cette théorie, mais si elle est correcte, il suffirait de procéder comme suit:

s.post(new Runnable() {
    public void run() {
        s.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
    }
});

J'aime mieux cela parce qu'introduire un retard arbitraire me semble hacky (même si c'était ma suggestion).

124
Ted Hopp

Je conviens avec @monxalo et @ granko87 que la meilleure approche consiste à utiliser un auditeur au lieu de supposer que la mise en page sera complète si on laisse passer un laps de temps arbitraire.

Au cas où, comme moi, vous n’auriez pas besoin d’utiliser une sous-classe HorizontalScrollView personnalisée, vous pouvez simplement ajouter OnLayoutChangeListener pour simplifier les choses:

mTagsScroller.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
        mTagsScroller.removeOnLayoutChangeListener(this);
        mTagsScroller.fullScroll(View.FOCUS_RIGHT);
    }
});
15
Newtz

Juste une autre suggestion, puisque cette question m'a beaucoup aidé :).

Vous pouvez mettre un auditeur à la fin de la phase de mise en page de la vue, et juste après le fullScroll, vous aurez besoin d'étendre la classe pour cela.

Je ne l'ai fait que parce que je voulais faire défiler une section juste après onCreate () pour éviter le scintillement du point de départ au point de défilement.

Quelque chose comme:

public class PagerView extends HorizontalScrollView {

    private OnLayoutListener mListener;
    ///...
    private interface OnLayoutListener {
        void onLayout();
    }

    public void fullScrollOnLayout(final int direction) {
        mListener = new OnLayoutListener() {            
            @Override
            public void onLayout() {
                 fullScroll(direction)
                 mListener = null;
            }
        };
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if(mListener != null)
             mListener.onLayout();
    }
}
12
monxalo

Utilisez la méthode View smoothScrollTo

postDelayed(new Runnable() {
    public void run() {
       scrollView.smoothScrollTo(newView.getLeft(), newView.getTop());
 }
 }, 100L);

Vous devez mettre un exécutable afin de donner le temps au processus de mise en page de positionner la nouvelle vue.

4
Gabriel Guerrero

C'est ce que j'ai fait.

//Observe for a layout change    
ViewTreeObserver viewTreeObserver = yourLayout.getViewTreeObserver();
   if (viewTreeObserver.isAlive()) {
     viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                yourHorizontalScrollView.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
            }
     });
   }
3
Arst

Utilisez le code ci-dessous pour le défilement horizontal { Faites défiler vers la droite

view.post(new Runnable() {
                @Override
                public void run() {
                    ((HorizontalScrollView) Iview
                            .findViewById(R.id.hr_scroll))
                            .fullScroll(HorizontalScrollView.FOCUS_RIGHT);
                }
            });
2
sharma_kunal

Je pense que la meilleure approche est celle publiée par Monxalo car vous ne pouvez pas savoir combien de temps cela prendra pour que la mise en page soit terminée. J'ai essayé et ça fonctionne parfaitement. La seule différence est que j'ai ajouté une seule ligne à ma vue de défilement horizontal personnalisée:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);
    this.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
}
1
granko87

L'enfant View ne peut pas être ajouté immédiatement dans une ViewGroup. Il est donc nécessaire de publier une Runnable qui sera exécutée après la fin des autres tâches en attente.

Donc, après avoir ajouté View, utilisez:

horizontalScrollView.post(new Runnable() {
    @Override
    public void run() {
        horizontalScrollView.fullScroll(View.FOCUS_RIGHT);
    }
});
0
Nabin Bhandari

Voici mon chemin pour 3 ImageViews dans kotlin:

var p1 = true; var p2 = false; var p3 = false
val x = -55
val handler = Handler()
handler.postDelayed(object : Runnable {
    override fun run() {

        if (p1){
            mainView.picsScrollViewID.smoothScrollTo(mainView.bigPic1ID.x.toInt() + x, 0)
            p1 = false
            p2 = true
            p3 = false
        }
        else if(p2){
            mainView.picsScrollViewID.smoothScrollTo(mainView.bigPic2ID.x.toInt() + x, 0)
            p1 = false
            p2 = false
            p3 = true
        }
        else if(p3){
            mainView.picsScrollViewID.smoothScrollTo(mainView.bigPic3ID.x.toInt() + x, 0)
            p1 = true
            p2 = false
            p3 = false
        }
        handler.postDelayed(this, 2000)
    }
}, 1400)
0
Gabriel