换种思路实现RecyclerView嵌套RecyclerView(购物车)复杂效果

其实在实际开发中,难免会遇到一些类似于需要listview嵌套listview或者RecyclerView嵌套RecyclerView的界面需要实现。作为开发人员的我们,当然希望这种需求越少越好,但是如果偏偏就是有这种需求,用哪一种方式去实现比较好呢?

首先看一个很变态的界面效果图,估计很多人看到这个界面的第一眼就是懵逼的,这尼玛什么玩意?


动态效果

其实倒不是说实现图中的这种效果有多难,而是这种类似于嵌套的界面,实现的最终效果往往会达不到预期。
要实现这么个界面,根据网上大部分资料来说,无非就是下面这2种方法:

  1. 使用addView到布局的方式将item添加进布局容器中;
  2. RecyclerView嵌套RecyclerView(ListView嵌套ListView);

使用addView到布局的方式将item添加进布局容器中

其实这种方式是一种比较好的实现方式,使用这种方式实现的话,实际上是将item分为两类,一类是Normal item,一类是Group item。在Group item中添加一个空布局作为容器,然后根据child item的数量在adapter中使用addView的方式将child item添加到Group item中。但是这种方法也有一个弊端,就是child item数据刷新的时候,需要先将容器中的child全部清除,然后再重新添加新的child,这种就会导致数据刷新的时候界面明显有一个先清空再添加的界面变化,对用户体验上来讲是很不友好的。

RecyclerView嵌套RecyclerView(ListView嵌套ListView)

ListView嵌套ListView网上有很多相关的资料,这种方式首先存在的问题就是item只显示一条的问题。然后随便一查,就能查到这么一条解决方法,就是重写ListView的onMeasure方法:

@Override  
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);  
    super.onMeasure(widthMeasureSpec, expandSpec);  
}  

RecyclerView嵌套RecyclerView的话就直接在Group item布局中添加一条child的RecyclerView然后绑定child adapter就可以了。虽然这样实现看起来很简单,但是不论是ListView嵌套ListView也好,RecyclerView嵌套RecyclerView也好,滑动的时候,由于要计算child的高度,最直观的体现就是滑动卡顿。然后还有一个问题就是这种嵌套的方式item的viewHolder无法复用,数据量大到一定程度就OOM了。

单RecyclerView实现

既然上面两种方式都有弊端,而且相对比较影响体验。我们不妨换一种思路来实现这种布局:使用单RecyclerView实现。
前面两种方式针对的是控件,现在既然要使用单RecyclerView实现,那么我们的做法就是从数据层面做修改,也就是不论Normal item、Group item、Child item,都只是作为一个RecyclerView的一种布局来实现,而不是将child作为Group item的子布局来实现。

首先看看模拟的实体类:

NormalItemBean

public class NormalItemBean {
    private String title;
    private int itemId;
    private boolean isChecked;

    public int getItemId() {
        return itemId;
    }

    public void setItemId(int itemId) {
        this.itemId = itemId;
    }

    public String getTitle() {
        return title;
    }

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

    public boolean isChecked() {
        return isChecked;
    }

    public void setChecked(boolean checked) {
        isChecked = checked;
    }
}

GroupItemBean

public class GroupItemBean {
    private String title;
    private boolean isChecked;
    private int itemId;
    private List<ChildItemBean> childs;

    public int getItemId() {
        return itemId;
    }

    public void setItemId(int itemId) {
        this.itemId = itemId;
    }

    public List<ChildItemBean> getChilds() {
        return childs;
    }

    public void setChilds(List<ChildItemBean> childs) {
        this.childs = childs;
    }

    public String getTitle() {

        return title;
    }

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

    public boolean isChecked() {
        return isChecked;
    }

    public void setChecked(boolean checked) {
        isChecked = checked;
    }
}

ChildItemBean

public class ChildItemBean {
    private String title;
    private boolean isChecked;
    private int groupId;
    private int itemId;

    public int getItemId() {
        return itemId;
    }

    public void setItemId(int itemId) {
        this.itemId = itemId;
    }

    public int getGroupId() {
        return groupId;
    }

    public void setGroupId(int groupId) {
        this.groupId = groupId;
    }

    public String getTitle() {
        return title;
    }

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

