C'è un modo per scorrere programmaticamente una vista di scorrimento fino a un testo di modifica specifico?


206

Ho una lunghissima attività con una scrollview. È un modulo con vari campi che l'utente deve compilare. Ho una casella di controllo a metà del mio modulo e quando l'utente lo seleziona, voglio scorrere fino a una parte specifica della vista. Esiste un modo per scorrere fino a un oggetto EditText (o qualsiasi altro oggetto vista) a livello di codice?

Inoltre, so che questo è possibile utilizzando le coordinate X e Y, ma voglio evitare di farlo poiché il modulo può cambiare da utente a utente.


3
the form may changed from user to userma puoi semplicemente usaremEditView.getTop();
nebkat il

1
se qualcuno sta utilizzando NestedScrollView all'interno CoordinatorLayout, è possibile scorrere fino a una vista specifica tramite stackoverflow.com/questions/52083678/...
user1506104

Risposte:


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

129
Ho scoperto che l'uso di smoothScrollTo (0, your_EditBox.getTop ()) ottiene un risultato migliore (smorzando lo scorrimento fino alla vista desiderata) ma a parte questo, un'ottima risposta.
Muzikant,

18
@ xmenW.K. Risposta breve: poiché l'interfaccia utente fa il suo lavoro in base a una coda di cose da fare e in pratica stai dicendo al thread dell'interfaccia utente, quando puoi, dopo aver fatto tutto quello che dovevi fare prima, farlo (scorrere) . In pratica stai mettendo il rotolo in coda e lasciando che il thread lo faccia quando può, rispettando l'ordine delle cose che stava facendo prima di richiederlo.
Martin Marconcini

37
È anche possibile pubblicare il Runnable su ScrollView stesso invece di creare un'istanza di un nuovo gestore:your_scrollview.post(...
Sherif elKhatib,

Esiste un metodo per ottenere il centro di visualizzazione anziché getBottom ()? perché il mio caso è googleMap invece di EditText.
Zin Vinci Htet il

5
getBottom () e getTop () sono relativi al genitore
cambognoso

57

La risposta di Sherif elKhatib può essere notevolmente migliorata, se si desidera scorrere la vista al centro della vista di scorrimento . Questo metodo riutilizzabile scorre uniformemente la vista fino al centro visibile di 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);
        }
    });
}

Per un ScrollViewuso verticale

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

