75

I have added a scrollview and the subchilds inside the scrollview. At some point i need to scroll to a specific view.

<scrollview>

1. <linearlayout>

    <textview></textview>
    <textview></textview>

   </linearlayout>

2. <linearlayout>

    <textview></textview>
    <textview></textview>

   </linearlayout>

3. <linearlayout>

    <textview></textview>
    <textview></textview>

   </linearlayout>

4. <linearlayout>

    <textview></textview>
    <textview></textview>

   </linearlayout>

5. <linearlayout>

    <textview></textview>
    <textview></textview>

   </linearlayout>

6. <linearlayout>

    <textview></textview>
    <textview></textview>

   </linearlayout>

7. <linearlayout>

    <textview></textview>
    <textview></textview>

   </linearlayout>

   <button>
   </button>

 </scrollview>

The above layout was created dynamically. so i can't have the xml file posted here. Layout creation is completely dynamic. Even the number of child view inside the linearlayout may also vary.

So when i click on the button i need to get scrolled to a particular view say here when i click on button i need to scroll to the linear layout 4. I tried with the scrollTo method but it scrolls to the top of the scrollview.

Please provide some suggestions.

Rajagopal
  • 931
  • 1
  • 8
  • 23
  • Why are you using ScrollView? Wouldn't it be easier to use a ListView and if necessary display several different row types via a custom adapter? – npace Jan 31 '14 at 15:05
  • @npace. Thanks for the response. No in some situations a single row will be splitted to 2 rows. It is a bit complex app. Everything will be rendered based on response from server. We will not be able to predict type of the row item. – Rajagopal Jan 31 '14 at 15:09

18 Answers18

136

If child that we need to scroll to is not a direct child then above solutions don't work

I have used below solution in my project, sharing it may be helpful to others

/**
 * Used to scroll to the given view.
 *
 * @param scrollViewParent Parent ScrollView
 * @param view View to which we need to scroll.
 */
private void scrollToView(final ScrollView scrollViewParent, final View view) {
    // Get deepChild Offset
    Point childOffset = new Point();
    getDeepChildOffset(scrollViewParent, view.getParent(), view, childOffset);
    // Scroll to child.
    scrollViewParent.smoothScrollTo(0, childOffset.y);
}

/**
 * Used to get deep child offset.
 * <p/>
 * 1. We need to scroll to child in scrollview, but the child may not the direct child to scrollview.
 * 2. So to get correct child position to scroll, we need to iterate through all of its parent views till the main parent.
 *
 * @param mainParent        Main Top parent.
 * @param parent            Parent.
 * @param child             Child.
 * @param accumulatedOffset Accumulated Offset.
 */
private void getDeepChildOffset(final ViewGroup mainParent, final ViewParent parent, final View child, final Point accumulatedOffset) {
    ViewGroup parentGroup = (ViewGroup) parent;
    accumulatedOffset.x += child.getLeft();
    accumulatedOffset.y += child.getTop();
    if (parentGroup.equals(mainParent)) {
        return;
    }
    getDeepChildOffset(mainParent, parentGroup.getParent(), parentGroup, accumulatedOffset);
}
whlk
  • 15,487
  • 13
  • 66
  • 96
Vasanth
  • 6,257
  • 4
  • 26
  • 19
  • 6
    This worked well...also tweaked slightly to keep the problem view roughly in the top third of the screen for context: int contextScrollViewOffset = scrollViewParent.getHeight()/3; int viewYOffset = Math.max(0, childOffset.y-contextScrollViewOffset); scrollViewParent.smoothScrollTo(0, viewYOffset); – kenyee Apr 07 '17 at 14:44
  • 2
    java.lang.ClassCastException: android.view.ViewRootImpl cannot be cast to android.view.ViewGroup – Ahamadullah Saikat May 07 '18 at 09:49
  • 2
    Works perfectly! Thank you adding this line will make the full view visible to user : val rHeight = scrollViewParent.height and scrollViewParent.smoothScrollTo(0, childOffset.y - (rHeight/3)) – Tamim Attafi Nov 29 '18 at 13:22
