是否有一种方法以编程方式滚动滚动视图到特定的编辑文本?

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

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

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

你应该让你的TextView请求聚焦:

    mTextView.requestFocus();

Sherif elKhatib的答案可以大大提高,如果你想将视图滚动到滚动视图的中心。这个可重用的方法平滑地将视图滚动到HorizontalScrollView的可见中心。

private final void focusOnView(final HorizontalScrollView scroll, final View view) {
new Handler().post(new Runnable() {
@Override
public void run() {
int vLeft = view.getLeft();
int vRight = view.getRight();
int sWidth = scroll.getWidth();
scroll.smoothScrollTo(((vLeft + vRight - sWidth) / 2), 0);
}
});
}

用于垂直ScrollView的使用

...
int vTop = view.getTop();
int vBottom = view.getBottom();
int sHeight = scroll.getBottom();
scroll.smoothScrollTo(0, ((vTop + vBottom - sHeight) / 2));
...

另一种说法是:

scrollView.postDelayed(new Runnable()
{
@Override
public void run()
{
scrollView.smoothScrollTo(0, img_transparent.getTop());
}
}, 200);

或者你可以使用post()方法。

我基于WarrenFaith的答案做了一个小工具方法,这段代码也考虑了视图是否已经在滚动视图中可见,不需要滚动。

public static void scrollToView(final ScrollView scrollView, final View view) {


// View needs a focus
view.requestFocus();


// Determine if scroll needs to happen
final Rect scrollBounds = new Rect();
scrollView.getHitRect(scrollBounds);
if (!view.getLocalVisibleRect(scrollBounds)) {
new Handler().post(new Runnable() {
@Override
public void run() {
scrollView.smoothScrollTo(0, view.getBottom());
}
});
}
}

我的EditText在ScrollView中嵌套了几个层,而ScrollView本身并不是布局的根视图。因为getTop()和getBottom()似乎在报告它所包含的视图中的坐标,所以我让它通过迭代EditText的父节点来计算从ScrollView顶部到EditText顶部的距离。

// Scroll the view so that the touched editText is near the top of the scroll view
new Thread(new Runnable()
{
@Override
public
void run ()
{
// Make it feel like a two step process
Utils.sleep(333);


// Determine where to set the scroll-to to by measuring the distance from the top of the scroll view
// to the control to focus on by summing the "top" position of each view in the hierarchy.
int yDistanceToControlsView = 0;
View parentView = (View) m_editTextControl.getParent();
while (true)
{
if (parentView.equals(scrollView))
{
break;
}
yDistanceToControlsView += parentView.getTop();
parentView = (View) parentView.getParent();
}


// Compute the final position value for the top and bottom of the control in the scroll view.
final int topInScrollView = yDistanceToControlsView + m_editTextControl.getTop();
final int bottomInScrollView = yDistanceToControlsView + m_editTextControl.getBottom();


// Post the scroll action to happen on the scrollView with the UI thread.
scrollView.post(new Runnable()
{
@Override
public void run()
{
int height =m_editTextControl.getHeight();
scrollView.smoothScrollTo(0, ((topInScrollView + bottomInScrollView) / 2) - height);
m_editTextControl.requestFocus();
}
});
}
}).start();

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

接下来的工作要好得多。

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

这对我来说很管用:

  targetView.getParent().requestChildFocus(targetView,targetView);

RequestChildFocus(查看子视图,查看焦点)

孩子 -这个ViewParent的子对象。该视图将包含聚焦视图。它并不一定是真正有焦点的观点。

集中 -实际上有焦点的child的后代视图

