156

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

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

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

4

9 回答 9

410

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
    }
});
于 2014-04-29T13:06:40.937 回答
62

这可能非常有用。使用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
        }
    });
于 2015-10-20T07:52:56.563 回答
21

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

}
于 2014-09-02T04:35:16.583 回答
13

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

nestedScrollView.setOnScrollChangeListener { _: NestedScrollView, scrollX: Int, scrollY: Int, _: Int, _: Int ->
    Log.d("ScrollView", "Scrolled to $scrollX, $scrollY")
}
于 2018-07-17T15:37:17.813 回答
7

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

  1. 您可以在您的活动/片段中实现 OnScrollChangedListener。

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

    scrollView.viewTreeObserver.addOnScrollChangedListener(this)
    
  3. 不再需要时移除监听器(即 onPause())

    scrollView.viewTreeObserver.removeOnScrollChangedListener(this)
    
于 2020-10-22T23:25:01.133 回答
6

如果你想知道一个视图的滚动位置,那么你可以在 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
        }
    }
}
于 2018-04-18T14:48:22.173 回答
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);
}

}
于 2018-12-03T12:23:08.670 回答
1

正在寻找正常ScrollView实现解决方案的 Kotlin 用户:

作为这个答案的扩展,我创建了一个自定义视图,很好地解决了我的问题。

视图(创建一个新的 Kotlin 文件,在第 1 行维护您的包引用):

import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.ScrollView
import kotlin.math.abs


class ScrollViewWithEndFunc (
    context: Context?,
    attrs: AttributeSet?,
    defStyle: Int
) : ScrollView(context, attrs, defStyle) {

    constructor(context: Context?) : this(context, null, 0) {}
    constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0) {}

    interface OnScrollListener {
        fun onScrollChanged(scrollView: ScrollViewWithEndFunc?, x: Int, y: Int, oldX: Int, oldY: Int)
        fun onEndScroll(scrollView: ScrollViewWithEndFunc?)
    }

    private var isScrolling = false
    private var isTouching = false
    private var scrollingRunnable: Runnable? = null
    private var onScrollListener: OnScrollListener? = null

    fun setOnScrollListener(onScrollListener: OnScrollListener) {
        this.onScrollListener = onScrollListener
    }

    fun removeOnScrollListener() {
        this.onScrollListener = null
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(ev: MotionEvent): Boolean {
        val action = ev.action
        if (action == MotionEvent.ACTION_MOVE) {
            isTouching = true; isScrolling = true
        } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
            if (isTouching && !isScrolling) {
                onScrollListener?.onEndScroll(this)
            }
            isTouching = false
        }
        return super.onTouchEvent(ev)
    }

    override fun onScrollChanged(x: Int, y: Int, oldX: Int, oldY: Int) {
        super.onScrollChanged(x, y, oldX, oldY)
        if (abs(oldY - y) > 0) {
            scrollingRunnable?.let { removeCallbacks(it) }
            scrollingRunnable = Runnable {
                if (isScrolling && !isTouching) {
                    onScrollListener?.onEndScroll(this@ScrollViewWithEndFunc)
                }
                isScrolling = false
                scrollingRunnable = null
            }
            postDelayed(scrollingRunnable, 200)
        }
        onScrollListener?.onScrollChanged(this, x, y, oldX, oldY)
     }
    }

XML 视图实现:

<your.package.here.ScrollViewWithEndFunc
        android:id="@+id/scrollview_main_dashboard"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fillViewport="true">

活动/片段实现:

scrollviewMainDashboard.setOnScrollListener(object : ScrollViewWithEndFunc.OnScrollListener {
            override fun onScrollChanged(scrollView: ScrollViewWithEndFunc?, x: Int, y: Int, oldX: Int, oldY: Int) { }
            override fun onEndScroll(scrollView: ScrollViewWithEndFunc?) {
                /* Scroll ended, handle here */
        })
于 2021-08-03T03:03:12.410 回答
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---------
于 2017-08-28T02:49:25.593 回答