拒接麻烦!RecyclerView 适配器简化

废话不在前面说,先介绍功能!

简化 Adapter

下面是简化过的 Adapter,后面简单介绍,底部还有高级 Adapter:

public abstract class BaseRecyclerAdapter<T> extends RecyclerView.Adapter<ViewHolder> {

    //布局
    private final int mItemLayoutRes;

    //数据
    public List<T> mItems;

    //点击事件回调接口
    private ItemClickListener<T> mItemClickListener ;
    private ItemLongClickListnener<T> mItemLongClickListnener ;

    public BaseRecyclerAdapter(@LayoutRes int mItemLayoutRes, List<T> items) {
        this.mItemLayoutRes = mItemLayoutRes;
        // 如果没有传入的数据 ,则自动创建一个空的集合 ,防止报空指针
        this.mItems = items == null ? new ArrayList<>() : items;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(mItemLayoutRes, parent, false);

        //根据view创建多功能view holder
        ViewHolder viewHolder = new ViewHolder(view);

        // 初始化Item事件监听
        initOnItemListener(viewHolder);

        return viewHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        //获取数据
        T item = mItems.get(position);
        //绑定数据到布局
        convertView(holder, item, position);
    }

    @Override
    public int getItemCount() {
        return mItems.size();
    }

    //关键--外部将数据绑定到布局去
    public abstract void convertView(ViewHolder viewHolder, T item, int position) ;

    //设置点击事件
    private void initOnItemListener(ViewHolder holder) {
        if (mItemClickListener != null) {
            //根布局点击事件
            holder.itemView.setOnClickListener(v -> {
                T o = mItems.get(holder.getLayoutPosition());
                mItemClickListener.onItemClick(v, o , holder.getLayoutPosition());
            });
        }

        if (mItemLongClickListnener != null) {
            //根布局长按事件
            holder.itemView.setOnLongClickListener(v -> {
                T o = mItems.get(holder.getLayoutPosition());
                mItemLongClickListnener.onItemLongClick(v, o, holder.getLayoutPosition());
                return true;
            });
        }
    }


    public void setOnItemClickListener(ItemClickListener<T> listener) {
        mItemClickListener = listener ;
    }

    public void setOnItemLongClickListener(ItemLongClickListnener<T> listener) {
        mItemLongClickListnener = listener ;
    }

    public interface ItemClickListener<T> {
        void onItemClick(View view, T itemObj, int position) ;
    }

    public interface ItemLongClickListnener<T> {
        void onItemLongClick(View view, T itemObj, int position) ;
    }

}

这里是使用办法:

recyclerView.setAdapter(new BaseRecyclerAdapter<Character>(R.layout.item_data_list, infos) {
    @Override
    public void convertView(ViewHolder viewHolder, Character item, int position) {
        viewHolder.setText(R.id.order, item.getOrder() + "")
            .setText(R.id.name, item.getName())
            .setOnClickListener(R.id.name, view -> showToast(say))
            .setText(R.id.old, item.getOld())
            .setText(R.id.sex, item.getSex())
            .setText(R.id.from, item.getFrom());
    }
});

是不是简单多了,创建的时候将泛型类型、布局、数据传进去,重写方法里面绑定数据就可以了。

下面是通用的 view holder,网上应该很多地方能找到:

public class ViewHolder extends RecyclerView.ViewHolder {

    private View mConvertView;
    private SparseArray<View> mViews;

    /**
     *
     * @param itemView
     * itemView
     */
    public ViewHolder(View itemView) {

        super(itemView);
        mViews = new SparseArray<>() ;
        mConvertView = itemView ;
    }

    /**
     *
     * @param view
     * view
     * @return
     * holder
     */
    public static ViewHolder create(View view) {
        return new ViewHolder(view);
    }

    /**
     * 根据资源获取View对象
     * @param res
     * 控件ID
     * @param <T>
     * 类型
     * @return
     * 控件
     */
    public <T extends View> T getView(@IdRes int res) {

        View view = mViews.get(res);
        if (view == null) {
            view = mConvertView.findViewById(res) ;
            mViews.put(res,view);
        }
        return (T) view;
    }