检查Android源代码,你会发现已经有一个成员函数ScrollView - scrollToChild(View) -,它做的正是所请求的。不幸的是,这个函数由于某种模糊的原因被标记为private。基于该函数,我写了以下函数,它找到作为参数指定的View之上的第一个ScrollView,并滚动它,使它在ScrollView中可见:

 private void make_visible(View view)
{
int vt = view.getTop();
int vb = view.getBottom();


View v = view;


for(;;)
{
ViewParent vp = v.getParent();


if(vp == null || !(vp instanceof ViewGroup))
break;


ViewGroup parent = (ViewGroup)vp;


if(parent instanceof ScrollView)
{
ScrollView sv = (ScrollView)parent;


// Code based on ScrollView.computeScrollDeltaToGetChildRectOnScreen(Rect rect) (Android v5.1.1):


int height = sv.getHeight();
int screenTop = sv.getScrollY();
int screenBottom = screenTop + height;


int fadingEdge = sv.getVerticalFadingEdgeLength();


// leave room for top fading edge as long as rect isn't at very top
if(vt > 0)
screenTop += fadingEdge;


// leave room for bottom fading edge as long as rect isn't at very bottom
if(vb < sv.getChildAt(0).getHeight())
screenBottom -= fadingEdge;


int scrollYDelta = 0;


if(vb > screenBottom && vt > screenTop)
{
// need to move down to get it in view: move down just enough so
// that the entire rectangle is in view (or at least the first
// screen size chunk).


if(vb-vt > height) // just enough to get screen size chunk on
scrollYDelta += (vt - screenTop);
else              // get entire rect at bottom of screen
scrollYDelta += (vb - screenBottom);


// make sure we aren't scrolling beyond the end of our content
int bottom = sv.getChildAt(0).getBottom();
int distanceToBottom = bottom - screenBottom;
scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
}
else if(vt < screenTop && vb < screenBottom)
{
// need to move up to get it in view: move up just enough so that
// entire rectangle is in view (or at least the first screen
// size chunk of it).


if(vb-vt > height)    // screen size chunk
scrollYDelta -= (screenBottom - vb);
else                  // entire rect at top
scrollYDelta -= (screenTop - vt);


// make sure we aren't scrolling any further than the top our content
scrollYDelta = Math.max(scrollYDelta, -sv.getScrollY());
}


sv.smoothScrollBy(0, scrollYDelta);
break;
}


// Transform coordinates to parent:
int dy = parent.getTop()-parent.getScrollY();
vt += dy;
vb += dy;


v = parent;
}
}

在我的例子中,这不是EditText,而是googleMap

    private final void focusCenterOnView(final ScrollView scroll, final View view) {
new Handler().post(new Runnable() {
@Override
public void run() {
int centreX=(int) (view.getX() + view.getWidth()  / 2);
int centreY= (int) (view.getY() + view.getHeight() / 2);
scrollView.smoothScrollBy(centreX, centreY);
}
});
}

在我看来,滚动到给定矩形的最好方法是通过View.requestRectangleOnScreen(Rect, Boolean)。你应该在你想要滚动到的View上调用它,并传递一个你想要在屏幕上可见的局部矩形。第二个参数应该是false用于平滑滚动,true用于立即滚动。

final Rect rect = new Rect(0, 0, view.getWidth(), view.getHeight());
view.requestRectangleOnScreen(rect, false);

问:有没有一种方法,以编程方式滚动滚动视图到特定的编辑文本?

答:嵌套滚动视图在recyclerview最后位置添加记录数据。

adapter.notifyDataSetChanged();
nested_scroll.setScrollY(more Detail Recycler.getBottom());

是否有一种方法以编程方式滚动滚动视图到特定的编辑文本?< / >

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

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

我的解决方案是:

            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]);

以下是我正在使用的:

int amountToScroll = viewToShow.getBottom() - scrollView.getHeight() + ((LinearLayout.LayoutParams) viewToShow.getLayoutParams()).bottomMargin;
// Check to see if scrolling is necessary to show the view
if (amountToScroll > 0){
scrollView.smoothScrollTo(0, amountToScroll);
}

这将获得显示视图底部所需的滚动量,包括视图底部的任何边距。

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

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

如果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));
}

