Могу ли я использовать onScrollListener для ScrollView?

143

Я использую HorizontalScrollViewв макете, и мне нужно определить, что пользователь достиг начальной и конечной точки прокрутки.

Потому что ListViewя попробовал, onScrollListenerи можно найти начальную и конечную точки прокрутки.

Я пытался сделать то же самое в своем, Scrollviewно это не представляется возможным. Есть ли другие возможные способы достичь того, что мне нужно?

arnp
источник
3
Это возможно. См. Ответ user2695685. Короче следующий onStartбудет делать трюк: hsv.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {@Override public void onScrollChanged() {Log.i(TAG,"scroll:"+hsv.getScrollX());}});в OnStart () , где hsvявляется HorizontalScrollViewработой.
JohnnyLambada
примите любой полезный ответ .. если еще опубликуйте свой собственный ответ ..
Ранджит Кумар
13
Почему в Android так сложно обнаружить событие прокрутки с помощью ScrollView? Это чокнутый, имо.
работал

Ответы:

394

Каждый экземпляр View вызывает getViewTreeObserver(). Теперь, удерживая экземпляр ViewTreeObserver, вы можете добавить OnScrollChangedListener()к нему с помощью метода addOnScrollChangedListener().

Вы можете увидеть больше информации об этом классе здесь .

Это позволяет вам быть в курсе каждого события прокрутки - но без координат. Однако вы можете получить их, используя getScrollY()или getScrollX()изнутри слушателя.

scrollView.getViewTreeObserver().addOnScrollChangedListener(new OnScrollChangedListener() {
    @Override
    public void onScrollChanged() {
        int scrollY = rootScrollView.getScrollY(); // For ScrollView
        int scrollX = rootScrollView.getScrollX(); // For HorizontalScrollView
        // DO SOMETHING WITH THE SCROLL COORDINATES
    }
});
Збун
источник
6
Этот ответ должен быть отмечен как правильный. hsv.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {@Override public void onScrollChanged() {Log.i(TAG,"scroll:"+hsv.getScrollX());}});в OnStart () , где hsvпредставляет собой HorizontalScrollViewпроизведение. Я подозреваю, что то же самое будет работать и для ScrollView.
JohnnyLambada
3
в яблочко. это лучший ответ. нет необходимости расширять HorizontalScrollView. Только что протестировано с помощью ScrollView, отлично работает: final ScrollView sv = (ScrollView) findViewById (R.id.scrollViewArticle); sv.getViewTreeObserver (). addOnScrollChangedListener (новый ViewTreeObserver.OnScrollChangedListener () {@Override public void onScrollChanged () {Log.d ("onScrollChanged Y", String.valueOf (sv.getScroll}} ()));
Raphael C
5
Вы должны сначала проверить ViewTreeObserver.isAlive ()
Саломаа
7
Пример кода в ответе указывает на утечку памяти. Поскольку метод есть addи нет set, все слушатели будут сохраняться до тех пор, пока явно не будут удалены. Таким образом, анонимный класс, используемый в качестве реализации прослушивателя в примере, будет протекать (вместе со всем, на что он ссылается, то есть внешним классом).
Бретт Дункэвидж
7
Наверное, тупой вопрос, а что такое rootScrollView?
Iqbal
55

Это может быть очень полезно. Используйте NestedScrollViewвместо ScrollView. Поддержка Библиотека 23,1 представила OnScrollChangeListenerв NestedScrollView. Итак, вы можете сделать что-то подобное.

 myScrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
        @Override
        public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
            Log.d("ScrollView","scrollX_"+scrollX+"_scrollY_"+scrollY+"_oldScrollX_"+oldScrollX+"_oldScrollY_"+oldScrollY);
            //Do something
        }
    });
Мидхун Виджаякумар
источник
2
Определенно проще, чем отслеживать, добавляли ли вы слушателя раньше с помощью getViewTreeObserver (). Благодарность!
Энтони Миллс,
Но как использовать NestedScrollView как горизонтальную прокрутку? Не могу найти ни одного ресурса
GensaGames
Что, если я хочу использовать горизонтальную прокрутку?
Никита Аксёнов
9
@ dazed'n'confused NestedScrollViewявляется частью библиотеки поддержки, его setOnScrollChangeListenerметод не требует минимальной версии.
Том
5
Не путать с View's, для setOnScrollChangeListenerкоторого требуется уровень API 23
Андерс Улнесс
18

Вот производный HorizontalScrollView, который я написал для обработки уведомлений о прокрутке и окончании прокрутки. Он правильно обрабатывает, когда пользователь прекратил активную прокрутку и когда он полностью замедляется после того, как пользователь отпускает:

public class ObservableHorizontalScrollView extends HorizontalScrollView {
    public interface OnScrollListener {
        public void onScrollChanged(ObservableHorizontalScrollView scrollView, int x, int y, int oldX, int oldY);
        public void onEndScroll(ObservableHorizontalScrollView scrollView);
    }

    private boolean mIsScrolling;
    private boolean mIsTouching;
    private Runnable mScrollingRunnable;
    private OnScrollListener mOnScrollListener;

    public ObservableHorizontalScrollView(Context context) {
        this(context, null, 0);
    }

    public ObservableHorizontalScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        int action = ev.getAction();

        if (action == MotionEvent.ACTION_MOVE) {
            mIsTouching = true;
            mIsScrolling = true;
        } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
            if (mIsTouching && !mIsScrolling) {
                if (mOnScrollListener != null) {
                    mOnScrollListener.onEndScroll(this);
                }
            }

