Виявити кінець ScrollView


76

У мене є додаток, який має Activity, який використовує ScrollView. Мені потрібно виявити, коли користувач потрапляє в суть ScrollView. Я трохи погуглив і знайшов цю сторінку, де пояснено. Але, у прикладі, що хлопці поширюється ScrollView. Як я вже сказав, мені потрібно розширити Діяльність.

Отже, я сказав "добре, спробуємо зробити власний клас, що розширюється ScrollView, замінити onScrollChanged()метод, виявити кінець прокрутки і діяти відповідно до цього".

Я зробив, але в цьому рядку:

scroll = (ScrollViewExt) findViewById(R.id.scrollView1);

він кидає a java.lang.ClassCastException. Я змінив <ScrollView>теги у своєму XML, але, очевидно, це не працює. Мої запитання: Чому, якщо ScrollViewExtпродовжує ScrollView, кидає мені в обличчя a ClassCastException? чи є спосіб виявити кінець прокрутки, не надто возившись?

Дякую вам, люди.

РЕДАКТУВАТИ: Як і обіцяли, ось частина мого XML, яка має значення:

<ScrollView
        android:id="@+id/scrollView1"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >


        <WebView
            android:id="@+id/textterms"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center_horizontal"
            android:textColor="@android:color/black" />

    </ScrollView>

Я змінив його з TextViewна, WebViewщоб мати змогу виправдати текст всередині. Що я хочу досягти, це "Кнопка" Прийняти "не активується, поки умови контракту не будуть повністю прочитані". Мій розширений клас називається ScrollViewExt. Якщо я змінити тег ScrollViewдля ScrollViewExtйого кидає

android.view.InflateException: Binary XML file line #44: Error inflating class ScrollViewExt

тому що він не розуміє тег ScrollViewEx. Я не думаю, що він має рішення ...

Дякую за відповіді!


Не потрібно користувацьких класів або складних розширень. Просто використовуйтеcanScrollVertically(int)
yuval

Відповіді:


108

Зробив це!

Окрім виправлення, яке Олександр люб’язно надав мені, мені довелося створити інтерфейс:

public interface ScrollViewListener {
    void onScrollChanged(ScrollViewExt scrollView, 
                         int x, int y, int oldx, int oldy);
}

Потім мені довелося перевизначити метод OnScrollChanged з ScrollView у моєму ScrollViewExt:

public class ScrollViewExt extends ScrollView {
    private ScrollViewListener scrollViewListener = null;
    public ScrollViewExt(Context context) {
        super(context);
    }

    public ScrollViewExt(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public ScrollViewExt(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void setScrollViewListener(ScrollViewListener scrollViewListener) {
        this.scrollViewListener = scrollViewListener;
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (scrollViewListener != null) {
            scrollViewListener.onScrollChanged(this, l, t, oldl, oldt);
        }
    }
}

Тепер, як сказав Олександр, вставте ім'я пакета в тег XML (з моєї вини), змусіть мій клас Activity реалізувати створений раніше інтерфейс, а потім складіть все разом:

scroll = (ScrollViewExt) findViewById(R.id.scrollView1);
scroll.setScrollViewListener(this);

А в методі OnScrollChanged, з інтерфейсу ...

@Override
public void onScrollChanged(ScrollViewExt scrollView, int x, int y, int oldx, int oldy) {
    // We take the last son in the scrollview
    View view = (View) scrollView.getChildAt(scrollView.getChildCount() - 1);
    int diff = (view.getBottom() - (scrollView.getHeight() + scrollView.getScrollY()));

    // if diff is zero, then the bottom has been reached
    if (diff == 0) {
        // do stuff
    }
}

І це спрацювало!

Дякуємо за допомогу, Олександре!


Я намагаюся використати цю відповідь у своєму додатку, і проблема полягає у -scrollView.getHeight () завжди дорівнює view.getBottom (), тоді як scrollView.getScrollY () завжди дорівнює 0. Чи знаєте ви, чому це може бути? ?
Marko Cakic

Дякую, це чудово, оскільки дозволяє створювати стрілки у верхній і нижній частині ScrollView і робити так, щоб вони з’являлися або зникали залежно від положення прокрутки, щоб користувач усвідомлював, що є більше предметів для перегляду ...
Lumis

@MarkoCakic Дивіться цю відповідь, щоб отримати висоту вмісту прокрутки. stackoverflow.com/questions/3609297 / ...
Uselessinfo

Оскільки scrollViewє тільки одна дитина , view = scrollView.getChildAt(0)і scrollView.getScrollY()==y в функції, чи може функція бути модифікована вище , використовуючи це, а не доступ до надмірності даних.
лаапту

У мене є одне запитання, я динамічно оновлюю свій прокручуваний перегляд, тому я не хочу, щоб це було на scrollChanged, я хочу виявити, чи прокрутив користувач (вимкнути автопрокрутку), якщо користувач прокручує вниз (увімкнути автопрокрутку). Допоможіть, будь ласка.
AL̲̳I

81

Я знайшов простий спосіб виявити це:

   scrollView.getViewTreeObserver()
       .addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
            @Override
            public void onScrollChanged() {
                if (scrollView.getChildAt(0).getBottom()
                     <= (scrollView.getHeight() + scrollView.getScrollY())) {
                    //scroll view is at bottom
                } else {
                    //scroll view is not at bottom
                }
            }
        });
  • Не потрібно настроювати ScrollView.
  • Scrollview може розмістити лише одну пряму дочірню організацію, тому scrollView.getChildAt (0) - це нормально.
  • Це рішення правильне, навіть якщо висота прямого дочірнього елемента перегляду прокрутки match_parent або wrap_content .

