我可以为ScrollView使用onScrollListener吗?


140

HorizontalScrollView在布局中使用,我需要确定用户已经到达滚动的起点和终点。

因为ListView我已经尝试过了onScrollListener,所以可以找到滚动的起点和终点。

我试图在我的身上做同样的事情,Scrollview但似乎不可能。还有其他可能的方法来实现我所需要的。


3
有可能的。请参阅user2695685的答案。简而言之,下面的in onStart可以解决问题:hsv.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {@Override public void onScrollChanged() {Log.i(TAG,"scroll:"+hsv.getScrollX());}});在onStart()中,这hsv是可行的HorizontalScrollView
JohnnyLambada 2014年

接受任何有用的答案..如果其他人发布您自己的答案..
Ranjith Kumar

12
为什么在Android中使用ScrollView检测滚动事件如此困难?这太疯狂了。
工作

Answers:


389

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 2014年

3
究竟。这是最好的答案。无需扩展Horizo​​ntalScrollView。刚刚使用ScrollView进行了测试,效果很好:final ScrollView sv =(ScrollView)findViewById(R.id.scrollViewArticle); sv.getViewTreeObserver()。addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener(){@Override public void onScrollChanged(){Log.d(“ onScrollChanged Y”,String.valueOf(sv.getScrollY()))}}));
拉斐尔C


6
答案中的示例代码引入了内存泄漏。由于方法add不是set,因此所有侦听器都将保留,直到将其显式删除为止。因此,示例中用作侦听器实现的匿名类将泄漏(以及它所引用的所有内容,即外部类)。
Brett Duncavage 2015年

7
可能是一个愚蠢的问题,但是rootScrollView是什么?
伊克巴尔

49

这可能非常有用。使用NestedScrollView代替ScrollView。支持库23.1引入了一个OnScrollChangeListenerNestedScrollView。所以你可以做这样的事情。

 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

如果我想使用水平滚动怎么办?
Nikita Axyonov

7
@ dazed'n'confused NestedScrollView是支持库的一部分,其setOnScrollChangeListener方法没有最低版本要求。
汤姆(Tom)

4
不要与混淆ViewsetOnScrollChangeListener需要API级别23
安德斯Ullnæss

18

这是我编写的派生的Horizo​​ntalScrollView,用于处理有关滚动和滚动结束的通知。它可以正确处理用户何时停止主动滚动以及用户放开后何时完全减速:

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) {
            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方法更好。仅仅因为您在一个活动中使用ListViews和ScrollViews加载了多个片段(例如3个带有滑动选项卡的片段),并且需要为特定视图注册事件。而如果注册了ViewTreeObserver,即使它不是活动视图,它也会触发事件。
Torsten Ojaperv,2015年

3
@ZaBlanc-您能给我打电话怎么从我的Activity中初始化这个类和监听器,Activity拥有ScrollView?我已经很好地实现了,但是听众还不会返回任何值。
Edmond Tamas 2015年

这实际上有效!并且不需要sdk> 23 :)
Matan Dahan

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

5

您可以使用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的方法会导致内存泄漏,必须手动将其删除,否则将添加多个滚动观察器。
李丽

1
    // --------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---------

请尝试添加一些描述以支持您的代码,这将使每个人都了解击球手
Aniruddha Das

这段代码与另一个答案非常相似,不是吗?
塞尔吉(Sergii)

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

}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.