ChatGPT解决这个技术问题 Extra ChatGPT

Is there a way to programmatically scroll a scroll view to a specific edit text?

I have a very long activity with a scrollview. It is a form with various fields that the user must fill in. I have a checkbox half way down my form, and when the user checks it I want to scroll to a specific part of the view. Is there any way to scroll to an EditText object (or any other view object) programmatically?

Also, I know this is possible using X and Y coords but I want to avoid doing this as the form may changed from user to user.

the form may changed from user to user but you can just use mEditView.getTop();
if someone is using NestedScrollView within CoordinatorLayout, you can scroll to a specific view via stackoverflow.com/questions/52083678/…

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

I found that using smoothScrollTo(0, your_EditBox.getTop()) gets a better result (smother scrolling to the desired view) but other than that - great answer.
@xmenW.K. Short Answer: because the UI does its work based on a queue of things to do, and you're basically telling the UI thread, when you can, after you've done all you had to do before now, do this (scroll). You're basically putting the scroll in the queue and letting the thread do it when it can, respecting the order of things it was doing before you requested it.
It is also possible to post the Runnable to the ScrollView itself instead of instantiating a new Handler: your_scrollview.post(...
getBottom() and getTop() are relative to the parent
Why is it getBottom(), and not getTop()?
a
ahmadalibaloch

The answer of Sherif elKhatib can be greatly improved, if you want to scroll the view to the center of the scroll view. This reusable method smooth scrolls the view to the visible center of a 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);
        }
    });
}

For a vertical ScrollView use

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

You have to adjust you code. You have to get the width of the scroll view and you have to change the formula to sv.smoothScrollTo(((vLeft + vRight) / 2) - (svWidth / 2), 0);
Otherwise the view in the scrollView won't get centered. I already did the editing for you.
@Elementary Yours and ahmads formulas are simplified and expanded versions of the same algebraic expression, there's no need for adjustments.
@k29 do you refer to the two visible formulas here? They are both from me, I further simplified the formula of my comment and edited the answer accordingly. But all the rest is still the same from the original answer and was also very helpful for me.
Isn't the x and y mixed up in your vertical ScrollView answer?
S
Swas_99

This works well for me :

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

public void RequestChildFocus (View child, View focused)

child - The child of this ViewParent that wants focus. This view will contain the focused view. It is not necessarily the view that actually has focus.

focused - The view that is a descendant of child that actually has focus


View kind of jumps in any smooth scroll
M
Michael

In my opinion the best way to scroll to a given rectangle is via View.requestRectangleOnScreen(Rect, Boolean). You should call it on a View you want to scroll to and pass a local rectangle you want to be visible on the screen. The second parameter should be false for smooth scrolling and true for immediate scrolling.

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

Worked for me inside a view pager there is scrollview and need to scroll to a view which is working now using the code mentioned here.
Definitely this one should be the accepted answer, scrollview.smoothScrollTo won't work if the required view is off screen, (tried in android emulator with sdk version 26)
@Michael Should I wrap in new Handler().post()?
@JohnnyFive It depends on the context where you use this code.
You are awesome! This is the perfect solution for nested views in ScrollView, thank you!
T
Tobrun

I made a small utility method based on Answer from WarrenFaith, this code also takes in account if that view is already visible in the scrollview, no need for scroll.

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

I guess scrollBounds should be the parameter for the scrollView.getHitRect method.
O
Ovidiu Latcu

You should make your TextView request focus:

    mTextView.requestFocus();

G
Goran Horia Mihail

Another varition would be:

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

or you can use the post() method.


I
Injured Platypus

My EditText was nested several layers inside my ScrollView, which itself isn't the layout's root view. Because getTop() and getBottom() were seeming to report the coordinates within it's containing view, I had it compute the distance from the top of the ScrollView to the top of the EditText by iterating through the parents of the 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();

S
Sam Fisher

The above answers will work fine if the ScrollView is the direct parent of the ChildView. If your ChildView is being wrapped in another ViewGroup in the ScrollView, it will cause unexpected behavior because the View.getTop() get the position relative to its parent. In such case, you need to implement this:

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

This is the most comprehensive solution as it accounts for the parents positions as well. Thanks for posting!
Finding the first ScrollView parent is a good solution, this solution can be combined to this one stackoverflow.com/a/58111428/8105771 if you don't know the parent. Then traverse method will apply the changes, and focusChangeListener can't be one member variable, you'll need to create new listeners for each component you need to scroll to. I don't know yet, I didn't try this, but may create memory problems at crowded forms. I'll try this, right now I'm not telling you'll experience.
S
Suphi ÇEVİKER

I know this may be too late for a better answer but a desired perfect solution must be a system like positioner. I mean, when system makes a positioning for an Editor field it places the field just up to the keyboard, so as UI/UX rules it is perfect.

What below code makes is the Android way positioning smoothly. First of all we keep the current scroll point as a reference point. Second thing is to find the best positioning scroll point for an editor, to do this we scroll to top, and then request the editor fields to make the ScrollView component to do the best positioning. Gatcha! We've learned the best position. Now, what we'll do is scroll smoothly from the previous point to the point we've found newly. If you want you may omit smooth scrolling by using scrollTo instead of smoothScrollTo only.

NOTE: The main container ScrollView is a member field named scrollViewSignup, because my example was a signup screen, as you may figure out a lot.

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

If you want to use this block for all EditText instances, and quickly integrate it with your screen code. You can simply make a traverser like below. To do this, I've made the main OnFocusChangeListener a member field named focusChangeListenerToScrollEditor, and call it during onCreate as below.

traverseEditTextChildren(scrollViewSignup, focusChangeListenerToScrollEditor);

And the method implementation is as below.

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

So, what we've done here is making all EditText instance children to call the listener at focus.

