我有一个很长的带有滚动视图的活动。它是一个包含用户必须填写的各种字段的表单。我在表单的中间有一个复选框,当用户选中它时,我想滚动到视图的特定部分。是否有办法以编程方式滚动到EditText对象(或任何其他视图对象)?

此外,我知道这是可能的使用X和Y坐标,但我想避免这样做,因为形式可能会从用户到用户的变化。


当前回答

将postDelayed添加到视图中,这样getTop()就不会返回0。

binding.scrollViewLogin.postDelayed({
            val scrollTo = binding.textInputLayoutFirstName.top
            binding.scrollViewLogin.isSmoothScrollingEnabled = true
            binding.scrollViewLogin.smoothScrollTo(0, scrollTo)
     }, 400
) 

还要确保视图是scrollView的直接子视图,否则你会得到getTop()为零。示例:嵌入在TextInputLayout中的edittext的getTop()将返回0。在这种情况下,我们需要计算TextInputLayout的getTop()它是ScrollView的直接子。

<ScrollView>
    <TextInputLayout>
        <EditText/>
    </TextInputLayout>
</ScrollView>

其他回答

如果ScrollView是ChildView的直接父类,上面的答案就可以很好地工作。如果你的ChildView被包装在ScrollView中的另一个ViewGroup中,它将导致意外的行为,因为View.getTop()获得相对于其父的位置。在这种情况下,你需要实现这个:

public static void scrollToInvalidInputView(ScrollView scrollView, View view) {
    int vTop = view.getTop();

    while (!(view.getParent() instanceof ScrollView)) {
        view = (View) view.getParent();
        vTop += view.getTop();
    }

    final int scrollPosition = vTop;

    new Handler().post(() -> scrollView.smoothScrollTo(0, scrollPosition));
}

我认为我已经找到了更优雅、更不容易出错的解决方案

ScrollView.requestChildRectangleOnScreen

它不涉及数学运算,与其他提出的解决方案相反,它将正确地处理上下滚动。

/**
 * Will scroll the {@code scrollView} to make {@code viewToScroll} visible
 * 
 * @param scrollView parent of {@code scrollableContent}
 * @param scrollableContent a child of {@code scrollView} whitch holds the scrollable content (fills the viewport).
 * @param viewToScroll a child of {@code scrollableContent} to whitch will scroll the the {@code scrollView}
 */
void scrollToView(ScrollView scrollView, ViewGroup scrollableContent, View viewToScroll) {
    Rect viewToScrollRect = new Rect(); //coordinates to scroll to
    viewToScroll.getHitRect(viewToScrollRect); //fills viewToScrollRect with coordinates of viewToScroll relative to its parent (LinearLayout) 
    scrollView.requestChildRectangleOnScreen(scrollableContent, viewToScrollRect, false); //ScrollView will make sure, the given viewToScrollRect is visible
}

如果ScrollView正在被更改,那么将它封装到postDelayed中以使其更加可靠是个好主意

/**
 * Will scroll the {@code scrollView} to make {@code viewToScroll} visible
 * 
 * @param scrollView parent of {@code scrollableContent}
 * @param scrollableContent a child of {@code scrollView} whitch holds the scrollable content (fills the viewport).
 * @param viewToScroll a child of {@code scrollableContent} to whitch will scroll the the {@code scrollView}
 */
private void scrollToView(final ScrollView scrollView, final ViewGroup scrollableContent, final View viewToScroll) {
    long delay = 100; //delay to let finish with possible modifications to ScrollView
    scrollView.postDelayed(new Runnable() {
        public void run() {
            Rect viewToScrollRect = new Rect(); //coordinates to scroll to
            viewToScroll.getHitRect(viewToScrollRect); //fills viewToScrollRect with coordinates of viewToScroll relative to its parent (LinearLayout) 
            scrollView.requestChildRectangleOnScreen(scrollableContent, viewToScrollRect, false); //ScrollView will make sure, the given viewToScrollRect is visible
        }
    }, delay);
}

垂直滚动,适合窗体。答案是基于艾哈迈迪巴洛赫水平卷轴。

private final void focusOnView(final HorizontalScrollView scroll, final View view) {
    new Handler().post(new Runnable() {
        @Override
        public void run() {
            int top = view.getTop();
            int bottom = view.getBottom();
            int sHeight = scroll.getHeight();
            scroll.smoothScrollTo(0, ((top + bottom - sHeight) / 2));
        }
    });
}
private final void focusOnView(){
    yourScrollView.post(new Runnable() {
        @Override
        public void run() {
            yourScrollView.scrollTo(0, yourEditText.getBottom());
        }
    });
}

如果有人正在寻找Kotlin版本,您可以使用扩展函数来实现这一点

fun ScrollView.scrollToChild(view: View, onScrolled: (() -> Unit)? = null) {
    view.requestFocus()
    val scrollBounds = Rect()
    getHitRect(scrollBounds)
    if (!view.getLocalVisibleRect(scrollBounds)) {
        findViewTreeLifecycleOwner()?.lifecycleScope?.launch(Dispatchers.Main) {
            smoothScrollTo(0, view.bottom - 40)
            onScrolled?.invoke()
        }
    }
}

有一个小回调让你在滚动之后做一些事情。