Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 開源項目GridViewWithHeaderAndFooter使用和源碼分析

開源項目GridViewWithHeaderAndFooter使用和源碼分析

編輯:關於Android編程

GridViewWithHeaderAndFooter控件,可以像使用ListView一樣為GridView添加Header和Footer。

項目地址:
https://github.com/liaohuqiu/android-GridViewWithHeaderAndFooter

效果圖:

\

一、項目使用

(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()方法,同樣只附上我們需要關心的部分。
mAdapter成員變量是通過setAdapter()方法傳入進來的,當調用了addHeaderView()方法時,將headerView添加到集合mHeaderViewInfos中,再創建一個HeaderViewListAdapter對象,並將mAdapter作為參數傳遞進去。此時,ListView內部使用的adapter對象將變成HeaderViewListAdapter類型。
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()方法
addHeaderView()和addFooterView()需要在setAdapter()方法之前調用。在setAdapter()內部,會判斷如果有header或footer,就會創建一個HeaderViewGridAdapter對象。
@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類
該類是整個開源項目最核心的部分。貼上其中的主要方法,包括構造方法、getCount()、getItem()、getView()、getItemViewType()和getViewTypeCount()。
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()方法
由於HeaderView的添加,會導致真正的列表數據索引發生改變,所以item點擊事件中的position會後移。內部封裝了一個ItemClickHandler類來統一處理OnItemClickListener和OnItemLongClickListener。
@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;
    }
}

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved