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

我想滚动到的 childViewCardView 父级,所以 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);
        }
    });
}