一次项目优化的体验--RecyclerView

时光匆匆,又一个版本上线了,迎来了较为轻松的一段时间。我有个习惯,在赶项目的开发过程中遇到fuck code会把它们记下来,趁着这段时间把开发过程中感觉自己写fuck code或者看到的别人写的fuck code拿出来优化一下!个人觉得这是个不错的习惯,能优化不少细节,提升能力,下次在遇到类似场景就能快速转化少写一些让人大呼“这是什么鬼”的代码。

主题锁定这次的RecyclerView。我们经常会在首页或者个人中心展示一个个Item,这些Item可能各不相同,可能有不同的类型每个类型又都只有几个item,又或者样式近乎一样的Item。如下:
这里写图片描述
这里写图片描述
遇到这样一个界面,试问你们会怎么进行code呢?

方案一:

每个Item都独立写一个layout,然后include进来,在界面里一个个findViewById。这样做所有代码会全部放到一个Activity/Fragment中,一堆的View,如果界面复杂,将导致整个类巨庞大。
变种:每个Item都独立写一个layout,然后每个item的layout都建一个数据类bean,每个数据类bean除了数据(get/set)还负责加载这个layout生成view并赋值,最终产出一个view。在Activity/fragment中需要根据需要addView即可。

方案二:

用RecyclerView/ListView做容器,根据type类型加载不同的Item。这也是常见的做法,下面是一个简单的例子

MultiAdapter.java

public class MultiAdapter extends RecyclerView.Adapter<ViewHolderBase> implements View.OnClickListener{

    RecyclerView recyclerView;
    private Context mContext;
    private LayoutInflater mInflater;
    private List<ItemBase> mItemBaseList = new ArrayList<>();
    public MultiAdapter(RecyclerView recyclerView,IMultiControllerListener iMultiControllerListener){
        this.recyclerView = recyclerView;
        this.iMultiControllerListener = iMultiControllerListener;
        mContext = recyclerView.getContext();
        mInflater = LayoutInflater.from(mContext);
    }

    public void setItems(List<ItemBase> list){
        mItemBaseList = list;
    }




    @Override
    public ViewHolderBase onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = mInflater.inflate(iMultiControllerListener.bindLayout(viewType),null);
        itemView.setOnClickListener(this);
        return new ViewHolderBase(this,itemView);
    }

    @Override
    public void onBindViewHolder(ViewHolderBase holder, int position) {
        iMultiControllerListener.bindItem(holder,mItemBaseList.get(position),position);
        holder.itemView.setTag(position);
    }

    @Override
    public int getItemViewType(int position) {
        return mItemBaseList.get(position).getViewType();
    }

    @Override
    public int getItemCount() {
        return mItemBaseList==null ? 0 : mItemBaseList.size();
    }

    IMultiControllerListener iMultiControllerListener;

    @Override
    public void onClick(View v) {
        iMultiControllerListener.itemClick((Integer) v.getTag());
    }

    interface IMultiControllerListener
    {
        int bindLayout(int viewType);
        void bindItem(ViewHolderBase holder,ItemBase itemBase,int position);
        void itemClick(int position);
    }
}

这边的处理主要是把RecyclerView中的绑定layout、数据赋值、点击事件都通过一个接口暴露出去,方便外部使用

ViewHolderBase.java

public class ViewHolderBase extends RecyclerView.ViewHolder{

    private MultiAdapter adapter;
    private View contentView;
    /*** 保存所有<id,view> 包括layout的 ***/
    private SparseArray<View> holderViews = new SparseArray<>();

    public ViewHolderBase(MultiAdapter adapter, View itemView) {
        super(itemView);
        this.adapter = adapter;
        contentView = itemView;
    }

    public <T extends View> T get(Class<T> clazz,int id){
        View view = holderViews.get(id);
        if(view != null){
            return (T) view;
        }
        view = contentView.findViewById(id);
        if(view != null){
            holderViews.put(id,view);
        }
        return (T) view;
    }


}

然后是几个bean

ItemBase.java

public abstract class ItemBase {
    public abstract int getViewType();
}

ImgItem.java

public class ImgItem extends ItemBase {

    public static final int IMG_ITEM_TYPE = 1;
    private String title;
    private int imgRes;