Дуже дякую! Працює!
DmitryKanunnikoff

Гарне рішення! Дякую.
Lee Hounshell

8
Щоб уникнути дзвінків кілька разів, замініть "<=" на "=="
Harshil Pansare

Ідеально підходить для невеликих налаштувань. :)
Джон Т

1
ДЯКУЮ, нам не потрібно розширювати ScrollView, щоб зробити щось таке загальне.
Ендрю Костер

67

Усі ці відповіді настільки складні, але є простий вбудований метод, який робить це: canScrollVertically(int)

Наприклад:

@Override
public void onScrollChanged() {
    if (!scrollView.canScrollVertically(1)) {
        // bottom of scroll view
    }
    if (!scrollView.canScrollVertically(-1)) {
        // top of scroll view
    }
}

Це також працює з RecyclerView, ListView та фактично з будь-яким іншим видом, оскільки метод реалізований на View.

Якщо у вас горизонтальний ScrollView, того ж можна досягти за допомогою canScrollHorizontally(int)


8
Ідеальна відповідь! Перевірено!
LionisIAm

4
дійсно допомогло. Дякую !!
nik

4
це повинен бути ПРИЙНЯТИЙ ВІДПОВІДЬ, він простий і працює ідеально

3
Для повноти, це !scrollView.canScrollHorizontally(1)перевірити, чи це вже в правому кінці, і !scrollView.canScrollHorizontally(-1)чи в лівому.
keithmaxx

1
ідеальне рішення!
Dimitri

17

Відповідь Фустігадора була чудовою, але я виявив, що деякий пристрій (як Samsung Galaxy Note V) не може досягти 0, залишилося 2 бали після розрахунку. Я пропоную додати трохи буфера, як показано нижче:

@Override
public void onScrollChanged(ScrollViewExt scrollView, int x, int y, int oldx, int oldy) {
    // We take the last son in the scrollview
    View view = (View) scrollView.getChildAt(scrollView.getChildCount() - 1);
    int diff = (view.getBottom() - (scrollView.getHeight() + scrollView.getScrollY()));

    // if diff is zero, then the bottom has been reached
    if (diff <= 10) {
        // do stuff
    }
}

11
scrollView = (ScrollView) findViewById(R.id.scrollView);

    scrollView.getViewTreeObserver()
            .addOnScrollChangedListener(new 
            ViewTreeObserver.OnScrollChangedListener() {
                @Override
                public void onScrollChanged() {

                    if (!scrollView.canScrollVertically(1)) {
                        // bottom of scroll view
                    }
                    if (!scrollView.canScrollVertically(-1)) {
                        // top of scroll view


                    }
                }
            });

10

РЕДАГУВАТИ

З вмістом вашого XML, я бачу, що ви використовуєте ScrollView. Якщо ви хочете використовувати власний вигляд, ви повинні написати com.your.packagename.ScrollViewExt, і ви зможете використовувати його у своєму коді.

<com.your.packagename.ScrollViewExt
    android:id="@+id/scrollView1"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <WebView
        android:id="@+id/textterms"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_horizontal"
        android:textColor="@android:color/black" />

</com.your.packagename.ScrollViewExt>

РЕДАКТУВАТИ КІНЕЦЬ

Не могли б ви опублікувати вміст xml?

Я думаю, що ви можете просто додати прослуховувач прокрутки та перевірити, чи останній елемент, що відображається, останній із списку, наприклад:

mListView.setOnScrollListener(new OnScrollListener() {      
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        // TODO Auto-generated method stub      
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
            int visibleItemCount, int totalItemCount) {
        // TODO Auto-generated method stub
        if(view.getLastVisiblePosition()==(totalItemCount-1)){
            //dosomething
        }
    }
});

Я зроблю це завтра, я зараз не на роботі. Але я бачу, що ви пропонуєте ListView. Я не використовую ListView, а лише TextView всередині ScrollView.
Фустігадор,

О, вибачте, я думав про ListView. Чи можете ви опублікувати xm, викликаний setContentViewl, за допомогою власного подання?
Александр Б.

4

Ви можете скористатися бібліотекою підтримки NestedScrollViewта її NestedScrollView.OnScrollChangeListenerінтерфейсом.

https://developer.android.com/reference/android/support/v4/widget/NestedScrollView.html

Якщо ваш додаток націлений на API 23 або вище, ви можете скористатися наступним методом на ScrollView:

