Czy istnieje sposób programowego przewijania widoku przewijania do określonego tekstu edycji?

206

Mam bardzo długą aktywność z przewijaniem. Jest to formularz z różnymi polami, które użytkownik musi wypełnić. Mam pole wyboru w połowie mojego formularza, a gdy użytkownik je sprawdzi, chcę przewinąć do określonej części widoku. Czy istnieje jakiś sposób programowego przewijania do obiektu EditText (lub innego obiektu widoku)?

Wiem też, że jest to możliwe przy użyciu współrzędnych X i Y, ale chcę tego uniknąć, ponieważ formularz może się zmieniać w zależności od użytkownika.

NotACleverMan
źródło
3
the form may changed from user to userale możesz po prostu użyćmEditView.getTop();
nebkat
1
jeśli ktoś używa NestedScrollView w ramach CoordinatorLayout, możesz przewinąć do konkretnego widoku przez stackoverflow.com/questions/52083678/…
user1506104 10.1018

Odpowiedzi:

451
private final void focusOnView(){
        your_scrollview.post(new Runnable() {
            @Override
            public void run() {
                your_scrollview.scrollTo(0, your_EditBox.getBottom());
            }
        });
    }
Sherif elKhatib
źródło
129
Odkryłem, że użycie smoothScrollTo (0, twoja_EditBox.getTop ()) daje lepszy wynik (uduszenie przewijania do pożądanego widoku), ale poza tym - świetna odpowiedź.
Muzikant
18
@ xmenW.K. Krótka odpowiedź: ponieważ interfejs użytkownika działa w oparciu o kolejkę rzeczy do zrobienia, a ty w zasadzie mówisz do wątku interfejsu użytkownika, kiedy możesz, po tym, co zrobiłeś wcześniej, zrób to (przewiń) . Zasadniczo umieszczasz zwój w kolejce i pozwalasz wątkowi zrobić to, kiedy to możliwe, przestrzegając kolejności rzeczy, które robił, zanim o to poprosiłeś.
Martin Marconcini,
37
Możliwe jest również opublikowanie Runnable w samym ScrollView zamiast tworzenia nowego programu obsługi:your_scrollview.post(...
Sherif elKhatib
Czy istnieje metoda uzyskiwania punktu widzenia zamiast metody getBottom ()? bo moja sprawa to googleMap zamiast EditText.
Zin Win Htet
5
getBottom () i getTop () są względne w stosunku do rodzica
kambuzowy
57

Odpowiedź Sherif elKhatib może zostać znacznie ulepszona, jeśli chcesz przewinąć widok do środka widoku przewijania . Ta metoda wielokrotnego użytku płynnie przewija widok do widocznego środka 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);
        }
    });
}

Do ScrollViewużytku pionowego

...
int vTop = view.getTop();
int vBottom = view.getBottom();
int sHeight = scroll.getBottom();
scroll.smoothScrollTo(((vTop + vBottom - sHeight) / 2), 0);
...
ahmadalibaloch
źródło
3
Musisz dostosować kod. Musisz uzyskać szerokość widoku przewijania i musisz zmienić formułę na sv.smoothScrollTo (((vLeft + vRight) / 2) - (svWidth / 2), 0);
Podstawowy
2
W przeciwnym razie widok w scrollView nie zostanie wyśrodkowany. Zrobiłem już dla ciebie edycję.
Podstawowy
Czy sprawdziłeś, czy formuła działa dokładnie, ponieważ starszy działał dla mnie dobrze
ahmadalibaloch
1
@Elementary Formuły twoje i ahmady są uproszczonymi i rozszerzonymi wersjami tego samego wyrażenia algebraicznego, nie ma potrzeby wprowadzania poprawek.
k29
1
@ k29 czy odnosisz się tutaj do dwóch widocznych wzorów? Oba pochodzą ode mnie, dodatkowo uprościłem formułę mojego komentarza i odpowiednio zredagowałem odpowiedź. Ale cała reszta jest nadal taka sama jak w oryginalnej odpowiedzi i była dla mnie bardzo pomocna.
Podstawowy
51

Działa to dla mnie dobrze:

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

