Existe uma maneira de rolar programaticamente uma exibição de rolagem para um texto de edição específico?


206

Eu tenho uma atividade muito longa com um scrollview. É um formulário com vários campos que o usuário deve preencher. Eu tenho uma caixa de seleção no meio do formulário e, quando o usuário o verifica, quero rolar para uma parte específica da visualização. Existe alguma maneira de rolar para um objeto EditText (ou qualquer outro objeto de exibição) programaticamente?

Além disso, eu sei que isso é possível usando as cordas X e Y, mas quero evitar isso, pois o formulário pode ser alterado de usuário para usuário.


3
the form may changed from user to usermas você pode apenas usarmEditView.getTop();
— nebkat

1
se alguém estiver usando NestedScrollView no CoordinatorLayout, você poderá rolar para uma exibição específica em stackoverflow.com/questions/52083678/…
— user1506104

Respostas:


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

129
Descobri que usar smoothScrollTo (0, your_EditBox.getTop ()) obtém um resultado melhor (rolagem mais suave para a visualização desejada), mas fora isso - ótima resposta.
— Muzikant 29/07

18
@ xmenW.K. Resposta curta: como a interface do usuário faz seu trabalho com base em uma fila de coisas a serem feitas, e você basicamente diz ao thread da interface do usuário, quando puder, depois de fazer tudo o que precisava antes, faça isso (role) . Você está basicamente colocando o pergaminho na fila e deixando o thread fazer o que pode, respeitando a ordem das coisas que estava fazendo antes de solicitá-lo.
— Martin Marconcini

