Reputation: 1607
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?
Upvotes: 17
Views: 19098
Reputation: 386
I implemented auto scrolling of the focused EditText (lying inside the NestedScrollView), if the EditText is hidden (partly hidden) under the keyboard.
Since RecyclerView doing this stuff without any additional work, my testers said that NestedScrollView without autoscrolling - is a bug.
fun EditText.setupAutoscrollInNestedScrollView() {
setOnFocusChangeListener { _, hasFocus ->
if (hasFocus.not()) return@setOnFocusChangeListener
/*
* The distance from the beginning of the NestedScrollView to our TextField.
* Since the EditText.top parameter returns a value relative to the direct parent,
* we must add up all the padding until we get to the NestedScrollView.
*/
var textFieldYFromTopScrollView = top
var viewParent = parent
while (true) {
if (viewParent is NestedScrollView || viewParent == null) break
textFieldYFromTopScrollView += (viewParent as? View)?.top.orZero()
viewParent = viewParent.parent
}
val offset = dpToPx(4f)
val textFieldHeight = height
val scrollView = viewParent as? NestedScrollView
val scrollViewVisibleHeight = scrollView?.height.orZero() - getKeyboardHeight()
val scrollViewYPosition = textFieldYFromTopScrollView - (scrollViewVisibleHeight - textFieldHeight - offset)
scrollView?.smoothScrollTo(0, scrollViewYPosition)
}}
fun Int?.orZero(): Int = this ?: 0
fun View.getKeyboardHeight(): Int = ViewCompat.getRootWindowInsets(this)?.getInsets(WindowInsetsCompat.Type.ime())?.bottom.orZero()
Upvotes: 0
Reputation: 680
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);
}
});
}
Upvotes: 0
Reputation: 3580
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.
Upvotes: 1
Reputation: 3991
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);
Upvotes: 4
Reputation: 1566
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)
}
Upvotes: 6
Reputation: 6251
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.
Upvotes: 2
Reputation: 31
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);
}
Upvotes: 0
Reputation: 1607
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);
Upvotes: 32
Reputation: 31438
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();
}
}
});
Upvotes: 1