換種思路實(shí)現(xiàn)RecyclerView嵌套RecyclerView(購物車)復(fù)雜效果

其實(shí)在實(shí)際開發(fā)中惧盹,難免會遇到一些類似于需要listview嵌套listview或者RecyclerView嵌套RecyclerView的界面需要實(shí)現(xiàn)事秀。作為開發(fā)人員的我們惠呼,當(dāng)然希望這種需求越少越好滋早,但是如果偏偏就是有這種需求恳守,用哪一種方式去實(shí)現(xiàn)比較好呢考婴?

首先看一個(gè)很變態(tài)的界面效果圖,估計(jì)很多人看到這個(gè)界面的第一眼就是懵逼的催烘,這尼瑪什么玩意沥阱?


動態(tài)效果

其實(shí)倒不是說實(shí)現(xiàn)圖中的這種效果有多難,而是這種類似于嵌套的界面伊群,實(shí)現(xiàn)的最終效果往往會達(dá)不到預(yù)期考杉。
要實(shí)現(xiàn)這么個(gè)界面,根據(jù)網(wǎng)上大部分資料來說舰始,無非就是下面這2種方法:

  1. 使用addView到布局的方式將item添加進(jìn)布局容器中崇棠;
  2. RecyclerView嵌套RecyclerView(ListView嵌套ListView);

使用addView到布局的方式將item添加進(jìn)布局容器中

其實(shí)這種方式是一種比較好的實(shí)現(xiàn)方式丸卷,使用這種方式實(shí)現(xiàn)的話枕稀,實(shí)際上是將item分為兩類,一類是Normal item谜嫉,一類是Group item抽莱。在Group item中添加一個(gè)空布局作為容器,然后根據(jù)child item的數(shù)量在adapter中使用addView的方式將child item添加到Group item中骄恶。但是這種方法也有一個(gè)弊端食铐,就是child item數(shù)據(jù)刷新的時(shí)候,需要先將容器中的child全部清除僧鲁,然后再重新添加新的child虐呻,這種就會導(dǎo)致數(shù)據(jù)刷新的時(shí)候界面明顯有一個(gè)先清空再添加的界面變化象泵,對用戶體驗(yàn)上來講是很不友好的。

RecyclerView嵌套RecyclerView(ListView嵌套ListView)

ListView嵌套ListView網(wǎng)上有很多相關(guān)的資料斟叼,這種方式首先存在的問題就是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就可以了忽孽。雖然這樣實(shí)現(xiàn)看起來很簡單,但是不論是ListView嵌套ListView也好谢床,RecyclerView嵌套RecyclerView也好兄一,滑動的時(shí)候,由于要計(jì)算child的高度识腿,最直觀的體現(xiàn)就是滑動卡頓出革。然后還有一個(gè)問題就是這種嵌套的方式item的viewHolder無法復(fù)用,數(shù)據(jù)量大到一定程度就OOM了渡讼。

單RecyclerView實(shí)現(xiàn)

既然上面兩種方式都有弊端骂束,而且相對比較影響體驗(yàn)。我們不妨換一種思路來實(shí)現(xiàn)這種布局:使用單RecyclerView實(shí)現(xiàn)成箫。
前面兩種方式針對的是控件展箱,現(xiàn)在既然要使用單RecyclerView實(shí)現(xiàn),那么我們的做法就是從數(shù)據(jù)層面做修改蹬昌,也就是不論Normal item析藕、Group item、Child item凳厢,都只是作為一個(gè)RecyclerView的一種布局來實(shí)現(xiàn)账胧,而不是將child作為Group item的子布局來實(shí)現(xiàn)。

首先看看模擬的實(shí)體類:

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;
    }
}

假設(shè)這3個(gè)實(shí)體類都是服務(wù)器返回的數(shù)據(jù)先紫,根據(jù)我們的思路治泥,Group、Child遮精、Normal都是只是RecyclerView的不同類型的item而已居夹,所以這里對實(shí)體類做一下調(diào)整,調(diào)整后的實(shí)體類如下:

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;
    }
}