    public boolean isChecked() {
        return isChecked;
    }

    public void setChecked(boolean checked) {
        isChecked = checked;
    }
}

假设这3个实体类都是服务器返回的数据,根据我们的思路,Group、Child、Normal都是只是RecyclerView的不同类型的item而已,所以这里对实体类做一下调整,调整后的实体类如下:

public class DemoItemBean {
    public static final int TYPE_NORMAL = 0;
    public static final int TYPE_GROUP = 1;
    public static final int TYPE_CHILD = 2;

    private String title;
    private boolean isChecked;
    private int itemType;
    private int itemId;

    public boolean isChecked() {
        return isChecked;
    }

    public void setChecked(boolean checked) {
        isChecked = checked;
    }

    public int getItemId() {
        return itemId;
    }

    public void setItemId(int itemId) {
        this.itemId = itemId;
    }

    public String getTitle() {
        return title;
    }

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

    public int getItemType() {
        return itemType;
    }

    public void setItemType(int itemType) {
        this.itemType = itemType;
    }
}

public class NormalItemBean extends DemoItemBean {
}

public class GroupItemBean extends DemoItemBean{
    private List<ChildItemBean> childs;

    public List<ChildItemBean> getChilds() {
        return childs;
    }

    public void setChilds(List<ChildItemBean> childs) {
        this.childs = childs;
    }
}


public class ChildItemBean extends DemoItemBean{
    private int groupId;

    public int getGroupId() {
        return groupId;
    }

    public void setGroupId(int groupId) {
        this.groupId = groupId;
    }
}

接下来写模拟数据的处理,将Normal、Group、Child整合成一个list。

public class ParseHelper {
    //=========== 这里模拟服务器返回的数据 ==========
    private static List<NormalItemBean> getNormalDatas() {
        List<NormalItemBean> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            NormalItemBean bean = new NormalItemBean();
            bean.setItemId(i);
            bean.setChecked(false);
            bean.setItemType(DemoItemBean.TYPE_NORMAL);
            bean.setTitle("Normal: " + i);
            list.add(bean);
        }
        return list;
    }

    private static List<GroupItemBean> getGroupDatas() {
        List<GroupItemBean> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            List<ChildItemBean> childList = new ArrayList<>();
            GroupItemBean bean = new GroupItemBean();
            bean.setItemId(i);
            bean.setItemType(DemoItemBean.TYPE_GROUP);
            bean.setTitle("Group: " + i);
            bean.setChecked(false);

            for (int j = 0; j < 3; j++) {
                ChildItemBean bean1 = new ChildItemBean();
                bean1.setTitle("group: " + i + " child: " + j);
                bean1.setChecked(false);
                bean1.setItemType(DemoItemBean.TYPE_CHILD);
                bean1.setGroupId(i);//child的groupId对应Group的itemId
                bean1.setItemId(bean.getItemId());//child的itemId和其父group的itemId一致
                childList.add(bean1);
            }
            bean.setChilds(childList);
            list.add(bean);
        }
        return list;
    }
    //===============================================

    public static List<DemoItemBean> getParseDatas() {
        List<DemoItemBean> list = new ArrayList<>();

        for (NormalItemBean bean : getNormalDatas()) {
            list.add(bean);//normal
        }

        for (GroupItemBean bean : getGroupDatas()) {
            list.add(bean);//group

            for (ChildItemBean bean1 : bean.getChilds()) {
                list.add(bean1);//child
            }
        }
        return list;
    }
}

接下来完成RecyclerView的Adapter。

