HorizontalScrollView: автоматическая прокрутка до конца при добавлении новых представлений?

У меня есть HorizontalScrollView, содержащий LinearLayout. На экране у меня есть кнопка, которая добавит новые представления в LinearLayout во время выполнения, и я бы хотел, чтобы представление прокрутки прокручивалось до конца списка при добавлении нового представления.

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

В своем приложении я использую настраиваемый объект View, но я создал небольшое тестовое приложение, которое использует ImageView и имеет тот же симптом. Я пробовал разные вещи, такие как requestLayout () как в Layout, так и в ScrollView, я пробовал scrollTo (Integer.MAX_VALUE), и он прокручивался в нижнюю часть мира :) Я нарушаю проблему потока пользовательского интерфейса или что-то в этом роде?

  • Рик

======

    public class Main extends Activity {
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);

             Button b = (Button) findViewById(R.id.addButton);
             b.setOnClickListener(new AddListener());

             add();
        }

        private void add() {
             LinearLayout l = (LinearLayout) findViewById(R.id.list);
             HorizontalScrollView s = 
                 (HorizontalScrollView) findViewById(R.id.scroller);

             ImageView i = new ImageView(getApplicationContext());
             i.setImageResource(android.R.drawable.btn_star_big_on);
             l.addView(i);

             s.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
        }

        private class AddListener implements View.OnClickListener {
             @Override
             public void onClick(View v) {
                 add();
             }
        }
    }

Макет XML:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">

        <HorizontalScrollView
            android:id="@+id/scroller"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:scrollbarSize="50px">
            <LinearLayout
                android:id="@+id/list"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:padding="4px"/>
        </HorizontalScrollView>

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"            
            android:layout_gravity="center">
            <Button
                android:id="@+id/addButton"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:paddingLeft="80px"
                android:paddingRight="80px"
                android:paddingTop="40px"
                android:paddingBottom="40px"
                android:text="Add"/>
        </LinearLayout>

    </LinearLayout>

person Rick Barkhouse    schedule 18.01.2011    source источник


Ответы (9)


Я думаю, есть проблема со сроками. Макет не выполняется при добавлении представления. Это запрашивается и делается вскоре после этого. Когда вы вызываете fullScroll сразу после добавления представления, ширина linearlayout не могла увеличиться.

Попробуйте заменить:

s.fullScroll(HorizontalScrollView.FOCUS_RIGHT);

с участием:

s.postDelayed(new Runnable() {
    public void run() {
        s.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
    }
}, 100L);

Короткая задержка должна дать системе достаточно времени для установки.

P.S. Может быть достаточно просто отложить прокрутку до завершения текущей итерации цикла пользовательского интерфейса. Я не проверял эту теорию, но если она верна, достаточно сделать следующее:

s.post(new Runnable() {
    public void run() {
        s.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
    }
});

Мне это нравится больше, потому что введение произвольной задержки кажется мне хакерским (хотя это и было моим предложением).

person Ted Hopp    schedule 18.01.2011
comment
Это сработало именно так, Тед, большое спасибо, это беспокоило меня несколько дней! :) - person Rick Barkhouse; 18.01.2011
comment
Привет, Тед .... когда я пытаюсь использовать приведенный выше код, он показывает ошибку не может разрешить метод postDelayed (java.lang.runnable, long) ... пожалуйста, помогите мне .... - person Vinit ...; 18.05.2015
comment
@VinitVikash - это метод из класса View. Возможно, вам придется сделать s.postDelayed(...). Или создайте Handler и используйте его postDelayed() метод. - person Ted Hopp; 18.05.2015
comment
new Handler().postDelayed() если вы не знаете, как использовать postDelayed() - person Pratik Butani; 11.06.2015
comment
@PratikButani - это работает, но было бы эффективнее выделить Handler один раз в качестве поля и использовать его, а не создавать его каждый раз на лету. Однако, поскольку у нас уже есть ссылка на представление s, лучше всего просто использовать s.postDelayed(...), что позволяет избежать необходимости в нашем собственном Handler. - person Ted Hopp; 11.06.2015

Я согласен с @monxalo и @ granko87 в том, что лучший подход - со слушателем, вместо того, чтобы делать предположение, что макет будет завершен, если мы дадим пройти некоторому произвольному количеству времени.

В случае, если вам, как и мне, не нужно использовать собственный подкласс HorizontalScrollView, вы можете просто добавить OnLayoutChangeListener, чтобы упростить задачу:

mTagsScroller.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
        mTagsScroller.removeOnLayoutChangeListener(this);
        mTagsScroller.fullScroll(View.FOCUS_RIGHT);
    }
});
person Newtz    schedule 03.03.2015
comment
Я тоже придумал это решение. Меньше кода, работа сделана. - person Paul Woitaschek; 10.07.2015
comment
Это, безусловно, правильный ответ на проблему. Ожидание в любое случайное время - ерунда. Подождать, пока механизм компоновки выполнит свою работу, - это правильный ответ. - person DrAL3X; 08.03.2018

Еще одно предложение, так как этот вопрос мне очень помог :).

Вы можете поместить слушателя, когда представление завершит этап своей компоновки, и сразу после этого выполните fullScroll, хотя для этого вам нужно будет расширить класс.

Я сделал это только потому, что хотел перейти к разделу сразу после onCreate (), чтобы избежать этого мерцания от начальной точки до точки прокрутки.

Что-то типа:

public class PagerView extends HorizontalScrollView {

    private OnLayoutListener mListener;
    ///...
    private interface OnLayoutListener {
        void onLayout();
    }

    public void fullScrollOnLayout(final int direction) {
        mListener = new OnLayoutListener() {            
            @Override
            public void onLayout() {
                 fullScroll(direction)
                 mListener = null;
            }
        };
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if(mListener != null)
             mListener.onLayout();
    }
}
person monxalo    schedule 16.03.2011

Используйте метод View smoothScrollTo

postDelayed(new Runnable() {
    public void run() {
       scrollView.smoothScrollTo(newView.getLeft(), newView.getTop());
 }
 }, 100L);

Вам нужно добавить исполняемый файл, чтобы дать время процессу макета, чтобы разместить новый вид.

person Gabriel Guerrero    schedule 04.02.2013
comment
У меня не работает. View # getTop () возвращает позицию относительно родителя, поэтому это не будет работать в более вложенных макетах. - person Vouskopes; 06.08.2020

Используйте приведенный ниже код для горизонтальной прокрутки {Прокрутка вправо}

view.post(new Runnable() {
                @Override
                public void run() {
                    ((HorizontalScrollView) Iview
                            .findViewById(R.id.hr_scroll))
                            .fullScroll(HorizontalScrollView.FOCUS_RIGHT);
                }
            });
person sharma_kunal    schedule 24.08.2014

Вот что я сделал.

//Observe for a layout change    
ViewTreeObserver viewTreeObserver = yourLayout.getViewTreeObserver();
   if (viewTreeObserver.isAlive()) {
     viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                yourHorizontalScrollView.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
            }
     });
   }
person Arst    schedule 08.02.2015

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

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);
    this.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
}
person granko87    schedule 15.05.2013
comment
Я не думаю, что количество времени имеет значение. Проблема в том, что прокрутка не должна происходить на текущей итерации цикла потока пользовательского интерфейса. После завершения текущей итерации иерархия представлений должна быть актуальной и размеченной. - person Ted Hopp; 06.01.2016

Дочерний View не может быть сразу добавлен в ViewGroup. Поэтому необходимо опубликовать Runnable, который будет запущен после завершения других незавершенных задач.

Итак, после добавления View используйте:

horizontalScrollView.post(new Runnable() {
    @Override
    public void run() {
        horizontalScrollView.fullScroll(View.FOCUS_RIGHT);
    }
});
person Nabin Bhandari    schedule 06.03.2018

Это мой способ для 3 ImageView в котлине:

var p1 = true; var p2 = false; var p3 = false
val x = -55
val handler = Handler()
handler.postDelayed(object : Runnable {
    override fun run() {

        if (p1){
            mainView.picsScrollViewID.smoothScrollTo(mainView.bigPic1ID.x.toInt() + x, 0)
            p1 = false
            p2 = true
            p3 = false
        }
        else if(p2){
            mainView.picsScrollViewID.smoothScrollTo(mainView.bigPic2ID.x.toInt() + x, 0)
            p1 = false
            p2 = false
            p3 = true
        }
        else if(p3){
            mainView.picsScrollViewID.smoothScrollTo(mainView.bigPic3ID.x.toInt() + x, 0)
            p1 = true
            p2 = false
            p3 = false
        }
        handler.postDelayed(this, 2000)
    }
}, 1400)
person Hossein Yousefpour    schedule 13.05.2019