            mIsTouching = false;
        }

        return super.onTouchEvent(ev);
    }

    @Override
    protected void onScrollChanged(int x, int y, int oldX, int oldY) {
        super.onScrollChanged(x, y, oldX, oldY);

        if (Math.abs(oldX - x) > 0) {
            if (mScrollingRunnable != null) {
                removeCallbacks(mScrollingRunnable);
            }

            mScrollingRunnable = new Runnable() {
                public void run() {
                    if (mIsScrolling && !mIsTouching) {
                        if (mOnScrollListener != null) {
                            mOnScrollListener.onEndScroll(ObservableHorizontalScrollView.this);
                        }
                    }

                    mIsScrolling = false;
                    mScrollingRunnable = null;
                }
            };

            postDelayed(mScrollingRunnable, 200);
        }

        if (mOnScrollListener != null) {
            mOnScrollListener.onScrollChanged(this, x, y, oldX, oldY);
        }
    }

    public OnScrollListener getOnScrollListener() {
        return mOnScrollListener;
    }

    public void setOnScrollListener(OnScrollListener mOnEndScrollListener) {
        this.mOnScrollListener = mOnEndScrollListener;
    }

}
ЗаБлан
источник
1
Похоже, это лучше, чем использование метода ViewTreeObserver. Просто потому, что когда у вас есть активность, в которой загружено несколько фрагментов (например, 3 фрагмента со скользящими вкладками) с ListViews и ScrollViews, и вам нужно регистрировать события для определенного представления. Тогда как если ViewTreeObserver зарегистрирован, он будет запускать события, даже если это не активное представление.
Torsten Ojaperv
3
@ZaBlanc - Не могли бы вы рассказать мне, как инициализировать этот класс и слушателей из моей Activity, которая содержит ScrollView? Я реализовал это очень хорошо, но слушатели пока не возвращают никаких значений.
Эдмонд Тамас
Это действительно работает! и нет необходимости в sdk> 23 для этого :)
Matan Dahan
6

Вы можете использовать NestedScrollViewвместо ScrollView. Однако при использовании Kotlin Lambda он не узнает, что вам нужен NestedScrollView setOnScrollChangeListenerвместо того, который находится в View (это уровень API 23). Вы можете исправить это, указав первый параметр как NestedScrollView.

nestedScrollView.setOnScrollChangeListener { _: NestedScrollView, scrollX: Int, scrollY: Int, _: Int, _: Int ->
    Log.d("ScrollView", "Scrolled to $scrollX, $scrollY")
}
Кристан
источник
Это должен быть принятый ответ, потому что метод, использующий viewTreeObserver, вызывает утечку памяти и должен быть удален вручную, иначе будут добавлены несколько наблюдателей прокрутки.
Ray Li
5

Если вы хотите узнать положение прокрутки представления, вы можете использовать следующую функцию расширения в классе View:

fun View?.onScroll(callback: (x: Int, y: Int) -> Unit) {
    var oldX = 0
    var oldY = 0
    this?.viewTreeObserver?.addOnScrollChangedListener {
        if (oldX != scrollX || oldY != scrollY) {
            callback(scrollX, scrollY)
            oldX = scrollX
            oldY = scrollY
        }
    }
}
Mcspayne
источник
1

вы можете определить собственный класс ScrollView и добавить интерфейс, который будет вызываться при прокрутке следующим образом:

public class ScrollChangeListenerScrollView extends HorizontalScrollView {


private MyScrollListener mMyScrollListener;

public ScrollChangeListenerScrollView(Context context) {
    super(context);
}

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

public ScrollChangeListenerScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}


public void setOnMyScrollListener(MyScrollListener myScrollListener){
    this.mMyScrollListener = myScrollListener;
}


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

}

public interface MyScrollListener {
    void onScrollChange(View view,int scrollX,int scrollY,int oldScrollX, int oldScrollY);
}

}
Чуансин Чжао
источник
0
    // --------Start Scroll Bar Slide--------
    final HorizontalScrollView xHorizontalScrollViewHeader = (HorizontalScrollView) findViewById(R.id.HorizontalScrollViewHeader);
    final HorizontalScrollView xHorizontalScrollViewData = (HorizontalScrollView) findViewById(R.id.HorizontalScrollViewData);
    xHorizontalScrollViewData.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
        @Override
        public void onScrollChanged() {
            int scrollX; int scrollY;
            scrollX=xHorizontalScrollViewData.getScrollX();
            scrollY=xHorizontalScrollViewData.getScrollY();
            xHorizontalScrollViewHeader.scrollTo(scrollX, scrollY);
        }
    });
    // ---------End Scroll Bar Slide---------
Вироте В.
источник
пожалуйста, попробуйте добавить какое-нибудь описание для поддержки вашего кода, который поможет всем понять тесто
Анируддха Дас
Этот код очень похож на другой ответ , не так ли?
Сергей
0

Помимо принятого ответа, вам нужно сохранить ссылку на слушателя и удалить, когда она вам не нужна. В противном случае вы получите исключение нулевого указателя для вашего ScrollView и утечки памяти ( упомянутого в комментариях к принятому ответу) .

  1. Вы можете реализовать OnScrollChangedListener в своей активности / фрагменте.

    MyFragment : ViewTreeObserver.OnScrollChangedListener
    
  2. Добавьте его в scrollView, когда ваше представление будет готово.

    scrollView.viewTreeObserver.addOnScrollChangedListener(this)
    
  3. Удалите слушателя, когда он больше не нужен (например, onPause ())

    scrollView.viewTreeObserver.removeOnScrollChangedListener(this)
    
Акн
источник