有没有一种编程方法可以自动滚动滚动视图到特定的编辑文本?

249

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

此外,我知道可以使用X和Y坐标来实现这一点,但我希望避免这样做,因为表单可能因用户而异。


3
表单的形式可能因用户而异,但你只需要使用mEditView.getTop() - nebkat
1
如果有人在CoordinatorLayout中使用了NestedScrollView,您可以通过https://dev59.com/vSP-s4cB2Jgan1znaKdi滚动到特定视图。 - user1506104
27个回答

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

143
我发现使用smoothScrollTo(0, your_EditBox.getTop())可以获得更好的效果(平滑滚动到所需的视图),但除此之外,这个回答很棒。 - Muzikant
20
简短回答:因为用户界面(UI)根据一系列待处理的任务队列进行工作,你告诉UI线程,在完成当前所有任务后,尽可能地滚动屏幕。实际上,你是将滚动操作放入队列中,在UI线程处理完之前的任务后让其自行处理,以保证请求顺序。 - Martin Marconcini
38
可以将Runnable直接发布到ScrollView本身,而不是实例化一个新的Handler:your_scrollview.post(... - Sherif elKhatib
7
getBottom() 和 getTop() 相对于父元素。 - cambunctious
2
为什么是 getBottom() 而不是 getTop() - Vadim Kotov
显示剩余3条评论

62

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

3
你需要调整你的代码。你需要获取滚动视图的宽度,并且你需要更改公式为sv.smoothScrollTo(((vLeft + vRight) / 2) - (svWidth / 2), 0); - Elementary
2
否则scrollView中的视图将无法居中。我已经为您进行了编辑。 - Elementary
1
@Elementary Yours和Ahmad的公式是同一个代数表达式的简化和展开版本,不需要进行调整。 - k29
1
@k29,你是指这里显示的两个公式吗?它们都是我写的,我进一步简化了我的评论中的公式,并相应地编辑了答案。但是除此之外,所有其他内容仍然来自原始答案,并且对我也非常有帮助。 - Elementary
1
你的垂直ScrollView答案中,x和y没有搞混吗? - Panther
显示剩余4条评论

58

这对我很有效:

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

public void RequestChildFocus (View child, View focused)

child - 请求获取焦点的该ViewParent的子视图。该视图将包含具有焦点的视图,但不一定是实际具有焦点的视图。

focused - 子视图child中实际具有焦点的视图。


查看在任何平滑滚动中的跳跃类型。 - silentsudo
为什么这是唯一有效的解决方案?所有其他解决方案都毫无作用:CoordinatorLayout -> NestedScrollView -> ConstraintLayout -> Target。唯一的缺点是焦点项目在底部。 - DevinM

43

在我看来,滚动到指定矩形的最佳方法是通过View.requestRectangleOnScreen(Rect, Boolean)。您应该在要滚动到的 View 上调用它,并传递一个本地矩形,您希望在屏幕上可见。第二个参数应为false以进行平稳滚动,而应为true以进行即时滚动。

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

1
在我的工作中,我需要在一个视图页面中使用ScrollView,并且需要滚动到一个视图中。现在,我已经成功地使用这里提到的代码实现了这个功能。 - Pankaj
3
这个答案应该被采纳,因为如果所需的视图不在屏幕上,scrollview.smoothScrollTo 将无法工作。(在 Android 模拟器上尝试,使用的 SDK 版本是 26)。 - Sony
@Michael 我应该用 new Handler().post() 包装吗? - Johnny Five
@JohnnyFive 这取决于您使用此代码的上下文。 - Michael
你太棒了!这是ScrollView中嵌套视图的完美解决方案,谢谢你! - Astrit Veliu

12
我基于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());
            }
        });
    } 
}

我猜scrollBounds应该是scrollView.getHitRect方法的参数。 - Vijay C

11

你需要让你的TextView请求获取焦点:

    mTextView.requestFocus();

9
另一种变体可能是:
scrollView.postDelayed(new Runnable()
{
    @Override
    public void run()
    {
        scrollView.smoothScrollTo(0, img_transparent.getTop());
    }
}, 200);

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

8

我的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();

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

这是最全面的解决方案,因为它考虑了父母的位置。感谢您的发布! - Gustavo Baiocchi Costa
找到第一个ScrollView父级是一个好的解决方案,如果你不知道父级,可以将此解决方案与https://dev59.com/Qmw15IYBdhLWcg3wGn5c#58111428相结合。然后遍历方法将应用更改,并且focusChangeListener不能成为一个成员变量,您需要为每个需要滚动到的组件创建新的侦听器。 我还不知道,我还没有尝试过,但在拥挤的表单中可能会创建内存问题。我会尝试这个,现在我不能告诉你会发生什么。 - Suphi ÇEVİKER

5

我知道现在回答可能有点晚了,但理想的完美解决方案必须是像定位器一样的系统。我的意思是当系统为编辑器字段进行定位时,它将字段放置在键盘的正上方,因此根据UI / UX规则,这是完美的。

以下代码实现的是Android平台的平滑定位方法。首先,我们将当前滚动点作为参考点。第二件事是找到编辑器最佳定位滚动点,为此,我们先向上滚动,然后请求编辑器字段使ScrollView组件做出最佳定位。咔嚓!我们学到了最佳位置。现在,我们要做的是从以前的点平滑地滚动到我们新找到的点。如果您愿意,您可以只使用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结果。
非常感谢所有其他答案对我有所启发。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接