    public ImgItem(String title, int imgRes) {
        this.title = title;
        this.imgRes = imgRes;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public int getImgRes() {
        return imgRes;
    }

    public void setImgRes(int imgRes) {
        this.imgRes = imgRes;
    }

    @Override
    public int getViewType() {
        return IMG_ITEM_TYPE;
    }

}

TxtItem.java

public class TxtItem extends  ItemBase{

    public static final int TXT_ITEM_TYPE = 2;
    private String title;
    private String summary;

    public TxtItem(String title, String summary) {
        this.title = title;
        this.summary = summary;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getSummary() {
        return summary;
    }

    public void setSummary(String summary) {
        this.summary = summary;
    }

    @Override
    public int getViewType() {
        return TXT_ITEM_TYPE;
    }
}

最后是Activity/Fragment中的使用

public class TMulitiRecyclerView  extends BaseActivity {

    RecyclerView mRecyclerView;
    MultiAdapter mMultiAdapter;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_muliti_recycler);
        mRecyclerView = findViewById(R.id.activity_multi_recycler);
        initRecyclerView();
    }

    private void initRecyclerView() {
        mMultiAdapter = new MultiAdapter(mRecyclerView,iMultiControllerListener);
        mRecyclerView.addItemDecoration(new DividerItemDecoration(this,
                DividerItemDecoration.VERTICAL));
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mRecyclerView.setAdapter(mMultiAdapter);
        initData();

        ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);
        itemTouchHelper.attachToRecyclerView(mRecyclerView);

    }

    private MultiAdapter.IMultiControllerListener iMultiControllerListener = new MultiAdapter.IMultiControllerListener() {
        @Override
        public int bindLayout(int viewType) {
            switch (viewType){
                case TxtItem.TXT_ITEM_TYPE:
                    return R.layout.item_txt;
                case ImgItem.IMG_ITEM_TYPE:
                    return R.layout.item_img;
            }
            return 0;
        }

        @Override
        public void bindItem(ViewHolderBase holder,ItemBase itemBase, int position) {
            int viewType = itemBase.getViewType();
            switch (viewType){
                case TxtItem.TXT_ITEM_TYPE:
                    if(itemBase instanceof TxtItem){
                        holder.get(TextView.class, R.id.txt_title).setText(((TxtItem) itemBase).getTitle());
                        holder.get(TextView.class,R.id.txt_summary).setText(((TxtItem) itemBase).getSummary());
                    }
                    break;
                case ImgItem.IMG_ITEM_TYPE:
                    if(itemBase instanceof ImgItem){
                        holder.get(ImageView.class, R.id.img_img).setImageResource(((ImgItem) itemBase).getImgRes());
                        holder.get(TextView.class,R.id.img_title).setText(((ImgItem) itemBase).getTitle());
                    }
                    break;
            }
        }

        @Override
        public void itemClick(int position) {
            Log.d("itemClick","position :" +position);
        }
    };

    private void initData() {
        List<ItemBase> itemBaseList = new ArrayList<>();
        for(int i=0;i<5;i++){
            itemBaseList.add(new ImgItem("img"+i,R.mipmap.ic_launcher));
            itemBaseList.add(new TxtItem("title"+i,"summary"));
        }
        mMultiAdapter.setItems(itemBaseList);
    }
}

布局文件就不贴了,一个RecyclerView即可。为了简单演示,并没有注意内存泄露的处理哦。可以发现这样处理后整个结构已经比较清晰,但是在实际项目中,往往我们每个Item都是无比复杂的、多元素的,这个时候你就会发现这个结构还是太过庞大而且不利于代码查找和理解。

变种一:
这个变种的想法是把所有Item的layout绑定、数据赋值、click处理都放到Bean中。

MultiAdapter.java

public class MultiAdapter extends RecyclerView.Adapter<ViewHolderBase> implements View.OnClickListener{

    RecyclerView recyclerView;
    private Context mContext;
    private LayoutInflater mInflater;
    private List<ItemBase> mItemBaseList = new ArrayList<>();
    public MultiAdapter(RecyclerView recyclerView){
        this.recyclerView = recyclerView;
        mContext = recyclerView.getContext();
        mInflater = LayoutInflater.from(mContext);
    }