接下來寫模擬數(shù)據(jù)的處理本冲,將Normal准脂、Group、Child整合成一個(gè)list檬洞。

public class ParseHelper {
    //=========== 這里模擬服務(wù)器返回的數(shù)據(jù) ==========
    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對應(yīng)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);
        }
    }
}

三個(gè)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數(shù)據(jù)
     * @param bean item數(shù)據(jù)
     */
    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數(shù)據(jù)
     * @param bean item數(shù)據(jù)
     */
    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數(shù)據(jù)
     *
     * @param bean item數(shù)據(jù)
     */
    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實(shí)現(xiàn)嵌套布局就實(shí)現(xiàn)了,其實(shí)整體實(shí)現(xiàn)起來還是挺簡單的添怔,child通過itemId和對應(yīng)的Group綁定湾戳,具體的item類型根據(jù)itemType來區(qū)分贤旷。
然后增加一些好玩的功能,包括選中item砾脑、添加item以及刪除選中item等幼驶,具體效果如下圖。


item點(diǎn)擊

item選中

添加item
刪除item

其實(shí)這些功能都是一個(gè)購物車界面比較常見的功能韧衣,只是剛好在這里順帶實(shí)現(xiàn)了盅藻。實(shí)現(xiàn)起來還是沒什么特別好說的,直接貼上一些關(guān)鍵代碼吧畅铭。

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

     ……
    
    /**
     * 刪除選中item
     */
    public void removeChecked() {
        int iMax = mDatas.size() - 1;
        //這里要倒序氏淑,因?yàn)橐獎h除mDatas中的數(shù)據(jù),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()) {//如果列表中沒有g(shù)roup顶瞒,直接在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);//通知數(shù)據(jù)與界面重新綁定
    }

    /**
     * 添加 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);//通知數(shù)據(jù)與界面重新綁定
    }

    /**
     * 添加 Child item
     * <p>
     * child item添加位置永遠(yuǎn)歸屬于最后一個(gè)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);//通知數(shù)據(jù)與界面重新綁定

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

    /**
     * 當(dāng)前l(fā)ist是否含有g(shù)roup
     *
     * @return 當(dāng)前l(fā)ist是否含有g(shù)roup
     */
    private boolean isHaveGroup() {
        boolean isHaveGroup = false;//當(dāng)前列表是否包含group

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

    /**
     * 獲取最后一個(gè)Normal item的position
     *
     * @return 最后一個(gè)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;
    }

    /**
     * 獲取最后一個(gè)item的position
     *
     * @return 最后一個(gè)item的position
     */
    public int getLastItemPosition() {
        return mDatas.size();
    }
}

public class ParseHelper {

    ……

    /**
     * 獲取group下的child list
     *
     * @param beans    整個(gè)數(shù)據(jù)list
     * @param position 當(dāng)前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;
    }

    /**
     * 根據(jù)itemId獲取child所在的group的position
     *
     * @param beans  整個(gè)數(shù)據(jù)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一個(gè)normal item數(shù)據(jù)
     *
     * @param itemId position
     * @return normal item數(shù)據(jù)
     */
    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選中狀態(tài)變化
     *
     * @param beans     數(shù)據(jù)
     * @param position  group position
     * @param isChecked 選中狀態(tài)
     */
    private void normalCheckChange(List<DemoItemBean> beans, int position, boolean isChecked) {
        if (rvNestDemo.getScrollState() == RecyclerView.SCROLL_STATE_IDLE
                && !rvNestDemo.isComputingLayout()) {//避免滑動時(shí)刷新數(shù)據(jù)
            beans.get(position).setChecked(isChecked);
        }
    }

