Android 中实现 ScrollView 的滚动事件监听
2014-11-06 17:15
661 查看
转自:http://www.rincliu.com/blog/2013/11/16/scrollview/
最近在自己实现一个类似 Pinterest 瀑布流展示效果的组件,GitHub 上其实有类似项目,比如 PinterestLikeAdapterView、PinterestListView ,
但都或多或少有些不足(详见这篇文章的分析),然后自己想基于
坑爹的是系统自带的
真够懒的,这货纯粹就是个毛胚啊,完全得靠开发者去继承后自己打磨。
首先当然是继承
然后需要两个判断位置的方法(top or bottom),比如要实现一个滚动到底部自动加载更多数据的功能,这个就是必须的:
顶部好说了,就是 Y 方向滚动为 0 。底部判断:Y 方向滚动等于最后一个 child 元素底部相对 parent 的位置减去 parent 高度,然后还要考虑到 parent 自身可能有
然后像瀑布流这种几乎没有底的东西,得需要个判断 child 是否处于屏幕可见范围内的方法,不然将所有 item 的
OOM 。
这里用到了
不过看方法名应该不难了解其功能:获取可见的矩形,参数也是个矩形,这里传过去的是 parent 边界所在的矩形,返回值
顺便提一下,StackOverflow 上有对这个神秘方法的讨论,提到了一个
回到这个用于实现瀑布流的加强版 ScrollView 上来,为了实现滚动到底部自动加载还有顶部下拉刷新这些功能,肯定还需要监听滚动是否停止。
首先想到的是
然后就想到自己去后台实时检测这个
首先设置
一旦检测到
post 一个
在这个
post 同样的
可能有人会担心这样频繁的 post 会不会是开启了大量的线程很影响性能?
答案是否定的,
比如这里实际上是发送了一个延时消息到UI线程的消息队列,由其
UI 线程只有一个,所以并不是开启了很多线程。
亲测效果不错,不仅检测到的滚动 stop 状态很准确,而且也并不会影响滚动流畅性。
项目完整代码
最近在自己实现一个类似 Pinterest 瀑布流展示效果的组件,GitHub 上其实有类似项目,比如 PinterestLikeAdapterView、PinterestListView ,
但都或多或少有些不足(详见这篇文章的分析),然后自己想基于
ScrollView去嵌套多列
LinearLayout实现。
坑爹的是系统自带的
ScrollView功能相当粗糙:连个最基本的
setOnScrollListener()的方法都没有,仅有个
onScrollChanged()方法,而且还是
protected的。不得不吐槽下,Google
真够懒的,这货纯粹就是个毛胚啊,完全得靠开发者去继承后自己打磨。
首先当然是继承
ScrollView,然后把最原始的
onScrollChanged()方法暴露给外部:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /** * * @return */ public boolean isAtTop(){ return getScrollY()<=0; } /** * * @return */ public boolean isAtBottom(){ return getScrollY()==getChildAt(getChildCount()-1).getBottom()+getPaddingBottom()-getHeight(); } |
paddingBottom。
然后像瀑布流这种几乎没有底的东西,得需要个判断 child 是否处于屏幕可见范围内的方法,不然将所有 item 的
View和
Bitmap都放在内存肯定是相当占用资源的,既影响滑动流畅性,又很容易
OOM 。
1 2 3 4 5 6 7 8 9 10 11 12 13 | /** * * @param child * @return */ public boolean isChildVisible(View child){ if(child==null){ return false; } Rect scrollBounds = new Rect(); getHitRect(scrollBounds); return child.getLocalVisibleRect(scrollBounds); } |
View的
getLocalVisibleRect()方法,官方文档没有给出任何相关说明。
不过看方法名应该不难了解其功能:获取可见的矩形,参数也是个矩形,这里传过去的是 parent 边界所在的矩形,返回值
boolean类型。也很好理解:不可见的 child 当然得不到可见的矩形了。
顺便提一下,StackOverflow 上有对这个神秘方法的讨论,提到了一个
getLocalVisibleRect()方法,这里就不深挖了。
回到这个用于实现瀑布流的加强版 ScrollView 上来,为了实现滚动到底部自动加载还有顶部下拉刷新这些功能,肯定还需要监听滚动是否停止。
首先想到的是
onScrollChanged()方法,在这个里面去判断
Y和
oldY是否相等,发现效果不是很理想。
然后就想到自己去后台实时检测这个
scrollY的变化,详细思路如下:
首先设置
onTouchListener,去监听手指屏幕操作;
一旦检测到
ACTION_UP事件(即手指离开屏幕)就记下当前滚动位置,然后延时
post 一个
Runnable到主线程;
在这个
Runnable中判断当前滚动位置是否和延时之前的位置相等(即 Y 方向位置不再变化),如果不相等则延时后继续
post 同样的
Runnable去检测。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 1516 | 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 状态很准确,而且也并不会影响滚动流畅性。
项目完整代码
相关文章推荐
- 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滚动监听)