如果scrlMain是你的NestedScrollView,那么使用以下方法:

scrlMain.post(new Runnable() {
@Override
public void run() {
scrlMain.fullScroll(View.FOCUS_UP);
}
});

你可以像这样使用ObjectAnimator:

ObjectAnimator.ofInt(yourScrollView, "scrollY", yourView.getTop()).setDuration(1500).start();

我知道这可能太迟了,没有更好的答案,但理想的完美解决方案必须是一个像定位器这样的系统。我的意思是,当系统为一个编辑器字段进行定位时,它将该字段放置在键盘上方,因此作为UI/UX规则,这是完美的。

下面的代码做的是Android方式的平稳定位。首先,我们保持当前滚动点作为参考点。第二件事是找到一个编辑器的最佳定位滚动点,为此我们滚动到顶部,然后请求编辑器字段使ScrollView组件做最佳定位。Gatcha !我们已经学会了最佳姿势。现在,我们要做的是平稳地从上一个点滚动到我们新发现的点。如果你愿意,你可以通过使用scrollTo而不是smoothScrollTo来省略平滑滚动。

主容器ScrollView是一个名为scrollViewSignup的成员字段,因为我的例子是一个注册屏幕,你可能会发现很多。

view.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(final View view, boolean b) {
if (b) {
scrollViewSignup.post(new Runnable() {
@Override
public void run() {
int scrollY = scrollViewSignup.getScrollY();
scrollViewSignup.scrollTo(0, 0);
final Rect rect = new Rect(0, 0, view.getWidth(), view.getHeight());
view.requestRectangleOnScreen(rect, true);


int new_scrollY = scrollViewSignup.getScrollY();
scrollViewSignup.scrollTo(0, scrollY);
scrollViewSignup.smoothScrollTo(0, new_scrollY);
}
});
}
}
});

如果你想将此块用于所有EditText实例,并快速将其与屏幕代码集成。您可以简单地像下面这样创建一个遍历器。为了做到这一点,我已经使主OnFocusChangeListener一个名为focusChangeListenerToScrollEditor的成员字段,并在onCreate期间调用它,如下所示。

traverseEditTextChildren(scrollViewSignup, focusChangeListenerToScrollEditor);

方法实现如下所示。

private void traverseEditTextChildren(ViewGroup viewGroup, View.OnFocusChangeListener focusChangeListenerToScrollEditor) {
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
View view = viewGroup.getChildAt(i);
if (view instanceof EditText)
{
((EditText) view).setOnFocusChangeListener(focusChangeListenerToScrollEditor);
}
else if (view instanceof ViewGroup)
{
traverseEditTextChildren((ViewGroup) view, focusChangeListenerToScrollEditor);
}
}
}

因此,我们在这里所做的是让所有EditText实例的子实例在焦点处调用侦听器。

为了达到这个解决方案,我检查了这里所有的解决方案,并生成了一个更好的UI/UX结果的新解决方案。

Many thanks to all other answers inspiring me much.

根据Sherif的回答,以下内容最适合我的用例。值得注意的变化是getTop()而不是getBottom()smoothScrollTo()而不是scrollTo()

    private void scrollToView(final View view){
final ScrollView scrollView = findViewById(R.id.bookmarksScrollView);
if(scrollView == null) return;


scrollView.post(new Runnable() {
@Override
public void run() {
scrollView.smoothScrollTo(0, view.getTop());
}
});
}
scrollView.post(new Runnable() {
@Override
public void run() {
scrollView.smoothScrollTo(0, myTextView.getTop());
}
});

回答来自我的实际项目。

项目代码示例 .

如果你想在软键盘打开时滚动到一个视图,那么它可能会有点棘手。 到目前为止,我得到的最佳解决方案是使用嵌套回调和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在可滚动的父滚动到聚焦视图后运行一个操作。

yourScrollView.smoothScrollTo(0, yourEditText.getTop());

尽管去做吧;)

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

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

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