源碼分析-ListView組件addHeaderView()方法的源碼解析

分析動力

最近在寫自己的項目,使用到RecyclerView這控件替代我之前常用的ListView。使用起來當然感覺比之前的ListView功能強大太多。但是目前RecyclerView卻沒有添加addHeadView()和addFooterView()這兩個列表組件常用的功能。網絡上有很多博客都有介紹他們的方法給RecyclerView添加這兩個功能,主要就是在onCreateViewHolderonBindViewHolder上做文章。作爲一個有追求的Android開發者不能直接照抄他們。也寫出一些自己的想法。所以我就回到ListView的源碼看Google是怎麼實現這功能的

addHeadView()源碼分析

源碼入口

兩個add重載方法最後來到這裏,先看英文註釋

/**
     * Add a fixed view to appear at the top of the list. If this method is
     * called more than once, the views will appear in the order they were
     * added. Views added using this call can take focus if they want.
     * <p>
     * Note: When first introduced, this method could only be called before
     * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
     * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
     * called at any time. If the ListView's adapter does not extend
     * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
     * instance of {@link WrapperListAdapter}.
     *
     * @param v The view to add.
     * @param data Data to associate with this view
     * @param isSelectable whether the item is selectable
     */
    public void addHeaderView(View v, Object data, boolean isSelectable) {}

先看方法的註釋部分,英文大概意思是:這是給adapter添加一個頂級視圖的方法,有使用提示和參數說明。

ListView採用MVC模式 模型(model)-視圖(view)-控制器(controller)
ListView負責UI顯示是View層
構造Adapter時傳入的Data數據是Model層,是數據的來源。
Adapter適配器負責控制View層需要顯示什麼樣的UI,是最關鍵的Controller層。

所以給ListView添加head其實就是在adapter上做修改。note:提示這個方法要在綁定適配器setAdapter()之前調用。

源碼

再看裏面的源碼,代碼不多。主要分三步操作

 public void addHeaderView(View v, Object data, boolean isSelectable) {
         //第一步 數據的初始化和賦值 並添加到頭部視圖list中
        final FixedViewInfo info = new FixedViewInfo();
        info.view = v;
        info.data = data;
        info.isSelectable = isSelectable;
        mHeaderViewInfos.add(info);
        mAreAllItemsSelectable &= isSelectable;

        // Wrap the adapter if it wasn't already wrapped.
        if (mAdapter != null) {
            //第二步 關鍵點
            //給mAdapter賦值成HeaderViewListAdapter對象 並把head、foot視圖列表和原來的adapter傳進去 
            //所以這裏就是關鍵 從這裏進入看HeaderViewListAdapter類的源碼
            if (!(mAdapter instanceof HeaderViewListAdapter)) {
                mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
            }
            //第三步 被觀察者發出修改通知
            // In the case of re-adding a header view, or adding one later on,
            // we need to notify the observer.
            if (mDataSetObserver != null) {
                mDataSetObserver.onChanged();
            }
        }
    }

FixedViewInfo類

是對HeadView視圖的包裝,裏面封裝了三個字段。

 /**
     * A class that represents a fixed view in a list, for example a header at the top
     * or a footer at the bottom.
     */
    public class FixedViewInfo {
        /** The view to add to the list */
        public View view;
        /** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */
        public Object data;
        /** <code>true</code> if the fixed view should be selectable in the list */
        public boolean isSelectable;
    }

HeaderViewListAdapter的源碼分析

內部對象

類中保存有3個關鍵內部對象

public class HeaderViewListAdapter implements WrapperListAdapter, Filterable {
    //關鍵對象-->開始
    private final ListAdapter mAdapter;

    // These two ArrayList are assumed to NOT be null.
    // They are indeed created when declared in ListView and then shared.
    ArrayList<ListView.FixedViewInfo> mHeaderViewInfos;
    ArrayList<ListView.FixedViewInfo> mFooterViewInfos;
    //關鍵對象-->結束
    // Used as a placeholder in case the provided info views are indeed null.
    // Currently only used by some CTS tests, which may be removed.
    //處理空 ArrayList<ListView.FixedViewInfo>的靜態不可變對象 作用是內存優化 值得學習
    static final ArrayList<ListView.FixedViewInfo> EMPTY_INFO_LIST =
        new ArrayList<ListView.FixedViewInfo>();

    boolean mAreAllFixedViewsSelectable;

    private final boolean mIsFilterable;

構造函數

三個參數的構造函數,負責初始化和賦值

public HeaderViewListAdapter(ArrayList<ListView.FixedViewInfo> headerViewInfos,
                                 ArrayList<ListView.FixedViewInfo> footerViewInfos,
                                 ListAdapter adapter) {
        mAdapter = adapter;
        mIsFilterable = adapter instanceof Filterable;

        if (headerViewInfos == null) {
        //如果爲null 內部對象被靜態不可變量賦值 避免new出新對象和非空處理 值得學習
            mHeaderViewInfos = EMPTY_INFO_LIST;
        } else {
            mHeaderViewInfos = headerViewInfos;
        }

        if (footerViewInfos == null) {
            mFooterViewInfos = EMPTY_INFO_LIST;
        } else {
            mFooterViewInfos = footerViewInfos;
        }

        mAreAllFixedViewsSelectable =
                areAllListInfosSelectable(mHeaderViewInfos)
                && areAllListInfosSelectable(mFooterViewInfos);
    }

添加head和foot視圖的關鍵

在getView上做修改

public View getView(int position, View convertView, ViewGroup parent) {
        // Header (negative positions will throw an IndexOutOfBoundsException)
        int numHeaders = getHeadersCount();
        //從position從0開始取view 先進入這裏從FixedViewInfo對象中直接取出View對象作爲返回值
        if (position < numHeaders) {
            return mHeaderViewInfos.get(position).view;
        }

        //普通的itemView視圖 從傳入的mAdapter的getView()方法中取 這步一般是我們自己寫的BaseAdapter.getView(),就是我們重寫的部分,由我們的代碼控制
        // Adapter
        final int adjPosition = position - numHeaders;
        int adapterCount = 0;
        if (mAdapter != null) {
            adapterCount = mAdapter.getCount();
            if (adjPosition < adapterCount) {
                return mAdapter.getView(adjPosition, convertView, parent);
            }
        }
        //foot視圖同Head原理
        // Footer (off-limits positions will throw an IndexOutOfBoundsException)
        return mFooterViewInfos.get(adjPosition - adapterCount).view;
    }

其他的方法

和一般的adapter沒有太大的差別,不是我們分析點。

總結

  1. 從這幾步的源碼分析,我們可以得知,ListView的addHeadView(),其實就是修改adapter的getView()方法。在使用時包裝HeaderViewListAdapter類有專門處理有head和foot的方法。這種思路和網絡上大多數的博客差不多,應該大家都是從源碼中找到思路的吧。
  2. 從ListView源碼上得到的思路,其實也可以運用在RecyclerView上,Google官方沒有提供我們就自己動手。當然RecyclerView比ListView要複雜它在MVC的C層分離出 RecyclerView.AdapterRecyclerView.LayoutManager,分別控制View的顯示內容和佈局位置。通過上面的分析得到的思路接下來我要寫個有addHeadView和addFootView的RecyclerView。
  3. 這裏有對RecyclerView使用addHeadView的分析整理
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章