128

This should do the trick:

View targetView = findViewById(R.id.DESIRED_VIEW_ID);  
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

Swas_99
  • 2,350
  • 1
  • 15
  • 19
  • 4
    This worked for me, where every other answer failed. – Chandra Sekhar Jan 25 '16 at 12:04
  • 1
    This solution is a good workaround since all these scrollTo or smoothScrollTo methods are flawed inside a scrollview. – Bevor May 16 '16 at 08:54
  • 12
    Thank you for this. Why Android just doesn't have a scrollView.scrollTo(viewId) baffles me. – terencey Oct 28 '16 at 15:11
  • Thanks, this puts the view at the bottom of the screen for me. How could I move the view to the vertical center? – seekingStillness Mar 12 '17 at 13:05
  • @seeking_stillness : To bring the desired view to vertical center you will have to use the smoothScrollTo(..) or scrollTo(..) methods. Tweaking Vasanth's solution(above mine) should work fine for you. You will have to add a value(= half of screen height) to what is used in his solution as y-coordinate for smoothScrollTo(.., ) – Swas_99 Mar 23 '17 at 05:40
  • I have a PopupWindow that contains a custom drawn view to annotate views. If the target view is outside of the viewframe, I can scroll to it using this method. Works fantastic as I have to load the annotations from a JSON file. – TheRealChx101 Nov 21 '17 at 05:52
  • Thank you very much! This works for me just fine! It will not scroll if the view is already fully viewable. But if any part of it are outside the visible area it will do the scrolling. It will not center the view but only scroll the minimum amount left or right to get the full view to show. – FrankKrumnow Sep 14 '18 at 12:25
  • In addition to this, I had to use Handler.post() method to get this working. – Ankur Mar 06 '20 at 11:35
32

try this:

ScrollView sv = // your scrollview
View insideView = // find the View that you need to scroll to which is inside this ScrollView
sv.scrollTo(0, (int)insideView.getY());

insideView.getY() will not work below API Level 11, for API Level below 11 you can replace insideView.getY() with insideView.getTop()

Cactus
  • 27,075
  • 9
  • 69
  • 149
Dev
  • 817
  • 10
  • 16
9

Since you can know the child LinearLayout that you need to scroll to, how about trying this.

ScrollView sv = // your scrollview
View highlightedItem = // find the LinearLayout or View that you need to scroll to which is inside this ScrollView
sv.scrollTo(0, highlightedItem.getY());

More information on scrollTo as well as smoothScrollTo

midhunhk
  • 5,560
  • 7
  • 52
  • 83
9

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

ScrollView.requestChildRectangleOnScreen

No math involved, and contrary to other proposed solutions, it will handle correctly scrolling both ways up and down.

