編輯:關於Android編程
效果圖:

一、項目使用
(1).項目引用。
dependencies {
compile 'in.srain.cube:grid-view-with-header-footer:1.0.12'
}
(2).添加Java代碼。GridViewWithHeaderAndFooter gridView = (GridViewWithHeaderAndFooter) v.findViewById(R.id.gridview); LayoutInflater layoutInflater = LayoutInflater.from(this); View headerView = layoutInflater.inflate(R.layout.header_view, null); View footerView = layoutInflater.inflate(R.layout.footer_view, null); gridView.addHeaderView(headerView);// 添加Header gridView.addFooterView(footerView);// 添加Footer注意,addHeaderView()和addFooterView()需要在setAdapter()方法之前調用。
二、源碼分析
(1).實現原理:
當為ListView或GridView設置數據時,一般是創建一個自己的adapter繼承自BaseAdapter類,然後調用setAdapter(ListAdapter adapter)方法。setAdapter()接收的是一個ListAdapter類型對象,而ListAdapter是一個接口,BaseAdapter類正是實現了ListAdapter接口。
ListView默認是擁有addHeaderView()和addFooterView()方法的,主要依靠的是android.widget.HeaderViewListAdapter類。HeaderViewListAdapter的主要作用是在ListAdapter基礎上封裝和升級,提供了添加Header和Footer的功能。該類一般不直接使用,它的主要目的是提供一個對包含Header和Footer的列表進行適配的一個類。HeaderViewListAdapter類實現了WrapperListAdapter接口。
來看WrapperListAdapter接口的定義。
package android.widget;
public interface WrapperListAdapter extends ListAdapter {
public ListAdapter getWrappedAdapter();
}
WrapperListAdapter繼承自ListAdapder接口,所以本身也是一個ListAdapter。同時內部嵌套了另一個ListAdapter,定義了一個方法用於取得嵌套的ListAdapter。
HeaderViewListAdapter類的成員變量和構造方法如下,這裡只附上我們需要關心的部分。
構造方法的參數中,傳入了headerView集合和footerView集合,另外還有一個adapter對象,這個adapter管理的就是真正的列表數據。
public class HeaderViewListAdapter implements WrapperListAdapter {
private final ListAdapter mAdapter;
ArrayList mHeaderViewInfos;
ArrayList mFooterViewInfos;
public HeaderViewListAdapter(ArrayList headerViewInfos,
ArrayList footerViewInfos,
ListAdapter adapter) {
mAdapter = adapter;
mHeaderViewInfos = headerViewInfos;
mFooterViewInfos = footerViewInfos;
}
}
再來看看ListView類的addHeaderView()方法,同樣只附上我們需要關心的部分。
public void addHeaderView(View v) {
FixedViewInfo info = new FixedViewInfo();
info.view = v;
mHeaderViewInfos.add(info);
// Wrap the adapter if it wasn't already wrapped.
if (mAdapter != null) {
if (!(mAdapter instanceof HeaderViewListAdapter)) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
}
}
}
GridViewWithHeaderAndFooter開源項目,正是借鑒了ListView中關於HeaderView和FooterView相關的代碼實現。添加HeaderView和FooterView的核心,在於對adapter的封裝和使用。因為ListView只有一列,而GridView列數大於1,所以還需要對參考的源碼HeaderViewListAdapter類做出相應的修改。
(2).項目源碼
由於項目源碼較多,這裡不逐行進行分析,只列出關於HeaderView和FooterView的重點部分。
1.addHeaderView(View v)和addFooterView(View v)方法
這兩個方法實現幾乎一致,唯一的區別是將參數view添加到不同的集合中。以addHeaderView(View v)為例。
public void addHeaderView(View v) {
addHeaderView(v, null, true);
}
public void addHeaderView(View v, Object data, boolean isSelectable) {
ListAdapter adapter = getAdapter();
if (adapter != null && !(adapter instanceof HeaderViewGridAdapter)) {
throw new IllegalStateException(
"Cannot add header view to grid -- setAdapter has already been called.");
}
ViewGroup.LayoutParams lyp = v.getLayoutParams();
FixedViewInfo info = new FixedViewInfo();
// 創建FullWidthFixedViewLayout
FrameLayout fl = new FullWidthFixedViewLayout(getContext());
if (lyp != null) {
v.setLayoutParams(new FrameLayout.LayoutParams(lyp.width, lyp.height));
fl.setLayoutParams(new AbsListView.LayoutParams(lyp.width, lyp.height));
}
// 將headerView添加到FullWidthFixedViewLayout中,之後會再將FullWidthFixedViewLayout添加到GridView
fl.addView(v);
info.view = v;
info.viewContainer = fl;
info.data = data;
info.isSelectable = isSelectable;
mHeaderViewInfos.add(info);
// in the case of re-adding a header view, or adding one later on,
// we need to notify the observer
if (adapter != null) {
((HeaderViewGridAdapter) adapter).notifyDataSetChanged();
}
}
FullWidthFixedViewLayout確保HeaderView和FooterView可以填充屏幕寬度。
/**
* full width
*/
private class FullWidthFixedViewLayout extends FrameLayout {
public FullWidthFixedViewLayout(Context context) {
super(context);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int realLeft = GridViewWithHeaderAndFooter.this.getPaddingLeft() + getPaddingLeft();
// Try to make where it should be, from left, full width
if (realLeft != left) {
offsetLeftAndRight(realLeft - left);
}
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 固定寬度
int targetWidth = GridViewWithHeaderAndFooter.this.getMeasuredWidth()
- GridViewWithHeaderAndFooter.this.getPaddingLeft()
- GridViewWithHeaderAndFooter.this.getPaddingRight();
widthMeasureSpec = MeasureSpec.makeMeasureSpec(targetWidth,
MeasureSpec.getMode(widthMeasureSpec));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
2.setAdapter()方法
@Override
public void setAdapter(ListAdapter adapter) {
mOriginalAdapter = adapter;
if (mHeaderViewInfos.size() > 0 || mFooterViewInfos.size() > 0) {
HeaderViewGridAdapter headerViewGridAdapter = new HeaderViewGridAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
super.setAdapter(headerViewGridAdapter);
} else {
super.setAdapter(adapter);
}
}
3.HeaderViewGridAdapter類
private static class HeaderViewGridAdapter implements WrapperListAdapter {
static final ArrayList EMPTY_INFO_LIST = new ArrayList();
// GridView的網格數據
private final ListAdapter mAdapter;
// HeaderView集合
ArrayList mHeaderViewInfos;
// FooterView集合
ArrayList mFooterViewInfos;
private int mNumColumns = 1;
private int mRowHeight = -1;
public HeaderViewGridAdapter(ArrayList headerViewInfos, ArrayList footViewInfos, ListAdapter adapter) {
mAdapter = adapter;
if (headerViewInfos == null) {
mHeaderViewInfos = EMPTY_INFO_LIST;
} else {
mHeaderViewInfos = headerViewInfos;
}
if (footViewInfos == null) {
mFooterViewInfos = EMPTY_INFO_LIST;
} else {
mFooterViewInfos = footViewInfos;
}
}
public int getHeadersCount() {
return mHeaderViewInfos.size();
}
public int getFootersCount() {
return mFooterViewInfos.size();
}
@Override
public int getCount() {
if (mAdapter != null) {
return (getFootersCount() + getHeadersCount()) * mNumColumns + getAdapterAndPlaceHolderCount();
} else {
return (getFootersCount() + getHeadersCount()) * mNumColumns;
}
}
private int getAdapterAndPlaceHolderCount() {
return (int) (Math.ceil(1f * mAdapter.getCount() / mNumColumns) * mNumColumns);
}
@Override
public Object getItem(int position) {
// Header數據
int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
if (position < numHeadersAndPlaceholders) {
if (position % mNumColumns == 0) {
return mHeaderViewInfos.get(position / mNumColumns).data;
}
return null;
}
// 真正的列表數據,mAdapter中的數據
final int adjPosition = position - numHeadersAndPlaceholders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = getAdapterAndPlaceHolderCount();
if (adjPosition < adapterCount) {
if (adjPosition < mAdapter.getCount()) {
return mAdapter.getItem(adjPosition);
} else {
return null;
}
}
}
// Footer數據
final int footerPosition = adjPosition - adapterCount;
if (footerPosition % mNumColumns == 0) {
return mFooterViewInfos.get(footerPosition).data;
} else {
return null;
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// HeaderView
int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
if (position < numHeadersAndPlaceholders) {
View headerViewContainer = mHeaderViewInfos
.get(position / mNumColumns).viewContainer;
if (position % mNumColumns == 0) {
return headerViewContainer;
} else {
if (convertView == null) {
convertView = new View(parent.getContext());
}
// We need to do this because GridView uses the height of the last item
// in a row to determine the height for the entire row.
convertView.setVisibility(View.INVISIBLE);
convertView.setMinimumHeight(headerViewContainer.getHeight());
return convertView;
}
}
// 真正的列表item,Adapter中的getView()返回
final int adjPosition = position - numHeadersAndPlaceholders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = getAdapterAndPlaceHolderCount();
if (adjPosition < adapterCount) {
if (adjPosition < mAdapter.getCount()) {
return mAdapter.getView(adjPosition, convertView, parent);
} else {
if (convertView == null) {
convertView = new View(parent.getContext());
}
convertView.setVisibility(View.INVISIBLE);
convertView.setMinimumHeight(mRowHeight);
return convertView;
}
}
}
// FooterView
final int footerPosition = adjPosition - adapterCount;
if (footerPosition < getCount()) {
View footViewContainer = mFooterViewInfos
.get(footerPosition / mNumColumns).viewContainer;
if (position % mNumColumns == 0) {
return footViewContainer;
} else {
if (convertView == null) {
convertView = new View(parent.getContext());
}
// We need to do this because GridView uses the height of the last item
// in a row to determine the height for the entire row.
convertView.setVisibility(View.INVISIBLE);
convertView.setMinimumHeight(footViewContainer.getHeight());
return convertView;
}
}
throw new ArrayIndexOutOfBoundsException(position);
}
@Override
public int getItemViewType(int position) {
final int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
final int adapterViewTypeStart = mAdapter == null ? 0 : mAdapter.getViewTypeCount() - 1;
int type = AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
// Header
if (position < numHeadersAndPlaceholders) {
if (position % mNumColumns != 0) {
type = adapterViewTypeStart + (position / mNumColumns + 1);
}
}
// Adapter
final int adjPosition = position - numHeadersAndPlaceholders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = getAdapterAndPlaceHolderCount();
if (adjPosition >= 0 && adjPosition < adapterCount) {
if (adjPosition < mAdapter.getCount()) {
type = mAdapter.getItemViewType(adjPosition);
} else {
type = adapterViewTypeStart + mHeaderViewInfos.size() + 1;
}
}
}
// Footer
final int footerPosition = adjPosition - adapterCount;
if (footerPosition >= 0 && footerPosition < getCount() && (footerPosition % mNumColumns) != 0) {
type = adapterViewTypeStart + mHeaderViewInfos.size() + 1 + (footerPosition / mNumColumns + 1);
}
return type;
}
@Override
public int getViewTypeCount() {
int count = mAdapter == null ? 1 : mAdapter.getViewTypeCount();
int offset = mHeaderViewInfos.size() + 1 + mFooterViewInfos.size();
count += offset;
return count;
}
}
4.setOnItemClickListener()和setOnItemLongClickListener()方法
@Override
public void setOnItemClickListener(OnItemClickListener l) {
mOnItemClickListener = l;
super.setOnItemClickListener(getItemClickHandler());
}
@Override
public void setOnItemLongClickListener(OnItemLongClickListener listener) {
mOnItemLongClickListener = listener;
super.setOnItemLongClickListener(getItemClickHandler());
}
private ItemClickHandler getItemClickHandler() {
if (mItemClickHandler == null) {
mItemClickHandler = new ItemClickHandler();
}
return mItemClickHandler;
}
private class ItemClickHandler implements android.widget.AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener {
@Override
public void onItemClick(AdapterView parent, View view, int position, long id) {
if (mOnItemClickListener != null) {
// 減去headerView占據的位置
int resPos = position - getHeaderViewCount() * getNumColumnsCompatible();
if (resPos >= 0) {
mOnItemClickListener.onItemClick(parent, view, resPos, id);
}
}
}
@Override
public boolean onItemLongClick(AdapterView parent, View view, int position, long id) {
if (mOnItemLongClickListener != null) {
// 減去headerView占據的位置
int resPos = position - getHeaderViewCount() * getNumColumnsCompatible();
if (resPos >= 0) {
mOnItemLongClickListener.onItemLongClick(parent, view, resPos, id);
}
}
return true;
}
}
Volley學習(四)NetworkImageView+LruCache(源碼簡讀)圖片請求小例子
今天來寫一個關於圖片請求的小例子,我們用NetworkImageView這個類來實現,這個類可以直接用在xml控件中,當作imageview,而且內部原理也是使用的Ima
Android OkHttp(二)實戰
Android OkHttp(一)初識,這篇文章最後提供了一個封裝Okhttp請求的類,今天就來看看在項目中具體的使用情況。一、簡單接口請求。接口請求,需要有一個服務端,
Android源碼解析ViewGroup的touch事件分發機制
概述本篇是繼上一篇Android 源碼解析View的touch事件分發機制之後的,關於ViewGroup事件分發機制的學習。同樣的,將采用案例結合源碼的方式來進行分析。前
Android Studio使用小技巧:布局預覽時填充數據
我們都知道Android Studio用起來很棒,其中布局預覽更棒。我們在調UI的時候基本是需要實時預覽來看效果的,在Android Studio中只需要切換到Desig