欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

高性能的给RecyclerView加上HeaderView和FooterView

程序员文章站 2022-06-16 08:42:32
...

源码地址: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即可

高性能的给RecyclerView加上HeaderView和FooterView

高性能的给RecyclerView加上HeaderView和FooterView

高性能的给RecyclerView加上HeaderView和FooterView,有问题欢迎留言交流。