Existe-t-il un moyen de faire défiler par programme une vue de défilement vers un texte d'édition spécifique?


206

J'ai une très longue activité avec un scrollview. C'est un formulaire avec différents champs que l'utilisateur doit remplir. J'ai une case à cocher à mi-chemin de mon formulaire, et lorsque l'utilisateur le vérifie, je veux faire défiler jusqu'à une partie spécifique de la vue. Existe-t-il un moyen de faire défiler un objet EditText (ou tout autre objet de vue) par programme?

De plus, je sais que cela est possible en utilisant les coordonnées X et Y mais je veux éviter de le faire car le formulaire peut changer d'un utilisateur à l'autre.


3
the form may changed from user to usermais vous pouvez simplement utilisermEditView.getTop();
nebkat

1
si quelqu'un utilise NestedScrollView dans CoordinatorLayout, vous pouvez faire défiler jusqu'à une vue spécifique via stackoverflow.com/questions/52083678/…
user1506104

Réponses:


452
private final void focusOnView(){
        your_scrollview.post(new Runnable() {
            @Override
            public void run() {
                your_scrollview.scrollTo(0, your_EditBox.getBottom());
            }
        });
    }

129
J'ai trouvé que l'utilisation de smoothScrollTo (0, your_EditBox.getTop ()) obtient un meilleur résultat (défilement plus fluide jusqu'à la vue souhaitée) mais à part cela - excellente réponse.
Muzikant

18
@ xmenW.K. Réponse courte: parce que l'interface utilisateur fait son travail en fonction d'une file d'attente de choses à faire, et vous dites essentiellement au thread d'interface utilisateur, quand vous pouvez, après avoir fait tout ce que vous aviez à faire avant maintenant, faites cela (faites défiler) . Vous mettez essentiellement le défilement dans la file d'attente et laissez le thread le faire quand il le peut, en respectant l'ordre des choses qu'il faisait avant de le demander.
Martin Marconcini

37
Il est également possible de publier le Runnable sur le ScrollView lui-même au lieu d'instancier un nouveau gestionnaire:your_scrollview.post(...
Sherif elKhatib

Existe-t-il une méthode pour obtenir le centre de vue plutôt que getBottom ()? cuz mon cas est googleMap au lieu de EditText.
Zin Win Htet

5
getBottom () et getTop () sont relatifs au parent
cambunctious

57

La réponse de Sherif elKhatib peut être grandement améliorée si vous souhaitez faire défiler la vue vers le centre de la vue de défilement . Cette méthode réutilisable fait défiler la vue vers le centre visible d'un HorizontalScrollView.

private final void focusOnView(final HorizontalScrollView scroll, final View view) {
    new Handler().post(new Runnable() {
        @Override
        public void run() {
            int vLeft = view.getLeft();
            int vRight = view.getRight();
            int sWidth = scroll.getWidth();
            scroll.smoothScrollTo(((vLeft + vRight - sWidth) / 2), 0);
        }
    });
}

Pour une ScrollViewutilisation verticale

...
int vTop = view.getTop();
int vBottom = view.getBottom();
int sHeight = scroll.getBottom();
scroll.smoothScrollTo(((vTop + vBottom - sHeight) / 2), 0);
...

3
Vous devez ajuster votre code. Vous devez obtenir la largeur de la vue de défilement et vous devez changer la formule en sv.smoothScrollTo (((vLeft + vRight) / 2) - (svWidth / 2), 0);
Élémentaire du

2
Sinon, la vue dans le scrollView ne sera pas centrée. J'ai déjà fait le montage pour toi.
Élémentaire du

Avez-vous vérifié que la formule fonctionnait exactement, parce que l'ancienne fonctionnait bien pour moi
ahmadalibaloch

1
Les formules @Elementary Yours et ahmads sont des versions simplifiées et étendues de la même expression algébrique, il n'y a pas besoin d'ajustements.
k29

1
@ k29 vous référez-vous aux deux formules visibles ici? Ils sont tous les deux de moi, j'ai encore simplifié la formule de mon commentaire et modifié la réponse en conséquence. Mais tout le reste est toujours le même que la réponse originale et m'a également été très utile.
Elementary

51

Cela fonctionne bien pour moi:

  targetView.getParent().requestChildFocus(targetView,targetView);

public void RequestChildFocus (Afficher l'enfant, Afficher le focus)

enfant - L'enfant de ce ViewParent qui veut se concentrer. Cette vue contiendra la vue focalisée. Ce n'est pas nécessairement le point de vue qui a le focus.

concentré - La vue qui est un descendant d'un enfant qui a réellement le focus


Voir le genre de sauts dans n'importe quel défilement lisse
silentsudo

32

À mon avis, la meilleure façon de faire défiler un rectangle donné est via View.requestRectangleOnScreen(Rect, Boolean). Vous devez l'appeler sur un que Viewvous souhaitez faire défiler et passer un rectangle local que vous souhaitez être visible à l'écran. Le deuxième paramètre doit être falsepour un défilement fluide et truepour un défilement immédiat.

final Rect rect = new Rect(0, 0, view.getWidth(), view.getHeight());
view.requestRectangleOnScreen(rect, false);

1
A travaillé pour moi dans un pager de vue, il y a scrollview et il faut faire défiler jusqu'à une vue qui fonctionne maintenant en utilisant le code mentionné ici.
Pankaj

2
Certainement, celle-ci devrait être la réponse acceptée, scrollview.smoothScrollTone fonctionnera pas si la vue requise est hors écran, (essayé dans l'émulateur Android avec la version sdk 26)
Sony

@Michael Dois-je terminer new Handler().post()?
Johnny Five

@JohnnyFive Cela dépend du contexte dans lequel vous utilisez ce code.
Michael

12

J'ai fait une petite méthode utilitaire basée sur Answer from WarrenFaith, ce code prend également en compte si cette vue est déjà visible dans le scrollview, pas besoin de faire défiler.

public static void scrollToView(final ScrollView scrollView, final View view) {

    // View needs a focus
    view.requestFocus();

    // Determine if scroll needs to happen
    final Rect scrollBounds = new Rect();
    scrollView.getHitRect(scrollBounds);
    if (!view.getLocalVisibleRect(scrollBounds)) {
        new Handler().post(new Runnable() {
            @Override
            public void run() {
                scrollView.smoothScrollTo(0, view.getBottom());
            }
        });
    } 
}

Je suppose que scrollBoundsdevrait être le paramètre de la scrollView.getHitRectméthode.
Vijay C


7

Mon EditText était imbriqué de plusieurs couches à l'intérieur de mon ScrollView, qui n'est pas lui-même la vue racine de la mise en page. Parce que getTop () et getBottom () semblaient signaler les coordonnées dans sa vue contenant, je l'ai fait calculer la distance entre le haut de ScrollView et le haut de EditText en itérant à travers les parents de EditText.

// Scroll the view so that the touched editText is near the top of the scroll view
new Thread(new Runnable()
{
    @Override
    public
    void run ()
    {
        // Make it feel like a two step process
        Utils.sleep(333);

        // Determine where to set the scroll-to to by measuring the distance from the top of the scroll view
        // to the control to focus on by summing the "top" position of each view in the hierarchy.
        int yDistanceToControlsView = 0;
        View parentView = (View) m_editTextControl.getParent();
        while (true)
        {
            if (parentView.equals(scrollView))
            {
                break;
            }
            yDistanceToControlsView += parentView.getTop();
            parentView = (View) parentView.getParent();
        }

        // Compute the final position value for the top and bottom of the control in the scroll view.
        final int topInScrollView = yDistanceToControlsView + m_editTextControl.getTop();
        final int bottomInScrollView = yDistanceToControlsView + m_editTextControl.getBottom();

        // Post the scroll action to happen on the scrollView with the UI thread.
        scrollView.post(new Runnable()
        {
            @Override
            public void run()
            {
                int height =m_editTextControl.getHeight();
                scrollView.smoothScrollTo(0, ((topInScrollView + bottomInScrollView) / 2) - height);
                m_editTextControl.requestFocus();
            }
        });
    }
}).start();

5

Une autre variante serait:

scrollView.postDelayed(new Runnable()
{
    @Override
    public void run()
    {
        scrollView.smoothScrollTo(0, img_transparent.getTop());
    }
}, 2000);

ou vous pouvez utiliser la post()méthode.


5

Je sais que cela peut être trop tard pour une meilleure réponse, mais une solution parfaite souhaitée doit être un système comme un positionneur. Je veux dire, quand le système fait un positionnement pour un champ Editor, il place le champ juste jusqu'au clavier, donc comme règles UI / UX, c'est parfait.

Ce que le code ci-dessous fait, c'est la façon dont Android se positionne en douceur. Tout d'abord, nous gardons le point de défilement actuel comme point de référence. La deuxième chose est de trouver le meilleur point de défilement de positionnement pour un éditeur, pour ce faire, nous faisons défiler vers le haut, puis demandons aux champs de l'éditeur de faire en sorte que le composant ScrollView fasse le meilleur positionnement. Gatcha! Nous avons appris la meilleure position. Maintenant, ce que nous allons faire, c'est faire défiler en douceur du point précédent au point que nous avons trouvé récemment. Si vous le souhaitez, vous pouvez omettre le défilement fluide en utilisant scrollTo au lieu de smoothScrollTo uniquement.

REMARQUE: le conteneur principal ScrollView est un champ membre nommé scrollViewSignup, car mon exemple était un écran d'inscription, comme vous pouvez le voir beaucoup.

view.setOnFocusChangeListener(new View.OnFocusChangeListener() {
        @Override
        public void onFocusChange(final View view, boolean b) {
            if (b) {
                scrollViewSignup.post(new Runnable() {
                    @Override
                    public void run() {
                        int scrollY = scrollViewSignup.getScrollY();
                        scrollViewSignup.scrollTo(0, 0);
                        final Rect rect = new Rect(0, 0, view.getWidth(), view.getHeight());
                        view.requestRectangleOnScreen(rect, true);

                        int new_scrollY = scrollViewSignup.getScrollY();
                        scrollViewSignup.scrollTo(0, scrollY);
                        scrollViewSignup.smoothScrollTo(0, new_scrollY);
                    }
                });
            }
        }
    });

Si vous souhaitez utiliser ce bloc pour toutes les instances EditText et l'intégrer rapidement à votre code d'écran. Vous pouvez simplement faire un traverseur comme ci-dessous. Pour ce faire, j'ai fait de OnFocusChangeListener un champ membre nommé focusChangeListenerToScrollEditor , et je l'ai appelé pendant onCreate comme ci-dessous.

traverseEditTextChildren(scrollViewSignup, focusChangeListenerToScrollEditor);

Et l'implémentation de la méthode est comme ci-dessous.

private void traverseEditTextChildren(ViewGroup viewGroup, View.OnFocusChangeListener focusChangeListenerToScrollEditor) {
    int childCount = viewGroup.getChildCount();
    for (int i = 0; i < childCount; i++) {
        View view = viewGroup.getChildAt(i);
        if (view instanceof EditText)
        {
            ((EditText) view).setOnFocusChangeListener(focusChangeListenerToScrollEditor);
        }
        else if (view instanceof ViewGroup)
        {
            traverseEditTextChildren((ViewGroup) view, focusChangeListenerToScrollEditor);
        }
    }
}

Donc, ce que nous avons fait ici, c'est que tous les enfants de l'instance EditText appellent l'écouteur au focus.

Pour atteindre cette solution, j'ai vérifié toutes les solutions ici et généré une nouvelle solution pour un meilleur résultat UI / UX.

Merci beaucoup à toutes les autres réponses qui m'inspirent beaucoup.

3

Les réponses ci-dessus fonctionneront bien si ScrollView est le parent direct de ChildView. Si votre ChildView est encapsulé dans un autre ViewGroup dans le ScrollView, il provoquera un comportement inattendu car View.getTop () obtient la position par rapport à son parent. Dans ce cas, vous devez implémenter ceci:

public static void scrollToInvalidInputView(ScrollView scrollView, View view) {
    int vTop = view.getTop();

    while (!(view.getParent() instanceof ScrollView)) {
        view = (View) view.getParent();
        vTop += view.getTop();
    }

    final int scrollPosition = vTop;

    new Handler().post(() -> scrollView.smoothScrollTo(0, scrollPosition));
}

Il s'agit de la solution la plus complète car elle tient également compte des positions des parents. Merci d'avoir posté!
Gustavo Baiocchi Costa


2

Je pense avoir trouvé une solution plus élégante et moins sujette aux erreurs en utilisant

ScrollView.requestChildRectangleOnScreen

Il n'y a pas de mathématiques impliquées, et contrairement aux autres solutions proposées, il gérera correctement le défilement vers le haut et vers le bas.

/**
 * Will scroll the {@code scrollView} to make {@code viewToScroll} visible
 * 
 * @param scrollView parent of {@code scrollableContent}
 * @param scrollableContent a child of {@code scrollView} whitch holds the scrollable content (fills the viewport).
 * @param viewToScroll a child of {@code scrollableContent} to whitch will scroll the the {@code scrollView}
 */
void scrollToView(ScrollView scrollView, ViewGroup scrollableContent, View viewToScroll) {
    Rect viewToScrollRect = new Rect(); //coordinates to scroll to
    viewToScroll.getHitRect(viewToScrollRect); //fills viewToScrollRect with coordinates of viewToScroll relative to its parent (LinearLayout) 
    scrollView.requestChildRectangleOnScreen(scrollableContent, viewToScrollRect, false); //ScrollView will make sure, the given viewToScrollRect is visible
}

C'est une bonne idée de l'envelopper postDelayedpour le rendre plus fiable, au cas où le ScrollViewchangement serait en cours

/**
 * Will scroll the {@code scrollView} to make {@code viewToScroll} visible
 * 
 * @param scrollView parent of {@code scrollableContent}
 * @param scrollableContent a child of {@code scrollView} whitch holds the scrollable content (fills the viewport).
 * @param viewToScroll a child of {@code scrollableContent} to whitch will scroll the the {@code scrollView}
 */
private void scrollToView(final ScrollView scrollView, final ViewGroup scrollableContent, final View viewToScroll) {
    long delay = 100; //delay to let finish with possible modifications to ScrollView
    scrollView.postDelayed(new Runnable() {
        public void run() {
            Rect viewToScrollRect = new Rect(); //coordinates to scroll to
            viewToScroll.getHitRect(viewToScrollRect); //fills viewToScrollRect with coordinates of viewToScroll relative to its parent (LinearLayout) 
            scrollView.requestChildRectangleOnScreen(scrollableContent, viewToScrollRect, false); //ScrollView will make sure, the given viewToScrollRect is visible
        }
    }, delay);
}

1

En examinant le code source Android, vous pouvez constater qu'il existe déjà une fonction membre de ScrollView- scrollToChild(View)- qui fait exactement ce qui est demandé. Malheureusement, cette fonction est marquée pour une raison obscure private. Sur la base de cette fonction, j'ai écrit la fonction suivante qui trouve la première ScrollViewau-dessus du Viewparamètre spécifié et la fait défiler de sorte qu'elle devienne visible dans ScrollView:

 private void make_visible(View view)
 {
  int vt = view.getTop();
  int vb = view.getBottom();

  View v = view;

  for(;;)
     {
      ViewParent vp = v.getParent();

      if(vp == null || !(vp instanceof ViewGroup))
         break;

      ViewGroup parent = (ViewGroup)vp;

      if(parent instanceof ScrollView)
        {
         ScrollView sv = (ScrollView)parent;

         // Code based on ScrollView.computeScrollDeltaToGetChildRectOnScreen(Rect rect) (Android v5.1.1):

         int height = sv.getHeight();
         int screenTop = sv.getScrollY();
         int screenBottom = screenTop + height;

         int fadingEdge = sv.getVerticalFadingEdgeLength();

         // leave room for top fading edge as long as rect isn't at very top
         if(vt > 0)
            screenTop += fadingEdge;

         // leave room for bottom fading edge as long as rect isn't at very bottom
         if(vb < sv.getChildAt(0).getHeight())
            screenBottom -= fadingEdge;

         int scrollYDelta = 0;

         if(vb > screenBottom && vt > screenTop) 
           {
            // need to move down to get it in view: move down just enough so
            // that the entire rectangle is in view (or at least the first
            // screen size chunk).

            if(vb-vt > height) // just enough to get screen size chunk on
               scrollYDelta += (vt - screenTop);
            else              // get entire rect at bottom of screen
               scrollYDelta += (vb - screenBottom);

             // make sure we aren't scrolling beyond the end of our content
            int bottom = sv.getChildAt(0).getBottom();
            int distanceToBottom = bottom - screenBottom;
            scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
           }
         else if(vt < screenTop && vb < screenBottom) 
           {
            // need to move up to get it in view: move up just enough so that
            // entire rectangle is in view (or at least the first screen
            // size chunk of it).

            if(vb-vt > height)    // screen size chunk
               scrollYDelta -= (screenBottom - vb);
            else                  // entire rect at top
               scrollYDelta -= (screenTop - vt);

            // make sure we aren't scrolling any further than the top our content
            scrollYDelta = Math.max(scrollYDelta, -sv.getScrollY());
           }

         sv.smoothScrollBy(0, scrollYDelta);
         break;
        }

      // Transform coordinates to parent:
      int dy = parent.getTop()-parent.getScrollY();
      vt += dy;
      vb += dy;

      v = parent;
     }
 }

1

Ma solution est:

            int[] spinnerLocation = {0,0};
            spinner.getLocationOnScreen(spinnerLocation);

            int[] scrollLocation = {0, 0};
            scrollView.getLocationInWindow(scrollLocation);

            int y = scrollView.getScrollY();

            scrollView.smoothScrollTo(0, y + spinnerLocation[1] - scrollLocation[1]);

1
 scrollView.post(new Runnable() {
                    @Override
                    public void run() {
                        scrollView.smoothScrollTo(0, myTextView.getTop());

                    }
                });

Répondre à mon projet pratique.

entrez la description de l'image ici


0

Dans mon cas, ce n'est pas EditTextça googleMap. Et cela fonctionne avec succès comme ça.

    private final void focusCenterOnView(final ScrollView scroll, final View view) {
    new Handler().post(new Runnable() {
        @Override
        public void run() {
            int centreX=(int) (view.getX() + view.getWidth()  / 2);
            int centreY= (int) (view.getY() + view.getHeight() / 2);
            scrollView.smoothScrollBy(centreX, centreY);
        }
    });
}

0

Que: existe-t-il un moyen de faire défiler par programme une vue de défilement vers un éditxt spécifique?

Rép: la vue de défilement imbriquée dans la dernière position de la vue de recyclage a ajouté des données d'enregistrement.

adapter.notifyDataSetChanged();
nested_scroll.setScrollY(more Detail Recycler.getBottom());

Existe-t-il un moyen de faire défiler par programme une vue de défilement vers un texte d'édition spécifique?


Pourquoi est-ce le fond?
développeur Android

0

Voici ce que j'utilise:

int amountToScroll = viewToShow.getBottom() - scrollView.getHeight() + ((LinearLayout.LayoutParams) viewToShow.getLayoutParams()).bottomMargin;
// Check to see if scrolling is necessary to show the view
if (amountToScroll > 0){
    scrollView.smoothScrollTo(0, amountToScroll);
}

Cela obtient la quantité de défilement nécessaire pour afficher le bas de la vue, y compris toute marge en bas de cette vue.


0

Défilement vertical, bon pour les formulaires. La réponse est basée sur le défilement horizontal Ahmadalibaloch.

private final void focusOnView(final HorizontalScrollView scroll, final View view) {
    new Handler().post(new Runnable() {
        @Override
        public void run() {
            int top = view.getTop();
            int bottom = view.getBottom();
            int sHeight = scroll.getHeight();
            scroll.smoothScrollTo(0, ((top + bottom - sHeight) / 2));
        }
    });
}

êtes-vous sûr que vous devriez utiliser un HorizontalScrollViewdans cette méthode?
Shahood ul Hassan

0

Vous pouvez utiliser ObjectAnimatorcomme ceci:

ObjectAnimator.ofInt(yourScrollView, "scrollY", yourView.getTop()).setDuration(1500).start();

0

Sur la base de la réponse de Sherif, les éléments suivants ont fonctionné le mieux pour mon cas d'utilisation. Les changements notables sont getTop()au lieu de getBottom()et smoothScrollTo()au lieu de scrollTo().

    private void scrollToView(final View view){
        final ScrollView scrollView = findViewById(R.id.bookmarksScrollView);
        if(scrollView == null) return;

        scrollView.post(new Runnable() {
            @Override
            public void run() {
                scrollView.smoothScrollTo(0, view.getTop());
            }
        });
    }

-1

Si scrlMain est votre NestedScrollView, utilisez ce qui suit,

scrlMain.post(new Runnable() {
                    @Override
                    public void run() {
                        scrlMain.fullScroll(View.FOCUS_UP);

                    }
                });
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.