Есть ли способ программно прокрутить представление прокрутки до определенного текста редактирования?

У меня очень долгая активность с прокруткой. Это форма с различными полями, которые пользователь должен заполнить. У меня есть флажок на полпути вниз по моей форме, и когда пользователь проверяет его, я хочу прокрутить до определенной части представления. Есть ли способ программной прокрутки до объекта EditText (или любого другого объекта представления)?

Кроме того, я знаю, что это возможно с использованием координат X и Y, но я хочу избежать этого, так как форма может меняться от пользователя к пользователю.

27 ответов

Решение
private final void focusOnView(){
        your_scrollview.post(new Runnable() {
            @Override
            public void run() {
                your_scrollview.scrollTo(0, your_EditBox.getBottom());
            }
        });
    }

Ответ Шерифа эль-Хатиба может быть значительно улучшен, если вы хотите прокрутить вид в центр вида прокрутки. Этот повторно используемый метод плавно прокручивает вид до видимого центра 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 изменить x а также y положение входов smoothScroll, И использовать view.getTop() вместо view.getLeft() а также view.getBottom() вместо v.getRight(),

:)

Это хорошо работает для меня:

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

public void RequestChildFocus (Просмотреть ребенка, Просмотреть сосредоточен)

child - дочерний элемент этого ViewParent, который хочет сосредоточиться. Это представление будет содержать сфокусированное представление. Это не обязательно точка зрения, которая на самом деле имеет фокус.

сфокусированный - взгляд, который является потомком ребенка, который на самом деле имеет фокус

На мой взгляд, лучший способ прокрутить до заданного прямоугольника с помощью View.requestRectangleOnScreen(Rect, Boolean), Вы должны позвонить на View Вы хотите прокрутить и передать локальный прямоугольник, который вы хотите видеть на экране. Второй параметр должен быть false для плавной прокрутки и true для немедленной прокрутки.

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

Я создал небольшой служебный метод, основанный на ответе от 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());
            }
        });
    } 
}

Вы должны сделать свой TextView Фокус запроса:

    mTextView.requestFocus();

Другое изменение будет:

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

или вы можете использовать post() метод.

Мой EditText был вложен в несколько слоев внутри моего ScrollView, который сам по себе не является корневым представлением макета. Поскольку getTop() и getBottom(), по-видимому, сообщали о координатах в пределах содержащего их представления, я сделал так, чтобы он вычислял расстояние от вершины ScrollView до вершины EditText, просматривая родительские элементы 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();

Я знаю, что может быть слишком поздно для лучшего ответа, но желаемое идеальное решение должно быть такой системой, как позиционер. Я имею в виду, что когда система позиционирует поле редактора, она помещает поле прямо к клавиатуре, так что, согласно правилам 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.

Большое спасибо всем остальным ответам, которые меня очень вдохновили.

Приведенные выше ответы будут работать нормально, если ScrollView является прямым родителем ChildView. Если ваш ChildView переносится в другую ViewGroup в ScrollView, это вызовет неожиданное поведение, потому что 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));
}

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

Просто сделай это ;)

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

                    }
                });

Отвечаю из моего практического проекта.

Ссылка: /questions/17537057/mogu-li-ya-prokrutit-scrollview-programmno-v-android/17537065#17537065

Следующее сработало намного лучше.

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

Я думаю, что я нашел более элегантное и подверженное ошибкам решение с использованием

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

Изучая исходный код Android, вы можете обнаружить, что уже есть функция-член ScrollView- scrollToChild(View) - это именно то, что требуется. К сожалению, эта функция по какой-то неясной причине помечена private, На основе этой функции я написал следующую функцию, которая находит первый ScrollView выше View указывается в качестве параметра и прокручивает его так, что он становится видимым внутри 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;
     }
 }

Вертикальная прокрутка, хорошо подходит для форм. Ответ основан на горизонтальной прокрутке Ахмадалибалоча.

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

Ты можешь использовать ObjectAnimator как это:

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

Основываясь на ответе Шерифа, для моего варианта использования лучше всего сработало следующее. Заметные изменения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());
            }
        });
    }

Мое решение:

            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() как ноль. Пример: getTop() edittext, который встроен в TextInputLayout, вернет 0. Так что в этом случае мы должны вычислить getTop() TextInputLayout, который является прямым дочерним элементом ScrollView.

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

Если кто-то ищет версию Kotlin

      private fun scrollToView(view: View) {
        view.requestFocus()
        val scrollBounds = Rect()
        scrollView.getHitRect(scrollBounds)
        if (!view.getLocalVisibleRect(scrollBounds)) {
            coroutineScope.launch(Dispatchers.Main) {
                scrollView.smoothScrollTo(0, view.bottom - 40)
            }
        }
    }

В моем случае это не 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);
        }
    });
}

вот еще одна лучшая версия для эффективной прокрутки:

код kotlin для прокрутки до определенной позиции просмотра, добавленной в scrollview(горизонтально)

       horizontalScrollView.post {
    val targetView = findViewById<View>(R.id.target_view)
    val targetX = targetView.left
    horizontalScrollView.smoothScrollTo(targetX, 0)
    }

для вертикальной прокрутки просто измените targetView.leftк targetView.top

для JAVA вот пример кода:

      scrollView.postDelayed(new Runnable() {
    @Override
    public void run() {
        int targetViewY = targetView.getTop();
        scrollView.smoothScrollTo(0, targetViewY);
    }
}, 500);

Если вы хотите прокрутить до представления, когда открыта программная клавиатура, это может быть немного сложно. Лучшее решение, которое у меня есть до сих пор, - использовать комбинацию обратных вызовов и метода вставки.

Во-первых, вам нужно настроить обратные вызовы вставки:

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

Получается сумма прокрутки, необходимая для отображения нижней части представления, включая любое поле в нижней части этого представления.

Que: Есть ли способ программно прокрутить представление прокрутки к определенному тексту редактирования?

Ответ: Вложенное представление прокрутки в окне просмотра последней позиции добавило данные записи.

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

Есть ли способ программно прокрутить представление прокрутки до определенного текста редактирования?

Если scrlMain - это ваш NestedScrollView, используйте следующее:

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

                    }
                });
Другие вопросы по тегам