    /**
     * 提供TextView和Button设置文本简化操作
     * @param idRes
     * 控件ID
     * @param charSequence
     * 字符串
     * @return
     * holder
     */
    public ViewHolder setText(@IdRes int idRes , CharSequence charSequence) {
        View view = getView(idRes);
        if (view instanceof TextView) {
            ((TextView)view).setText(charSequence);
        }else if (view instanceof Button) {
            ((Button)view).setText(charSequence);
        }

        return this ;
    }

    /**
     * 提供TextView和Button设置文本颜色简化操作
     * @param idRes
     * 控件ID
     * @param color
     * 颜色
     * @return
     * holder
     */
    public ViewHolder setTextColor(@IdRes int idRes , int color) {
        View view = getView(idRes);
        if (view instanceof TextView) {
            ((TextView)view).setTextColor(color);
        }else if (view instanceof Button) {
            ((Button)view).setTextColor(color);
        }
        return this ;
    }


    /**
     * 设置指定ViewId的背景颜色
     * @param idRes
     * 控件ID
     * @param color
     * 颜色
     * @return
     * holder
     */
    public ViewHolder setBackgroundColor(@IdRes int idRes , int color) {

        View view = getView(idRes);
        view.setBackgroundColor(color);

        return this;
    }


    /**
     * 设置指定ViewId的可见度
     * @param idRes
     * 控件ID
     * @param visibility
     * 可见度
     * @return
     * holder
     */
    public ViewHolder setVisibility(@IdRes int idRes , @DrawableRes int visibility) {

        View view = getView(idRes);
        view.setVisibility(visibility);

        return this ;
    }

    /**
     * 设置ImageView显示图片
     * @param idRes
     * 控件ID
     * @param res
     * 图片路径
     * @return
     * holder
     */
    public ViewHolder setImageResource(@IdRes int idRes , @DrawableRes int res) {
        View view = getView(idRes);
        if (view instanceof ImageView) {
            ((ImageView)view).setImageResource(res);
        }

        return this ;
    }

    /**
     * 设置指定控件ID的点击事件
     * @param idRes
     * 控件ID
     * @param listener
     * 监听接口
     * @return
     * holder
     */
    public ViewHolder setOnClickListener(@IdRes int idRes , View.OnClickListener listener) {

        View view = getView(idRes);
        view.setOnClickListener(listener);

        return this;
    }

    /**
     * 设置指定控件ID的长按事件
     * @param idRes
     * 控件ID
     * @param listener
     * 监听接口
     * @return
     * holder
     */
    public ViewHolder setOnLongClickListener(@IdRes int idRes , View.OnLongClickListener listener) {
        View view = getView(idRes);
        view.setOnLongClickListener(listener);

        return this;
    }

    /**
     * 设置指定控件的TAG
     * @param idRes
     * 控件ID
     * @param tag
     * tag
     * @return
     * holder
     */
    public ViewHolder setTag(@IdRes int idRes , Object tag) {
        View view = getView(idRes);
        view.setTag(tag);

        return this;
    }

    /**
     * 获取指定控件的TAG
     * @param idRes
     * 控件ID
     * @return
     * holder
     */
    public Object getTag(@IdRes int idRes) {
        View view = getView(idRes);
        return  view.getTag() ;
    }

}
简单讲讲

还记得我们之前要重写的 onCreateViewHolder、onBindViewHolder、getItemCount 函数吗?onCreateViewHolder 根据布局创建 ViewHolder,我们这根据传进去的布局 id,创建了一个通用的 view holder;onBindViewHolder 函数绑定数据到布局,这里我们取得数据,调用了一个抽象函数去实现,这个抽象函数就是我们继承的时候要写的,在里面绑定数据;getItemCount 函数返回数据数目,数据传进去了可以直接获得数目返回。至于外层的点击事件,很简单,不必多说。

通用的 ViewHolder 提供了很多绑定数据到布局的方法,例如文字、背景、点击事件等,也可以自己增加,类似写就可以,很简单。

