Android:当ScrollView停止滚动时检测

我在Android中使用ScrollView ,并且ScrollView的可见部分与Scrollview其中一个单元格的大小相同。 每个“细胞”是相同的高度。 所以我想要做的是在ScrollView被滚动后捕捉到位。

目前,我正在检测用户何时触摸了ScrollView ,何时开始滚动并从那里开始工作,但是它却非常麻烦。 当用户只是轻弹它并滚动然后减速时,它也需要工作。

在iPhone上有一个类似didDecelerate的function,在ScrollView完成滚动时,我可以做任何我想要的代码。 Android有这样的事情吗? 还是有一些代码,我可以看看找出一个更好的方法呢?

我查看了Android文档,找不到像这样的东西。

我最近不得不执行你描述的function。 我做的是有一个Runnable检查,如果ScrollView已经停止滚动,通过比较getScrollY()当onTouchEvent第一次触发时返回的值与variablesnewCheck定义的时间后返回的值。

看下面的代码(工作解决scheme):

 public class MyScrollView extends ScrollView{ private Runnable scrollerTask; private int initialPosition; private int newCheck = 100; private static final String TAG = "MyScrollView"; public interface OnScrollStoppedListener{ void onScrollStopped(); } private OnScrollStoppedListener onScrollStoppedListener; public MyScrollView(Context context, AttributeSet attrs) { super(context, attrs); scrollerTask = new Runnable() { public void run() { int newPosition = getScrollY(); if(initialPosition - newPosition == 0){//has stopped if(onScrollStoppedListener!=null){ onScrollStoppedListener.onScrollStopped(); } }else{ initialPosition = getScrollY(); MyScrollView.this.postDelayed(scrollerTask, newCheck); } } }; } public void setOnScrollStoppedListener(MyScrollView.OnScrollStoppedListener listener){ onScrollStoppedListener = listener; } public void startScrollerTask(){ initialPosition = getScrollY(); MyScrollView.this.postDelayed(scrollerTask, newCheck); } } 

然后我有:

 scroll.setOnTouchListener(new OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) { scroll.startScrollerTask(); } return false; } }); scroll.setOnScrollStoppedListener(new OnScrollStoppedListener() { public void onScrollStopped() { Log.i(TAG, "stopped"); } }); 

顺便说一句,我用我从其他答复的一些想法做到这一点在我的应用程序。 希望这可以帮助。 有任何问题请随时询问我(们)。 干杯。

这是另一个修复,恕我直言,缺lessScrollView中的OnEndScroll事件错误。

它的灵感来自于不协调的回答。 简单地把这个类放到你的项目中(改变包来匹配你自己的)并使用下面的xml

 package com.thecrag.components.ui; import android.content.Context; import android.util.AttributeSet; import android.widget.ScrollView; public class ResponsiveScrollView extends ScrollView { public interface OnEndScrollListener { public void onEndScroll(); } private boolean mIsFling; private OnEndScrollListener mOnEndScrollListener; public ResponsiveScrollView(Context context) { this(context, null, 0); } public ResponsiveScrollView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ResponsiveScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public void fling(int velocityY) { super.fling(velocityY); mIsFling = true; } @Override protected void onScrollChanged(int x, int y, int oldX, int oldY) { super.onScrollChanged(x, y, oldX, oldY); if (mIsFling) { if (Math.abs(y - oldY) < 2 || y >= getMeasuredHeight() || y == 0) { if (mOnEndScrollListener != null) { mOnEndScrollListener.onEndScroll(); } mIsFling = false; } } } public OnEndScrollListener getOnEndScrollListener() { return mOnEndScrollListener; } public void setOnEndScrollListener(OnEndScrollListener mOnEndScrollListener) { this.mOnEndScrollListener = mOnEndScrollListener; } } 

再次更改包名称以匹配您的项目

 <com.thecrag.components.ui.ResponsiveScrollView android:id="@+id/welcome_scroller" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@+id/welcome_scroll_command_help_container" android:layout_alignParentLeft="true" android:layout_alignParentRight="true" android:layout_below="@+id/welcome_header_text_thecrag" android:layout_margin="6dp"> .... </com.thecrag.components.ui.ResponsiveScrollView> 

我subclassed(水平)滚动查看,并做了这样的事情:

 @Override protected void onScrollChanged(int x, int y, int oldX, int oldY) { if (Math.abs(x - oldX) > SlowDownThreshold) { currentlyScrolling = true; } else { currentlyScrolling = false; if (!currentlyTouching) { //scrolling stopped...handle here } } super.onScrollChanged(x, y, oldX, oldY); } 

我使用SlowDownThreshold的值为1,因为它似乎总是最后一个onScrollChanged事件的差异。

为了使这个行为正确的拖慢时,我必须这样做:

 @Override public boolean onInterceptTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: currentlyTouching = true; } return super.onInterceptTouchEvent(event); } @Override public boolean onTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: currentlyTouching = false; if (!currentlyScrolling) { //I handle the release from a drag here return true; } } return false; } 

我的方法是通过每次调用onScrollChanged()时更改的时间戳确定滚动状态。 确定滚动开始和结束的时间非常简单。 您也可以更改阈值(我使用100毫秒)来修复灵敏度。

 public class CustomScrollView extends ScrollView { private long lastScrollUpdate = -1; private class ScrollStateHandler implements Runnable { @Override public void run() { long currentTime = System.currentTimeMillis(); if ((currentTime - lastScrollUpdate) > 100) { lastScrollUpdate = -1; onScrollEnd(); } else { postDelayed(this, 100); } } } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); if (lastScrollUpdate == -1) { onScrollStart(); postDelayed(new ScrollStateHandler(), 100); } lastScrollUpdate = System.currentTimeMillis(); } private void onScrollStart() { // do something } private void onScrollEnd() { // do something } } 

我的这个问题的方法是使用计时器来检查以下2个“事件”。

1)onScrollChanged()停止被调用

2)用户的手指从滚动视图中抬起

 public class CustomScrollView extends HorizontalScrollView { public CustomScrollView(Context context, AttributeSet attrs) { super(context, attrs); } Timer ntimer = new Timer(); MotionEvent event; @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { checkAgain(); super.onScrollChanged(l, t, oldl, oldt); } public void checkAgain(){ try{ ntimer.cancel(); ntimer.purge(); } catch(Exception e){} ntimer = new Timer(); ntimer.schedule(new TimerTask() { @Override public void run() { if(event.getAction() == MotionEvent.ACTION_UP){ // ScrollView Stopped Scrolling and Finger is not on the ScrollView } else{ // ScrollView Stopped Scrolling But Finger is still on the ScrollView checkAgain(); } } },100); } @Override public boolean onTouchEvent(MotionEvent event) { this.event = event; return super.onTouchEvent(event); } } 

我认为这已经出现了。 AFAIK,你不能轻易察觉。 我的build议是,你看看ScrollView.java (这是我们如何在Android的土地🙂 ),并找出如何扩展类提供您正在寻找的function。 这是我会先尝试:

  @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { if (mScroller.isFinished()) { // do something, for example call a listener } } 

这是另一个解决scheme,在我看来,这个解决scheme非常简单,干净,自然受到上述答案的启发 基本上一旦用户结束手势检查getScrollY()是否仍然在短暂的延迟(这里是50ms)后改变。

 public class ScrollViewWithOnStopListener extends ScrollView { OnScrollStopListener listener; public interface OnScrollStopListener { void onScrollStopped(int y); } public ScrollViewWithOnStopListener(Context context) { super(context); } public ScrollViewWithOnStopListener(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_UP: checkIfScrollStopped(); } return super.onTouchEvent(ev); } int initialY = 0; private void checkIfScrollStopped() { initialY = getScrollY(); this.postDelayed(new Runnable() { @Override public void run() { int updatedY = getScrollY(); if (updatedY == initialY) { //we've stopped if (listener != null) { listener.onScrollStopped(getScrollY()); } } else { initialY = updatedY; checkIfScrollStopped(); } } }, 50); } public void setOnScrollStoppedListener(OnScrollStopListener yListener) { listener = yListener; } } 

尝试看看这个问题在这里StackOverflow – 这是不完全一样的问题,但它提供了一个想法,你怎么可以pipe理ScrollView的滚动事件。

基本上,你需要通过扩展ScrollView来创build你自己的CustomScrollView ,并覆盖onScrollChanged(int x, int y, int oldx, int oldy) 。 然后你需要在布局文件中引用它,而不是像com.mypackage.CustomScrollView这样的标准ScrollView

对于你所描述的一个简单的例子,你可能会在自定义滚动视图中忽略重载的方法。 每次用户从屏幕上抬起手指时,都会调用Fling方法来执行“减速”。

所以你应该做的是这样的:

  1. 子类ScrollView。

     public class MyScrollView extends ScrollView { private Scroller scroller; private Runnable scrollerTask; //... public MyScrollView(Context context, AttributeSet attrs) { super(context, attrs); scroller = new Scroller(getContext()); //or OverScroller for 3.0+ scrollerTask = new Runnable() { @Override public void run() { scroller.computeScrollOffset(); scrollTo(0, scroller.getCurrY()); if (!scroller.isFinished()) { MyScrollView.this.post(this); } else { //deceleration ends here, do your code } } }; //... } } 
  2. 子类抛出方法,不要调用超类的实现。

     @Override public void fling(int velocityY) { scroller.fling(getScrollX(), getScrollY(), 0, velocityY, 0, 0, 0, container.getHeight()); post(scrollerTask); //add any extra functions you need from android source code: //show scroll bars //change focus //etc. } 
  3. 如果用户在抬起手指之前停止滚动,则不会触发Fling(velocityY == 0)。 如果你想拦截这类事件,也可以重写onTouchEvent。

     @Override public boolean onTouchEvent(MotionEvent ev) { boolean eventConsumed = super.onTouchEvent(ev); if (eventConsumed && ev.getAction() == MotionEvent.ACTION_UP) { if (scroller.isFinished()) { //do your code } } return eventConsumed; } 

注意虽然这是有效的,但是重写一次方法可能是一个坏主意。 它是公开的,但它几乎没有为子类devise。 现在它做了3件事情 – 它启动私人mScroller一掷,处理可能的焦点变化,并显示滚动条。 这可能会改变在未来的Android版本。 例如,私人mScroller实例将其类从Scroller更改为OvershootScroller 2.3和3.0之间。 你必须记住所有这些小的差异。 无论如何,准备好将来无法预料的后果。

我已经对ZeroG的答案做了一些改进。 主要是取消多余的任务调用,并将整个事件作为一个私有的OnTouchListener来实现,这样所有的滚动检测代码就会在一个地方。

将以下代码粘贴到您自己的ScrollView实现中:

 private class ScrollFinishHandler implements OnTouchListener { private static final int SCROLL_TASK_INTERVAL = 100; private Runnable mScrollerTask; private int mInitialPosition = 0; public ScrollFinishHandler() { mScrollerTask = new Runnable() { public void run() { int newPosition = getScrollY(); if(mInitialPosition - newPosition == 0) {//has stopped onScrollStopped(); // Implement this on your main ScrollView class }else{ mInitialPosition = getScrollY(); ExpandingLinearLayout.this.postDelayed(mScrollerTask, SCROLL_TASK_INTERVAL); } } }; } @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) { startScrollerTask(); } else { stopScrollerTask(); } return false; } } 

然后在你的ScrollView实现中:

 setOnTouchListener( new ScrollFinishHandler() ); 

这里有一些很好的答案,但是我的代码可以检测滚动停止而不必扩展ScrollView类。 每个视图实例都可以调用getViewTreeObserver()。 当持有ViewTreeObserver的这个实例时,你可以使用函数addOnScrollChangedListener()来添加一个OnScrollChangedListener。

声明如下:

 private ScrollView scrollListener; private volatile long milesec; private Handler scrollStopDetector; private Thread scrollcalled = new Thread() { @Override public void run() { if (System.currentTimeMillis() - milesec > 200) { //scroll stopped - put your code here } } }; 

并在你的onCreate(或其他地方)添加:

  scrollListener = (ScrollView) findViewById(R.id.scroll); scrollListener.getViewTreeObserver().addOnScrollChangedListener(new OnScrollChangedListener() { @Override public void onScrollChanged() { milesec = System.currentTimeMillis(); scrollStopDetector.postDelayed(scrollcalled, 200); } }); 

您可能需要在这些检查之间花费更长或更慢的时间,但是当滚动此列表器时,调用速度非常快,因此工作速度非常快。

这是我的解决scheme,其中包括滚动跟踪和滚动结束:

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

我的解决scheme是林宇程伟大的解决scheme的变化,也检测滚动开始和停止。

第1步。定义一个Horizo​​ntalScrollView和OnScrollChangedListener:

 CustomHorizontalScrollView scrollView = (CustomHorizontalScrollView) findViewById(R.id.horizontalScrollView); horizontalScrollListener = new CustomHorizontalScrollView.OnScrollChangedListener() { @Override public void onScrollStart() { // Scrolling has started. Insert your code here... } @Override public void onScrollEnd() { // Scrolling has stopped. Insert your code here... } }; scrollView.setOnScrollChangedListener(horizontalScrollListener); 

第2步。添加CustomHorizo​​ntalScrollView类:

 public class CustomHorizontalScrollView extends HorizontalScrollView { public interface OnScrollChangedListener { // Developer must implement these methods. void onScrollStart(); void onScrollEnd(); } private long lastScrollUpdate = -1; private int scrollTaskInterval = 100; private Runnable mScrollingRunnable; public OnScrollChangedListener mOnScrollListener; public CustomHorizontalScrollView(Context context) { this(context, null, 0); init(context); } public CustomHorizontalScrollView(Context context, AttributeSet attrs) { this(context, attrs, 0); init(context); } public CustomHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } private void init(Context context) { // Check for scrolling every scrollTaskInterval milliseconds mScrollingRunnable = new Runnable() { public void run() { if ((System.currentTimeMillis() - lastScrollUpdate) > scrollTaskInterval) { // Scrolling has stopped. lastScrollUpdate = -1; //CustomHorizontalScrollView.this.onScrollEnd(); mOnScrollListener.onScrollEnd(); } else { // Still scrolling - Check again in scrollTaskInterval milliseconds... postDelayed(this, scrollTaskInterval); } } }; } public void setOnScrollChangedListener(OnScrollChangedListener onScrollChangedListener) { this.mOnScrollListener = onScrollChangedListener; } public void setScrollTaskInterval(int scrollTaskInterval) { this.scrollTaskInterval = scrollTaskInterval; } //void onScrollStart() { // System.out.println("Scroll started..."); //} //void onScrollEnd() { // System.out.println("Scroll ended..."); //} @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); if (mOnScrollListener != null) { if (lastScrollUpdate == -1) { //CustomHorizontalScrollView.this.onScrollStart(); mOnScrollListener.onScrollStart(); postDelayed(mScrollingRunnable, scrollTaskInterval); } lastScrollUpdate = System.currentTimeMillis(); } } } 
  this.getListView().setOnScrollListener(new OnScrollListener(){ @Override public void onScrollStateChanged(AbsListView view, int scrollState) {} @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if( firstVisibleItem + visibleItemCount >= totalItemCount ) // Last item is shown... } 

希望该片段帮助:)