分享一個Android控件,PinnedHeaderListView , 大致是像圖釘一樣,能夠固定顯示一個頭部在ListView的頂部,類似於Android原版通訊錄中聯系人按照字母分組排列, 這個東西其實出來很久了,今天仔細閱讀了源碼,再次做一個分享。
效果預覽
下面的圖左邊是預覽的效果,右邊則是項目涉及的重要類。
原理概述
- 為了便於分析,我們先做一些命名的約定。這個List繼承自ListView,灰色半透明item暫且稱其為section view,而其他的白色條目暫稱為item view,當section view滑動至頂部后將停留在頂部,而白色的item view可以繼續上划消失,這里固定后的section view 我們暫時稱其為current header view,注意它實際上不存在於ListView內部,而是實時可變的畫在頂部的view。
- 通過滑動事件監聽,在onScroll()內設置current header view的內容和位置的偏移量,在每次滑動事件的結尾觸發lisview進行重繪,這時再dispatchDraw()中通過canvas來繪制視圖。
-
與之配套的adapter則實現一些具體的view的實例化操作和區分不同的view,比如section view和item view,以及這兩種view各自的view type處理等等。
實現分析
我們已經知道他的實現機制,現在先來具體分析一下的內部實現,涉及到的類可以在上面的預覽圖中看到,主要是PinnedHeaderListView 和SectionedBaseAdapter以及相關接口定義和實現。
- PinnedHeaderListView
下面是它定義的一些重要成員,具體作用我已經在后面加了注釋, 其中的mCurrentHeader即為current header view。
private View mCurrentHeader;// pinned top header view
private int mCurrentHeaderViewType = 0;
private float mHeaderOffset;// offset for pinned header view
private boolean mShouldPin = true;//default to enable pinned header view
在PinnedHeaderListView內部他繼承了ListView並實現了OnScrollListener接口,現在我們先分析onScroll里面所做的事情,大致可以分為四塊:
1.外部on scroll的的觸發,
首先是重載了ListView得setOnScrollListener()方法,通過成員變量mOnScrollListener來引用外部設置的listener,然后再已經實現的OnScrollListener接口中調用mOnScrollListener,
這樣一來我們任然可以在空間外部設置setOnScrollListener做其他的事情.
2. 處理section view尚未接觸頂部的狀態
如果listview是空的或者我們通過setPinHeaders()使得listview不要pinned效果或則listview有設置header並且滑動時header還未完全出屏幕外,那么走下面的段代碼,把current header view和他的offset置空,然后遍歷所有子view設置為visible的狀態,注意這一步是是必須的,因為在其他地方我們有可能會把子view設為invisible,而這個地方就是在設置pinned current header view的時候。
3. 復用或實例化pinned current header view
我已經在代碼中加了注釋,首先是計算出listview 原始header之外的item的索引起始位置,此時header如果存在,那么已經划出屏幕了,也就是當前第一個顯示的位置減去header個數;根據這個item的position來獲取對應的section view的position和view type,具體的代碼實現在SectionBaseAdapter里面。接着是獲取current header view的實例,根據view type和和當前的位置position是否變化,可能是復用的也可能是全新創建的
4. 遍歷所有的section view設置visibility
現在從屏幕上可見的子section view開始遍歷,計算出section view的頂部Y坐標,和pinned current header view的高度作比較,當section view向上滑動到開始與pinned current header view的區域相交時,我們計算出交叉的高度作為current header view的Y軸偏移量,繼續向上滑動,當section view的頂部Y坐標小於0,也就是開始要划出屏幕時我們設置它為invisible,這樣做的目的在於造成錯覺,好像section view被定在了list view得頂部,實際上如果這里我們不把他設為invisible那么在demo運行時你將更清晰的發現原來向上滑動的section view其實一直在向上滑動。
最后我們需要調用 invalidate();來重繪界面,這樣我們剛才更新的current header view 的偏移量就會在繪制的時候生效。
分析到這里我們對PinnedHeaderListView已經有了更深刻的理解,如果你發現思路有點跟不上下面的這張簡略的流程圖可能會有所幫助。
對與之配套的Adapter我將在下一篇文章在做分析…..