    public void setItems(List<ItemBase> list){
        mItemBaseList = list;
    }




    @Override
    public ViewHolderBase onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = mInflater.inflate(ItemBase.bindLayout(viewType),null);
        itemView.setOnClickListener(this);
        return new ViewHolderBase(this,itemView);
    }

    @Override
    public void onBindViewHolder(ViewHolderBase holder, int position) {
        holder.onBindViewHolder(mItemBaseList.get(position));
        holder.itemView.setTag(position);
    }

    @Override
    public int getItemViewType(int position) {
        return mItemBaseList.get(position).getViewType();
    }

    @Override
    public int getItemCount() {
        return mItemBaseList==null ? 0 : mItemBaseList.size();
    }


    @Override
    public void onClick(View v) {
        mItemBaseList.get((Integer) v.getTag()).onClick((Integer)v.getTag());
    }

}

一样是把layout绑定、数据赋值、点击事件提取出来,但这次是传递给每个Item的Bean。其中layout的绑定放在ItemBase中,这部分一直没有想到比较好的idea,也可以换成和之前一样用接口来进行暴露。如果你有好的idea也欢迎留言,thk!

ViewHolderBase.java

public class ViewHolderBase extends RecyclerView.ViewHolder{

    private MultiAdapter adapter;
    private View contentView;
    /*** 保存所有<id,view> 包括layout的 ***/
    private SparseArray<View> holderViews = new SparseArray<>();

    public ViewHolderBase(MultiAdapter adapter, View itemView) {
        super(itemView);
        this.adapter = adapter;
        contentView = itemView;
    }

    public <T extends View> T get(Class<T> clazz,int id){
        View view = holderViews.get(id);
        if(view != null){
            return (T) view;
        }
        view = contentView.findViewById(id);
        if(view != null){
            holderViews.put(id,view);
        }
        return (T) view;
    }


    public void onBindViewHolder(ItemBase itemBase) {
        itemBase.onBindView(this);
    }
}

接下来是Bean

ItemBase.java TxtItem.java ImgItem.java

public abstract class ItemBase {
    public static final int TXT_ITEM_TYPE = 1;
    public static final int IMG_ITEM_TYPE = 2;
    public abstract int getViewType();
    public static int bindLayout(int viewType) {
        switch (viewType){
            case TXT_ITEM_TYPE:
                return R.layout.item_txt;
            case ItemBase.IMG_ITEM_TYPE:
                return R.layout.item_img;
        }
        return 0;
    }

    public abstract void onBindView(ViewHolderBase viewHolderBase);

    public abstract void onClick(int position);
}
public class TxtItem extends ItemBase {

    private String title;
    private String summary;

    @Override
    public void onBindView(ViewHolderBase holder) {
        holder.get(TextView.class, R.id.txt_title).setText(title);
        holder.get(TextView.class,R.id.txt_summary).setText(summary);
    }

    @Override
    public void onClick(int position) {
        Log.d("th","txtitem position : "+position);
    }

    public TxtItem(String title, String summary) {
        this.title = title;
        this.summary = summary;
    }


    @Override
    public int getViewType() {
        return TXT_ITEM_TYPE;
    }
}
public class ImgItem extends ItemBase {

    private String title;
    private int imgRes;




    @Override
    public void onBindView(ViewHolderBase holder) {
        holder.get(TextView.class, R.id.img_title).setText(title);
        holder.get(ImageView.class,R.id.img_img).setImageResource(imgRes);
    }

    @Override
    public void onClick(int position) {
        Log.d("th","imgitem position : "+position);
    }

    public ImgItem(String title, int imgRes) {
        this.title = title;
        this.imgRes = imgRes;
    }

    @Override
    public int getViewType() {
        return IMG_ITEM_TYPE;
    }

}

最后是 Activity/Fragment 中的使用

THMulitiRecyclerView.java

public class THMulitiRecyclerView extends BaseActivity {