View.setOnScrollChangeListener(OnScrollChangeListener listener) 

Тоді слідуйте прикладу, який @Fustigador описав у своїй відповіді. Однак зауважте, що, як @Will описано, вам слід подумати про додавання невеликого буфера на випадок, якщо користувач чи система не зможе досягти повного кінця списку з будь-якої причини.

Також варто зазначити, що слухач зміни прокрутки іноді буде викликатися з від'ємними значеннями або значеннями, що перевищують висоту перегляду. Імовірно, ці значення представляють "імпульс" дії прокрутки. Однак, якщо їх не обробити належним чином (підлога / абс), вони можуть спричинити проблеми з визначенням напрямку прокрутки, коли вигляд прокручується вгору або внизу діапазону.

https://developer.android.com/reference/android/view/View.html#setOnScrollChangeListener(android.view.View.OnScrollChangeListener)


4

Ми завжди повинні додавати, scrollView.getPaddingBottom()щоб відповідати повній висоті прокрутки, оскільки деякий час подання прокрутки має відступ у файлі xml, тому цей випадок не буде працювати.

scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
        @Override
        public void onScrollChanged() {
            if (scrollView != null) {
               View view = scrollView.getChildAt(scrollView.getChildCount()-1);
               int diff = (view.getBottom()+scrollView.getPaddingBottom()-(scrollView.getHeight()+scrollView.getScrollY()));

          // if diff is zero, then the bottom has been reached
               if (diff == 0) {
               // do stuff
                }
            }
        }
    });

2

Для того, щоб визначити, чи ви закінчуєте користувальницькі налаштування, ScrollViewви також можете використовувати змінну-член і зберігати останню позицію y. Тоді ви можете порівняти останню позицію y з поточною позицією прокрутки.

private int scrollViewPos;

...

@Override
public void onScrollChanged(ScrollViewExt scrollView, int x, int y, int oldx, int oldy) {

   //reached end of scrollview
   if (y > 0 && scrollViewPos == y){
      //do something
   }

   scrollViewPos = y;
}

2

Більшість відповідей працює, крім того, що коли ви прокручуєте вниз, слухач запускається кілька разів , що в моєму випадку небажано . Щоб уникнути такої поведінки, я додав прапорець scrollPositionChanged, який перевіряє, чи змінилася позиція прокрутки перед повторним викликом методу.

public class EndDetectingScrollView extends ScrollView {
    private boolean scrollPositionChanged = true;

    private ScrollEndingListener scrollEndingListener;

    public interface ScrollEndingListener {
        void onScrolledToEnd();
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);

        View view = this.getChildAt(this.getChildCount() - 1);
        int diff = (view.getBottom() - (this.getHeight() + this.getScrollY()));
        if (diff <= 0) {
            if (scrollPositionChanged) {
                scrollPositionChanged = false;
                if (scrollEndingListener != null) {
                    scrollEndingListener.onScrolledToEnd();
                }
            }
        } else {
            scrollPositionChanged = true;
        }
    }

    public void setScrollEndingListener(ScrollEndingListener scrollEndingListener) {
        this.scrollEndingListener = scrollEndingListener;
    }
}

Тоді просто налаштуйте слухача

scrollView.setScrollEndingListener(new EndDetectingScrollView.ScrollEndingListener() {
    @Override
    public void onScrolledToEnd() {
        //do your stuff here    
    }
});

Ви можете зробити те саме, якщо ви робите подібне

scrollView.getViewTreeObserver().addOnScrollChangedListener(...)

але ви повинні надати прапор з класу, додавши цей слухач.


Приємно! Добре працює для попереднього API рівня 23!
Абхіруп Нанді Рей,

1

Я переглянув рішення в Інтернеті. Здебільшого рішення не працювали в проекті, над яким я працюю. Наступні рішення для мене добре працюють.

Використання onScrollChangeListener (працює на API 23):

   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        scrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
            @Override
            public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {

                int bottom =   (scrollView.getChildAt(scrollView.getChildCount() - 1)).getHeight()-scrollView.getHeight()-scrollY;

                if(scrollY==0){
                    //top detected
                }
                if(bottom==0){
                    //bottom detected
                }
            }
        });
    }

за допомогою scrollChangeListener на TreeObserver

 scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
        @Override
        public void onScrollChanged() {
            int bottom =   (scrollView.getChildAt(scrollView.getChildCount() - 1)).getHeight()-scrollView.getHeight()-scrollView.getScrollY();

            if(scrollView.getScrollY()==0){
                //top detected
            }
            if(bottom==0) {
                //bottom detected
            }
        }
    });

Сподіваюся, це рішення допоможе :)


0

Я хотів показати / сховати FAB зі зміщенням перед самим дном прокрутки. Це рішення, яке я придумав (Kotlin):

scrollview.viewTreeObserver.addOnScrollChangedListener {
    if (scrollview.scrollY < scrollview.getChildAt(0).bottom - scrollview.height - offset) {
        // fab.hide()
    } else {
        // fab.show()
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.