    /**
     * group選中狀態(tài)變化
     *
     * @param beans     數(shù)據(jù)
     * @param position  group position
     * @param isChecked 選中狀態(tài)
     */
    private void groupCheckChange(List<DemoItemBean> beans, int position, boolean isChecked) {
        if (rvNestDemo.getScrollState() == RecyclerView.SCROLL_STATE_IDLE
                && !rvNestDemo.isComputingLayout()) {//避免滑動時(shí)刷新數(shù)據(jù)
            beans.get(position).setChecked(isChecked);
            setChildCheck(beans, position, isChecked);
        }
    }

    /**
     * child選中狀態(tài)變化
     *
     * @param beans     數(shù)據(jù)
     * @param position  child position
     * @param isChecked 選中狀態(tài)
     */
    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()) {//避免滑動時(shí)刷新數(shù)據(jù)

            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()) {//只要有一個(gè)child沒有選中夸政,group就不是選中
                    if (groupBean.isChecked() && !isChecked) {//group為選中狀態(tài)
                        setGroupCheck(beans, itemId, false);
                        mDemoAdapter.notifyItemChanged(ParseHelper.getGroupPosition(beans,
                                itemId));
                    }
                    return;
                }
            }

            //child全部選中元旬,group設(shè)置選中
            setGroupCheck(beans, itemId, true);
            mDemoAdapter.notifyItemChanged(ParseHelper.getGroupPosition(beans, itemId));
        }
    }

    /**
     * 一次設(shè)置group下所有child item選中狀態(tài)
     *
     * @param beans     整個(gè)數(shù)據(jù)list
     * @param position  group position
     * @param isChecked 設(shè)置選中狀態(tài)
     */
    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);
                }
            }
        }
    }

    /**
     * 設(shè)置group item選中狀態(tài)
     *
     * @param beans     整個(gè)數(shù)據(jù)list
     * @param itemId    child的itemId
     * @param isChecked 設(shè)置選中狀態(tài)
     */
    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;
            }
        }
    }
}

寫在最后:其實(shí)這篇文章并沒有涉及到說一些很難的知識點(diǎn)榴徐,只是單純的做一種實(shí)現(xiàn)思路的描述。天下程序千千萬匀归,實(shí)現(xiàn)的方法當(dāng)然也各不相同坑资。個(gè)人覺得,換一種思路看待問題穆端,然后再解決這個(gè)問題袱贮,得到的收獲遠(yuǎn)遠(yuǎn)要大于照本宣科的去解決問題。畢竟學(xué)習(xí)的目的是在解決問題的過程中拓寬思維体啰,提升自己攒巍,而不僅僅是為了解決問題。

gitHub源碼鏈接:https://github.com/Horrarndoo/NestingRecyclerViewDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末荒勇,一起剝皮案震驚了整個(gè)濱河市柒莉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌沽翔,老刑警劉巖兢孝,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異仅偎,居然都是意外死亡跨蟹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門橘沥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來窗轩,“玉大人,你說我怎么就攤上這事座咆∑沸眨” “怎么了寝并?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長腹备。 經(jīng)常有香客問我衬潦,道長,這世上最難降的妖魔是什么植酥? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任镀岛,我火速辦了婚禮,結(jié)果婚禮上友驮,老公的妹妹穿的比我還像新娘漂羊。我一直安慰自己,他們只是感情好卸留,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布走越。 她就那樣靜靜地躺著,像睡著了一般耻瑟。 火紅的嫁衣襯著肌膚如雪旨指。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天喳整,我揣著相機(jī)與錄音谆构,去河邊找鬼。 笑死框都,一個(gè)胖子當(dāng)著我的面吹牛搬素,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播魏保,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼熬尺,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了谓罗?” 一聲冷哼從身側(cè)響起粱哼,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎妥衣,沒想到半個(gè)月后皂吮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡税手,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年蜂筹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芦倒。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡艺挪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情麻裳,我是刑警寧澤口蝠,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站津坑,受9級特大地震影響妙蔗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜疆瑰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一眉反、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧穆役,春花似錦寸五、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至淹接,卻和暖如春十性,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蹈集。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工烁试, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留雇初,地道東北人拢肆。 一個(gè)月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像靖诗,于是被迫代替她去往敵國和親郭怪。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345