ChatGPT解决这个技术问题 Extra ChatGPT

我可以为 ScrollView 设置 onScrollListener 吗?

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

对于 ListView,我尝试了 onScrollListener 并且可以找到滚动的起点和终点。

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

有可能的。请参阅 user2695685 的答案。简而言之,onStart 中的以下内容可以解决问题: onStart() 中的 hsv.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {@Override public void onScrollChanged() {Log.i(TAG,"scroll:"+hsv.getScrollX());}}); 其中 hsvHorizontalScrollView 有效。
接受任何有用的答案..如果其他人发布您自己的答案..
为什么在 Android 中使用 ScrollView 检测滚动事件如此困难?这太疯狂了。
幸运的是十年后这现在很容易

P
Pavneet_Singh

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

您可以查看有关此类 here 的更多信息。

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

此答案应标记为正确。 hsv.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {@Override public void onScrollChanged() {Log.i(TAG,"scroll:"+hsv.getScrollX());}}); 在 onStart() 中,其中 hsvHorizontalScrollView 有效。我怀疑这同样适用于 ScrollView。
确切地。这是最好的答案。无需扩展 HorizontalScrollView。刚刚用 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())); } });
您应该首先检查 ViewTreeObserver.isAlive()
答案中的示例代码引入了内存泄漏。由于方法是 add 而不是 set,所有侦听器将被保留,直到显式删除。因此,示例中用作侦听器实现的匿名类将泄漏(连同它所引用的任何东西,即外部类)。
可能是一个愚蠢的问题,但什么是 rootScrollView?
L
Lavekush Agrawal

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

绝对比使用 getViewTreeObserver() 跟踪您之前是否添加过侦听器更容易。谢谢!
但是如何使用 NestedScrollView 作为水平滚动视图呢?找不到任何资源
如果我想使用水平滚动怎么办?
@dazed'n'confused NestedScrollView 是支持库的一部分,它的 setOnScrollChangeListener 方法没有最低版本要求。
不要与需要 API 级别 23 的 ViewsetOnScrollChangeListener 混淆
G
Gary Chen

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

}

这似乎比使用 ViewTreeObserver 方法更好。仅仅因为当您有一个活动时,您使用 ListViews 和 ScrollViews 加载了多个片段(例如 3 个带有滑动选项卡的片段),并且您需要为特定视图注册事件。而如果 ViewTreeObserver 已注册,即使它不是活动视图,它也会触发事件。
@ZaBlanc - 你能告诉我如何初始化这个类和我的 Activity 中的侦听器,它包含 ScrollView 吗?我已经很好地实现了它,但是监听器还不会返回任何值。
这确实有效!并且不需要 sdk > 23 :)
C
Cristan

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

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

这应该是公认的答案,因为使用 viewTreeObserver 的方法会导致内存泄漏并且必须手动删除,否则将添加多个滚动观察者。
我完全同意。切换到 NestedScrollView 并设置它的 OnScrollChangeListener 解决了我的问题。当我在 ScrollView 的 ViewTreeObserver 上设置 OnScrollChangedListener 时,监听器被无缘无故地调用了多次。
A
Akn

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

您可以在您的活动/片段中实现 OnScrollChangedListener。 MyFragment : ViewTreeObserver.OnScrollChangedListener 当您的视图准备好时将其添加到 scrollView。 scrollView.viewTreeObserver.addOnScrollChangedListener(this) 不再需要时移除监听器(即 onPause()) scrollView.viewTreeObserver.removeOnScrollChangedListener(this)


4
476rick

如果你想知道一个视图的滚动位置,那么你可以在 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
        }
    }
}

N
Nikhil

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

}

L
LucidSoftworksLLC

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

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

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

F
Fattie

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


W
Wirote W.
    // --------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---------

请尝试添加一些描述以支持您的代码,这将使每个人都理解面糊
此代码与另一个 answer 非常相似,不是吗?