¿Hay alguna manera de desplazar mediante programación una vista de desplazamiento a un texto de edición específico?

206

Tengo una actividad muy larga con una vista de desplazamiento. Es un formulario con varios campos que el usuario debe completar. Tengo una casilla de verificación a la mitad de mi formulario, y cuando el usuario lo marca, quiero desplazarme a una parte específica de la vista. ¿Hay alguna forma de desplazarse a un objeto EditText (o cualquier otro objeto de vista) mediante programación?

Además, sé que esto es posible usando las coordenadas X e Y, pero quiero evitar hacerlo, ya que el formulario puede cambiar de un usuario a otro.

NotACleverMan
fuente
3
the form may changed from user to userpero puedes usarlomEditView.getTop();
nebkat
1
si alguien está usando NestedScrollView dentro de CoordinatorLayout, puede desplazarse a una vista específica a través de stackoverflow.com/questions/52083678/…
user1506104

Respuestas:

452
private final void focusOnView(){
        your_scrollview.post(new Runnable() {
            @Override
            public void run() {
                your_scrollview.scrollTo(0, your_EditBox.getBottom());
            }
        });
    }
Sherif elKhatib
fuente
129129
Descubrí que el uso de smoothScrollTo (0, your_EditBox.getTop ()) obtiene un mejor resultado (un desplazamiento más suave hacia la vista deseada), pero aparte de eso, una gran respuesta.
Muzikant
18
@ xmenW.K. Respuesta corta: debido a que la interfaz de usuario hace su trabajo en función de una cola de cosas que hacer, y básicamente le está diciendo al hilo de la interfaz de usuario, cuando puede, después de haber hecho todo lo que tenía que hacer antes, haga esto (desplazarse) . Básicamente, está poniendo el pergamino en la cola y dejando que el hilo lo haga cuando pueda, respetando el orden de las cosas que estaba haciendo antes de solicitarlo.
Martin Marconcini
37
También es posible publicar el Runnable en el ScrollView en lugar de crear una instancia de un nuevo your_scrollview.post(...
controlador
¿Hay algún método para obtener el centro de visión en lugar de getBottom ()? porque mi caso es googleMap en lugar de EditText.
Zin Win Htet
55
getBottom () y getTop () son relativos al padre
desagradable
57

La respuesta de Sherif elKhatib puede mejorarse enormemente si desea desplazar la vista al centro de la vista de desplazamiento . Este método reutilizable desplaza suavemente la vista al centro visible de 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);
        }
    });
}

Para ScrollViewuso vertical

...
int vTop = view.getTop();
int vBottom = view.getBottom();
int sHeight = scroll.getBottom();
scroll.smoothScrollTo(((vTop + vBottom - sHeight) / 2), 0);
...
ahmadalibaloch
fuente
3
Tienes que ajustar tu código. Debe obtener el ancho de la vista de desplazamiento y debe cambiar la fórmula a sv.smoothScrollTo (((vLeft + vRight) / 2) - (svWidth / 2), 0);
Elemental
2
De lo contrario, la vista en scrollView no se centrará. Ya hice la edición para ti.
Elemental
¿Te ha marcado la fórmula de trabajo exactamente, porque una mayor estaba trabajando para mí agradable
ahmadalibaloch
1
@Elementary Las fórmulas suyas y ahmads son versiones simplificadas y ampliadas de la misma expresión algebraica, no hay necesidad de ajustes.
k29
1
@ k29 ¿te refieres a las dos fórmulas visibles aquí? Ambos son de mi parte, simplifiqué aún más la fórmula de mi comentario y edité la respuesta en consecuencia. Pero todo lo demás sigue siendo el mismo de la respuesta original y también fue muy útil para mí.
Elemental
51

Esto funciona bien para mi :

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

public void RequestChildFocus (Ver hijo, Ver enfocado)

child : el hijo de este ViewParent que quiere enfocarse. Esta vista contendrá la vista enfocada. No es necesariamente la vista la que realmente tiene foco.

Enfocado : la vista que es descendiente de un niño que realmente tiene foco

Swas_99
fuente
Ver tipo de saltos en cualquier desplazamiento suave
silencio el
32

En mi opinión, la mejor manera de desplazarse a un rectángulo dado es a través de View.requestRectangleOnScreen(Rect, Boolean). Debe llamarlo en el Viewque desea desplazarse y pasar un rectángulo local que desea que sea visible en la pantalla. El segundo parámetro debe ser falsepara un desplazamiento suave y truepara un desplazamiento inmediato.

final Rect rect = new Rect(0, 0, view.getWidth(), view.getHeight());
view.requestRectangleOnScreen(rect, false);
Miguel
fuente
1
Me funcionó dentro de un localizador de vistas, hay una vista de desplazamiento y necesito desplazarme a una vista que funciona ahora usando el código mencionado aquí.
Pankaj
2
Definitivamente, esta debería ser la respuesta aceptada, scrollview.smoothScrollTono funcionará si la vista requerida está fuera de la pantalla, (probado en el emulador de Android con SDK versión 26)
Sony
@ Michael ¿Debo envolverme new Handler().post()?
Johnny Five
@JohnnyFive Depende del contexto en el que use este código.
Michael
12

Hice un pequeño método de utilidad basado en la Respuesta de WarrenFaith, este código también tiene en cuenta si esa vista ya está visible en la vista de desplazamiento, no es necesario desplazarse.

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
fuente
Supongo que scrollBoundsdebería ser el parámetro para el scrollView.getHitRectmétodo.
Vijay C
10

Debe hacer que su TextViewsolicitud se centre:

    mTextView.requestFocus();
Ovidiu Latcu
fuente
7