多类型 Adapter

如果是简单使用 RecyclerView 上面的适配器应该是满足要求了,但是如果需要使用多类型呢?这个需求也很常见,下面看简化的多类型适配器:

public abstract class BaseMultiRecyclerAdapter extends RecyclerView.Adapter<ViewHolder> {

    //需要存储不同类型的对象
    private final List<Object> mItems;

    //点击事件回调接口
    private ItemClickListener mItemClickListener ;
    private ItemLongClickListnener mItemLongClickListnener ;

    public BaseMultiRecyclerAdapter(List<Object> items) {
        // 如果没有传入的数据 ,则自动创建一个空的集合 ,防止报空指针
        this.mItems = items == null ? new ArrayList<>() : items;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

        //获取资源文件id
        int mItemLayoutRes = convertType(viewType);

        //根据给到的不同资源文件id,创建不同的view
        View view = LayoutInflater.from(parent.getContext()).inflate(mItemLayoutRes, parent, false);

        //根据view创建多功能view holder
        ViewHolder viewHolder = new ViewHolder(view);

        // 初始化Item事件监听
        initOnItemListener(viewHolder);

        return viewHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position){
        //所有对象都继承自object
        Object o = mItems.get(position);
        //绑定数据到布局
        convertView(holder, o, getItemViewType(position));
    }

    @Override
    public int getItemCount(){
        return mItems.size();
    }

    //将重写getItemViewType获得的类型转换成布局
    public abstract @LayoutRes int convertType(int viewType);

    //关键--外部将数据绑定到布局去
    public abstract void convertView(ViewHolder viewHolder, Object itemObj, int viewType);

    //设置点击事件
    private void initOnItemListener(final RecyclerView.ViewHolder holder) {
        if (mItemClickListener != null) {
            //根布局点击事件
            holder.itemView.setOnClickListener(v -> {
                Object o = mItems.get(holder.getLayoutPosition());
                mItemClickListener.onItemClick(v,o ,holder.getLayoutPosition());
            });
        }

        if (mItemLongClickListnener != null) {
            //根布局长按事件
            holder.itemView.setOnLongClickListener(v -> {
                Object o = mItems.get(holder.getLayoutPosition());
                mItemLongClickListnener.onItemLongClick(v,o,holder.getLayoutPosition());
                return true;
            });
        }
    }

    public void setOnItemClickListener(ItemClickListener listener) {
        mItemClickListener = listener ;
    }

    public void setOnItemLongClickListener(ItemLongClickListnener listener) {
        mItemLongClickListnener = listener ;
    }

    public interface ItemClickListener {
        void onItemClick(View view , Object itemObj , int position) ;
    }

    public interface ItemLongClickListnener {
        void onItemLongClick(View view ,Object itemObj, int position) ;
    }

}

下面是使用:

final BaseMultiRecyclerAdapter adapter = new BaseMultiRecyclerAdapter(datas) {

            @Override
            public int getItemViewType(int position) {
                Object item = datas.get(position);
                if (item instanceof People) return 0;
                if (item instanceof Character) return 1;
                if (item instanceof Monster) return 2;
                return 0;
            }

            @Override
            public int convertType(int viewType) {
                switch (viewType){
                    case 0:
                        return R.layout.item_people_list;
                    case 1:
                        return R.layout.item_data_list;
                    case 2:
                        return R.layout.item_monster_list;
                        default:
                }
                return R.layout.item_people_list;
            }

            @Override
            public void convertView(ViewHolder viewHolder, Object itemObj, int viewType) {
                switch (viewType){
                    case 0:
                        viewHolder.setText(R.id.people_sex, ((People)itemObj).getSex());
                        break;
                    case 1:
                        viewHolder.setText(R.id.order, ((Character)itemObj).getOrder() + "");
                        viewHolder.setText(R.id.name, ((Character)itemObj).getName());
                        viewHolder.setText(R.id.old, ((Character)itemObj).getOld());
                        viewHolder.setText(R.id.sex, ((Character)itemObj).getSex());
                        viewHolder.setText(R.id.from, ((Character)itemObj).getFrom());

                        final String say = "我叫" + ((Character)itemObj).getName() + "请多关照!";
                        viewHolder.setOnClickListener(R.id.name, view -> showToast(say));
                        break;
                    case 2:
                        viewHolder.setText(R.id.monster_name, ((Monster)itemObj).getName());
                        viewHolder.setText(R.id.monster_belong, ((Monster)itemObj).getBelong());
                        viewHolder.setText(R.id.monster_property, ((Monster)itemObj).getProperty());
                        break;
                    default:
                }
            }
        };
	multiList.setAdapter(adapter);