public void RequestChildFocus (Wyświetl dziecko, Wyświetl skoncentrowane)

child - Dziecko tego ViewParent, które chce się skupić. Ten widok będzie zawierał widok skupiony. Niekoniecznie musi to być widok.

skoncentrowany - widok, który jest potomkiem dziecka, który faktycznie jest skupiony

Swas_99
źródło
Zobacz rodzaj skoków w dowolnym płynnym przewijaniu
silentsudo
32

Moim zdaniem najlepszym sposobem przewijania do danego prostokąta jest View.requestRectangleOnScreen(Rect, Boolean). Powinieneś zadzwonić na miejsce, do Viewktórego chcesz przewinąć i przekazać lokalny prostokąt, który ma być widoczny na ekranie. Drugim parametrem powinno być falsepłynne przewijanie i truenatychmiastowe przewijanie.

final Rect rect = new Rect(0, 0, view.getWidth(), view.getHeight());
view.requestRectangleOnScreen(rect, false);
Michael
źródło
1
Pracowałem dla mnie wewnątrz pagera widokowego, jest on scrollview i muszę przewinąć do widoku, który działa teraz, używając kodu wymienionego tutaj.
Pankaj,
2
Zdecydowanie ta odpowiedź powinna być zaakceptowana, scrollview.smoothScrollTonie zadziała, jeśli wymagany widok jest poza ekranem, (wypróbowany w emulatorze Androida z wersją 26 SDK)
Sony
@Michael powinienem się owinąć new Handler().post()?
Johnny Five,
@JohnnyFive Zależy to od kontekstu, w którym używasz tego kodu.
Michael
12

Zrobiłem małą metodę narzędzia opartą na odpowiedzi z WarrenFaith, ten kod bierze również pod uwagę, jeśli ten widok jest już widoczny w widoku przewijania, nie ma potrzeby przewijania.

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());
            }
        });
    } 
}
Tobrun
źródło
Myślę, że scrollBoundspowinien być parametrem tej scrollView.getHitRectmetody.
Vijay C
10

Powinieneś TextViewskupić się na swoim żądaniu:

    mTextView.requestFocus();
Ovidiu Latcu
źródło
7

Mój EditText został zagnieżdżony kilka warstw w moim ScrollView, który sam nie jest głównym widokiem układu. Ponieważ getTop () i getBottom () zdawały się zgłaszać współrzędne w obrębie zawierającego widok, kazałem mu obliczyć odległość od górnej części ScrollView do górnej części EditText poprzez iterację przez rodziców 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();
Ranny dziobak
źródło
5

Inną odmianą byłoby:

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

lub możesz użyć tej post()metody.

Goran Horia Mihail
źródło
5

Wiem, że może być za późno na lepszą odpowiedź, ale pożądanym idealnym rozwiązaniem musi być systemowy pozycjoner. Mam na myśli, że gdy system dokonuje pozycjonowania dla pola edytora, umieszcza to pole tylko do klawiatury, więc zgodnie z regułami UI / UX jest on idealny.

Poniższy kod sprawia, że ​​pozycjonowanie w systemie Android przebiega płynnie. Przede wszystkim utrzymujemy bieżący punkt przewijania jako punkt odniesienia. Po drugie, znajdź najlepszy punkt pozycjonowania przewijania dla edytora, w tym celu przewijamy do góry, a następnie żądamy od pól edytora, aby komponent ScrollView wykonał najlepsze pozycjonowanie. Gatcha! Nauczyliśmy się najlepszej pozycji. Teraz będziemy płynnie przewijać od poprzedniego punktu do punktu, który niedawno odnaleźliśmy. Jeśli chcesz, możesz pominąć płynne przewijanie za pomocą scrollTo zamiast tylko smoothScrollTo .

UWAGA: Główny kontener ScrollView jest polem członkowskim o nazwie scrollViewSignup, ponieważ mój przykład był ekranem rejestracyjnym, jak można się wiele domyślić.

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

Jeśli chcesz użyć tego bloku dla wszystkich instancji EditText i szybko zintegruj go ze swoim kodem ekranowym. Możesz po prostu zrobić trawers jak poniżej. Aby to zrobić, uczyniłem z głównego OnFocusChangeListener pole członka o nazwie focusChangeListenerToScrollEditor i wywołałem go podczas onCreate, jak poniżej.