37
Também é possível postar o Runnable no próprio ScrollView em vez de instanciar um novo manipulador: your_scrollview.post(...
— Sherif elKhatib

Existe algum método para obter o centro da visão em vez de getBottom ()? porque meu caso é googleMap em vez de EditText.
— Zin Win Htet

5
getBottom () e getTop () são relativos ao pai
— cambunctious

57

A resposta do Sherif elKhatib pode ser bastante aprimorada, se você quiser rolar a exibição para o centro da exibição de rolagem . Esse método reutilizável rola a exibição suavemente para o centro visível de um 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 uma vertical ScrollView uso

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

3
Você tem que ajustar seu código. Você precisa obter a largura da exibição de rolagem e alterar a fórmula para sv.smoothScrollTo (((vLeft + vRight) / 2) - (svWidth / 2), 0);
— Elementar,

2
Caso contrário, a visualização no scrollView não será centralizada. Eu já fiz a edição para você.
— Elementar,

Será que você verificou a fórmula trabalhando exatamente, porque um mais velho estava trabalhando bom para mim
— ahmadalibaloch

1
@Elementary As suas fórmulas e ahmads são versões simplificadas e expandidas da mesma expressão algébrica, não há necessidade de ajustes.
— K29 17/08

1
@ k29 você se refere às duas fórmulas visíveis aqui? Ambos são meus, simplifiquei ainda mais a fórmula do meu comentário e editei a resposta de acordo. Mas o resto ainda é o mesmo da resposta original e também foi muito útil para mim.
— Elementar

51

Isto funciona bem para mim :

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

public void RequestChildFocus (Ver filho, Ver focado)

child - O filho deste ViewParent que deseja foco. Essa visualização conterá a visualização focada. Não é necessariamente a visão que realmente tem foco.

focado - A visão de um descendente de criança que realmente tem foco


Veja tipos de saltos em qualquer rolagem suave
— silentsudo

32

Na minha opinião, a melhor maneira de rolar para um determinado retângulo é via View.requestRectangleOnScreen(Rect, Boolean). Você deve chamá- Viewlo para onde deseja rolar e passar um retângulo local que deseja que seja visível na tela. O segundo parâmetro deve ser falsepara rolagem suave e truerolagem imediata.

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

1
Trabalhou para mim dentro de um pager de exibição. Há scrollview e é necessário rolar para uma exibição que está funcionando agora usando o código mencionado aqui.
— Pankaj

2
Definitivamente, esta deve ser a resposta aceita, scrollview.smoothScrollTonão vai funcionar se a visão necessária é fora da tela, (tentou em emulador android com SDK versão 26)
— Sony

@ Michael Devo embrulhar new Handler().post()?
— Johnny Cinco

@JohnnyFive Depende do contexto em que você usa esse código.
— Michael

12

Eu criei um pequeno método utilitário baseado em Resposta do WarrenFaith, esse código também leva em consideração se essa exibição já estiver visível na visualização de rolagem, sem a necessidade de rolagem.

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

Eu acho que scrollBoundsdeveria ser o parâmetro para o scrollView.getHitRectmétodo
— Vijay C

10

Você deve TextViewfocar sua solicitação:

    mTextView.requestFocus();

7

Meu EditText foi aninhado várias camadas dentro do meu ScrollView, que por si só não é a visualização raiz do layout. Como getTop () e getBottom () pareciam relatar as coordenadas dentro de sua exibição, eu calculei a distância da parte superior do ScrollView até a parte superior do EditText, percorrendo os pais do 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

Outra variação seria:

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

ou você pode usar o post()método


5

Eu sei que isso pode ser tarde demais para uma resposta melhor, mas a solução perfeita desejada deve ser um sistema como posicionador. Quero dizer, quando o sistema faz um posicionamento para um campo do Editor, ele coloca o campo apenas no teclado, de modo que as regras da UI / UX são perfeitas.

O que o código abaixo faz é a maneira como o Android se posiciona sem problemas. Primeiro, mantemos o ponto de rolagem atual como um ponto de referência. A segunda coisa é encontrar o melhor ponto de rolagem de posicionamento para um editor, para fazer isso, role para cima e solicite os campos do editor para que o componente ScrollView faça o melhor posicionamento. Gatcha! Nós aprendemos a melhor posição. Agora, o que faremos é rolar sem problemas do ponto anterior até o ponto que encontramos recentemente. Se desejar, você pode omitir a rolagem suave usando scrollTo em vez de smoothScrollTo apenas.

NOTA: O container principal ScrollView é um campo de membro chamado scrollViewSignup, porque meu exemplo foi uma tela de inscrição, como você pode descobrir muito.

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 você deseja usar este bloco para todas as instâncias do EditText , integre-o rapidamente ao seu código de tela. Você pode simplesmente fazer uma travessia como abaixo. Para fazer isso, eu criei o OnFocusChangeListener principal como um campo de membro chamado focusChangeListenerToScrollEditor e chamei durante onCreate, conforme abaixo.

traverseEditTextChildren(scrollViewSignup, focusChangeListenerToScrollEditor);

E a implementação do método é como abaixo.

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

Portanto, o que fizemos aqui é fazer com que todos os filhos da instância EditText chamem o ouvinte em foco.

Para chegar a essa solução, verifiquei todas as soluções aqui e criei uma nova solução para obter melhores resultados de UI / UX.

Muito obrigado a todas as outras respostas que me inspiraram muito.

3

As respostas acima funcionarão bem se o ScrollView for o pai direto do ChildView. Se o seu ChildView estiver sendo agrupado em outro ViewGroup no ScrollView, ele causará um comportamento inesperado porque o View.getTop () obtém a posição em relação ao pai. Nesse caso, você precisa implementar isso:

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

Esta é a solução mais abrangente, pois também é responsável pelas posições dos pais. Obrigado por publicar!
— Gustavo Baiocchi Costa


2

Acho que encontrei uma solução mais elegante e menos propensa a erros usando

ScrollView.requestChildRectangleOnScreen

Não há matemática envolvida e, ao contrário de outras soluções propostas, ele manipulará a rolagem correta tanto para cima quanto para baixo.

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

É uma boa idéia envolvê-lo postDelayedpara torná-lo mais confiável, caso ScrollViewesteja sendo alterado no 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);
}

1

Examinando o código fonte do Android, você pode descobrir que já existe uma função membro de ScrollView- scrollToChild(View)- que faz exatamente o que é solicitado. Infelizmente, esta função está marcada por alguma razão obscura private. Com base nessa função, escrevi a seguinte função que encontra o primeiro ScrollViewacima do Viewespecificado como parâmetro e o rola para que fique visível dentro do 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

Minha solução é:

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

                    }
                });

Respondendo do meu projeto prático.

insira a descrição da imagem aqui


0

No meu caso, não EditTexté isso googleMap. E funciona com sucesso assim.

    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

A seguir, é o que estou 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);
}

Isso obtém a quantidade de rolagem necessária para mostrar a parte inferior da visualização, incluindo qualquer margem na parte inferior da visualização.


0

Rolagem vertical, boa para formulários. A resposta é baseada na rolagem 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));
        }
    });
}

tem certeza de que deve usar a HorizontalScrollViewnesse método?
— Shahood ul Hassan

0

Você pode usar ObjectAnimatorassim:

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

0

Com base na resposta de Sherif, o seguinte funcionou melhor no meu caso de uso. Mudanças notáveis ​​são em getTop()vez de getBottom()e em smoothScrollTo()vez 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

Se scrlMain é seu NestedScrollView, use o seguinte,

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

                    }
                });
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.