在 NestedScrollView 中平滑滚动到 Child
Smoothy scroll to Child in NestedScrollView
我在 CardView
内有可扩展的视图,那是 parent 是 NestedScrollView
。当展开动画结束时,我正在尝试创建到 child 的平滑滚动。但我只找到一个解决方案:
scrollView.requestChildFocus(someView, someView);
这段代码工作正常,但是,当调用 requestChildFocus
时它会立即滚动,这让我有点恼火。是否可以顺利滚动到child?
尝试阅读源代码。
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
是您希望视图在屏幕上显示的位置。
你可以使用我的图书馆 ViewPropertyObjectAnimator
。
假设 mNestedScrollView
是您的 NestedScrollView
并且 mChildView
是您要滚动到的 child View
,您可以执行以下操作:
ViewPropertyObjectAnimator.animate(mNestedScrollView).scrollY(mChildView.getTop()).start();
只需确保 mChildView.getTop()
在调用 .animate(...)
时不是 0
。
编辑:
正如我所说:确保你的 View's
top 在 CALL .animate(...)
时是 non-zero。换句话说:仅当您的 child View
已经具有维度时才调用 .animate(...)
。你怎么能确定呢?例如像这样:
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();
}
}
});
我想滚动到的 childView
有 CardView
父级,所以 childView.getTop()
returns 相对于 CardView
的值不ScrollView
。因此,要获得相对于 ScrollView
的顶部,我应该获得 childView.getParent().getParent()
,然后将其转换为 View
并调用 getTop()
。
滚动位置的计算方式类似于
int scrollTo = ((View) childView.getParent().getParent()).getTop() + childView.getTop();
nestedScrollView.smoothScrollTo(0, scrollTo);
这应该可以完成工作,其中 childView 是您要滚动到的视图
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);
}
我在滚动视图的不同级别有子视图,因此根据接受的答案制作此函数来计算距离和滚动
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;
}
然后使用
滚动
journal_scrollview.smoothScrollTo(0, distance);
@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)
}
主要问题是计算与 nestedscrollview 的 x/y 相对 的正确 位置。但是,这里的大多数答案都试图通过在层次结构中导航并对所需视图的层次结构级别进行硬编码来提出解决方案。恕我直言,如果您更改视图组层次结构,这很容易出错。
因此,我建议使用 much more cleaner approach, which computes the relative position based on a Rect
. Then, you can use the nestedscrollview's smoothScrollTo(..) 方法滚动到所需位置。
我发现可接受的答案有效,但它目前特定于他们的视图结构,并且不能用作静态方法以用于所有具有不同视图结构的类似卷轴。我在实用程序 class 中对它进行了各种测量,直到它找到它的 ScrollView 或 NestedScrollView 父级。我这样做是为了让 scrollToView(View,View) 方法应该与 ScrollView 和 NestedScrollView 一起工作,以防我更新我稍后使用的或其他任何东西。您当然可以直接调用正确的“子方法”。
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);
}
});
}
我在 CardView
内有可扩展的视图,那是 parent 是 NestedScrollView
。当展开动画结束时,我正在尝试创建到 child 的平滑滚动。但我只找到一个解决方案:
scrollView.requestChildFocus(someView, someView);
这段代码工作正常,但是,当调用 requestChildFocus
时它会立即滚动,这让我有点恼火。是否可以顺利滚动到child?
尝试阅读源代码。
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
是您希望视图在屏幕上显示的位置。
你可以使用我的图书馆 ViewPropertyObjectAnimator
。
假设 mNestedScrollView
是您的 NestedScrollView
并且 mChildView
是您要滚动到的 child View
,您可以执行以下操作:
ViewPropertyObjectAnimator.animate(mNestedScrollView).scrollY(mChildView.getTop()).start();
只需确保 mChildView.getTop()
在调用 .animate(...)
时不是 0
。
编辑:
正如我所说:确保你的 View's
top 在 CALL .animate(...)
时是 non-zero。换句话说:仅当您的 child View
已经具有维度时才调用 .animate(...)
。你怎么能确定呢?例如像这样:
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();
}
}
});
我想滚动到的 childView
有 CardView
父级,所以 childView.getTop()
returns 相对于 CardView
的值不ScrollView
。因此,要获得相对于 ScrollView
的顶部,我应该获得 childView.getParent().getParent()
,然后将其转换为 View
并调用 getTop()
。
滚动位置的计算方式类似于
int scrollTo = ((View) childView.getParent().getParent()).getTop() + childView.getTop();
nestedScrollView.smoothScrollTo(0, scrollTo);
这应该可以完成工作,其中 childView 是您要滚动到的视图
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);
}
我在滚动视图的不同级别有子视图,因此根据接受的答案制作此函数来计算距离和滚动
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;
}
然后使用
滚动journal_scrollview.smoothScrollTo(0, distance);
@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)
}
主要问题是计算与 nestedscrollview 的 x/y 相对 的正确 位置。但是,这里的大多数答案都试图通过在层次结构中导航并对所需视图的层次结构级别进行硬编码来提出解决方案。恕我直言,如果您更改视图组层次结构,这很容易出错。
因此,我建议使用 much more cleaner approach, which computes the relative position based on a Rect
. Then, you can use the nestedscrollview's smoothScrollTo(..) 方法滚动到所需位置。
我发现可接受的答案有效,但它目前特定于他们的视图结构,并且不能用作静态方法以用于所有具有不同视图结构的类似卷轴。我在实用程序 class 中对它进行了各种测量,直到它找到它的 ScrollView 或 NestedScrollView 父级。我这样做是为了让 scrollToView(View,View) 方法应该与 ScrollView 和 NestedScrollView 一起工作,以防我更新我稍后使用的或其他任何东西。您当然可以直接调用正确的“子方法”。
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);
}
});
}