ChatGPT解决这个技术问题 Extra ChatGPT

有没有办法以编程方式将滚动视图滚动到特定的编辑文本?

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

另外,我知道这可以使用 X 和 Y 坐标,但我想避免这样做,因为表单可能会因用户而异。

the form may changed from user to user 但您可以只使用 mEditView.getTop();
如果有人在 CoordinatorLayout 中使用 NestedScrollView,您可以通过 stackoverflow.com/questions/52083678/… 滚动到特定视图

S
Sherif elKhatib
private final void focusOnView(){
        your_scrollview.post(new Runnable() {
            @Override
            public void run() {
                your_scrollview.scrollTo(0, your_EditBox.getBottom());
            }
        });
    }

我发现使用 smoothScrollTo(0, your_EditBox.getTop()) 可以获得更好的结果(使滚动到所需的视图更加窒息),但除此之外 - 很好的答案。
@xmenW.K。简短的回答:因为 UI 是根据待办事项的队列来完成工作的,而您基本上是在告诉 UI 线程,在您完成之前必须做的所有事情之后,何时可以执行此操作(滚动) .您基本上是将滚动放入队列中,并让线程在可能的情况下执行此操作,尊重在您请求之前它正在执行的操作的顺序。
也可以将 Runnable 发布到 ScrollView 本身,而不是实例化一个新的 Handler:your_scrollview.post(...
getBottom() 和 getTop() 是相对于父级的
为什么是 getBottom() 而不是 getTop()
a
ahmadalibaloch

如果要将视图滚动到滚动视图的中心,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(((vTop + vBottom - sHeight) / 2), 0);
...

你必须调整你的代码。您必须获取滚动视图的宽度,并且必须将公式更改为 sv.smoothScrollTo(((vLeft + vRight) / 2) - (svWidth / 2), 0);
否则 scrollView 中的视图不会居中。我已经为你做了编辑。
@Elementary Yours 和 ahmads 公式是同一代数表达式的简化和扩展版本,无需调整。
@k29 你在这里指的是两个可见的公式吗?他们都来自我,我进一步简化了我的评论公式并相应地编辑了答案。但是其余的都与原始答案相同,对我也很有帮助。
您的垂直 ScrollView 答案中的 x 和 y 不是混淆了吗?
S
Swas_99

这对我很有效:

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

public void RequestChildFocus(查看孩子,查看重点)

child - 此 ViewParent 需要焦点的孩子。此视图将包含焦点视图。实际关注的不一定是视图。

聚焦 - 实际具有焦点的孩子的后代视图


查看任何平滑滚动中的跳跃类型
M
Michael

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

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

在视图寻呼机中为我工作,有滚动视图,需要使用此处提到的代码滚动到现在正在工作的视图。
绝对这个应该是公认的答案,如果所需的视图不在屏幕上,scrollview.smoothScrollTo 将不起作用,(在带有 sdk 版本 26 的 android 模拟器中尝试过)
@Michael 我应该用 new Handler().post() 包装吗?
@JohnnyFive 这取决于您使用此代码的上下文。
你太棒了!这是 ScrollView 中嵌套视图的完美解决方案,谢谢!
T
Tobrun

我根据 WarrenFaith 的 Answer 制作了一个小型实用方法,此代码还考虑了该视图是否已经在滚动视图中可见,无需滚动。

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 方法的参数。
O
Ovidiu Latcu

您应该将您的 TextView 请求重点放在:

    mTextView.requestFocus();

G
Goran Horia Mihail

另一种变化是:

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

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


I
Injured Platypus

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

S
Sam Fisher

如果 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 父级是一个很好的解决方案,如果您不知道父级,可以将此解决方案组合到这个 stackoverflow.com/a/58111428/8105771。然后 traverse 方法将应用更改,并且 focusChangeListener 不能是一个成员变量,您需要为需要滚动到的每个组件创建新的侦听器。我还不知道,我没有尝试过,但可能会在拥挤的表格中产生内存问题。我会试试这个,现在我不告诉你会体验。
S
Suphi ÇEVİKER

我知道这对于更好的答案可能为时已晚,但理想的完美解决方案必须是像定位器这样的系统。我的意思是,当系统为编辑器字段进行定位时,它会将字段放在键盘上,因此按照 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 结果。

Many thanks to all other answers inspiring me much.

A
Alireza Noorali

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

去做就对了 ;)


如果这不起作用,那么您应该将其发布延迟 100 毫秒,可能是因为如果您在创建视图后直接滚动,getTop() 将返回 0,因为您需要添加延迟,您可以使用 yourScrollView.postDelayed({//smooth scroll}, 100)lifecycleScope.launch {delay(100); yourScrollView.smoothScrollTo(0, yourEditText.getTop());}
X
XpressGeek
 scrollView.post(new Runnable() {
                    @Override
                    public void run() {
                        scrollView.smoothScrollTo(0, myTextView.getTop());

                    }
                });

从我的实际项目中回答。

https://i.stack.imgur.com/l0fhL.jpg


J
James Jordan Taylor

我想我找到了更优雅、更不容易出错的解决方案

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
}

最好将其包装到 postDelayed 中以使其更可靠,以防目前正在更改 ScrollView

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

C
Community

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

以下效果要好得多。

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

J
Jaromír Adamec

检查 Android 源代码,您会发现 ScrollViewscrollToChild(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;
     }
 }

w
w4kskl

我的解决方案是:

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

Q
Qamar

垂直滚动,适用于表单。答案基于 Ahmadalibaloch 水平滚动。

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

你确定你应该在这个方法中使用HorizontalScrollView吗?
T
Tazeem Siddiqui

您可以像这样使用 ObjectAnimator

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

Z
Zin Win Htet

就我而言,这不是 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);
        }
    });
}

C
Community

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

Ans:recyclerview 最后位置的嵌套滚动视图添加了记录数据。

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

Is there a way to programmatically scroll a scroll view to a specific edit text?


为什么是底部?
L
LukeWaggoner

以下是我正在使用的:

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

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


A
Ashwin Balani

如果 scrlMain 是您的 NestedScrollView,则使用以下内容,

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

                    }
                });

d
datchung

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

B
Bogdan Kornev

如果您想在打开软键盘时滚动到视图,那么它可能会有点棘手。到目前为止,我得到的最佳解决方案是结合使用 inset 回调和 requestRectangleOnScreen 方法。

首先,您需要设置插入回调:

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

我们正在根视图上设置回调以确保我们被调用。 Insets 可以在我们有问题的视图收到它们之前被消耗掉,所以我们必须在这里做额外的工作。

现在几乎很容易:

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

您可以摆脱焦点检查。它可以限制对 requestRectangleOnScreen 的调用次数。在可滚动父计划滚动到焦点视图后,我使用 post 运行操作。


O
OhhhThatVarun

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

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


R
Ramakrishna Joshi

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