简单讲讲
  1. 储存不同类型对象

    这里我们要储存不同类型的对象,所以数据的 list 不能再使用泛型了,即使是通配符 ? ,也是不行的,因为只要加入一个数据,整个 list 的类型就确定了。所以这里使用了 Object 作为数据类型,所有对象都继承自 Object。

  2. 传入不同资源布局

    因为我们的数据有多种类型,对应的布局可能也有多种类型,我们要根据不同的数据创建不同的 view holder对象。这里 onCreateViewHolder 方法中已经给到了 viewType,即应该创建的 view 的类型,但是需要我们重写 RecyclerView.Adapter 已经有的 getItemViewType 函数,在 getItemViewType 中根据数据返回对应的类型,拿到 viewType 了,我们就能创建不同的 view 及 view holder 了。

    总结一下就是要我们自己判断下类型(getItemViewType),再拿着类型去绑定数据。

  3. 根据不同类型绑定数据

    和前面的绑定数据类似,但是我们绑定数据到布局也需要分类型,这里再次调用了 getItemViewType 函数,并将得到的 viewType 传递出去。绑定数据的时候,数据是 Object 类型的,根据 viewType 类型转换就可以了。

列表展开及折叠

使用多类型列表的地方很有可能会用到折叠、展开功能,像什么使用说明啊,下面是我很久之前写的一个辅助类,其实上面的代码也是刚毕业的时候写的,现在改了改,下面看折叠展开的辅助类:

public class ThreeLayerListHelper {

    //按节点整理数据,可以展开多条数据
    private static final List<Object> openedDatas = new ArrayList<>();//已经展开的节点
    public static void sortDatasByNode(List<Object> datas, Object itemObj, List<? extends HeadDataBean> headDatas, List<? extends MediumDataBean> mediumDatas, List<? extends TailDataBean> tailDatas){
        if (openedDatas.contains(itemObj)){
            removeChildDataByNode(datas, itemObj, headDatas, mediumDatas, tailDatas);
            openedDatas.remove(itemObj);
        }else {
            addChildDataByNode(datas, itemObj, headDatas, mediumDatas, tailDatas);
            openedDatas.add(itemObj);
        }
    }

    //按节点增加数据
    private static void addChildDataByNode(List<Object> datas, Object itemObj, List<? extends HeadDataBean> headDatas, List<? extends MediumDataBean> mediumDatas, List<? extends TailDataBean> tailDatas){
        List<Object> increments = new ArrayList<>();

        //在第一层增加
        if (itemObj instanceof HeadDataBean){

            //获取前向属性
            String headProperty = ((HeadDataBean)itemObj).getHeadProperty();

            for (MediumDataBean mediumDataBean : mediumDatas){
                if (mediumDataBean.getHeadProperty().equals(headProperty)){
                    increments.add(mediumDataBean);
                }
            }
        }

        //在第二层增加
        if (itemObj instanceof MediumDataBean){

            //后向属性
            String footProperty = ((MediumDataBean)itemObj).getFootProperty();

            for (TailDataBean tailDataBean : tailDatas){
                if (tailDataBean.getFootProperty().equals(footProperty)){
                    increments.add(tailDataBean);
                }
            }
        }

        //执行插入操作
        List<Object> temp = new ArrayList<>();
        for (Object data : datas){
            temp.add(data);
            //当前点后面加入
            if (data.equals(itemObj)){
                temp.addAll(increments);
            }
        }

        datas.clear();
        datas.addAll(temp);
    }

