安卓ScrollView的onScrollChanged方法

25

我在一个滚动视图的文本视图中有一个固定内容。当用户滚动到特定位置时,我想启动一个活动或触发一个 Toast

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="fill_parent" android:layout_height="fill_parent"
 android:id="@+id/scroller">
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical" android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <TextView android:layout_width="fill_parent" android:id="@+id/story"
   android:layout_height="wrap_content" android:text="@string/lorem"
   android:gravity="fill" />
 </LinearLayout>
</ScrollView>

我遇到的问题是实现受保护方法onScrollChanged以查找滚动视图的位置。

我发现了这个答案,除了像我发布的链接中所看到的那样声明接口、重写滚动视图等方式,是否有更简单的解决方案?


1
你可以使用以下代码来检测ScrollView的滚动是否到达底部: - Satria Akbar
11个回答

50

与其创建ScrollView的子类,使用ScrollViewViewTreeObserver对象可以更轻松地监听滚动。

由于ScrollViewViewTreeObserver对象在其生命周期中可能会发生变化,因此我们需要在ScrollView上注册一个OnTouchListener来获取它的ViewTreeObserver

final ViewTreeObserver.OnScrollChangedListener onScrollChangedListener = new
                           ViewTreeObserver.OnScrollChangedListener() {

    @Override
    public void onScrollChanged() {
        //do stuff here 
    }
};

final ScrollView scrollView = (ScrollView) findViewById(R.id.scroller);
scrollView.setOnTouchListener(new View.OnTouchListener() {
    private ViewTreeObserver observer;

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (observer == null) {
            observer = scrollView.getViewTreeObserver();
            observer.addOnScrollChangedListener(onScrollChangedListener);
        }
        else if (!observer.isAlive()) {
            observer.removeOnScrollChangedListener(onScrollChangedListener);
            observer = scrollView.getViewTreeObserver();
            observer.addOnScrollChangedListener(onScrollChangedListener);
        }

        return false;
    }
});

