RecyclerView實現addHeadView的三種方法原理說明和利弊分析(footHead同理)

介紹

上一篇博客我分析了ListView的源碼看Google是怎麼樣實現addHeadView的,源碼的思路是對綁定在ListView的Adapter做轉換,在我們調用addHeadView的時候把已經寫好的BaseAdapter轉換成HeaderViewListAdapter這一組件,在代碼內部調用BaseAdapter.getView方法。這樣寫的好處是解耦和不影響我們原有代碼的前提下做好轉換。這是最好的解決方案。這幾天我從網絡上看到很多人對RecyclerView添加HeadView的理解。整理如下。

直接修改RecyclerView,Adapter

這是原文鏈接還有這篇原文鏈接感謝這兩位提供的博客
他們的思路都是修改了adapter的三個主要方法

  • getItemViewType 返回視圖類型
  • onCreateViewHolder 創建ViewHolder
  • onBindViewHolder 綁定ViewHolder內容

然後添加addHeadView和addFootView方法
在不同的position位置上做類型判斷,返回不同類型結果。

特別說明

當使用StaggeredGridLayoutManager實現瀑布流效果時需要調用setFullSpan才能讓某個位置的View佔滿格,下面是使用示例代碼。

//能夠讓某個view滿格的 setFullSpan 方法
ViewGroup.LayoutParams layoutParams=holder.mView.getLayoutParams();
if (layoutParams!=null &&layoutParams instanceof StaggeredGridLayoutManager.LayoutParams){
    StaggeredGridLayoutManager.LayoutParams params= (StaggeredGridLayoutManager.LayoutParams) layoutParams;
params.setFullSpan(holder.getLayoutPosition()==0);
}

這樣實現比較簡單。也比較能夠理解。但缺點是耦合的太高,比較影響現有代碼。比如我已經實現了adapter這樣的話就需要對整體的adapter代碼進行重構。並且這個adapter的作用也被侷限在是一個專門處理addHeadView的RecyclerView,Adapter。這裏有個GitHub地址是封裝好的Adapter

總結:耦合度太高

嵌套RecyclerView

  • 就是給RecyclerView嵌套上可滑動的父視圖,然後代碼控制Head和Foot的顯示。
  • 我簡單的嘗試一下給RecyclerView嵌套ScrollingView,代碼運行效率奇低,只要滑動就會不停的調用RecyclerView,Adapter的onCreateViewHolder產生無數個ViewHolder根本沒有Recycler的複用機制,主要是ScrollingView影響了子視圖的顯示問題。
  • 當然也有大神解決了這個問題,用其他的思路實現嵌套
    –比如這個Github地址在RecyclerView上套上FrameLayout,然後添加準備好的HeadView視圖,監聽滑動。合適的同學可以傳送過去看看給大神star。缺點是隻實現了addHeadView。
    –還有這個用CoordinatorLayout 把 header 抽離出 RecyclerView,也算是很奇特的思路。

總結:實現複雜,可能產生滑動衝突,導致問題更加複雜

重點介紹

上面總結怎麼多,重點終於來了。前面提到Google是用一個內部HeaderViewListAdapter替換我們的adapter,實現addHeadView並且代碼解耦不影響現有代碼。本來想自己寫的,後來在Github上看到已經有位大神實現了這個思路的解決方案。哪就不重複造輪子了。
隆重介紹—>GitHub地址
下面直接上源碼,—>源碼在這裏

關鍵代碼

/**
     * 設置adapter 得到綁定的adapter 賦值給內部變量adapter
     * @param adapter
     */
    public void setAdapter(RecyclerView.Adapter<RecyclerView.ViewHolder> adapter) {

        if (adapter != null) {
            if (!(adapter instanceof RecyclerView.Adapter))
                throw new RuntimeException("your adapter must be a RecyclerView.Adapter");
        }

        if (mInnerAdapter != null) {
            notifyItemRangeRemoved(getHeaderViewsCount(), mInnerAdapter.getItemCount());
            mInnerAdapter.unregisterAdapterDataObserver(mDataObserver);
        }

        this.mInnerAdapter = adapter;
        mInnerAdapter.registerAdapterDataObserver(mDataObserver);
        notifyItemRangeInserted(getHeaderViewsCount(), mInnerAdapter.getItemCount());
    }
/**
     * 根據 viewType 和 headerViewsCountCount 的數量 
     * 決定創建的 ViewHolder是 使用List<View> mHeaderViews 還是內部adapter的 onCreateViewHolder
     * @param parent
     * @param viewType
     * @return
     */
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        int headerViewsCountCount = getHeaderViewsCount();
        if (viewType < TYPE_HEADER_VIEW + headerViewsCountCount) {
            return new ViewHolder(mHeaderViews.get(viewType - TYPE_HEADER_VIEW));
        } else if (viewType >= TYPE_FOOTER_VIEW && viewType < Integer.MAX_VALUE / 2) {
            return new ViewHolder(mFooterViews.get(viewType - TYPE_FOOTER_VIEW));
        } else {
            return mInnerAdapter.onCreateViewHolder(parent, viewType - Integer.MAX_VALUE / 2);
        }
    }


