Scrollview或者NestScrollView没有提供对其状态的监听(类似Recyclerview的OnScrollListener.onScrollSateChanged接口),只能自己实现。
在百度以后,发现大多数采用的都是新开一个线程,延迟一小段时间来判断是否已经暂停,都是直接在onTouchEvent()的MotionEvent.ACTION_UP中判断是否已经暂停,没有对Fling状态进行判断,所以对于停止的状态是不准确的。
在他们的基础上,本文将对Fling状态进行判断,能得到正确的停止时机。
到达暂停的路径只有两种:
1.ACTION_DOWN->MOVE->UP->停止
2.ACTION_DOWN->MOVE->UP->FLING->停止
本例子采用两个线程来判断是否停止
如何判断停止:
开监听线程,并且在滚动的时候一直更新改线程中的最后刷新时间,当最后(线程当前时间=刷新时间(外部更新)+200ms)时,判定为停止。
满足上述条件的那一秒可以发送多条SCROLL_STATE_IDLE的监听,但是我们只需要一条,所以需要标志位来控制每次停止只发送一次监听。
对于第一种情况:
MotionUpThread用于判断情况一的停止,MotionUp的时候刷新MotionUpThread中的计时,如果200ms后没有移动,则认为是暂停,发送停止回调。
对于第二种情况:
FlingThread用于判断情况二的停止,MotionUp以后,继续Fling,此时isScroll为true,MotionUpThread不会发送停止的回调。而FlingThread在最后的Fling滚动结束后200ms会发送停止回调。
经过测试,打开关闭该只有该布局的网页,会使内存增加,但是再检查问题的时候也一直没有找到是哪里泄露了,如果有人测试并且发现了问题在哪,请留言告诉我一起交流交流,万分感谢。
以下为代码
public class ScrollViewWithListener extends NestedScrollView {
private OnScrollListener mScrollListener;
// 是否在触摸状态
private boolean inTouch = false;
private FlingThread mFlingThread;
private MotionUpThread mMotionUpThread;
private MyRunnable mRunnable;
//避免一秒内发送多次回调
private boolean isMotionUpRun = false;
private boolean isFlingRun = false;
//判断是否在滚动
private boolean isScroll = false;
private boolean isDestory = false;
public ScrollViewWithListener(Context context) {
this(context, null);
}
public ScrollViewWithListener(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ScrollViewWithListener(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mFlingThread = new FlingThread(this);
mFlingThread.start();
mMotionUpThread = new MotionUpThread(this);
mMotionUpThread.start();
mRunnable = new MyRunnable(this);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
isFlingRun = false;
break;
case MotionEvent.ACTION_MOVE:
inTouch = true;
isScroll = true;
break;
case MotionEvent.ACTION_UP:
inTouch = false;
isScroll = false;
mMotionUpThread.setRefreshTime(System.currentTimeMillis());
break;
case MotionEvent.ACTION_CANCEL:
inTouch = false;
isScroll = false;
mMotionUpThread.setRefreshTime(System.currentTimeMillis());
break;
}
return super.onTouchEvent(event);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (inTouch) {
// 有手指触摸,并且位置有滚动 为滚动状态
if (mScrollListener != null) {
mScrollListener.onScrollStateChanged(this, OnScrollListener.SCROLL_STATE_TOUCH_SCROLL, isBottom());
}
} else {
isScroll = true;
// 没有手指触摸,并且位置有滚动 为fling状态
if (mScrollListener != null) {
mScrollListener.onScrollStateChanged(this, OnScrollListener.SCROLL_STATE_FLING, isBottom());
}
mFlingThread.setRefreshTime(System.currentTimeMillis());
}
if (mScrollListener != null) {
mScrollListener.onScroll(l, t, oldl, oldt);
}
}
static class FlingThread extends Thread {
private WeakReference scrollViewWithListenerWeakReference;
private long refreshTime;
public FlingThread(ScrollViewWithListener scrollViewWithListenerWeakReference) {
this.scrollViewWithListenerWeakReference = new WeakReference<>(scrollViewWithListenerWeakReference);
}
public void setRefreshTime(long refreshTime) {
ScrollViewWithListener context = scrollViewWithListenerWeakReference.get();
if (context != null) {
this.refreshTime = refreshTime;
}
}
@Override
public void run() {
ScrollViewWithListener context = scrollViewWithListenerWeakReference.get();
while (context != null && !context.isDestory) {
if (System.currentTimeMillis() == refreshTime + 200 && !context.isFlingRun) {
context.isFlingRun = true;
context.removeCallbacks(context.mRunnable);
context.postDelayed(context.mRunnable, 20);
}
}
Log.d("testScroll: ", "FlingThread: dead");
}
}
static class MotionUpThread extends Thread {
private WeakReference scrollViewWithListenerWeakReference;
private long refreshTime;
public MotionUpThread(ScrollViewWithListener scrollViewWithListenerWeakReference) {
this.scrollViewWithListenerWeakReference = new WeakReference<>(scrollViewWithListenerWeakReference);
}
public void setRefreshTime(long refreshTime) {
ScrollViewWithListener context = scrollViewWithListenerWeakReference.get();
if (context != null) {
this.refreshTime = refreshTime;
}
}
@Override
public void run() {
ScrollViewWithListener context = scrollViewWithListenerWeakReference.get();
while (context != null && !context.isDestory) {
// isScroll标志位用于判断抬起后的fling事件
if (System.currentTimeMillis() == refreshTime + 200 && !context.isMotionUpRun && !context.isScroll) {
context.isMotionUpRun = true;
context.removeCallbacks(context.mRunnable);
context.postDelayed(context.mRunnable, 20);
}
}
Log.d("testScroll: ", "MotionUpThread: dead");
}
}
static class MyRunnable implements Runnable {
private WeakReference scrollViewWithListenerWeakReference;
public MyRunnable(ScrollViewWithListener scrollViewWithListenerWeakReference) {
this.scrollViewWithListenerWeakReference = new WeakReference<>(scrollViewWithListenerWeakReference);
}
@Override
public void run() {
ScrollViewWithListener context = scrollViewWithListenerWeakReference.get();
Log.d("testScroll: ", "is stoping");
if (context != null && context.mScrollListener != null) {
context.mScrollListener.onScrollStateChanged(context, OnScrollListener.SCROLL_STATE_IDLE, context.isBottom());
context.isFlingRun = false;
context.isMotionUpRun = false;
context.isScroll = false;
}
Log.d("testScroll: ", "stop finish");
}
}
@SuppressLint("RestrictedApi")
private boolean isBottom() {
return getScrollY() + getHeight() >= computeVerticalScrollRange();
}
/**
* 设置ScrollView的滚动监听
*
* @param scrollListener
*/
public void setOnScrollListener(OnScrollListener scrollListener) {
mScrollListener = scrollListener;
}
/**
* 滚动监听事件
*/
public interface OnScrollListener {
/**
* 状态:停止
*/
int SCROLL_STATE_IDLE = 0;
/**
* 状态:手动拖拽
*/
int SCROLL_STATE_TOUCH_SCROLL = 1;
/**
* 状态:惯性滑动
*/
int SCROLL_STATE_FLING = 2;
/**
* 滑动状态回调
*
* @param view 当前的scrollView
* @param scrollState 当前的状态
* @param arriveBottom 是否到达底部
*/
void onScrollStateChanged(NestedScrollView view, int scrollState, boolean arriveBottom);
/**
* 滑动位置回调
*
* @param l
* @param t
* @param oldl
* @param oldt
*/
void onScroll(int l, int t, int oldl, int oldt);
}
@Override
protected void onDetachedFromWindow() {
isDestory = true;
boolean isRemove = removeCallbacks(mRunnable);
Log.d("testScroll: ", "mRunnable isRemove:" + isRemove);
Log.d("testScroll: ", "mFlingThread isAlive:" + mFlingThread.isAlive());
Log.d("testScroll: ", "mMotionUpThread isAlive:" + mMotionUpThread.isAlive());
super.onDetachedFromWindow();
}
}