16

I have expandable views inside CardView thats parent is NestedScrollView. I'm trying to create smooth scroll to child when expand animation ended. But I found only one solution:

 scrollView.requestChildFocus(someView, someView);

This code works fine, but, when call requestChildFocus it scrolls immediately, and that annoying me a little bit. Is it possible to scroll to child smoothly?

Near1999
  • 1,547
  • 2
  • 18
  • 37
  • https://developer.android.com/reference/android/support/v4/widget/NestedScrollView.html#smoothScrollTo(int, int) ? – Budius Nov 04 '15 at 13:03
  • @Budius, if I'm not mistaken, there is no such method – Near1999 Nov 04 '15 at 13:06
  • I've passed the link to the official documentation, I'm pretty sure it exists as the documentation is auto-generated based on the code comments. Maybe you have to update to the latest support lib ? – Budius Nov 04 '15 at 13:12
  • You mean there is no such method like the one described in the original Android documentation? – LilaQ Nov 04 '15 at 13:12

8 Answers8

30

The childView, to which I wanted to scroll, has CardView parrent, so childView.getTop() returns the value relative to the CardView not to the ScrollView. So, to get top relative to ScrollView I should get childView.getParent().getParent() then cast it to View and call getTop().

Scroll position calculates like

int scrollTo = ((View) childView.getParent().getParent()).getTop() + childView.getTop();
nestedScrollView.smoothScrollTo(0, scrollTo);
Near1999
  • 1,547
  • 2
  • 18
  • 37
6

more to the answer from @jerry-sha

fun NestedScrollView.smoothScrollTo(view: View) {
  var distance = view.top
  var viewParent = view.parent
  //traverses 10 times
  for (i in 0..9) {
    if ((viewParent as View) === this) break
    distance += (viewParent as View).top
    viewParent = viewParent.getParent()
  }
  smoothScrollTo(0, distance)
}
CHAN
  • 1,518
  • 18
  • 16
4

I had child views at different levels to the scrollview so made this function based off the accepted answer to calculate the distance and scroll

  private int findDistanceToScroll(View view){
    int distance = view.getTop();
    ViewParent viewParent = view.getParent();
    //traverses 10 times
    for(int i = 0; i < 10; i++) {
        if (((View) viewParent).getId() == R.id.journal_scrollview) {
            return distance;
        }
        distance += ((View) viewParent).getTop();
        viewParent = viewParent.getParent();
    }
    Timber.w("view not found");
    return 0;
}

Then scroll using

journal_scrollview.smoothScrollTo(0, distance);
Jerry Sha
  • 3,931
  • 2
  • 23
  • 16
1

Try to read the source code.

svMain.setSmoothScrollingEnabled(true);
Rect rect = new Rect();
rect.top = 0;
rect.left = 0;
rect.right = tv4.getWidth();
rect.bottom =tv4.getHeight();
svMain.requestChildRectangleOnScreen(tv4,rect,false);

rect is the place u want the view to be shown on screen.

SMR
  • 6,628
  • 2
  • 35
  • 56
tiny sunlight
  • 6,231
  • 3
  • 21
  • 42
1

You can use my library ViewPropertyObjectAnimator for that.

Assuming mNestedScrollView is your NestedScrollView and mChildView is the child View you want to scroll to, you can do the following:

ViewPropertyObjectAnimator.animate(mNestedScrollView).scrollY(mChildView.getTop()).start();

Just make sure mChildView.getTop() is not 0 at the moment of calling .animate(...).

Edit:

As I said: make sure your View's top is non-zero when CALL .animate(...). In other words: call .animate(...) only when your child View already has dimensions. How can you determine that? For example like this:

mChildView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

    @Override
    public void onGlobalLayout() {
       int width = mChildView.getWidth();
       int height = mChildView.getHeight();
        if (width > 0 && height > 0) {
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
              mChildView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            } else {
              mChildView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
            }

            ViewPropertyObjectAnimator.animate(mNestedScrollView)
                    .scrollY(mChildView.getTop())
                    .start();
        }
    }
});
Bartek Lipinski
  • 30,698
  • 10
  • 94
  • 132
  • My child view's top is always 0 when animation ends ((( – Near1999 Nov 04 '15 at 18:14
  • I cant check this answer now ( I'll do it tomorrow. Could You say me if '.scrollY()' is trying to place myChildView to top of screen or to first it's visible pixel? – Near1999 Nov 04 '15 at 21:11
  • `mChildView.getTop()` represents the number of pixels from the top of your `View` to the top of the `NestedScrollView` content. And this command will scroll your `NestedScrollView` exactly by this amount. – Bartek Lipinski Nov 04 '15 at 21:22
1

The main issue is to calculate the correct x/y position relative to the nestedscrollview. However, most of the answers here try to propose a solution by navigating in the hierarchy and hardcoding the hierarchy-level of the desired view. Imho, this is very error prone, if you change your viewgroup hierarchy.

Therefore, I would suggest to use a much more cleaner approach, which computes the relative position based on a Rect. Then, you can use the nestedscrollview's smoothScrollTo(..) methods to scroll to the desired position.

mathew11
  • 3,382
  • 3
  • 25
  • 32
0

This should get the work done, where childView is the view you want to scroll to

public static void scrollToView(final NestedScrollView nestedScrollView, final View viewToScrollTo) {
        final int[] xYPos = new int[2];
        viewToScrollTo.getLocationOnScreen(xYPos);
        final int[] scrollxYPos = new int[2];
        nestedScrollView.getLocationOnScreen(scrollxYPos);
        int yPosition = xYPos[1];
        if (yPosition < 0) {
            yPosition = 0;
        }
        nestedScrollView.scrollTo(0, scrollxYPos[1] - yPosition);
    }
0

I find the accepted answer to work, but it is specific to their view structure as of now and it could not be used as a static method to use for all similar scrolls with different view structures. I made a variety of it in a utility class that measures until it find it's ScrollView or NestedScrollView parent. I made it so that the scrollToView(View,View) method should work with both ScrollView and NestedScrollView in case I would update which I use later on or whatever. You could of course call the right "child method" directly.

public static void scrollToView(View scrollView, View scrollToView) {
    if (scrollToView == null) {
        Log.d(TAG, "scrollToView() failed due to scrollToView == NULL!");
        return;
    }
    if (scrollView instanceof NestedScrollView) {
        scrollToInNestedView((NestedScrollView) scrollView, scrollToView);
    } else if (scrollView instanceof ScrollView) {
        scrollToInScrollView((ScrollView) scrollView, scrollToView);
    } else {
        Log.d(TAG, "scrollToView() failed due to scrollView not appearing to be any kind of scroll view!");
    }
}

public static void scrollToInNestedView(NestedScrollView scrollView, View scrollToView) {
    if (scrollView == null || scrollToView == null) {
        return;
    }
    scrollView.post(() -> {
        int startY = scrollView.getScrollY();
        int requiredY = scrollToView.getTop();
        View parent = (View) scrollToView.getParent();
        while (parent != null && !(parent instanceof NestedScrollView)) {
            requiredY += parent.getTop();
            parent = (View) parent.getParent();
        }
        if (requiredY != startY) {
            scrollView.smoothScrollTo(0, requiredY);
        }
    });
}

public static void scrollToInScrollView(ScrollView scrollView, View scrollToView) {
    if (scrollView == null || scrollToView == null) {
        return;
    }
    scrollView.post(() -> {
        int startY = scrollView.getScrollY();
        int requiredY = scrollToView.getTop();
        View parent = (View) scrollToView.getParent();
        while (parent != null && !(parent instanceof ScrollView)) {
            requiredY += parent.getTop();
            parent = (View) parent.getParent();
        }
        if (requiredY != startY) {
            scrollView.smoothScrollTo(0, requiredY);
        }
    });
}
Vanheden
  • 582
  • 5
  • 14