1
这并不一定比子类化ScrollView更容易。实际上,这需要更多的代码。如果您子类化,只需覆盖onScrollChanged方法即可。 - Christopher Perry
1
我尝试了这个,似乎对于相同的滚动Y值触发了20次或更多的事件。这看起来并不理想。 - RandomEngy
嗯...这段代码存在一个巨大的问题!而且这个问题会导致像@RandomEngy评论中那样的错误。因此,我不知道为什么它会得到如此高的赞数。它在每次滚动时都会添加此侦听器。因此,如果您点击滚动条20次,它将导致20次调用侦听器。应该只添加一次侦听器。例如:scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() { @Override public void onScrollChanged() { //do stuff }});。不要在onTouch方法内部添加。 - Bartek Lipinski
甚至更糟的是,在这个例子中,它可能会添加至少40个侦听器(每次点击至少TOUCH_DOWN和TOUCH_UP)。 - Bartek Lipinski
1
我使用了OnTouchListener,因为文档中提到:“返回的ViewTreeObserver观察器不能保证在此View的生命周期内始终有效”。我已经编辑了答案,以便如果ViewTreeObserver不再存在,则将其回调从中删除并添加到View的当前ViewtreeObserver中。 - timemanx
我建议使用子类化。这样可以减少代码量,更易于重用,并避免上述提到的问题。 - Gabor Peto

15

不行。

ScrollView没有提供滚动事件的监听器,甚至没有一种方法来检查用户向下滚动了多远,因此您必须按照链接中建议的方式进行操作。


14
有一个名为"onScrollChanged"的方法可以被重写。同样地,可以使用"getScrollX"、"getScrollY"方法。 - user458577
2
看起来这个答案现在已经无效了。从API 23(M)开始,有一个名为"OnScrollChangeListener"的监听器scrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() { @Override public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { //使用参数进行操作 } }); - Chanaka Fernando
@S.D.NChanakaFernando 还应该注意到,当涉及到滚动时,它仍然没有提供内置的特定解决方案。例如,如果您想检测滚动结束,您将不得不使用 onScrollChangeListener 手动完成,这非常烦人。如果有人遇到这个问题,以下是一些建议:https://dev59.com/T2sy5IYBdhLWcg3w-zF4 - Benjamin Basmaci

10

这个问题相当老了,但是如果有人需要(像我一样),从API 23开始,Android的View具有一个OnScrollChangeListener以及匹配的setter

来自Support库的NestedScrollView也支持在那个API级别之前设置滚动监听器。据我所知,NestedScrollView可以作为正常的ScrollView的替代品而没有任何问题。


没有向后兼容性。 - dephinera
@define 嗯,Support库中有NestedScrollView,您可以使用它来代替常规的ScrollView而不会出现问题。 - Lovis
抱歉,由于某些原因我错过了你提到的那部分内容。无论如何还是谢谢。 - dephinera
@define 我只是没有提到您可以使用NestedScrollView而不是普通的ScrollView。我会将其添加到答案中。 - Lovis
1
太好了。我们支持API 18+,我真的很想要这个原生功能。谢谢! - the_dude_abides

8

实际上,有一种方法可以知道用户滚动了多远。ScrollView 的 getScrollY() 方法就能告诉您这点。


不是的,根据这个:https://dev59.com/OXI95IYBdhLWcg3w1Bro - Timmmm
3
我在公司的一个项目中使用了这个解决方案。在我发布任何内容之前,我会先进行测试。或许你也应该这样做? - tulio84z
抱歉,我看错了问题。它适用于 ScrollView,但不适用于 ListViewGridView - Timmmm

2
您可以使用此触发器来显示Toast或启动Activity。
 scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
        @Override
        public void onScrollChanged() {
             // do something when Scroll  
        }
    });

2

我扩展了我的scrollView。这个链接可能会有所帮助。

class MyScroll extends ScrollView {
    boolean onTop=true;
    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        //Log.d(TAG, "scroll changed: " + this.getTop() + " "+t);
        if(t <= 0){
            onTop = true;
            //Log.d(TAG, "scroll top: " + t);
            super.onScrollChanged(l, t, oldl, oldt);
            return;
            // reaches the top end
        }
        onTop = false;

        View view = (View) getChildAt(getChildCount()-1);
        int diff = (view.getBottom()-(getHeight()+getScrollY()+view.getTop()));// Calculate the scrolldiff
        if( diff <= 0 ){
            // if diff is zero, then the bottom has been reached
        }
        super.onScrollChanged(l, t, oldl, oldt);
    }
}

2
你可以使用这段代码检测页面是否向上或向下滚动。
scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
            int lastScroll=0;
            @Override
            public void onScrollChanged() {
                int scrollY = scrollView.getScrollY(); // For ScrollView herzontial use getScrollX()

                if (scrollY > lastScroll ) {
                    Log.e("scroll","down scroll"+scrollY);
                } else if (scrollY < lastScroll ) {
                    Log.e("scroll","up scroll"+scrollY);
                }
                lastScroll=scrollY;
            }
        });

1

NestedScrollView类似于android.widget.ScrollView,但它支持在新旧版本的Android上作为嵌套滚动父级和子级。

1. 使用NestedScrollView绑定而不是ScrollView

2. 设置滚动监听器,例如通过以下方式检测Y轴移动以获取HeaderView。

    nestedScrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {

        @Override
        public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
            Log.d(TAG, "onScrollChangeForY - scrollY: " + scrollY + " oldScrollY: " + oldScrollY);

            int MOVE = -1, SCROLL_UP = 0, SCROLL_DOWN = 1;
            float initialPositionY = headerView.getY();

            MOVE = scrollY > oldScrollY ? SCROLL_UP : SCROLL_DOWN;

            if (MOVE == SCROLL_UP) {

                int incrementY = scrollY - oldScrollY;

                headerView.setY(initialPositionY - incrementY);

            } else {

                int incrementY = oldScrollY - scrollY;

                headerView.setY(initialPositionY + incrementY);
            }
        }
    });

0
只需使用NestedScrollNestedScroll.setOnScrollChangeListener()

0

有一个可用组件, 它可以帮助在Android中监听任意视图的滚动事件。在旧版本的Android API上,该组件会内部添加ViewTreeObserver滚动事件监听器(类似于Shubhadeep Chaudhuri的答案中提到的方法),而在新版本的Android API上(API级别23+),则会添加View滚动事件监听器。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接