組件介紹
所有的View分組器一,每一組都有一個Header,上下滑動到某一個組的時候,它的Header都會懸浮在頂部
- 當組的頭部從屏幕頂部消失笼痛,而且組還有成員在屏幕內(nèi)的時候,組的頭部懸浮在屏幕頂部
- 當下一個組的頭部滑到屏幕頂部與懸浮頭部挨著的時候信轿,把懸浮頭部
頂走晃痴,最終懸浮的頭部被替代
為了更容易理解,需要首先來說說明一下PinnedHeaderListView的大概思路财忽。它是通過將ListView所有的子item分成不同的section倘核,每個section的item的數(shù)目不一樣,每一個section的第一個item稱為header即彪。我們姑且將這兩種不同的類型稱之為section header和section item紧唱。不過值得注意的是,只是從邏輯上做了這樣的劃分隶校,實際上所有的item漏益,無論是否是header都是ListView里普通的一項。
基于上面的說明深胳,首先看一下SectionedBaseAdapter绰疤,它就是一個BaseAdapter,
private static int HEADER_VIEW_TYPE = 0;
private static int ITEM_VIEW_TYPE = 0;
/**
* Holds the calculated values of @{link getPositionInSectionForPosition}
*/
private SparseArray<Integer> mSectionPositionCache;
/**
* Holds the calculated values of @{link getSectionForPosition}
*/
private SparseArray<Integer> mSectionCache;
/**
* Holds the calculated values of @{link getCountForSection}
*/
private SparseArray<Integer> mSectionCountCache;
/**
* Caches the item count
*/
private int mCount;
/**
* Caches the section count
*/
private int mSectionCount;
public SectionedBaseAdapter() {
super();
mSectionCache = new SparseArray<Integer>();
mSectionPositionCache = new SparseArray<Integer>();
mSectionCountCache = new SparseArray<Integer>();
mCount = -1;
mSectionCount = -1;
}
1-2行舞终,定義了兩種類型HEADER_VIEW_TYPE和ITEM_VIEW_TYPE轻庆,分別對應(yīng)section header和section item。
7敛劝、11余爆、15行定義了三個SparseArray,它是Android上對HashMap<Integer, Object>的性能更優(yōu)的替代品夸盟。我們可以將他們當做HashMap<Integer, ?Integer>來理解蛾方。他們的作用是用來對section的信息做記錄(緩存)。具體來講,mSectionPositionCache表示第i個位置的item在對應(yīng)的section中是第幾個位置桩砰,mSectionCache表示第i個位置的item是屬于第幾個section拓春,mSectionCountCache表示每個section有幾個item。
20亚隅、24行痘儡,表示總的item數(shù)(包括每個section的header)和section數(shù)。
@Override
public final int getCount() {
if (mCount >= 0) {
return mCount;
}
int count = 0;
for (int i = 0; i < internalGetSectionCount(); i++) {
count += internalGetCountForSection(i);
count++; // for the header view
}
mCount = count;
return count;
}
@Override
public final Object getItem(int position) {
return getItem(getSectionForPosition(position), getPositionInSectionForPosition(position));
}
@Override
public final long getItemId(int position) {
return getItemId(getSectionForPosition(position), getPositionInSectionForPosition(position));
}
@Override
public final View getView(int position, View convertView, ViewGroup parent) {
if (isSectionHeader(position)) {
return getSectionHeaderView(getSectionForPosition(position), convertView, parent);
}
return getItemView(getSectionForPosition(position), getPositionInSectionForPosition(position), convertView, parent);
}
@Override
public final int getItemViewType(int position) {
if (isSectionHeader(position)) {
return getItemViewTypeCount() + getSectionHeaderViewType(getSectionForPosition(position));
}
return getItemViewType(getSectionForPosition(position), getPositionInSectionForPosition(position));
}
@Override
public final int getViewTypeCount() {
return getItemViewTypeCount() + getSectionHeaderViewTypeCount();
}
這段代碼就是重寫了BaseAdapter的幾個方法枢步,大家對此應(yīng)該很熟悉沉删,所不同的是在方法的內(nèi)部實現(xiàn),對section的item和header做了區(qū)分處理醉途。
public abstract Object getItem(int section, int position);
public abstract long getItemId(int section, int position);
public abstract int getSectionCount();
public abstract int getCountForSection(int section);
public abstract View getItemView(int section, int position, View convertView, ViewGroup parent);
public abstract View getSectionHeaderView(int section, View convertView, ViewGroup parent);
這幾個方法是需要子類去實現(xiàn)的矾瑰。
public final int getSectionForPosition(int position) {
// first try to retrieve values from cache
Integer cachedSection = mSectionCache.get(position);
if (cachedSection != null) {
return cachedSection;
}
int sectionStart = 0;
for (int i = 0; i < internalGetSectionCount(); i++) {
int sectionCount = internalGetCountForSection(i);
int sectionEnd = sectionStart + sectionCount + 1;
if (position >= sectionStart && position < sectionEnd) {
mSectionCache.put(position, i);
return i;
}
sectionStart = sectionEnd;
}
return 0;
}
private int internalGetCountForSection(int section) {
Integer cachedSectionCount = mSectionCountCache.get(section);
if (cachedSectionCount != null) {
return cachedSectionCount;
}
int sectionCount = getCountForSection(section);
mSectionCountCache.put(section, sectionCount);
return sectionCount;
}
private int internalGetSectionCount() {
if (mSectionCount >= 0) {
return mSectionCount;
}
mSectionCount = getSectionCount();
return mSectionCount;
}
這三個方法與開頭的三個SparseArray對應(yīng),方法中先分別從這三個Cache中獲取對應(yīng)的值隘擎,如果獲取不到殴穴,就根據(jù)條件進行計算,將計算后的結(jié)果放入Cache中货葬。
getPositionInSectionForPosition(int position)用于獲取指定位置的item在它所在的section是第幾個位置采幌。
internalGetCountForSection(int section)用于獲取指定section中item的數(shù)目。
internalGetSectionCount()用戶獲得section總的數(shù)目震桶。
之前提到有幾個抽象方法需要實現(xiàn)休傍,下面就看一下SectionedBaseAdapter的實現(xiàn)類TestSectionedAdapter。
public class TestSectionedAdapter extends SectionedBaseAdapter {
@Override
public Object getItem(int section, int position) {
// TODO Auto-generated method stub
return null;
}
@Override
public long getItemId(int section, int position) {
// TODO Auto-generated method stub
return 0;
}
@Override
public int getSectionCount() {
return 7;
}
@Override
public int getCountForSection(int section) {
return 15;
}
@Override
public View getItemView(int section, int position, View convertView, ViewGroup parent) {
LinearLayout layout = null;
if (convertView == null) {
LayoutInflater inflator = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layout = (LinearLayout) inflator.inflate(R.layout.pinned_header_listview_list_item, null);
} else {
layout = (LinearLayout) convertView;
}
((TextView) layout.findViewById(R.id.textItem)).setText("Section " + section + " Item " + position);
return layout;
}
@Override
public View getSectionHeaderView(int section, View convertView, ViewGroup parent) {
LinearLayout layout = null;
if (convertView == null) {
LayoutInflater inflator = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layout = (LinearLayout) inflator.inflate(R.layout.pinned_header_listview_header_item, null);
} else {
layout = (LinearLayout) convertView;
}
((TextView) layout.findViewById(R.id.textItem)).setText("Header for section " + section);
return layout;
}
}
由于在這個例子中蹲姐,getItem和getItemId兩個方法沒有實際的作用磨取,所以直接返回0和null了。
15-23行可以看出柴墩,要實現(xiàn)的這個ListView有7個section忙厌,每個section有15個item。
25-49行可以獲得section header和section item的View江咳。
Adapter的代碼我們就分析完了逢净,大致就是將ListView分成section,然后其他的方法都是圍繞著section的管理來做的歼指。
下面來看一下PinnedHeaderListView這個類爹土,它有一個內(nèi)部接口,這個接口就是在上面提到的Adapter中實現(xiàn)的东臀,在這里都會用到着饥,相信通過上面的講解犀农,大家可以看出來每個接口的大概意思惰赋。
public static interface PinnedSectionedHeaderAdapter {
public boolean isSectionHeader(int position);
public int getSectionForPosition(int position);
public View getSectionHeaderView(int section, View convertView, ViewGroup parent);
public int getSectionHeaderViewType(int section);
public int getCount();
}
這個類里的變量定義和構(gòu)造函數(shù)等內(nèi)容我們不在這里啰嗦了,直接看最重要的一部分代碼,這也是實現(xiàn)這個功能的關(guān)鍵赁濒。
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (mOnScrollListener != null) {
mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
if (mAdapter == null || mAdapter.getCount() == 0 || !mShouldPin || (firstVisibleItem < getHeaderViewsCount())) {
mCurrentHeader = null;
mHeaderOffset = 0.0f;
for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {
View header = getChildAt(i);
if (header != null) {
header.setVisibility(VISIBLE);
}
}
return;
}
firstVisibleItem -= getHeaderViewsCount();
int section = mAdapter.getSectionForPosition(firstVisibleItem);
int viewType = mAdapter.getSectionHeaderViewType(section);
mCurrentHeader = getSectionHeaderView(section, mCurrentHeaderViewType != viewType ? null : mCurrentHeader);
ensurePinnedHeaderLayout(mCurrentHeader);
mCurrentHeaderViewType = viewType;
mHeaderOffset = 0.0f;
for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {
if (mAdapter.isSectionHeader(i)) {
View header = getChildAt(i - firstVisibleItem);
float headerTop = header.getTop();
float pinnedHeaderHeight = mCurrentHeader.getMeasuredHeight();
header.setVisibility(VISIBLE);
if (pinnedHeaderHeight >= headerTop && headerTop > 0) {
mHeaderOffset = headerTop - header.getHeight();
} else if (headerTop <= 0) {
header.setVisibility(INVISIBLE);
}
}
}
invalidate();
}
ListView滾動的時候轨奄,會不斷的回調(diào)這個方法,然后在這個方法里實現(xiàn)懸浮header顯示邏輯的控制拒炎。
7-17行先對ListView添加的Header的情況進行處理挪拟,這里的Header不是我們說的section header,而是我們通過ListView的addHeaderView()添加的击你,文章開始的使用方法介紹中就是添加了兩個Header玉组。這種情況下,剛開始是不會有懸浮效果的丁侄,因為還沒有進入section惯雳。
23行得到了mCurrentHeader,就是要懸浮顯示的View鸿摇。
24行代碼保證mCurrentHeader可以懸浮在ListView頂部的固定位置石景。
29-41行代碼就是用來控制header移動的。因為當下方section的header快要到達頂端時拙吉,會將之前懸浮的header頂出顯示區(qū)域潮孽,然后直到之前header消失,新的header就會懸浮在ListView頂端筷黔。這里的關(guān)鍵就是通過View的位置來計算之前懸浮header的偏移量mHeaderOffset往史,然后通過invalidate觸發(fā)dispatchDraw方法以重繪View。
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mAdapter == null || !mShouldPin || mCurrentHeader == null)
return;
int saveCount = canvas.save();
canvas.translate(0, mHeaderOffset);
canvas.clipRect(0, 0, getWidth(), mCurrentHeader.getMeasuredHeight()); // needed
// for
// <
// HONEYCOMB
mCurrentHeader.draw(canvas);
canvas.restoreToCount(saveCount);
}
從dispatchDraw的實現(xiàn)中我們可以看到佛舱,確實是用到了偏移量mHeaderOffset怠堪。其中,先將canvas在Y軸方向上移動了mHeaderOffset的距離名眉,然后截取畫布粟矿,在截取后的畫布上繪制header。
通過上面一系列的處理损拢,最終實現(xiàn)了我們在開頭看到的ListView的懸浮效果陌粹。總結(jié)一下PinnedHeaderListView的基本思路:將ListView邏輯上分成若干個section福压,每個section有一個header掏秩,當header滑動到頂端時,會在ListView上繪制一個懸浮的View荆姆,View的內(nèi)容就是這個header蒙幻,當下面的header2達到頂部與header相交時,根據(jù)滑動距離將header向上移胆筒,直到header消失邮破,header2會懸浮在頂端诈豌,這樣就實現(xiàn)了我們看到的效果。
版本控制
版本號 | 更新內(nèi)容 | 修改人 | 修改時間 |
---|---|---|---|
1.0 | 初次發(fā)布 | lucky_tiger | 2017/7/13 |
項目地址
所在文件夾 | demo位置 |
---|---|
widget.PinnedHeaderListView | com.qr.demo.widget.PinnedHeaderListViewActivity |