public class DemoAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private List<DemoItemBean> mDatas;
    private Context mContext;
    private OnCheckChangeListener onCheckChangeListener;

    public void setOnCheckChangeListener(OnCheckChangeListener l) {
        onCheckChangeListener = l;
    }

    public DemoAdapter(Context context, List<DemoItemBean> datas) {
        mContext = context;
        mDatas = datas;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Log.w("tag", "onCreateViewHolder");
        LayoutInflater mInflater = LayoutInflater.from(mContext);
        RecyclerView.ViewHolder holder = null;
        switch (viewType) {
            case DemoItemBean.TYPE_NORMAL:
                View v = mInflater.inflate(R.layout.item_normal, parent, false);
                holder = new NormalViewHolder(v);
                break;
            case DemoItemBean.TYPE_GROUP:
                View v1 = mInflater.inflate(R.layout.item_group, parent, false);
                holder = new GroupViewHolder(v1);
                break;
            case DemoItemBean.TYPE_CHILD:
                View v2 = mInflater.inflate(R.layout.item_child, parent, false);
                holder = new ChildViewHolder(v2);
                break;
        }
        return holder;
    }

    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
        Log.w("tag", "onBindViewHolder");
        if (holder instanceof NormalViewHolder) {
            NormalViewHolder nHolder = (NormalViewHolder) holder;
            nHolder.bindData((NormalItemBean) mDatas.get(position));
            nHolder.tvNormal.setText(mDatas.get(position).getTitle());
            nHolder.cbNormal.setOnCheckedChangeListener(new OnCheckedChangeListener(position,
                    DemoItemBean.TYPE_NORMAL));
            nHolder.cbNormal.setChecked(mDatas.get(position).isChecked());
        } else if (holder instanceof GroupViewHolder) {
            GroupViewHolder gHolder = (GroupViewHolder) holder;
            gHolder.bindData((GroupItemBean) mDatas.get(position));
            gHolder.tvGroup.setText(mDatas.get(position).getTitle());
            gHolder.cbGroup.setOnCheckedChangeListener(new OnCheckedChangeListener(position,
                    DemoItemBean.TYPE_GROUP));
            gHolder.cbGroup.setChecked(mDatas.get(position).isChecked());
        } else if (holder instanceof ChildViewHolder) {
            ChildViewHolder cHolder = (ChildViewHolder) holder;
            cHolder.bindData((ChildItemBean) mDatas.get(position));
            cHolder.tvChild.setText(mDatas.get(position).getTitle());
            cHolder.cbChild.setOnCheckedChangeListener(new OnCheckedChangeListener(position,
                    DemoItemBean.TYPE_CHILD));
            cHolder.cbChild.setChecked(mDatas.get(position).isChecked());
        }
    }

    @Override
    public int getItemViewType(int position) {
        return mDatas.get(position).getItemType();
    }

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

    /**
     * CheckBox CheckedChangeListener
     */
    private class OnCheckedChangeListener implements CompoundButton.OnCheckedChangeListener {
        int mPosition, mItemType;

        public OnCheckedChangeListener(int position, int itemType) {
            mPosition = position;
            mItemType = itemType;
        }

        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if (onCheckChangeListener != null)
                onCheckChangeListener.onCheckedChanged(mDatas, mPosition, isChecked, mItemType);
        }
    }
}

三个item的ViewHolder:

public class NormalViewHolder extends RecyclerView.ViewHolder {
    private NormalItemBean bean;
    public TextView tvNormal;
    public LinearLayout llNormal;
    public CheckBox cbNormal;

    public NormalViewHolder(View itemView) {
        super(itemView);
        tvNormal = (TextView) itemView.findViewById(R.id.tv_normal);
        llNormal = (LinearLayout) itemView.findViewById(R.id.ll_normal);
        cbNormal = (CheckBox) itemView.findViewById(R.id.cb_normal);
        llNormal.setOnClickListener(new OnClickListener());
    }

    /**
     * 绑定item数据
     * @param bean item数据
     */
    public void bindData(NormalItemBean bean){
        this.bean = bean;
    }

    private class OnClickListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.ll_normal:
                    ToastUtils.showToast(bean.getTitle() + " is clicked.");
                    break;
            }
        }
    }
}

public class GroupViewHolder extends RecyclerView.ViewHolder {
    private GroupItemBean bean;
    public TextView tvGroup, tvSub1, tvSub2, tvSub3;
    public CheckBox cbGroup;
    public LinearLayout llGroup, subEdit;

