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.


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

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

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


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

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

Myślę, że scrollBoundspowinien być parametrem tej scrollView.getHitRectmetody.
Vijay C


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

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.


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ą.

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

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

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

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

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

                    }
                });

Odpowiedzi z mojego praktycznego projektu.

wprowadź opis zdjęcia tutaj


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


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.


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

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

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

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

                    }
                });
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.