To reach this solution, I've checked it out all the solutions here, and generated a new solution for better UI/UX result.

Many thanks to all other answers inspiring me much.

A
Alireza Noorali

yourScrollView.smoothScrollTo(0, yourEditText.getTop());

Just Do It ;)


if this doesnt work then you should postDelay it for 100 milliseconds probably because if you scroll directly after creating the view, getTop() will return 0 for that you need to add delay you can use yourScrollView.postDelayed({//smooth scroll}, 100) or lifecycleScope.launch {delay(100); yourScrollView.smoothScrollTo(0, yourEditText.getTop());}
X
XpressGeek
 scrollView.post(new Runnable() {
                    @Override
                    public void run() {
                        scrollView.smoothScrollTo(0, myTextView.getTop());

                    }
                });

Answering from my practical project.

https://i.stack.imgur.com/l0fhL.jpg


J
James Jordan Taylor

I think I have found more elegant and less error prone solution using

ScrollView.requestChildRectangleOnScreen

There is no math involved, and contrary to other proposed solutions, it will handle correctly scrolling both up and down.

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

It is a good idea to wrap it into postDelayed to make it more reliable, in case the ScrollView is being changed at the moment

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

C
Community

reference : https://stackoverflow.com/a/6438240/2624806

Following worked far better.

mObservableScrollView.post(new Runnable() {
            public void run() { 
                mObservableScrollView.fullScroll([View_FOCUS][1]); 
            }
        });

J
Jaromír Adamec

Examining Android source code, you can find that there already is a member function of ScrollViewscrollToChild(View) – that does exactly what is requested. Unfortunatelly, this function is for some obscure reason marked private. Based on that function I've written following function that finds the first ScrollView above the View specified as a parameter and scrolls it so that it becomes visible within the 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;
     }
 }

w
w4kskl

My solution is:

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

Q
Qamar

Vertical scroll, good for forms. Answer is based on Ahmadalibaloch horizontal scroll.

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

are u sure u should use a HorizontalScrollView in this method?
T
Tazeem Siddiqui

You can use ObjectAnimator like this:

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

Z
Zin Win Htet

In my case, that's not EditText, that's googleMap. And it works successfully like this.

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

C
Community

Que:Is there a way to programmatically scroll a scroll view to a specific edittext?

Ans:Nested scroll view in recyclerview last position added record data.

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

Is there a way to programmatically scroll a scroll view to a specific edit text?


Why is it the bottom?
L
LukeWaggoner

The following is what I'm using:

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

This gets the scroll amount necessary to show the bottom of the view, including any margin on the bottom of that view.


A
Ashwin Balani

If scrlMain is your NestedScrollView, then use the following,

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

                    }
                });

d
datchung

Based on Sherif's answer, the following worked best for my use case. Notable changes are getTop() instead of getBottom() and smoothScrollTo() instead of 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());
            }
        });
    }

B
Bogdan Kornev

If you want to scroll to a view when a soft keyboard is opened, then it might get a bit tricky. The best solution I've got so far is to use a combination of inset callbacks and requestRectangleOnScreen method.

First, you need to setup inset callbacks:

fun View.doOnApplyWindowInsetsInRoot(block: (View, WindowInsetsCompat, Rect) -> Unit) {
    val initialPadding = recordInitialPaddingForView(this)
    val root = getRootForView(this)
    ViewCompat.setOnApplyWindowInsetsListener(root) { v, insets ->
        block(v, insets, initialPadding)
        insets
    }
    requestApplyInsetsWhenAttached()
}

fun View.requestApplyInsetsWhenAttached() {
    if (isAttachedToWindow) {
        requestApplyInsets()
    } else {
        addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
            override fun onViewAttachedToWindow(v: View) {
                v.removeOnAttachStateChangeListener(this)
                v.requestApplyInsets()
            }

            override fun onViewDetachedFromWindow(v: View) = Unit
        })
    }
}

We are setting a callback on a root view to make sure we get called. Insets could be consumed before our view in question received them, so we have to do additional work here.

Now it's almost easy:

doOnApplyWindowInsetsInRoot { _, _, _ ->
    post {
        if (viewInQuestion.hasFocus()) {
            requestRectangleOnScreen(Rect(0, 0, width, height))
        }
    }
}

You can get rid of a focus check. It's there to limit number of calls to requestRectangleOnScreen. I use post to run an action after scrollable parent scheduled scroll to a focused view.


O
OhhhThatVarun

If anybody is looking for a Kotlin version you can do this with an extension function

fun ScrollView.scrollToChild(view: View, onScrolled: (() -> Unit)? = null) {
    view.requestFocus()
    val scrollBounds = Rect()
    getHitRect(scrollBounds)
    if (!view.getLocalVisibleRect(scrollBounds)) {
        findViewTreeLifecycleOwner()?.lifecycleScope?.launch(Dispatchers.Main) {
            smoothScrollTo(0, view.bottom - 40)
            onScrolled?.invoke()
        }
    }
}

There is a little callback that lets you do something after the scroll.


R
Ramakrishna Joshi

Add postDelayed to the view so that getTop() does not return 0.

binding.scrollViewLogin.postDelayed({
            val scrollTo = binding.textInputLayoutFirstName.top
            binding.scrollViewLogin.isSmoothScrollingEnabled = true
            binding.scrollViewLogin.smoothScrollTo(0, scrollTo)
     }, 400
) 

Also make sure the view is a direct child of scrollView, otherwise you would get getTop() as zero. Example: getTop() of edittext which is embedded inside TextInputLayout would return 0. So in this case, we have to compute getTop() of TextInputLayout which is a direct child of ScrollView.

<ScrollView>
    <TextInputLayout>
        <EditText/>
    </TextInputLayout>
</ScrollView>