3
Devi modificare il tuo codice. Devi ottenere la larghezza della vista di scorrimento e cambiare la formula in sv.smoothScrollTo ((((vLeft + vRight) / 2) - (svWidth / 2), 0);
Elementare

2
Altrimenti la vista in scrollView non sarà centrata. Ho già fatto il montaggio per te.
Elementare

Hai controllato che la formula funzionasse esattamente, perché quella più vecchia funzionava bene per me
ahmadalibaloch,

1
@Elementary Le tue e ahmads formule sono versioni semplificate ed espanse della stessa espressione algebrica, non è necessario alcun aggiustamento.
k29,

1
@ k29 ti riferisci alle due formule visibili qui? Sono entrambi da me, ho ulteriormente semplificato la formula del mio commento e modificato la risposta di conseguenza. Ma tutto il resto è sempre lo stesso della risposta originale ed è stato anche molto utile per me.
Elementare

51

Questo funziona bene per me:

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

void pubblico RequestChildFocus (Visualizza figlio, Visualizza focalizzato)

child - Il figlio di questo ViewParent che desidera concentrarsi. Questa vista conterrà la vista focalizzata. Non è necessariamente la visione che in realtà ha il focus.

focalizzato - La vista che è un discendente del bambino che in realtà ha il focus


Visualizza i tipi di salti in qualsiasi scroll liscio
silentsudo

32

Secondo me il modo migliore per scorrere fino a un determinato rettangolo è via View.requestRectangleOnScreen(Rect, Boolean). Dovresti chiamarlo su uno Viewche vuoi scorrere e passare un rettangolo locale che vuoi essere visibile sullo schermo. Il secondo parametro dovrebbe essere falseper lo scorrimento regolare e trueper lo scorrimento immediato.

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

1
Ha funzionato per me all'interno di un cercapersone di visualizzazione, è presente la vista di scorrimento e è necessario scorrere fino a una vista che funziona ora utilizzando il codice menzionato qui.
Pankaj,

2
Sicuramente questa dovrebbe essere la risposta accettata, scrollview.smoothScrollTonon funzionerà se la vista richiesta è fuori dallo schermo, (provato nell'emulatore Android con sdk versione 26)
Sony

@Michael Dovrei concludere new Handler().post()?
Johnny Five,

@JohnnyFive Dipende dal contesto in cui si utilizza questo codice.
Michael,

12

Ho creato un piccolo metodo di utilità basato sulla risposta di WarrenFaith, questo codice tiene conto anche se quella vista è già visibile nella vista di scorrimento, senza necessità di scorrimento.

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

Immagino che scrollBoundsdovrebbe essere il parametro per il scrollView.getHitRectmetodo.
Vijay C,

10

Dovresti TextViewfocalizzare la tua richiesta:

    mTextView.requestFocus();

7

Il mio EditText è stato nidificato su diversi livelli all'interno di ScrollView, che di per sé non è la vista radice del layout. Poiché getTop () e getBottom () sembravano riportare le coordinate all'interno della sua vista contenente, ho fatto calcolare la distanza dalla cima di ScrollView alla cima di EditText ripetendo i genitori di 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

Un'altra variante sarebbe:

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

oppure puoi usare il post()metodo


5

So che potrebbe essere troppo tardi per una risposta migliore, ma una soluzione perfetta desiderata deve essere un sistema come il posizionatore. Voglio dire, quando il sistema effettua un posizionamento per un campo dell'editor, posiziona il campo fino alla tastiera, quindi come regola l'interfaccia utente / UX è perfetto.

Ciò che rende il codice seguente è il modo in cui Android si posiziona senza problemi. Innanzitutto manteniamo l'attuale punto di scorrimento come punto di riferimento. La seconda cosa è trovare il punto di scorrimento del posizionamento migliore per un editor, per fare ciò scorrere verso l'alto e quindi richiedere i campi dell'editor per fare in modo che il componente ScrollView esegua il posizionamento migliore. Gatcha! Abbiamo imparato la posizione migliore. Ora, quello che faremo è scorrere senza problemi dal punto precedente al punto che abbiamo trovato di nuovo. Se si vuole si può omettere Smooth scrolling utilizzando scrollTo invece smoothScrollTo solo.

NOTA: Il contenitore principale ScrollView è un campo membro chiamato scrollViewSignup, perché il mio esempio era una schermata di iscrizione, come puoi capire molto.

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

Se si desidera utilizzare questo blocco per tutte le istanze di EditText e integrarlo rapidamente con il codice dello schermo. Puoi semplicemente fare un attraversatore come di seguito. Per fare questo, ho trasformato OnFocusChangeListener principale in un campo membro chiamato focusChangeListenerToScrollEditor e lo ho chiamato durante onCreate come di seguito.

traverseEditTextChildren(scrollViewSignup, focusChangeListenerToScrollEditor);

E l'implementazione del metodo è la seguente.

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

Quindi, ciò che abbiamo fatto qui è fare in modo che tutti i figli dell'istanza EditText chiamino l'ascoltatore in primo piano.

Per raggiungere questa soluzione, ho verificato tutte le soluzioni qui e ho generato una nuova soluzione per un migliore risultato UI / UX.

Mille grazie a tutte le altre risposte che mi hanno ispirato molto.

3

Le risposte sopra funzioneranno perfettamente se ScrollView è il genitore diretto di ChildView. Se ChildView viene racchiuso in un altro ViewGroup in ScrollView, causerà comportamenti imprevisti perché View.getTop () ottiene la posizione relativa al suo genitore. In tal caso, è necessario implementare questo:

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

Questa è la soluzione più completa in quanto tiene conto anche delle posizioni dei genitori. Grazie per la pubblicazione!
Gustavo Baiocchi Costa


2

Penso di aver trovato una soluzione più elegante e meno soggetta a errori

ScrollView.requestChildRectangleOnScreen

Non è coinvolta la matematica e, contrariamente alle altre soluzioni proposte, gestirà lo scorrimento corretto sia su che giù.

/**
 * 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
}

È una buona idea avvolgerlo postDelayedper renderlo più affidabile, nel caso in cui ScrollViewal momento sia cambiato

/**
 * 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

Esaminando il codice sorgente di Android, è possibile trovare che ci sia già una funzione di membro di ScrollView- scrollToChild(View)- che fa esattamente ciò che viene richiesto. Sfortunatamente, questa funzione è contrassegnata per qualche oscura ragione private. Sulla base di quella funzione ho scritto la seguente funzione che trova il primo ScrollViewsopra il Viewspecificato come parametro e lo scorre in modo che diventi visibile all'interno di 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

La mia soluzione è:

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

                    }
                });

Rispondendo al mio progetto pratico.

inserisci qui la descrizione dell'immagine


0

Nel mio caso, non EditTextè così googleMap. E funziona con successo in questo modo.

    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

Quello che sto usando è il seguente:

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

Questo ottiene la quantità di scorrimento necessaria per mostrare la parte inferiore della vista, incluso qualsiasi margine nella parte inferiore di quella vista.


0

Scorrimento verticale, buono per i moduli. La risposta si basa sul rotolo orizzontale 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));
        }
    });
}

sei sicuro che dovresti usare a HorizontalScrollViewin questo metodo?
Shahood ul Hassan,

0

Puoi usare ObjectAnimatorcosì:

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

0

Sulla base della risposta di Sherif, quanto segue ha funzionato meglio per il mio caso d'uso. Notevoli cambiamenti sono getTop()invece di getBottom()e smoothScrollTo()invece di 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

Se scrlMain è NestedScrollView, utilizzare quanto segue,

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

                    }
                });
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.