    //按节点删除数据
    private static void removeChildDataByNode(List<Object> datas, Object itemObj, List<? extends HeadDataBean> headDatas, List<? extends MediumDataBean> mediumDatas, List<? extends TailDataBean> tailDatas){
        List<Object> decrements = new ArrayList<>();

        //在第一层查找
        if (itemObj instanceof HeadDataBean){

            //获取前向属性
            String headProperty = ((HeadDataBean)itemObj).getHeadProperty();

            for (MediumDataBean mediumDataBean : mediumDatas){
                if (mediumDataBean.getHeadProperty().equals(headProperty)){
                    decrements.add(mediumDataBean);

                    //该第二层对应的第三层同样应该需要移除
                    String footProperty = mediumDataBean.getFootProperty();
                    for (TailDataBean tailDataBean : tailDatas){
                        if (tailDataBean.getFootProperty().equals(footProperty)){
                            decrements.add(tailDataBean);
                        }
                    }
                }
            }
        }

        //在第二层查找
        if (itemObj instanceof MediumDataBean){

            //后向属性
            String footProperty = ((MediumDataBean)itemObj).getFootProperty();

            for (TailDataBean tailDataBean : tailDatas){
                if (tailDataBean.getFootProperty().equals(footProperty)){
                    decrements.add(tailDataBean);
                }
            }
        }

        //执行移除操作,不需要找节点
        List<Object> temp = new ArrayList<>();
        for (Object data : datas){
            //判断每一个data值是否在消除数组中
            boolean isInDecrementsFlag = false;
            for (Object decrement : decrements){
                if (decrement.equals(data)){
                    isInDecrementsFlag = true;
                }
            }

            if (!isInDecrementsFlag){
                temp.add(data);
            }
        }

        datas.clear();
        datas.addAll(temp);
    }
    