traverseEditTextChildren(scrollViewSignup, focusChangeListenerToScrollEditor);

A implementacja metody jest następująca.

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

To, co zrobiliśmy tutaj, sprawia, że ​​wszystkie dzieci instancji EditText są w stanie wywoływać nasłuchiwanie.

Aby osiągnąć to rozwiązanie, sprawdziłem wszystkie rozwiązania tutaj i wygenerowałem nowe rozwiązanie dla lepszego wyniku UI / UX.

Ogromne podziękowania dla wszystkich innych odpowiedzi, które mnie bardzo inspirują.
Suphi ÇEVİKER
źródło
3

Powyższe odpowiedzi będą działać poprawnie, jeśli ScrollView jest bezpośrednim rodzicem ChildView. Jeśli twój ChildView jest zawijany w innej ViewGroup w ScrollView, spowoduje to nieoczekiwane zachowanie, ponieważ View.getTop () otrzymuje pozycję względem swojego rodzica. W takim przypadku musisz to zaimplementować:

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));
}
Sam Fisher
źródło
Jest to najbardziej kompleksowe rozwiązanie, ponieważ uwzględnia również pozycje rodziców. Dzięki za opublikowanie!
Gustavo Baiocchi Costa
2

Myślę, że znalazłem bardziej eleganckie i mniej podatne na błędy rozwiązanie

ScrollView.requestChildRectangleOnScreen

Nie wymaga matematyki i w przeciwieństwie do innych proponowanych rozwiązań, poradzi sobie z prawidłowym przewijaniem w górę i w dół.

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

Dobrym pomysłem jest owinięcie go, postDelayedaby uczynić go bardziej niezawodnym, na wypadek, gdyby ScrollVieww tej chwili był zmieniany

/**
 * 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);
}
Štarke
źródło
1

Analizując kod źródłowy Androida, możesz stwierdzić, że istnieje już funkcja członka ScrollView- scrollToChild(View)- która robi dokładnie to, o co prosiłeś. Niestety ta funkcja jest z jakiegoś niejasnego powodu zaznaczona private. Na podstawie tej funkcji napisałem następującą funkcję, która znajduje pierwszą ScrollViewpowyżej Viewokreślonego jako parametr i przewija ją, aby stała się widoczna w 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;
     }
 }
Jaromír Adamec
źródło
1

Moje rozwiązanie to:

            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]);
w4kskl
źródło
1
 scrollView.post(new Runnable() {
                    @Override
                    public void run() {
                        scrollView.smoothScrollTo(0, myTextView.getTop());

                    }
                });

Odpowiedzi z mojego praktycznego projektu.

wprowadź opis zdjęcia tutaj

XpressGeek
źródło
0

W moim przypadku tak nie EditTextjest googleMap. I działa z powodzeniem w ten sposób.

    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);
        }
    });
}
Zin Win Htet
źródło
0

Oto, czego używam:

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

Pobiera to ilość przewijania niezbędną do wyświetlenia dolnej części widoku, w tym marginesu na dole tego widoku.

LukeWaggoner
źródło
0

Przewijanie w pionie, dobre dla formularzy. Odpowiedź oparta jest na przewijaniu poziomym 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));
        }
    });
}
Qamar
źródło
czy na pewno powinieneś użyć HorizontalScrollVieww tej metodzie?
Shahood ul Hassan
0

Możesz użyć w ObjectAnimatorten sposób:

ObjectAnimator.ofInt(yourScrollView, "scrollY", yourView.getTop()).setDuration(1500).start();
Tazeem Siddiqui
źródło
0

W oparciu o odpowiedź Sherif, w moim przypadku użycia najlepiej działały następujące. Znaczące zmiany są getTop()zamiast getBottom()i smoothScrollTo()zamiast 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());
            }
        });
    }
datchung
źródło
-1

Jeśli scrlMain jest Twoim NestedScrollView, użyj następujących poleceń,

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

                    }
                });
Ashwin Balani
źródło