/**
     * head和foot的視圖不復用 不需要特別的 onBindViewHolder
     * 只是在 使用StaggeredGridLayoutManager 瀑布流時候 讓head和foot 視圖佔據滿格 setFullSpan(true)
     * @param holder
     * @param position
     */
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        int headerViewsCountCount = getHeaderViewsCount();
        if (position >= headerViewsCountCount && position < headerViewsCountCount + mInnerAdapter.getItemCount()) {
            mInnerAdapter.onBindViewHolder(holder, position - headerViewsCountCount);
        } else {
            ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
            if(layoutParams instanceof StaggeredGridLayoutManager.LayoutParams) {
                ((StaggeredGridLayoutManager.LayoutParams) layoutParams).setFullSpan(true);
            }
        }
    }

代碼太多,只說明關鍵部分。實現的思路我在 上一篇博客 有相同的分析的源碼的思路,看不懂的點擊鏈接看博客。
我做了部分修改 ,符合我的項目使用,主要就是添加了部分功能的調用,這樣我adapter才能得到方法調用。否則我的很多adapter方法無效。

修改部分代碼


    @Override
    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
        super.onViewAttachedToWindow(holder);
        //添加代碼 需要調用內部adapter 才能收到通知
        mInnerAdapter.onViewAttachedToWindow(holder);

    }

    @Override
    public void onViewRecycled(RecyclerView.ViewHolder holder) {
        super.onViewRecycled(holder);
        //添加代碼 需要調用內部adapter 才能收到通知
        mInnerAdapter.onViewRecycled(holder);
    }

使用說明

看完上面代碼,怎麼使用你心裏也應該有底了,就是和系統幾乎一樣。來自GitHub

mHeaderAndFooterRecyclerViewAdapter = new HeaderAndFooterRecyclerViewAdapter(mDataAdapter);
        mRecyclerView.setAdapter(mHeaderAndFooterRecyclerViewAdapter);

        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

        //add a HeaderView
        RecyclerViewUtils.setHeaderView(mRecyclerView, new SampleHeader(this));

        //add a FooterView
        RecyclerViewUtils.setFooterView(mRecyclerView, new SampleFooter(this));

總結:這就是我要的方式,代碼解耦,不影響現有代碼結構。

我的使用心得

當時看到代碼很激動,但是直接使用我的項目App會啓動崩潰。拋出ClassCastException類型轉換異常。

分析

這是因爲解耦的關鍵,我的項目Adapter和HeaderAndFooterRecyclerViewAdapter沒有直接繼承關鍵,都是
extends RecyclerView.Adapter<RecyclerView.ViewHolder>
繼承自系統,算是兄弟類關係。這在ListView上就是這樣沒有問題,但是RecyclerView添加了ViewHolder,直接調用方法通知使用ViewHolder類會類型轉換異常。
道理是這樣的:

繼承中,子類可以自動轉型爲父類,但是父類強制轉換爲子類時只有當引用類型真正的身份爲子類時纔會強制轉換成功,否則失敗

解決辦法

這是我原來的Adapter類結構

public class RecyclerCardAdapter extends RecyclerView.Adapter<RecyclerCardAdapter.ViewHolder>

修改後

public class RecyclerHeadCardAdapter extends RecyclerView.Adapter

相應的類的方法也得修改

@Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 
        ViewHolderGeneral holder = null;//ViewHolder的子類

        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.cardview_item_image, parent, false);
        holder = new ViewHolderGeneral(view);//使用子類初始化ViewHolder 
        //子類可以自動轉型爲父類
        return holder;
    }
@Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
         ViewHolder viewHolder = (ViewHolder) holder;
         //強制轉化,父類轉子類
         //當父類的引用類型真正的身份爲子類時纔會強制轉換成功
         //因爲在onCreateViewHolder中是用子類初始化的父類 所以能成功
        onBindData(viewHolder, bean); //綁定操作    
    }
@Override
    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
        super.onViewAttachedToWindow(holder);

       //這是添加headView後 需要修正的position位置
        mAdapterPosition = RecyclerViewUtils.getAdapterPosition(mRecyclerView, holder);
    }

總結

  1. 這篇博客是我對RecyclerView的理解和使用,addHeadView這個方法我從ListView上找思路,在GitHub上看到一模一樣的實現。感謝Cundong大神的無私貢獻。
  2. 這個項目我花了我很多心思,希望能通過這項目提高我的代碼水平。快畢業了希望能夠找到一份好的Android開發工作。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章