    //能用,但是属于硬搞,可以忽略,我自己也看不懂了
    private static String passedFirstLayerProperty = "";
    private static String passedSecondLayerProperty = "";
    public static void sortDatas(List<Object> datas, Object itemObj, List<? extends HeadDataBean> headDatas, List<? extends MediumDataBean> mediumDatas, List<? extends TailDataBean> tailDatas){

        List<Object> tempdatas = new ArrayList<>();
        if (itemObj instanceof HeadDataBean){

            //获取前向属性
            String headProperty = ((HeadDataBean)itemObj).getHeadProperty();

            //二次点击事件
            if (headProperty.equals(passedFirstLayerProperty)){
                datas.clear();
                datas.addAll(headDatas);

                passedFirstLayerProperty = "";
                passedSecondLayerProperty = "";

                return;
            }else {
                passedFirstLayerProperty = headProperty;
                passedSecondLayerProperty = "";
            }

            //第一层
            for (HeadDataBean headDataBean : headDatas) {
                //先加后判断
                tempdatas.add(headDataBean);
                if (headDataBean.getHeadProperty().equals(headProperty)) {

                    //第二层
                    for (MediumDataBean mediumDataBean : mediumDatas) {
                        //先判断后加
                        if (mediumDataBean.getHeadProperty().equals(headProperty)) {
                            tempdatas.add(mediumDataBean);
                        }
                    }
                }
            }
        }

        if (itemObj instanceof MediumDataBean){

            //前向属性
            String headProperty = ((MediumDataBean)itemObj).getHeadProperty();
            //后向属性
            String footProperty = ((MediumDataBean)itemObj).getFootProperty();

            //二次点击事件
            boolean thirdLayerDisplayFlag = true;
            if (footProperty.equals(passedSecondLayerProperty)){
                thirdLayerDisplayFlag = false;
                passedSecondLayerProperty = "";
            }else {
                //passedFirstLayerProperty = "";
                passedSecondLayerProperty = footProperty;
            }

            //第一层
            for (HeadDataBean headDataBean : headDatas){
                //先加后判断
                tempdatas.add(headDataBean);
                if (headDataBean.getHeadProperty().equals(headProperty)){

                    //第二层
                    for (MediumDataBean mediumDataBean : mediumDatas){
                        //先判断后加
                        if (mediumDataBean.getHeadProperty().equals(headProperty)){
                            tempdatas.add(mediumDataBean);

                            //转折处
                            if (mediumDataBean.getFootProperty().equals(footProperty)){

                                //第三层
                                for (TailDataBean tailDataBean : tailDatas){
                                    if (tailDataBean.getFootProperty().equals(footProperty) && thirdLayerDisplayFlag){
                                        tempdatas.add(tailDataBean);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        if (itemObj instanceof TailDataBean){
            tempdatas.addAll(datas);
        }


        datas.clear();
        datas.addAll(tempdatas);
    }
}

这里写的非常不行,还得依赖三个类型:

//一级标题
public class HeadDataBean {
    private String headProperty;

    public String getHeadProperty() {
        return headProperty;
    }

    public void setHeadProperty(String headProperty) {
        this.headProperty = headProperty;
    }
}

//二级标题
public class MediumDataBean {
    private String headProperty;
    private String footProperty;

    public String getHeadProperty() {
        return headProperty;
    }

    public void setHeadProperty(String headProperty) {
        this.headProperty = headProperty;
    }

    public String getFootProperty() {
        return footProperty;
    }

    public void setFootProperty(String footProperty) {
        this.footProperty = footProperty;
    }
}

//三级标题
public class TailDataBean {

    private String footProperty;

    public String getFootProperty() {
        return footProperty;
    }

    public void setFootProperty(String footProperty) {
        this.footProperty = footProperty;
    }
}

下面是使用:

        //注意使用viewholder注册点击事件,第一项无点击效果,即点击事件在第一项中注册
        adapter.setOnItemClickListener((view, itemObj, position) -> {

            //方法一,直接数据操作,只能展开一项数据(不推荐)
            ThreeLayerListHelper.sortDatas(datas, itemObj, peoples, characters, monsters);
            //方法二,通过节点形式增删数据,能展开多项(推荐)
            //ThreeLayerListHelper.sortDatasByNode(datas, itemObj, peoples, characters, monsters);

            //做一些其他操作 - 例如:第三项Monster提示语
            if (itemObj instanceof Monster){
                showToast("这是" + ((Monster)itemObj).getBelong()
                        + "的" + ((Monster)itemObj).getName());
            }
            adapter.notifyDataSetChanged();
        });
简单讲讲

这里就讲讲那个按结点增加删除子列表数据的方法,另一个我也看不懂了。

实际就是类似于指针,每个对象有至少包含一个上一级节点,或一个下一级节点,有一种属于关系,当点击该点的时候,先找到属于它的下一级节点列表,并在它的后面插入数据就可以了。这里还用到了一个 openedDatas 来记录展开的节点,用来判断是展开还是折叠,如果包含该节点就是展开,否则就折叠,并从 openedDatas 中移除该节点。

思路很简单,写的有点复杂,能用就用,不行就参考参考。

结语

RecyclerView 是安卓开发中必不可少的东西,处处用得到,可是有时候,仅仅编写一个简单的列表,就要新建一个 Adapter 类,代码一多,Adapter 遍地都是,有点烦。

虽然 Adapter 的适配器思想很不错,RecyclerView.Adapter 的分层也很合理,可是就是麻烦,每次重写都得写一个 ViewHolder ,再重写 onCreateViewHolder、onBindViewHolder、getItemCount 等函数,实际上我们就是想向布局绑定数据而已。

虽然我觉得我写的适配器不错了,可是使用的话还是先推荐 BRVAH 列表适配器吧!

https://github.com/CymChad/BaseRecyclerViewAdapterHelper

功能类似,但是上面这些代码都是没看 BRVAH 的情况下自己写的,写的不怎么样,如果觉得有帮助但又没看懂的话,可以看我写的一个 demo,已经上传的 GitHub 了:

https://github.com/silencefly96/Utils

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值