    public GroupViewHolder(View itemView) {
        super(itemView);
        tvGroup = (TextView) itemView.findViewById(R.id.tv_group);
        cbGroup = (CheckBox) itemView.findViewById(R.id.cb_group);
        llGroup = (LinearLayout) itemView.findViewById(R.id.ll_group);
        subEdit = (LinearLayout) itemView.findViewById(R.id.sub_edit);
        tvSub1 = (TextView) itemView.findViewById(R.id.tv_sub_1);
        tvSub2 = (TextView) itemView.findViewById(R.id.tv_sub_2);
        tvSub3 = (TextView) itemView.findViewById(R.id.tv_sub_3);

        llGroup.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                subEdit.setVisibility(View.VISIBLE);
                return true;
            }
        });

        llGroup.setOnClickListener(new OnClickListener());
        tvSub1.setOnClickListener(new OnClickListener());
        tvSub2.setOnClickListener(new OnClickListener());
        tvSub3.setOnClickListener(new OnClickListener());
    }

    /**
     * 绑定item数据
     * @param bean item数据
     */
    public void bindData(GroupItemBean bean){
        this.bean = bean;
    }

    private class OnClickListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            subEdit.setVisibility(View.GONE);
            switch (v.getId()) {
                case R.id.ll_group:
                    ToastUtils.showToast(bean.getTitle() + " is clicked.");
                    break;
                case R.id.tv_sub_1:
                    ToastUtils.showToast(bean.getTitle() + " subItem 1 is clicked.");
                    break;
                case R.id.tv_sub_2:
                    ToastUtils.showToast(bean.getTitle() + " subItem 2 is clicked.");
                    break;
                case R.id.tv_sub_3:
                    ToastUtils.showToast(bean.getTitle() + " subItem 3 is clicked.");
                    break;
            }
        }
    }
}

public class ChildViewHolder extends RecyclerView.ViewHolder {
    private ChildItemBean bean;
    public TextView tvChild;
    public CheckBox cbChild;
    public LinearLayout llChild;

    public ChildViewHolder(View itemView) {
        super(itemView);

        tvChild = (TextView) itemView.findViewById(R.id.tv_child);
        cbChild = (CheckBox) itemView.findViewById(R.id.cb_child);
        llChild = (LinearLayout) itemView.findViewById(R.id.ll_child);

        llChild.setOnClickListener(new OnClickListener());
    }

    /**
     * 绑定item数据
     *
     * @param bean item数据
     */
    public void bindData(ChildItemBean bean) {
        this.bean = bean;
    }

    private class OnClickListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.ll_child:
                    ToastUtils.showToast(bean.getTitle() + " is clicked.");
                    break;
            }
        }
    }
}

这样基本的单RecyclerView实现嵌套布局就实现了,其实整体实现起来还是挺简单的,child通过itemId和对应的Group绑定,具体的item类型根据itemType来区分。
然后增加一些好玩的功能,包括选中item、添加item以及删除选中item等,具体效果如下图。


item点击

item选中

添加item
删除item

其实这些功能都是一个购物车界面比较常见的功能,只是刚好在这里顺带实现了。实现起来还是没什么特别好说的,直接贴上一些关键代码吧。

