最近在本身實現一個相似 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
/** *.net
而後像瀑布流這種幾乎沒有底的東西,得須要個判斷 child 是否處於屏幕可見範圍內的方法,否則將全部 item 的 View 和 Bitmap 都放在內存確定是至關佔用資源的,既影響滑動流暢性,又很容易 OOM 。線程
/** *rest
不過看方法名應該不難了解其功能:獲取可見的矩形,參數也是個矩形,這裏傳過去的是 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 真夠懶的,這貨純粹就是個毛胚啊,徹底得靠開發者去繼承後本身打磨。