Android 我可以用onScrollListener查看滚动视图吗?

Android 我可以用onScrollListener查看滚动视图吗?,android,listener,scrollview,Android,Listener,Scrollview,我正在布局中使用水平滚动视图,我需要确定用户是否已到达滚动的起点和终点 对于ListView,我尝试了onscrollstener上的滚动,可以找到滚动的起点和终点 我尝试在我的滚动视图中执行相同的操作,但似乎不可能。是否有其他可能的方法来实现我所需要的。视图的每个实例都调用getViewTreeObserver()。现在,当持有一个ViewTreeObserver的实例时,您可以使用addOnScrollChangedListener()方法向其添加一个OnScrollChangedListe

我正在布局中使用
水平滚动视图
,我需要确定用户是否已到达滚动的起点和终点

对于
ListView
,我尝试了
onscrollstener
上的滚动,可以找到滚动的起点和终点


我尝试在我的
滚动视图中执行相同的操作,但似乎不可能。是否有其他可能的方法来实现我所需要的。

视图的每个实例都调用
getViewTreeObserver()
。现在,当持有一个
ViewTreeObserver
的实例时,您可以使用
addOnScrollChangedListener()
方法向其添加一个
OnScrollChangedListener()

您可以查看有关该类的更多信息

它可以让你知道每一个滚动事件,但没有坐标。您可以通过在侦听器中使用
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
    }
});

这是我编写的一个派生的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;
    }

}
这可能非常有用。 使用
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
        }
    });

如果您想知道视图的滚动位置,则可以在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
        }
    }
}

您可以使用
NestedScrollView
而不是
ScrollView
。然而,当使用Kotlin Lambda时,它不会知道您想要的是NestedScrollView的
setOnScrollChangeListener
,而不是视图中的一个(API级别23)。可以通过将第一个参数指定为NestedScrollView来解决此问题

nestedScrollView.setOnScrollChangeListener { _: NestedScrollView, scrollX: Int, scrollY: Int, _: Int, _: Int ->
    Log.d("ScrollView", "Scrolled to $scrollX, $scrollY")
}

您可以定义一个自定义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);
}

}

除了被接受的答案之外,您还需要保留一个侦听器的引用,并在不需要时将其删除。否则,您的ScrollView和内存泄漏(在accepted answer的注释中提到)将得到一个空指针异常

  • 您可以在活动/片段中实现OnScrollChangedListener

    MyFragment : ViewTreeObserver.OnScrollChangedListener
    
  • 当视图准备就绪时,将其添加到scrollView

    scrollView.viewTreeObserver.addOnScrollChangedListener(this)
    
  • 不再需要时删除侦听器(即onPause())



  • 这个答案应该是正确的<代码>hsv.getViewTreeObserver().addOnScrollChangedListener(新ViewTreeObserver.OnScrollChangedListener(){@Override public void onScrollChanged(){Log.i(标记,“scroll:+hsv.getScrollX());})其中
    hsv
    是一个
    水平滚动视图
    工作。我怀疑这同样适用于ScrollView。这是最好的答案。无需扩展水平滚动视图。刚刚用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.getScrollY());});您应该首先检查答案中的示例代码是否存在内存泄漏。由于方法是
    add
    而不是
    set
    ,因此所有侦听器都将保留,直到显式删除为止。因此,示例中用作侦听器实现的匿名类将泄漏(以及它引用的任何对象,即外部类)。这可能是一个愚蠢的问题,但什么是rootScrollView?这是可能的。请参阅user2695685的答案。简而言之,
    onStart
    中的以下内容将起作用:
    hsv.getViewTreeObserver().addOnScrollChangedListener(新的ViewTreeObserver.OnScrollChangedListener(){@Override public void onScrollChanged(){Log.i(标记,“scroll:+hsv.getScrollX());})
    在onStart()中,
    hsv
    是一个
    水平滚动视图
    可以工作。接受任何有用的答案..如果其他人发布你自己的答案..为什么在Android中用滚动视图检测滚动事件如此困难?这是胡说八道。这似乎比使用ViewTreeObserver方法要好。这仅仅是因为当您有一个活动,其中加载了多个带有ListView和ScrollView的片段(例如3个带有滑动选项卡的片段),并且您需要为特定视图注册事件。然而,如果ViewTreeObserver已注册,即使它不是活动视图,它也会触发事件。@ZaBlanc-您能告诉我如何从保存ScrollView的活动中初始化此类和侦听器吗?我已经很好地实现了它,但是监听器还不会返回任何值。这确实有效!而且不需要sdk>23:)这肯定比使用getViewTreeObserver()跟踪是否添加了侦听器更容易。谢谢但是如何使用NestedScrollView作为水平滚动视图呢?找不到任何资源如果我想使用水平滚动怎么办?@dazed'n'fuzzle
    NestedScrollView
    是支持库的一部分,它的
    setOnScrollChangeListener
    方法没有最低版本要求。不要与
    View
    setOnScrollChangeListener
    混淆,后者需要API级别23。请尝试添加一些描述来支持您的代码,这将使每个人都理解此代码与另一代码非常相似,不是吗?这应该是公认的答案,因为使用viewTreeObserver的方法会导致内存泄漏,必须手动删除,否则将添加多个滚动观察者。我完全同意。切换到
    NestedScrollView
    并将其
    OnScrollChangeListener
    解决了我的问题。当我在ScrollView的ViewTreeObserver上设置
    OnScrollChangedListener
    时,侦听器被无故调用了多次。
    scrollView.viewTreeObserver.addOnScrollChangedListener(this)
    
    scrollView.viewTreeObserver.removeOnScrollChangedListener(this)