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 真夠懶的,這貨純粹就是個毛胚啊,徹底得靠開發者去繼承後本身打磨。

相關文章
相關標籤/搜索