Android中实现ScrollView的滚动事件监听
2014-04-21 20:08
756 查看
最近在自己实现一个类似Pinterest瀑布流展示效果的组件,GitHub上其实有类似项目,比如PinterestLikeAdapterView、PinterestListView,但都或多或少有些不足(详见这篇文章的分析),然后自己想基于ScrollView去嵌套多列LinearLayout实现。坑爹的是系统自带的ScrollView功能相当粗糙:连个最基本的setOnScrollListener()的方法都没有,仅有个onScrollChanged()方法,而且还是protected的。不得不吐槽下,Google真够懒的,这货纯粹就是个毛胚啊,完全得靠开发者去继承后自己打磨。
首先当然是继承ScrollView,然后把最原始的onScrollChanged()方法暴露给外部:
然后需要两个判断位置的方法(top or bottom),比如要实现一个滚动到底部自动加载更多数据的功能,这个就是必须的:
顶部好说了,就是Y方向滚动为0。底部判断:Y方向滚动等于最后一个child元素底部相对parent的位置减去parent高度,然后还要考虑到parent自身可能有paddingBottom。
然后像瀑布流这种几乎没有底的东西,得需要个判断child是否处于屏幕可见范围内的方法,不然将所有item的view和bitmap都放在内存肯定是相当占用资源的,既影响滑动流畅性,又很容易OOM。
这里用到了view的getLocalVisibleRect()方法,官方文档没有给出任何相关说明。
不过看方法名应该不难了解其功能:获取可见的矩形,参数也是个矩形,这里传过去的是parent边界所在的矩形,返回值boolean类型。也很好理解:不可见的child当然得不到可见的矩形了。
顺便提一下,StackOverflow上有对这个神秘方法的讨论,提到了一个getLocalVisibleRect方法,这里就不深挖了。
回到这个用于实现瀑布流的加强版ScrollView上来,为了实现滚动到底部自动加载还有顶部下拉刷新这些功能,肯定还需要监听滚动是否停止。
首先想到的是onScrollChanged方法,在这个里面去判断Y和oldY是否相等,发现效果不是很理想。
然后就想到自己去后台实时检测这个scrollY的变化,详细思路如下:
1.首先设置onTouchListener,去监听手指屏幕操作;2.一旦检测到ACTION_UP事件(即手指离开屏幕)就记下当前滚动位置,然后延时post一个Runnable到主线程;3.在这个Runnable中判断当前滚动位置是否和延时之前的位置相等(即Y方向位置不再变化),如果不相等则延时后继续post同样的Runnable去检测。
可能有人会担心这样频繁的post会不会是开启了大量的线程很影响性能?
答案是否定的,Runnable不同于Thread,它只是一个可执行对象,具体在哪个线程执行看情况。
比如这里实际上是发送了一个延时消息到UI线程的消息队列,由其Looper按照FIFO规则一个个抽取后给Handler去处理,而UI线程只有一个,所以并不是开启了很多线程。
亲测效果不错,不仅检测到的滚动stop状态很准确,而且也并不会影响滚动流畅性。
最后附上本项目的GitHub地址,然后欢迎大家去watch/star/fork和commit issues.
PS:禁止ScrollView在子控件的布局改变时自动滚动的的方法
用上面的方法完成scrollview嵌套listview的功能后,如果listview数据是动态改变的,比如一个留言列表界面,上面是动态详情,下面是留言列表,进入该页面先加载详情,再加载留言列表,等加载完留言后,scrollview会自动滚动到listview,导致详情信息只显示一部分或者完全显示不了,解决办法,在自定义的scrollview中重写scrollview中的如下方法,并将其返回值设为0即可:
@Override
protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
return 0;
}
首先当然是继承ScrollView,然后把最原始的onScrollChanged()方法暴露给外部:
public class RLScrollView extends ScrollView{ public RLScrollView(Context context) { super(context); } public RLScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public RLScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public interface OnScrollChangedListener{ public void onScrollChanged(int x, int y, int oldxX, int oldY); } private OnScrollChangedListener onScrollChangedListener; /** * * @param onScrollChangedListener */ public void setOnScrollListener(OnScrollChangedListener onScrollChangedListener){ this.onScrollChangedListener=onScrollChangedListener; } @Override protected void onScrollChanged(int x, int y, int oldX, int oldY){ super.onScrollChanged(x, y, oldX, oldY); if(onScrollChangedListener!=null){ onScrollChangedListener.onScrollChanged(x, y, oldX, oldY); } } } |
/** * * @return */ public boolean isAtTop(){ return getScrollY()<=0; } /** * * @return */ public boolean isAtBottom(){ return getScrollY()==getChildAt(getChildCount()-1).getBottom()+getPaddingBottom()-getHeight(); } |
然后像瀑布流这种几乎没有底的东西,得需要个判断child是否处于屏幕可见范围内的方法,不然将所有item的view和bitmap都放在内存肯定是相当占用资源的,既影响滑动流畅性,又很容易OOM。
/** * * @param child * @return */ public boolean isChildVisible(View child){ if(child==null){ return false; } Rect scrollBounds = new Rect(); getHitRect(scrollBounds); return child.getLocalVisibleRect(scrollBounds); } |
不过看方法名应该不难了解其功能:获取可见的矩形,参数也是个矩形,这里传过去的是parent边界所在的矩形,返回值boolean类型。也很好理解:不可见的child当然得不到可见的矩形了。
顺便提一下,StackOverflow上有对这个神秘方法的讨论,提到了一个getLocalVisibleRect方法,这里就不深挖了。
回到这个用于实现瀑布流的加强版ScrollView上来,为了实现滚动到底部自动加载还有顶部下拉刷新这些功能,肯定还需要监听滚动是否停止。
首先想到的是onScrollChanged方法,在这个里面去判断Y和oldY是否相等,发现效果不是很理想。
然后就想到自己去后台实时检测这个scrollY的变化,详细思路如下:
1.首先设置onTouchListener,去监听手指屏幕操作;2.一旦检测到ACTION_UP事件(即手指离开屏幕)就记下当前滚动位置,然后延时post一个Runnable到主线程;3.在这个Runnable中判断当前滚动位置是否和延时之前的位置相等(即Y方向位置不再变化),如果不相等则延时后继续post同样的Runnable去检测。
... setOnTouchListener(new OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) { currentScroll = sv.getScrollY(); postDelayed(scrollCheckTask, 300); } return false; } }); ... Runnable scrollCheckTask = new Runnable() { @Override public void run() { int newScroll = sv.getScrollY(); if(currentScroll==newScroll){ if(onWaterfallScrollListener!=null){ //TODO: onScrollStopped; if(sv.isAtTop()){ //TODO: onScrollStoppedAtTop; } if(isScrollViewAtBottom(sv)){ //TODO: onScrollStoppedAtBottom; } } }else{ currentScroll=sv.getScrollY(); postDelayed(scrollCheckTask, 300); } } }; |
答案是否定的,Runnable不同于Thread,它只是一个可执行对象,具体在哪个线程执行看情况。
比如这里实际上是发送了一个延时消息到UI线程的消息队列,由其Looper按照FIFO规则一个个抽取后给Handler去处理,而UI线程只有一个,所以并不是开启了很多线程。
亲测效果不错,不仅检测到的滚动stop状态很准确,而且也并不会影响滚动流畅性。
最后附上本项目的GitHub地址,然后欢迎大家去watch/star/fork和commit issues.
PS:禁止ScrollView在子控件的布局改变时自动滚动的的方法
用上面的方法完成scrollview嵌套listview的功能后,如果listview数据是动态改变的,比如一个留言列表界面,上面是动态详情,下面是留言列表,进入该页面先加载详情,再加载留言列表,等加载完留言后,scrollview会自动滚动到listview,导致详情信息只显示一部分或者完全显示不了,解决办法,在自定义的scrollview中重写scrollview中的如下方法,并将其返回值设为0即可:
@Override
protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
return 0;
}
相关文章推荐
- Android中实现ScrollView的滚动事件监听
- Android 中实现 ScrollView 的滚动事件监听
- Android 中实现 ScrollView 的滚动事件监听
- Android中实现ScrollView的滚动事件监听
- Android 对ScrollView滚动监听,实现美团、大众点评的购买悬浮效果
- Android对ScrollView滚动监听,实现美团、大众点评的购买悬浮效果
- Android中实现监听ScrollView滑动事件
- Android 对ScrollView滚动监听,实现美团、大众点评的购买悬浮效果
- Android - 小功能 - 对ScrollView滚动监听,实现美团、大众点评的购买悬浮效果
- Android 对ScrollView滚动监听,实现美团、大众点评的购买悬浮效果
- Android 对ScrollView滚动监听,实现美团、大众点评的购买悬浮效果
- Android 对ScrollView滚动监听,实现美团、大众点评的购买悬浮效果
- Android 对ScrollView滚动监听,实现美团、大众点评的购买悬浮效果
- Android的ScrollView的滚动的监听以及屏幕静止状态的监听实现
- Android 对ScrollView滚动监听,实现美团、大众点评的购买悬浮效果
- Android中实现监听ScrollView滑动事件
- Android 对ScrollView滚动监听,实现美团、大众点评的购买悬浮效果
- Android 对ScrollView滚动监听,实现美团、大众点评的购买悬浮效果
- Android 对ScrollView滚动监听,实现美团、大众点评的购买悬浮效果
- Android对ScrollView滚动监听,实现美团、大众点评的购买悬浮效果