高效能的給RecyclerView加上HeaderView和FooterView
原始碼地址:https://github.com/bravinshi/AdvancedRecyclerView
自己寫的是對於他人的改進,修復了若干問題。
建議閱讀之前先讀一下這兩篇文章
1,http://blog.csdn.net/lmj623565791/article/details/51854533
2,http://blog.csdn.net/zxt0601/article/details/52267325
在此先感謝上面兩篇文章的作者,做IT,本來就是要互相學習嘛。
先說下二篇文章中的wrapper存在的問題。
第一篇鴻翔大神的文章
1,大神是把header和footer直接以View的形式引用的
private SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>(); private SparseArrayCompat<View> mFootViews = new SparseArrayCompat<>();
並且新增View的時候,type是漸漸累加的
public void addFootView(View view)
{
mFootViews.put(mFootViews.size() + BASE_ITEM_TYPE_FOOTER, view);
}
這會導致什麼情況呢,很簡單,如果加上20個View作為Header,如果介面滑到底,首先記憶體中就確確實實存在20個Header不會被GC掉,因為引用還是在的。另外RecyclerView的快取邏輯和ListView的快取邏輯相似,它們都會給不同的Type開闢一個單獨的ArrayList,原始碼如下
private ArrayList<ViewHolder> getScrapHeapForType(int viewType) { ArrayList<ViewHolder> scrap = mScrap.get(viewType); if (scrap == null) { scrap = new ArrayList<ViewHolder>(); mScrap.put(viewType, scrap); if (mMaxScrap.indexOfKey(viewType) < 0) { mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP); } } return scrap; }
也就是說,對於一個Header來說mHeaderViews裡面引用了一次,另外還建立了一個單獨的快取用ArrayList,而這個ArrayList裡面只會放一個Holder,就是這個type對應的Holder。那麼Holder是什麼?Holder其實就是一個View,它在這裡可以看成是Header的一個copy。那麼如果有20個Header,那麼記憶體裡有20個Header,還要有20個ArrayList要存放他們的Holder,結果就是記憶體瞬間爆炸!!!
2
這裡的mInnerAdapter是一個Adapter,而大神的wrapper本身也是繼承了Adapter,這個看起來算是很奇怪的。而mInnerAdater在這個類裡面是不會被設定為某個RecyclerView的adapter的,因為在使用的時候是把這個wrapper設定為RecyclerView的dapter的,這裡面mInnerAdapter只是僅僅作為非headerView和footerView的資料的載體而已。那為什麼不自己寫一個ArrayList代替卻用一個更重量的Adapter呢。
3,泛型沒必要。
第二篇
1,首先還有和鴻翔大神一樣的問題,就是mInnerAdater容器化,和它本身的設計意義不符,所以完全可以去掉。
2,作者為了讓Header能夠被GC掉而設定header的快取數是0,首先能不能被GC是要打問號的,另外,如果Header足夠大,跨多個螢幕,那麼,把Header滑出介面後在滑回來,這個Header是要重新建立的,就算被GC掉了還要重新建立,那這就是典型的時間換空間,由於是不一定被GC掉,那麼這買賣就算虧了啊。怒 (ノ`ー´)ノ・・・~~┻━┻,這個和RecyclerView的設計初衷相違背,感覺沒必要這麼做。
3,Footer還是和鴻翔大神的一樣,一個footer單獨對應一個Type,這樣記憶體爆炸的很快。
4,泛型沒必要。
好了,客官們要問了,怎麼改?
來來來,直接上程式碼
package com.yalantis.phoenix.wrapper;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.ViewGroup;
import java.util.ArrayList;
/**
* Created by shijianguo on 2017/8/25.
*/
public abstract class HeaderAndFooterWrapper extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int BASE_ITEM_TYPE_FOOTER = 2000000;//footerView預設type
private static final int BASE_ITEM_TYPE_HEADER = 1000000;// headerView預設type
private static final int BASE_ITEM_TYPE_GENERAL= 0;// 一般view預設type 0也是android原始碼中的預設的type值
private ArrayList<DataBean> mHeaders = new ArrayList<>();
private ArrayList<DataBean> mFooters = new ArrayList<>();
private ArrayList<DataBean> mGeneralData = new ArrayList<>();
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (isHeaderType(viewType)){
return onCreateHeaderViewHolder(parent,viewType);
}else if(isFooterType(viewType)){
return onCreateFooterViewHolder(parent,viewType);
}
return onCreateGeneralViewHolder(parent,viewType);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (isHeaderViewPos(position)){
onBindHeaderViewHolder(holder,position);
}else if(isFooterViewPos(position)){
onBindFooterViewHolder(holder,position - getHeaderViewCount() - getInnerItemCount());
}else {
onBindGeneralViewHolder(holder,position - getHeaderViewCount());
}
}
public abstract RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent, int viewType);
public abstract RecyclerView.ViewHolder onCreateFooterViewHolder(ViewGroup parent, int viewType);
public abstract RecyclerView.ViewHolder onCreateGeneralViewHolder(ViewGroup parent, int viewType);
public abstract void onBindHeaderViewHolder(RecyclerView.ViewHolder holder, int position);
public abstract void onBindFooterViewHolder(RecyclerView.ViewHolder holder, int position);
public abstract void onBindGeneralViewHolder(RecyclerView.ViewHolder holder, int position);
@Override
public int getItemCount() {
return getInnerItemCount() + getHeaderViewCount() + getFooterViewCount();
}
public int getHeaderViewCount() {
return mHeaders.size();
}
public int getFooterViewCount() {
return mFooters.size();
}
private int getInnerItemCount() {
return mGeneralData.size();
}
private boolean isHeaderViewPos(int position) {
return getHeaderViewCount() > position;
}
private boolean isFooterViewPos(int position) {
return position >= getHeaderViewCount() + getInnerItemCount();
}
private boolean isHeaderType(int type){
if (mHeaders.size() > 0){
for (DataBean bean : mHeaders){
if(bean.getType() == type){
return true;
}
}
}
return false;
}
private boolean isFooterType(int type){
if (mFooters.size() > 0){
for (DataBean bean : mFooters){
if(bean.getType() == type){
return true;
}
}
}
return false;
}
@Override
public int getItemViewType(int position) {
if (isHeaderViewPos(position)) {
return mHeaders.get(position).getType();
} else if (isFooterViewPos(position)) {
return mFooters.get(position - getHeaderViewCount() - getInnerItemCount()).getType();
}
return mGeneralData.get(position - getHeaderViewCount()).getType();
}
public void addHeader(Object data){
addHeader(data,BASE_ITEM_TYPE_HEADER);
}
public void addHeader(Object data,int type){
mHeaders.add(new DataBean(data,type));
}
public void addFooter(Object data){
addFooter(data,BASE_ITEM_TYPE_FOOTER);
}
public void addFooter(Object data,int type){
mFooters.add(new DataBean(data,type));
}
public void addGeneral(Object data){
addGeneral(data,BASE_ITEM_TYPE_GENERAL);
}
public void addGeneral(Object data,int type){
mGeneralData.add(new DataBean(data,type));
}
private class DataBean {
private Object data;
private int type;
private DataBean(Object data, int type){
super();
this.data = data;
this.type = type;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
//為了相容GridLayout
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
final GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (isHeaderViewPos(position)) {
return gridLayoutManager.getSpanCount();
} else if (isFooterViewPos(position)) {
return gridLayoutManager.getSpanCount();
}
if (spanSizeLookup != null)
return spanSizeLookup.getSpanSize(position);
return 1;
}
});
gridLayoutManager.setSpanCount(gridLayoutManager.getSpanCount());
}
}
@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
super.onViewAttachedToWindow(holder);
int position = holder.getLayoutPosition();
if (isHeaderViewPos(position) || isFooterViewPos(position)) {
ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
if (lp != null
&& lp instanceof StaggeredGridLayoutManager.LayoutParams) {
StaggeredGridLayoutManager.LayoutParams p =
(StaggeredGridLayoutManager.LayoutParams) lp;
p.setFullSpan(true);
}
}
}
}
首先說下DataBean存在的意義,DataBean是把資料本身和type做了分離,這樣type由coder本身定義,不和資料本身有強繫結。
innerAdapter被一個ArrayList替代,這樣降低了記憶體開銷,Header和Footer也是ArrayList,這樣data和view解耦。
說一下我嘗試新增泛型時遇見的一個問題。我嘗試新增三個泛型對應HeaderViewHolder,FooterViewHolder和中間的view用到的GeneralViewHolder,但是由於系統本身的Adapter泛型只能新增一個ViewHoler,另外這個泛型是onCreateViewHoler的返回型別。header和footer的viewholer都是也要用這個方法建立的,這樣導致單獨新增HeaderViewHolder和FooterViewHolder沒有意義(沒法返回啊),那隻好返回RecyvlerView.ViewHolder,coder在用的時候只需要自己強轉就好。
說下怎麼用,非常簡單的好麼。直接上程式碼
package com.yalantis.phoenix.sample;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.yalantis.phoenix.AdvancedRecyclerView;
import com.yalantis.phoenix.viewholder.MyViewHolder;
import com.yalantis.phoenix.wrapper.HeaderAndFooterWrapper;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by shijianguo on 2017/8/18.
*/
public class AdvancedRecyclerActivity extends AppCompatActivity {
public static final String KEY_ICON = "icon";
public static final String KEY_COLOR = "color";
protected List<Map<String, Integer>> mSampleList;
private AdvancedRecyclerView mRecyclerView;
private MyHeaderAndFooterWrapper myAdapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recycler_view);
myAdapter = new MyHeaderAndFooterWrapper();
myAdapter.addHeader(0);
myAdapter.addHeader(0);
myAdapter.addHeader(0);
myAdapter.addHeader(0);
myAdapter.addHeader(0);
myAdapter.addHeader(0);
myAdapter.addHeader(0);
myAdapter.addHeader(0);
myAdapter.addHeader(0);
myAdapter.addHeader(0);
myAdapter.addHeader(0);
myAdapter.addFooter(0);
myAdapter.addFooter(0);
myAdapter.addFooter(0);
myAdapter.addFooter(0);
myAdapter.addFooter(0);
myAdapter.addFooter(0);
myAdapter.addFooter(0);
myAdapter.addFooter(0);
myAdapter.addFooter(0);
myAdapter.addFooter(0);
myAdapter.addGeneral(0);
myAdapter.addGeneral(0);
myAdapter.addGeneral(0);
myAdapter.addGeneral(0);
myAdapter.addGeneral(0);
myAdapter.addGeneral(0);
myAdapter.addGeneral(0);
myAdapter.addGeneral(0);
myAdapter.addGeneral(0);
myAdapter.addGeneral(0);
Map<String, Integer> map;
mSampleList = new ArrayList<>();
int[] icons = {
R.drawable.icon_1,
R.drawable.icon_2,
R.drawable.icon_3};
int[] colors = {
R.color.saffron,
R.color.eggplant,
R.color.sienna};
for (int i = 0; i < 5; i++) {
map = new HashMap<>();
map.put(KEY_ICON, icons[i%3]);
map.put(KEY_COLOR, colors[i%3]);
mSampleList.add(map);
}
mRecyclerView = (AdvancedRecyclerView) findViewById(R.id.recycler_view);
mRecyclerView.setLayoutManager(new GridLayoutManager(this,2));
mRecyclerView.setAdapter(myAdapter);
}
class MyHeaderAndFooterWrapper extends HeaderAndFooterWrapper{
@Override
public MyViewHolder onCreateHeaderViewHolder(ViewGroup parent, int viewType) {
TextView view = new TextView(parent.getContext());
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,400));
return MyViewHolder.createViewHolder(parent.getContext(),view);
}
@Override
public MyViewHolder onCreateFooterViewHolder(ViewGroup parent, int viewType) {
TextView view = new TextView(parent.getContext());
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,400));
return MyViewHolder.createViewHolder(parent.getContext(),view);
}
@Override
public RecyclerView.ViewHolder onCreateGeneralViewHolder(ViewGroup parent, int viewType) {
return new MyViewHolder1(LayoutInflater.from(
AdvancedRecyclerActivity.this).inflate(R.layout.list_item, parent,
false));
}
@Override
public void onBindHeaderViewHolder(RecyclerView.ViewHolder holder, int position) {
((TextView)holder.itemView).setText("這是一個Header");
}
@Override
public void onBindFooterViewHolder(RecyclerView.ViewHolder holder, int position) {
((TextView)holder.itemView).setText("這是一個Footer");
}
@Override
public void onBindGeneralViewHolder(RecyclerView.ViewHolder holder, int position) {
((MyViewHolder1)holder).imageViewIcon.setImageResource(mSampleList.get(position % 3).get(KEY_ICON));
((MyViewHolder1)holder).itemView.setBackgroundResource(mSampleList.get(position % 3).get(KEY_COLOR));
}
}
class MyViewHolder1 extends RecyclerView.ViewHolder
{
ImageView imageViewIcon;
MyViewHolder1(View view)
{
super(view);
imageViewIcon = (ImageView) view.findViewById(R.id.image_view_icon);
}
}
}
點此獲取原始碼,把simple中的配置檔案中的Activity入口改為AdvancedRecyclerActivity即可
,有問題歡迎留言交流。