public class DemoAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

     ……
    
    /**
     * 删除选中item
     */
    public void removeChecked() {
        int iMax = mDatas.size() - 1;
        //这里要倒序,因为要删除mDatas中的数据,mDatas的长度会变化
        for (int i = iMax; i >= 0; i--) {
            if (mDatas.get(i).isChecked()) {
                mDatas.remove(i);
                notifyItemRemoved(i);
                notifyItemRangeChanged(0, mDatas.size());
            }
        }
    }

    /**
     * 添加 Normal item
     */
    public void addNormal() {
        int addPosition = 0;
        int itemId = 0;
        for (int i = 0; i < mDatas.size(); i++) {
            if (mDatas.get(i).getItemType() == DemoItemBean.TYPE_GROUP) {
                addPosition = i;//得到要插入的position
                break;
            }
        }

        if (!isHaveGroup()) {//如果列表中没有group,直接在list末尾添加item
            if (addPosition == 0) {
                addPosition = mDatas.size();
            }
        }

        if (addPosition > 0) {
            itemId = mDatas.get(addPosition - 1).getItemId() + 1;
        }

        mDatas.add(addPosition, ParseHelper.newNormalItem(itemId));
        notifyItemInserted(addPosition);//通知演示插入动画
        notifyItemRangeChanged(addPosition, mDatas.size() - addPosition);//通知数据与界面重新绑定
    }

    /**
     * 添加 Group item
     */
    public void addGroup() {
        int addPosition = mDatas.size();
        int itemId = 0;

        if (isHaveGroup()) {
            for (int i = 0; i < mDatas.size(); i++) {
                if (mDatas.get(i).getItemType() == DemoItemBean.TYPE_GROUP) {
                    itemId = mDatas.get(i).getItemId() + 1;
                }
            }
        }

        mDatas.add(addPosition, ParseHelper.newGroupItem(itemId));
        notifyItemInserted(addPosition);//通知演示插入动画
        notifyItemRangeChanged(addPosition, mDatas.size() - addPosition);//通知数据与界面重新绑定
    }

    /**
     * 添加 Child item
     * <p>
     * child item添加位置永远归属于最后一个Group item
     */
    public void addChild() {
        int addPosition = 0;
        int itemId = 0;
        int childId = 0;

        if (!isHaveGroup() || mDatas.get(mDatas.size() - 1).getItemType() == DemoItemBean
                .TYPE_NORMAL) {
            addGroup();
        }

        for (int i = 0; i < mDatas.size(); i++) {
            if (mDatas.get(i).getItemType() == DemoItemBean.TYPE_GROUP) {
                itemId = mDatas.get(i).getItemId();
            }
        }

        for (int i = 0; i < mDatas.size(); i++) {
            if (mDatas.get(i).getItemId() == itemId && mDatas.get(i).getItemType() ==
                    DemoItemBean.TYPE_CHILD) {
                childId++;
            }
        }

        addPosition = mDatas.size();
        mDatas.add(addPosition, ParseHelper.newChildItem(mDatas, itemId, childId));
        notifyItemInserted(addPosition);//通知演示插入动画
        notifyItemRangeChanged(addPosition, mDatas.size() - addPosition);//通知数据与界面重新绑定

        if (onCheckChangeListener != null)
            onCheckChangeListener.onCheckedChanged(mDatas, addPosition, mDatas.get(addPosition)
                    .isChecked(), DemoItemBean.TYPE_CHILD);
    }

    /**
     * 当前list是否含有group
     *
     * @return 当前list是否含有group
     */
    private boolean isHaveGroup() {
        boolean isHaveGroup = false;//当前列表是否包含group

        for (int i = 0; i < mDatas.size(); i++) {
            if (mDatas.get(i).getItemType() == DemoItemBean.TYPE_GROUP) {
                isHaveGroup = true;
                break;
            }
        }
        return isHaveGroup;
    }

    /**
     * 获取最后一个Normal item的position
     *
     * @return 最后一个Normal item的position
     */
    public int getLastNormalItemPosition() {
        int addPosition = 0;
        for (int i = 0; i < mDatas.size(); i++) {
            if (mDatas.get(i).getItemType() == DemoItemBean.TYPE_GROUP) {
                addPosition = i;
                break;
            }
        }

        if (addPosition == 0) {
            addPosition = mDatas.size();
        }

        return addPosition - 1;
    }

    /**
     * 获取最后一个item的position
     *
     * @return 最后一个item的position
     */
    public int getLastItemPosition() {
        return mDatas.size();
    }
}

public class ParseHelper {

    ……

    /**
     * 获取group下的child list
     *
     * @param beans    整个数据list
     * @param position 当前group的position
     */
    public static List<ChildItemBean> getChildList(List<DemoItemBean> beans, int position) {
        List<ChildItemBean> childList = new ArrayList<>();
        for (DemoItemBean bean : beans) {
            //item id不相同直接跳过
            if (bean.getItemId() != beans.get(position).getItemId())
                continue;

            if (bean.getItemType() == DemoItemBean.TYPE_CHILD) {
                childList.add((ChildItemBean) bean);
            }
        }
        return childList;
    }

    /**
     * 取出list中的groupBean
     *
     * @param beans
     * @param itemId
     * @return
     */
    public static GroupItemBean getGroupBean(List<DemoItemBean> beans, int itemId) {
        for (DemoItemBean bean : beans) {
            if (bean.getItemType() == DemoItemBean.TYPE_GROUP && bean.getItemId() == itemId)
                return (GroupItemBean) bean;
        }
        return null;
    }

