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.

NotACleverMan
fonte
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());
            }
        });
    }
Sherif elKhatib
fonte
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);
...
ahmadalibaloch
fonte
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

Swas_99
fonte
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);
Michael
fonte
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());
            }
        });
    } 
}
Tobrun
fonte
Eu acho que scrollBoundsdeveria ser o parâmetro para o scrollView.getHitRectmétodo
Vijay C
10

Você deve TextViewfocar sua solicitação:

    mTextView.requestFocus();
Ovidiu Latcu
fonte
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();
Ornitorrinco ferido
fonte
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

Goran Horia Mihail
fonte
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.
Suphi ÇEVİKER
fonte
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));
}
Sam Fisher
fonte
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);
}
Štarke
fonte
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;
     }
 }
Jaromír Adamec
fonte
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]);
w4kskl
fonte
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

XpressGeek
fonte
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);
        }
    });
}
Zin Win Htet
fonte
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.

LukeWaggoner
fonte
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));
        }
    });
}
Qamar
fonte
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();
Tazeem Siddiqui
fonte
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());
            }
        });
    }
datchung
fonte
-1

Se scrlMain é seu NestedScrollView, use o seguinte,

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

                    }
                });
Ashwin Balani
fonte