Android 中实现 ScrollView 的滚动事件监听

最近在本身实现一个相似 Pinterest 瀑布流展现效果的组件,GitHub 上其实有相似项目,好比 PinterestLikeAdapterView 、 PinterestListView , 但都或多或少有些不足(详见 这篇文章 的分析),而后本身想基于 ScrollView 去嵌套多列 LinearLayout 实现。多线程

坑爹的是系统自带的 ScrollView 功能至关粗糙:连个最基本的 setOnScrollListener() 的方法都没有,仅有个 onScrollChanged() 方法,并且仍是 protected 的。不得不吐槽下,Google 真够懒的,这货纯粹就是个毛胚啊,彻底得靠开发者去继承后本身打磨。ide

首先固然是继承 ScrollView ,而后把最原始的 onScrollChanged() 方法暴露给外部:oop

public class RLScrollView extends ScrollView{post

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](http://my.oschina.net/u/2303379) 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);
	}
}

} 而后须要两个判断位置的方法(top or bottom),好比要实现一个滚动到底部自动加载更多数据的功能,这个就是必须的:性能

/** *this

  • @return */ public boolean isAtTop(){ return getScrollY()<=0; }

/** *.net

  • @return */ public boolean isAtBottom(){ return getScrollY()==getChildAt(getChildCount()-1).getBottom()+getPaddingBottom()-getHeight(); } 顶部好说了,就是 Y 方向滚动为 0 。底部判断:Y 方向滚动等于最后一个 child 元素底部相对 parent 的位置减去 parent 高度,而后还要考虑到 parent 自身可能有 paddingBottom 。

而后像瀑布流这种几乎没有底的东西,得须要个判断 child 是否处于屏幕可见范围内的方法,否则将全部 item 的 View 和 Bitmap 都放在内存确定是至关占用资源的,既影响滑动流畅性,又很容易 OOM 。线程

/** *rest

  • @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 固然得不到可见的矩形了。code

顺便提一下, StackOverflow 上有对这个神秘方法的讨论,提到了一个 getLocalVisibleRect() 方法,这里就不深挖了。

回到这个用于实现瀑布流的增强版 ScrollView 上来,为了实现滚动到底部自动加载还有顶部下拉刷新这些功能,确定还须要监听滚动是否中止。

首先想到的是 onScrollChanged() 方法,在这个里面去判断 Y 和 oldY 是否相等,发现效果不是很理想。

而后就想到本身去后台实时检测这个 scrollY 的变化,详细思路以下:

首先设置 onTouchListener ,去监听手指屏幕操做;

一旦检测到 ACTION_UP 事件(即手指离开屏幕)就记下当前滚动位置,而后延时 post 一个 Runnable 到主线程;

在这个 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); } } }; 可能有人会担忧这样频繁的 post 会不会是开启了大量的线程很影响性能?

答案是否认的, Runnable 不一样于 Thread ,它只是一个可执行对象,具体在哪一个线程执行看状况。

好比这里其实是发送了一个延时消息到UI线程的消息队列,由其 Looper 按照 FIFO 规则一个个抽取后给 Handler 去处理,而 UI 线程只有一个,因此并非开启了不少线程。

亲测效果不错,不只检测到的滚动 stop 状态很准确,并且也并不会影响滚动流畅性。

项目完整代码

最近在本身实现一个相似 Pinterest 瀑布流展现效果的组件,GitHub 上其实有相似项目,好比 PinterestLikeAdapterView 、 PinterestListView , 但都或多或少有些不足(详见 这篇文章 的分析),而后本身想基于 ScrollView 去嵌套多列 LinearLayout 实现。

坑爹的是系统自带的 ScrollView 功能至关粗糙:连个最基本的 setOnScrollListener() 的方法都没有,仅有个 onScrollChanged() 方法,并且仍是 protected 的。不得不吐槽下,Google 真够懒的,这货纯粹就是个毛胚啊,彻底得靠开发者去继承后本身打磨。

相关文章
相关标签/搜索