    /**
     * 根据itemId获取child所在的group的position
     *
     * @param beans  整个数据list
     * @param itemId child的itemId
     * @return group的position
     */
    public static int getGroupPosition(List<DemoItemBean> beans, int itemId) {
        for (int i = 0; i < beans.size(); i++) {
            if (beans.get(i).getItemType() == DemoItemBean.TYPE_GROUP
                    && beans.get(i).getItemId() == itemId)
                return i;
        }
        return 0;
    }

    /**
     * new一个normal item数据
     *
     * @param itemId position
     * @return normal item数据
     */
    public static NormalItemBean newNormalItem(int itemId) {
        NormalItemBean bean = new NormalItemBean();
        bean.setItemId(itemId);
        bean.setChecked(false);
        bean.setTitle("Normal: " + itemId);
        bean.setItemType(DemoItemBean.TYPE_NORMAL);
        return bean;
    }

    public static GroupItemBean newGroupItem(int itemId) {
        List<ChildItemBean> childList = new ArrayList<>();
        GroupItemBean bean = new GroupItemBean();
        bean.setItemId(itemId);
        bean.setItemType(DemoItemBean.TYPE_GROUP);
        bean.setTitle("Group: " + itemId);
        bean.setChilds(childList);
        bean.setChecked(false);
        return bean;
    }

    public static ChildItemBean newChildItem(List<DemoItemBean> beans, int itemId, int childId) {
        GroupItemBean groupItemBean = getGroupBean(beans, itemId);
        ChildItemBean bean = new ChildItemBean();
        bean.setGroupId(itemId);
        bean.setItemId(itemId);
        bean.setItemType(DemoItemBean.TYPE_CHILD);
        bean.setTitle("group: " + itemId + " child: " + childId);
        bean.setChecked(false);
        if (groupItemBean != null)
            groupItemBean.getChilds().add(bean);
        return bean;
    }
}
public class MainActivity extends AppCompatActivity {

