我可以使用 onScrollListener 进行 ScrollView 吗?

我在一个布局中使用 HorizontalScrollView,我需要识别用户已经到达了滚动的起点和终点。

对于 ListView我已经尝试了一个 onScrollListener,它是可能的,找到起点和终点的滚动。

我试图在我的 Scrollview做同样的事情,但似乎是不可能的。有没有其他可能的方法来达到我的目的。

169051 次浏览

每个 View 实例调用 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在 NestedScrollView中引入了 OnScrollChangeListener。 所以你可以做这样的事情。

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

如果你想知道一个视图的滚动位置,那么你可以在视图类中使用下面的扩展函数:

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,而不是 View (API 级别为23)的 setOnScrollChangeListener。可以通过将第一个参数指定为 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 和内存泄漏(在已接受的回答的评论中提及))获得空指针异常。

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

    MyFragment : ViewTreeObserver.OnScrollChangedListener
    
  2. 视图准备好后,将其添加到 scrollView。

    scrollView.viewTreeObserver.addOnScrollChangedListener(this)
    
  3. 当不再需要时删除侦听器(即 onPuse ())

    scrollView.viewTreeObserver.removeOnScrollChangedListener(this)
    

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

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

视图(创建一个新的 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 */
})

2022年的答案——现在它变得微不足道了:

ScrollView anyScrollView;
...


anyScrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
@Override
public void onScrollChange(View view, int x, int y, int oldX, int oldY) {
Log.d("yo", "it works " + y + " " + x);
}
});

不要忘记,在 Android 的实践中,你需要将所有东西前后转换为 dp:

public void onScrollChange(View view, int x, int y, int oldX, int oldY) {
float ydp =
y / getBaseContext().getResources().getDisplayMetrics().density;
Log.d("yo", "it works " + ydp);
}

因此,复制粘贴:

anyScrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
float dsy = getBaseContext().getResources().getDisplayMetrics().density;
@Override
public void onScrollChange(View view, int x, int y, int oldX, int oldY) {
float ydp = y / dsy;
Log.d("yo", "offset " + ydp);
}
});

教练:

Https://developer.android.com/reference/android/view/view.onscrollchangelistener