    RecyclerView mRecyclerView;
    MultiAdapter mMultiAdapter;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_muliti_recycler);
        mRecyclerView = findViewById(R.id.activity_multi_recycler);
        initRecyclerView();
    }

    private void initRecyclerView() {
        mMultiAdapter = new MultiAdapter(mRecyclerView);
        mRecyclerView.addItemDecoration(new DividerItemDecoration(this,
                DividerItemDecoration.VERTICAL));
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mRecyclerView.setAdapter(mMultiAdapter);
        initData();

        ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);
        itemTouchHelper.attachToRecyclerView(mRecyclerView);

    }

    private void initData() {
        List<ItemBase> itemBaseList = new ArrayList<>();
        for(int i=0;i<5;i++){
            itemBaseList.add(new ImgItem("img"+i,R.mipmap.ic_launcher));
            itemBaseList.add(new TxtItem("title"+i,"summary"));
        }
        mMultiAdapter.setItems(itemBaseList);
    }
}

变种二:
这种变种适合Item数量有限,有共同的特征属性,且非常之复杂。核心就是把共同的部分抽取出来,同时对Item进行统一管理(定时刷新机制、竞争排名机制、显示隐藏控制、控件伸展控制等,示例中这部分被删了,代码量太大,但预留出了几个控制属性,有兴趣可自行扩展实现)。

MultiAdapter.java

public class MultiAdapter extends RecyclerView.Adapter<ViewHolderBase>{

    private RecyclerView mRecyclerView;
    private Context mContext;
    public LayoutInflater mInflater;
    private List<ItemBase> mItemBaseList = new ArrayList<>();

    public MultiAdapter(RecyclerView recyclerView){
        if (recyclerView == null) {
            throw new NullPointerException("recyclerView is null");
        }
        mRecyclerView = recyclerView;
        mContext = mRecyclerView.getContext();
        mInflater = LayoutInflater.from(mContext);
    }


    @Override
    public ViewHolderBase onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = mInflater.inflate(R.layout.item_base,parent,false);
        return new ViewHolderBase(this,itemView);
    }


    @Override
    public void onBindViewHolder(ViewHolderBase holder, int position) {
        /*** 布局传递到ViewHolderBase中进行处理 ***/
        holder.onBindViewHolder(mItemBaseList.get(position));
    }

    public List<ItemBase> getmItemBaseList() {
        return mItemBaseList;
    }
    public void setmItemBaseList(List<ItemBase> mItemBaseList) {
        this.mItemBaseList = mItemBaseList;
    }
    public void insertItem(ItemBase item ){
        mItemBaseList.add(item);
    }

    @Override
    public int getItemCount() {
        return mItemBaseList==null? 0 : mItemBaseList.size();
    }
}

ViewHolderBase.java

class ViewHolderBase extends RecyclerView.ViewHolder{
    private MultiAdapter mMultiAdapter;

    private TextView mTvTitle;
    private FrameLayout mFlContent;
    private Context mContext;
    /*** 保存所有<id,view> 包括layout的 ***/
    private SparseArray<View> holderViews = new SparseArray<>();
    private ItemBase base;

    public ViewHolderBase(MultiAdapter adapter , View itemView) {
        super(itemView);
        mMultiAdapter = adapter;
        mTvTitle = itemView.findViewById(R.id.item_base_title);
        mFlContent = itemView.findViewById(R.id.item_base_content);
        mContext = itemView.getContext();
    }


    public synchronized void onBindViewHolder(ItemBase itemBase) {
        if(!itemBase.isInit){
            itemBase.isInit = true;
            itemBase.onCreateView(mContext);
        }

        int layoutResId = itemBase.getLayout();
        boolean replace = base == null || base.getLayout() != layoutResId || base.ID != itemBase.ID;
        base = itemBase;
        if(replace){
            View view = holderViews.get(layoutResId);
            if(view == null){
                view = mMultiAdapter.mInflater.inflate(layoutResId,mFlContent,false);
                holderViews.put(itemBase.layoutRes,view);
            }
            if (mFlContent != null){
                mFlContent.removeAllViews();
                clearCardHolder();
            }
            if(view != null) {
                mFlContent.addView(view);
            }
        }

        mTvTitle.setText(itemBase.getTitle());
        itemBase.onBindView(this);
    }

    private void clearCardHolder() {
        SparseArray<View> holderMap = (SparseArray) mFlContent.getTag();
        if (holderMap != null) {
            holderMap.clear();
        }
    }