Mi EditText estaba anidado en varias capas dentro de mi ScrollView, que en sí mismo no es la vista raíz del diseño. Debido a que getTop () y getBottom () parecían informar las coordenadas dentro de la vista que lo contiene, tuve que calcular la distancia desde la parte superior de ScrollView hasta la parte superior de EditText iterando a través de los padres del 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();
Ornitorrinco herido
fuente
5

Otra variación sería:

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

o puedes usar el post()método.

Goran Horia Mihail
fuente
5

Sé que esto puede ser demasiado tarde para una mejor respuesta, pero una solución perfecta deseada debe ser un sistema como el posicionador. Quiero decir, cuando el sistema hace un posicionamiento para un campo Editor, coloca el campo justo encima del teclado, por lo que, como reglas UI / UX, es perfecto.

Lo que se muestra a continuación en el código es el posicionamiento de Android sin problemas. En primer lugar, mantenemos el punto de desplazamiento actual como punto de referencia. Lo segundo es encontrar el mejor punto de desplazamiento de posicionamiento para un editor, para hacer esto nos desplazamos hacia arriba, y luego solicitamos los campos del editor para que el componente ScrollView haga el mejor posicionamiento. Gatcha! Hemos aprendido la mejor posición. Ahora, lo que haremos es desplazarnos suavemente desde el punto anterior al punto que hemos encontrado recientemente. Si lo desea, puede omitir el desplazamiento suave utilizando scrollTo en lugar de smoothScrollTo solamente.

NOTA: El contenedor principal ScrollView es un campo miembro llamado scrollViewSignup, porque mi ejemplo era una pantalla de registro, ya que puede descubrir muchas cosas.

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 desea utilizar este bloque para todas las instancias de EditText e integrarlo rápidamente con su código de pantalla. Simplemente puede hacer un recorrido como a continuación. Para hacer esto, hice que el OnFocusChangeListener principal sea un campo miembro llamado focusChangeListenerToScrollEditor , y lo llamo durante onCreate como se muestra a continuación.

traverseEditTextChildren(scrollViewSignup, focusChangeListenerToScrollEditor);

Y la implementación del método es la siguiente.

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

Entonces, lo que hemos hecho aquí es hacer que todos los niños de la instancia de EditText llamen al oyente en foco.

Para llegar a esta solución, revisé todas las soluciones aquí y generé una nueva solución para un mejor resultado UI / UX.

Muchas gracias a todas las otras respuestas que me inspiraron mucho.
Suphi ÇEVİKER
fuente
3

Las respuestas anteriores funcionarán bien si ScrollView es el padre directo de ChildView. Si su ChildView se está envolviendo en otro ViewGroup en ScrollView, causará un comportamiento inesperado porque View.getTop () obtiene la posición relativa a su padre. En tal caso, debe implementar esto:

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
fuente
Esta es la solución más completa, ya que también representa las posiciones de los padres. ¡Gracias por publicar!
Gustavo Baiocchi Costa
2

Creo que he encontrado una solución más elegante y menos propensa a errores usando

ScrollView.requestChildRectangleOnScreen

No hay matemática involucrada, y al contrario de otras soluciones propuestas, manejará el desplazamiento correcto tanto hacia arriba como hacia abajo.

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

Es una buena idea envolverlo postDelayedpara hacerlo más confiable, en caso de que ScrollViewse esté cambiando en este momento

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

Al examinar el código fuente de Android, se puede encontrar que ya hay una función miembro de ScrollView- scrollToChild(View)- que hace exactamente lo que se solicita. Desafortunadamente, esta función está marcada por alguna oscura razón private. En función de esa función, he escrito la siguiente función que encuentra la primera ScrollViewarriba de la Viewespecificada como parámetro y la desplaza para que sea visible dentro de 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
fuente
1

Mi solución es:

            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
fuente
1
 scrollView.post(new Runnable() {
                    @Override
                    public void run() {
                        scrollView.smoothScrollTo(0, myTextView.getTop());

                    }
                });

Respondiendo desde mi proyecto práctico.

ingrese la descripción de la imagen aquí

XpressGeek
fuente
0

En mi caso, eso no es EditText, eso es googleMap. Y funciona con éxito así.

    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
fuente
0

Que: ¿Hay alguna manera de desplazar mediante programación una vista de desplazamiento a un texto de edición específico?

Respuesta: La vista de desplazamiento anidada en la vista de reciclaje de la última posición agregó datos de registro.

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

¿Hay alguna manera de desplazar mediante programación una vista de desplazamiento a un texto de edición específico?

Ravi Bhalala
fuente
¿Por qué es el fondo?
Desarrollador de Android
0

Lo siguiente es lo que estoy usando:

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

Esto obtiene la cantidad de desplazamiento necesaria para mostrar la parte inferior de la vista, incluido cualquier margen en la parte inferior de esa vista.

LukeWaggoner
fuente
0

Desplazamiento vertical, bueno para formas. La respuesta se basa en el desplazamiento 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));
        }
    });
}
Qamar
fuente
¿Estás seguro de que debes usar un HorizontalScrollViewen este método?
Shahood ul Hassan
0

Puedes usar ObjectAnimatorasí:

ObjectAnimator.ofInt(yourScrollView, "scrollY", yourView.getTop()).setDuration(1500).start();
Tazeem Siddiqui
fuente
0

Según la respuesta de Sherif, lo siguiente funcionó mejor para mi caso de uso. Los cambios notables son en getTop()lugar de getBottom()y en smoothScrollTo()lugar 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());
            }
        });
    }
datchung
fuente
-1

Si scrlMain es su NestedScrollView, use lo siguiente,

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

                    }
                });
Ashwin Balani
fuente