編輯:關於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再來看看ListView類的addHeaderView()方法,同樣只附上我們需要關心的部分。mHeaderViewInfos; ArrayList mFooterViewInfos; public HeaderViewListAdapter(ArrayList headerViewInfos, ArrayList footerViewInfos, ListAdapter adapter) { mAdapter = adapter; mHeaderViewInfos = headerViewInfos; mFooterViewInfos = footerViewInfos; } }
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 ArrayList4.setOnItemClickListener()和setOnItemLongClickListener()方法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; } }
@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; } }
今天來寫一個關於圖片請求的小例子,我們用NetworkImageView這個類來實現,這個類可以直接用在xml控件中,當作imageview,而且內部原理也是使用的Ima
Android OkHttp(一)初識,這篇文章最後提供了一個封裝Okhttp請求的類,今天就來看看在項目中具體的使用情況。一、簡單接口請求。接口請求,需要有一個服務端,
概述本篇是繼上一篇Android 源碼解析View的touch事件分發機制之後的,關於ViewGroup事件分發機制的學習。同樣的,將采用案例結合源碼的方式來進行分析。前
我們都知道Android Studio用起來很棒,其中布局預覽更棒。我們在調UI的時候基本是需要實時預覽來看效果的,在Android Studio中只需要切換到Desig