void scrollToRow(ScrollView scrollView, LinearLayout linearLayout, TextView textViewToShow) {
    Rect textRect = new Rect(); //coordinates to scroll to
    textViewToShow.getHitRect(textRect); //fills textRect with coordinates of TextView relative to its parent (LinearLayout) 
    scrollView.requestChildRectangleOnScreen(linearLayout, textRect, false); //ScrollView will make sure, the given textRect 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

private void scrollToRow(final ScrollView scrollView, final LinearLayout linearLayout, final TextView textViewToShow) {
    long delay = 100; //delay to let finish with possible modifications to ScrollView
    scrollView.postDelayed(new Runnable() {
        public void run() {
            Rect textRect = new Rect(); //coordinates to scroll to
            textViewToShow.getHitRect(textRect); //fills textRect with coordinates of TextView relative to its parent (LinearLayout) 
            scrollView.requestChildRectangleOnScreen(linearLayout, textRect, false); //ScrollView will make sure, the given textRect is visible
        }
    }, delay);
}

Just nice isn`t?

Štarke
  • 627
  • 6
  • 10
  • This works great for me. I realized that my problem was the view size. when I was trying smoothScrollTo (0, view.getBottom()), my view doesnt had populated yet and the .getBottom() method was returning 0 – Arthur Melo May 26 '17 at 21:29
8

After so much researching into this and trying many ways, the following worked WONDERS for me.

Considering your main parent is ScrollView and child view(s) are/is LinearLayout or RelativeLayout which might have further views in them, there can be 2 ways to go-

Case 1: You want to focus on top of childView. Use the following code-

public void scrollToViewTop(ScrollView scrollView, View childView) {
    long delay = 100; //delay to let finish with possible modifications to ScrollView
    scrollView.postDelayed(new Runnable() {
        public void run() {
            scrollView.smoothScrollTo(0, childView.getTop());
        }
    }, delay);
}

Case 2: Focus on bottom of your childView. Use the following code-

public void scrollToViewBottom(ScrollView scrollView, View childView) {
    long delay = 100; //delay to let finish with possible modifications to ScrollView
    scrollView.postDelayed(new Runnable() {
        public void run() {
            scrollView.smoothScrollTo(0, childView.getBottom());
        }
    }, delay);
}

To use these functions, simply call them like this-

scrollToViewTop(scrollView, childView);
scrollToViewBottom(scrollView, childView);

Hope it saves your day. Happy coding!

Kuhuk Sharma
  • 81
  • 1
  • 4
5

Kotlin: Try this, it works for me like charm !

parentScrollView.post(object : Runnable {
        override fun run() {
            parentScrollView.smoothScrollTo(0, targetView.bottom)
        }

    })
syed dastagir
  • 419
  • 6
  • 8
3

For me, the best way to achieve this is to use the requestRectangleOnScreen() function. It is available from View that you want to scroll. You don't need access to the ScrollView and you don't need to calculate the childOffset. It does not change the focus and does not seems to be a hack in code. (I would never guess that requestChildFocus() is used for scrolling)

Also in kotlin you can add some small extension functions to make it's usage a way simpler:

  1. Add two extension functions for View:
fun View.requestOnScreen() = post {
    requestRectangleOnScreen(getDrawingRect())
}
    
fun View.getDrawingRect() = Rect().also(::getDrawingRect)
  1. Use that functions whenether you need:

view.requestOnScreen()

And that's all.

Warcello
  • 418
  • 7
  • 13
2

For Kotlin user. If above code are not worked. Try it.

val your_scrollview = findView...
val your_view_inside_SV = findView...
your_scrollview.post( { your_scrollview.scrollTo(0, your_view_inside_SV.getY().toInt()) })
P. Mohanta
  • 130
  • 2
  • 15
1

Here's a solution that works if the target view is not a direct child of your ScrollView:

public int findYPositionInView (View rootView, View targetView)
{
  return findYPositionInView (rootView, targetView, 0);
}


// returns -1 if targetView not found
private int findYPositionInView (View rootView, View targetView, int yCumulative)
{
  if (rootView == targetView)
    return yCumulative;

  if (rootView instanceof ViewGroup)
  {
    ViewGroup parentView = (ViewGroup)rootView;
    for (int i = 0;  i < parentView.getChildCount ();  i++)
    {
      View child = parentView.getChildAt (i);
      int yChild = yCumulative + (int)child.getY ();

      int yNested = findYPositionInView (child, targetView, yChild);
      if (yNested != -1)
        return yNested;
    }
  }

  return -1; // not found
}

Use it like this:

int yScroll = findYPositionInView (scrollView, targetView);
scrollView.scrollTo (0, yScroll);

Further, if you wish to set focus, do this:

targetView.requestFocus ();

And, if you want the keyboard to show, do this:

if (targetView instanceof EditText)
{
  targetView.post (new Runnable ()
  {
    @Override public void run ()
    {
      InputMethodManager imm = (InputMethodManager)context.getSystemService (Context.INPUT_METHOD_SERVICE);
      imm.showSoftInput (targetView, InputMethodManager.SHOW_FORCED);
    }
  });
}
Peri Hartman
  • 19,314
  • 18
  • 55
  • 101
0

Use :

final  ScrollView scrollView= (ScrollView) 
int speed=1000;
findViewById(R.id.main_scrollView);
final View view = findViewById(R.id.your_view); 
ViewTreeObserver vto = view.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() { view.getViewTreeObserver().removeOnGlobalLayoutListener(this);   
                ObjectAnimator.ofInt(scrollView, "scrollY",  (int) view.getY()).setDuration(speed).start();
            }
        }); 
Mohammad Javad
  • 573
  • 1
  • 7
  • 29
0

I got the desired result using requestChildFocus() as suggested by Swas_99's answer above.

However since it doesn't do smooth scrolling, I looked into the source code for requestChildFocus which calls scrollToChild which calls offsetDescendantRectToMyCoords. From there I got the idea to use the following C#/Xamarin code which gives a similar effect to using requestChildFocus() but with smooth scrolling:

private static void ScrollToView(View ArgTarget) {
    var CurParent = ArgTarget.Parent;

    while (CurParent != null) {
      var ScrollArea = CurParent as ScrollView;

      if (ScrollArea != null) {
        var ViewRect = new Rect();
        ArgTarget.GetDrawingRect(ViewRect);

        ScrollArea.OffsetDescendantRectToMyCoords(ArgTarget, ViewRect);
        ScrollArea.SmoothScrollTo(ViewRect.Left, ViewRect.Top);

        break;
      }

      CurParent = CurParent.Parent;
   }
}

The basic idea is to find the target view's containing ScrollView. Then get the target view's drawing rectangle, adjust its coordinates so they are understood by the ScrollView then ask the ScrollView to scroll to smooth-scroll to that position. The end effect is that the view you want to scroll to always ends up being fully visible near the top of the screen (you can play around with ViewRect.Top if you want to center the view on the screen instead).

Eric Mutta
  • 1,144
  • 1
  • 11
  • 15
0

Here rootView is your parent view and childView is a view where you want to scroll your view

public static int getRelativeTop(View rootView, View childView) {
    if(childView.getParent() == rootView) return childView.getTop();
    else return childView.getTop() + getRelativeTop(rootView, (View) childView.getParent());
}
GYaN
  • 2,327
  • 4
  • 19
  • 39
ad joy
  • 11
  • 5
0

You can use method smoothScrollTo to scroll to specific view in scroll view. - Example:

my_scroll_View.smoothScrollTo(0, editText.getBottom());

Good luck!

0

I use this like a extension

fun ScrollView.focusOnView(toView: View){

    Handler().post(Runnable {
        this.smoothScrollTo(0, toView.top)
    })

}

Usage:

myScrollView.focusOnView(myView)
Danielvgftv
  • 547
  • 7
  • 6
0

From API 29 there is an available method on ScrollView scrollToDescendant(View child) that can replace other custom solutions.

Android docs: https://developer.android.com/reference/android/widget/ScrollView#scrollToDescendant(android.view.View)

stevyhacker
  • 1,847
  • 2
  • 23
  • 31
0

kotlin:

scrollView.post { scrollView.smoothScrollTo(0, yourView.bottom) }
m.sajjad.s
  • 711
  • 7
  • 19
-4

Try to call view.requestFocus() on the view you want to scroll to.

MikhailKrishtop
  • 492
  • 4
  • 17
  • 4
    Hi KrishTop.. requestFocus will get the field focused in case of it is a edit text. In case of a textView or a viewGroup it will not receive focus. – Rajagopal Feb 01 '14 at 04:23
  • @Gopu you need to add android:focusable="true" – Maragues Mar 06 '15 at 12:18
  • @Maragues What if the necessary view to focus is a ViewGroup, like a Spinner. I tried setting focusable and focusableInTouchMode to true, but with no success (parent ScrollView does not scroll to it). Any ideas? – DoruAdryan Feb 08 '16 at 09:35