    private RecyclerView rvNestDemo;
    private DemoAdapter mDemoAdapter;
    private List<DemoItemBean> mDatas;
    private Button btnDelete, btnAddGroup, btnAddNormal, btnChild;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initData();
        initView();
    }

    private void initData() {
        mDatas = ParseHelper.getParseDatas();
    }

    private void initView() {
        rvNestDemo = (RecyclerView) findViewById(R.id.rv_nest_demo);
        btnDelete = (Button) findViewById(R.id.btn_delete);
        btnAddGroup = (Button) findViewById(R.id.btn_add_group);
        btnAddNormal = (Button) findViewById(R.id.btn_add_normal);
        btnChild = (Button) findViewById(R.id.btn_add_child);

        rvNestDemo.setLayoutManager(new LinearLayoutManager(this));
        mDemoAdapter = new DemoAdapter(this, mDatas);
        mDemoAdapter.setOnCheckChangeListener(new OnCheckChangeListener() {
            @Override
            public void onCheckedChanged(List<DemoItemBean> beans, int position, boolean
                    isChecked, int itemType) {
                switch (itemType) {
                    case DemoItemBean.TYPE_NORMAL:
                        normalCheckChange(beans, position, isChecked);
                        break;
                    case DemoItemBean.TYPE_GROUP:
                        groupCheckChange(beans, position, isChecked);
                        break;
                    case DemoItemBean.TYPE_CHILD:
                        childCheckChange(beans, position, isChecked);
                        break;
                }
            }
        });
        rvNestDemo.setAdapter(mDemoAdapter);

        btnDelete.setOnClickListener(new OnClickListener());
        btnAddGroup.setOnClickListener(new OnClickListener());
        btnAddNormal.setOnClickListener(new OnClickListener());
        btnChild.setOnClickListener(new OnClickListener());
    }

    /**
     * normal选中状态变化
     *
     * @param beans     数据
     * @param position  group position
     * @param isChecked 选中状态
     */
    private void normalCheckChange(List<DemoItemBean> beans, int position, boolean isChecked) {
        if (rvNestDemo.getScrollState() == RecyclerView.SCROLL_STATE_IDLE
                && !rvNestDemo.isComputingLayout()) {//避免滑动时刷新数据
            beans.get(position).setChecked(isChecked);
        }
    }

    /**
     * group选中状态变化
     *
     * @param beans     数据
     * @param position  group position
     * @param isChecked 选中状态
     */
    private void groupCheckChange(List<DemoItemBean> beans, int position, boolean isChecked) {
        if (rvNestDemo.getScrollState() == RecyclerView.SCROLL_STATE_IDLE
                && !rvNestDemo.isComputingLayout()) {//避免滑动时刷新数据
            beans.get(position).setChecked(isChecked);
            setChildCheck(beans, position, isChecked);
        }
    }

    /**
     * child选中状态变化
     *
     * @param beans     数据
     * @param position  child position
     * @param isChecked 选中状态
     */
    private void childCheckChange(List<DemoItemBean> beans, int position, boolean isChecked) {
        int itemId = beans.get(position).getItemId();

        if (rvNestDemo.getScrollState() == RecyclerView.SCROLL_STATE_IDLE
                && !rvNestDemo.isComputingLayout()) {//避免滑动时刷新数据

            beans.get(position).setChecked(isChecked);

            GroupItemBean groupBean = ParseHelper.getGroupBean(beans, itemId);

            List<ChildItemBean> childList = ParseHelper.getChildList(beans, position);
            for (int i = 0; i < childList.size(); i++) {
                if (!childList.get(i).isChecked()) {//只要有一个child没有选中,group就不是选中
                    if (groupBean.isChecked() && !isChecked) {//group为选中状态
                        setGroupCheck(beans, itemId, false);
                        mDemoAdapter.notifyItemChanged(ParseHelper.getGroupPosition(beans,
                                itemId));
                    }
                    return;
                }
            }

            //child全部选中,group设置选中
            setGroupCheck(beans, itemId, true);
            mDemoAdapter.notifyItemChanged(ParseHelper.getGroupPosition(beans, itemId));
        }
    }

    /**
     * 一次设置group下所有child item选中状态
     *
     * @param beans     整个数据list
     * @param position  group position
     * @param isChecked 设置选中状态
     */
    private void setChildCheck(List<DemoItemBean> beans, int position, boolean isChecked) {
        for (int i = 0; i < beans.size(); i++) {
            //item id不相同直接跳过
            if (beans.get(i).getItemId() != beans.get(position).getItemId())
                continue;

            if (beans.get(i).getItemType() == DemoItemBean.TYPE_CHILD) {//让group下的所有child选中
                if (beans.get(i).isChecked() != isChecked) {
                    beans.get(i).setChecked(isChecked);
                    mDemoAdapter.notifyItemChanged(i);
                }
            }
        }
    }

    /**
     * 设置group item选中状态
     *
     * @param beans     整个数据list
     * @param itemId    child的itemId
     * @param isChecked 设置选中状态
     */
    private void setGroupCheck(List<DemoItemBean> beans, int itemId, boolean isChecked) {
        for (DemoItemBean bean : beans) {
            if (bean.getItemType() == DemoItemBean.TYPE_GROUP
                    && bean.getItemId() == itemId) {
                bean.setChecked(isChecked);
            }
        }
    }

    private class OnClickListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.btn_add_normal:
                    mDemoAdapter.addNormal();
                    rvNestDemo.smoothScrollToPosition(mDemoAdapter.getLastNormalItemPosition());
                    break;
                case R.id.btn_add_group:
                    mDemoAdapter.addGroup();
                    rvNestDemo.smoothScrollToPosition(mDemoAdapter.getLastItemPosition());
                    break;
                case R.id.btn_add_child:
                    mDemoAdapter.addChild();
                    rvNestDemo.smoothScrollToPosition(mDemoAdapter.getLastItemPosition());
                    break;
                case R.id.btn_delete:
                    mDemoAdapter.removeChecked();
                    break;
            }
        }
    }
}

写在最后:其实这篇文章并没有涉及到说一些很难的知识点,只是单纯的做一种实现思路的描述。天下程序千千万,实现的方法当然也各不相同。个人觉得,换一种思路看待问题,然后再解决这个问题,得到的收获远远要大于照本宣科的去解决问题。毕竟学习的目的是在解决问题的过程中拓宽思维,提升自己,而不仅仅是为了解决问题。

gitHub源码链接:https://github.com/Horrarndoo/NestingRecyclerViewDemo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345