iOS上的bounce功能給人的感覺很爽,當一個可以滾動的區域被拖到邊界時,它允許用戶將內容拖過界,放手後再彈回來,以一種非常棒的方式提示了用戶邊界的存在,是iOS的一大特色。Android2.3新增了Overscroll功能,聽名字就知道應該是bounce功能的翻版,但也許是出於專利方面的考慮,google的默認實現跟iOS有所不同,它只是在list拖到邊界處時做了一個發光的動畫,個人覺得體驗比iOS差遠了。而且這個黃色的發光在黑色背景下雖然效果不錯,在其它背景下可就難說了,因此很多人想要關掉它。
日前google上搜索“Android Overscroll”,對此效果的介紹很多,但關於其具體使用方式和實現,則很少涉及,偶有提及,也經常答非所問或似是而非,反而誤導了別人。於是我查閱了Android相關源碼,並做了一些測試,在此講講我的理解。
首先是Overscroll功能本身,在最頂層的View類提供了支持,可通過setOverscrollMode函數控制其出現條件。但其實View中並沒有實現Overscroll功能,它僅僅提供了一個輔助函數OverscrollBy,該函數根據OverscrollMode和內容是否需要滾動控制最大滾動范圍,最後將計算結果傳給onOverscrolled實現具體的Overscroll功能,但此函數在View類中是全空的。
Overscroll功能真正的實現分別在ScrollView、AbsListView、HorizontalScrollView和WebView中各有一份,代碼基本一樣。以ScrollView為例,它在處理筆點移動消息時調用OverscrollBy來滾動視圖,然後重載了OverscrollBy函數來實現具體功能,其位置計算通過Overscroller類實現。Overscroller作為一個計算引擎,應該是一個獨立的模塊,具體滾動效果和范圍都不可能通過它來設置,我覺得沒有必要細看。但滾動位置最終是它給出的,那相關數據肯定要傳遞給它,回頭看OverscrollBy函數,它有兩個控制Overscroll出界范圍的參數,幾個實現裡面都是取自ViewConfiguration.getScaledOverscrollDistance,而這個參數的值在我的源碼中都是0,而且我沒找到任何可以影響其結果的設置。
真悲催,繞了半天,Android的默認實現裡面根本沒有給出Overscroll功能,它只是提供了實現機制,要想用起來還得應用程序自己顯式重寫相關控件,估計還有一層隱含的意思,法律風險自負。在我的系統中一試,果然一個像素都不能拉出界。但那個閃光是怎麼回事呢?
在處理筆點消息處,OverscrollBy後面不遠處有一段mEdgeGlowTop的操作代碼,看名字就像,用它一搜,相關機制就全明白了。mEdgeGlowTop在setOverscrollMode函數時創建,它使用的圖片都是系統中固有的,甚至不能通過theme改變。它的實現原理也很簡單,僅僅是兩張png圖片的合成,通過透明度的變化制造閃光的效果。更無語的是它既不能被應用程序訪問,也不受任何控制,要關閉它的唯一辦法是setOverscrollMode(View.OVER_SCROLL_NEVER)。否則就重寫onTouchEvent函數吧,想干啥都可以,只是得自己做。
談到Overscroll,很多文章都提到了ListView的setOverscrollHeader和setOverscrollFooter,很多人想通過這個來控制那個閃光效果。這兩玩意不但可以通過函數設置,也可以在xml中指定,相當方便。但最後很多人發現沒有任何作用,百思不得其解。其實這兩張圖片是用來作為Overscroll拖過界時的背景的,默認系統不能拖過界,自然永遠都看不到,有些定制的系統中能拖出界幾個像素,但也很難看清。