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

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


当前回答

如果有人正在寻找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()
        }
    }
}

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

其他回答

private final void focusOnView(){
    yourScrollView.post(new Runnable() {
        @Override
        public void run() {
            yourScrollView.scrollTo(0, yourEditText.getBottom());
        }
    });
}

如果你想在打开软键盘时滚动到一个视图,那么这可能有点棘手。 到目前为止,我得到的最好的解决方案是使用嵌套回调和requestRectangleOnScreen方法的组合。

首先,你需要设置inset回调:

fun View.doOnApplyWindowInsetsInRoot(block: (View, WindowInsetsCompat, Rect) -> Unit) {
    val initialPadding = recordInitialPaddingForView(this)
    val root = getRootForView(this)
    ViewCompat.setOnApplyWindowInsetsListener(root) { v, insets ->
        block(v, insets, initialPadding)
        insets
    }
    requestApplyInsetsWhenAttached()
}

fun View.requestApplyInsetsWhenAttached() {
    if (isAttachedToWindow) {
        requestApplyInsets()
    } else {
        addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
            override fun onViewAttachedToWindow(v: View) {
                v.removeOnAttachStateChangeListener(this)
                v.requestApplyInsets()
            }

            override fun onViewDetachedFromWindow(v: View) = Unit
        })
    }
}

我们在根视图上设置了一个回调来确保我们被调用。插图可能在我们的视图接收到它们之前就被消耗掉了,所以我们必须在这里做额外的工作。

现在很简单了:

doOnApplyWindowInsetsInRoot { _, _, _ ->
    post {
        if (viewInQuestion.hasFocus()) {
            requestRectangleOnScreen(Rect(0, 0, width, height))
        }
    }
}

你可以摆脱焦点检查。它的存在是为了限制requestRectangleOnScreen的调用数量。我使用post在可滚动的父滚动到聚焦视图后运行一个操作。

我的解决方案是:

            int[] spinnerLocation = {0,0};
            spinner.getLocationOnScreen(spinnerLocation);

            int[] scrollLocation = {0, 0};
            scrollView.getLocationInWindow(scrollLocation);

            int y = scrollView.getScrollY();

            scrollView.smoothScrollTo(0, y + spinnerLocation[1] - scrollLocation[1]);

将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>

参考资料:https://stackoverflow.com/a/6438240/2624806

接下来的工作要好得多。

mObservableScrollView.post(new Runnable() {
            public void run() { 
                mObservableScrollView.fullScroll([View_FOCUS][1]); 
            }
        });