    public <T extends View> T get(Class<T> clazz, int id) {
        SparseArray<View> holderMap = (SparseArray) mFlContent.getTag();
        if (holderMap == null) {
            holderMap = new SparseArray<View>();
            mFlContent.setTag(holderMap);
        }
        View view = holderMap.get(id);
        if (view != null) {
            return (T) view;
        }
        view = mFlContent.findViewById(id);
        if (view != null) {
            holderMap.put(id, view);
        }
        return (T) view;
    }
}

Bean类

ItemBase.java ImgItem.java TxtItem.java StudentItem.java

abstract class ItemBase {

    public boolean isInit = false;
    public static final int ID_UNSET = -1;
    public static final int ID_IMG = 1;
    public static final int ID_TXT = 2;
    public static final int ID_STU = 3;
    public int ID = ID_UNSET;

    private Context mContext;
    public int layoutRes;
    private LayoutInflater mInflater;

    //共同属性 (这部分属性都是每个Item都有的,可以在多加一个控制类来进行控制,由子类进行行为申请,控制类统一规划)
    private String title;
    public int order = -1;
    public boolean isHide = false;
    public boolean isNeedRefresh = false;

    public ItemBase(int ID) {
        this.ID = ID;
        title = "这是一个共同的属性";
    }

    public void onCreateView(Context context) {
        mContext = context;
        mInflater = LayoutInflater.from(mContext);
        layoutRes = layoutId();
    }

    public abstract void onBindView(ViewHolderBase viewHolderBase);
    public abstract int layoutId();

    public int getLayout(){
        return layoutRes;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}
public class ImgItem extends ItemBase{

    public ImgItem() {
        super(ID_IMG);
    }

    @Override
    public void onBindView(ViewHolderBase viewHolderBase) {
        TextView mTvTitle = viewHolderBase.get(TextView.class,R.id.img_title);
        mTvTitle.setText("iiiiiiiiiii");
        ImageView mIvIcon = viewHolderBase.get(ImageView.class,R.id.img_img);
        mIvIcon.setImageResource(R.mipmap.ic_launcher);
    }

    @Override
    public int layoutId() {
        return R.layout.item_img;
    }
}
public class TxtItem extends ItemBase{

    public TxtItem() {
        super(ID_TXT);
    }

    @Override
    public void onBindView(ViewHolderBase viewHolderBase) {
        TextView mTvTitle = viewHolderBase.get(TextView.class, R.id.txt_title);
        mTvTitle.setText("Txt Item ");

        TextView mTvSummary = viewHolderBase.get(TextView.class,R.id.txt_summary);
        mTvSummary.setText("简介...");

    }

    @Override
    public int layoutId() {
        return R.layout.item_txt;
    }

}
public class StudentItem extends ItemBase {
    public StudentItem() {
        super(ID_STU);
    }

    @Override
    public void onBindView(ViewHolderBase viewHolderBase) {
        TextView mTvTitle = viewHolderBase.get(TextView.class,R.id.name);
        mTvTitle.setText("huangshunbo");
        viewHolderBase.get(TextView.class,R.id.age).setText("27");
        viewHolderBase.get(TextView.class,R.id.sex).setText("男");
    }

    @Override
    public int layoutId() {
        return R.layout.item_student;
    }
}

最好Activity中的使用

public class OMultiRecyclerView extends BaseActivity {


    @BindView(R.id.recycler)
    RecyclerView mRecyclerView;
    MultiAdapter mMultiAdapter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.omulti_recycler);

        mMultiAdapter = new MultiAdapter(mRecyclerView);
        initData();
        mRecyclerView.addItemDecoration(new DividerItemDecoration(this,
                DividerItemDecoration.VERTICAL));
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mRecyclerView.setAdapter(mMultiAdapter);

    }

    private void initData() {
        for(int i=0;i<20;i++){
            TxtItem txt = new TxtItem();
            mMultiAdapter.insertItem(txt);
            mMultiAdapter.insertItem(new StudentItem());
        }

    }
}

最后附上整理的demo:MultiRecyclerView

猜你喜欢

转载自blog.csdn.net/xiaoru5127/article/details/78929673