Android使用RecyclerView+RadioGroup实现选择添加标签的效果

目录

一丶概述

二丶Layout布局

三丶Activity

四丶最重要的Adapter了

五丶说点其他的


一丶概述

最近遇到一个需求,如下面的图所示。

                 

功能可能并不难,就是实现一个添加标签,涉及到流式布局,很多人都知道怎么做,但是作为刚入行不久的我还是稍微有点难度,所以决定写来记录下自己的实现方式,其实连这都是请教了朋友才完成的,他帮我完成了一大部分。这里除了记录还得感谢我的这位朋友。当然,肯定有很多很多更好的方式有待我日后慢慢去学习。小生知识微薄,所以只能使用最笨的方式。还请见谅。不足之处,还望大家多多指点。

二丶Layout布局

因为需求是怕以后会有改动,所以我们要做成动态的列表去展示。根据图来可以看成有n个类型,没个类型里面有很多元素。并且最后一项是其他标签,可以多选,其他只能单选。博主其实刚开始想用三方FlowTagLayout来做的,可是做完过后发现有很多地方并不满足,所以就放弃了,可能是因为博主学艺不精吧。

最后博主决定使用RecyclerView来做,RecyclerView的item使用自定义RedioGroup,然后addview就好了。不废话了代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:orientation="vertical"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:fadeScrollbars="true"
    android:background="@color/white"
    android:layout_height="match_parent">
    <include layout="@layout/includ_toolbar"/>
    <View
        android:layout_width="match_parent"
        android:background="@color/background"
        android:layout_height="@dimen/Height_10dp"/>
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1">
        <android.support.v7.widget.RecyclerView
            android:id="@+id/tag_recyclerView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            />
    </LinearLayout>
    <Button
        android:id="@+id/save_label"
        android:layout_width="match_parent"
        android:text="@string/save_tag"
        android:textColor="@color/white"
        android:textSize="14dp"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:textStyle="bold"
        android:layout_marginBottom="10dp"
        android:background="@drawable/button_bule_selector"
        android:layout_height="35dp" />
</LinearLayout>

外层布局,其实没有什么,就是一个RecyclerView。

接下里是item的布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:background="@color/white"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <LinearLayout
        android:id="@+id/ll_tag_0"
        android:layout_marginTop="@dimen/Height_10dp"
        android:orientation="horizontal"
        android:layout_marginRight="20dp"
        android:layout_marginLeft="20dp"
        android:gravity="center_vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <ImageView
            android:id="@+id/im_name"
            android:layout_width="20dp"
            android:background="@mipmap/excavate_spot"
            android:layout_height="20dp" />
        <TextView
            android:id="@+id/t_name"
            android:layout_marginLeft="10dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/title_text_color"
            android:textSize="16dp"
            android:text="客户意向"
            />
    </LinearLayout>
    <com.cnct.crm.widegt.FlowRadioGroup
        android:id="@+id/flowRadioGroup"
        android:layout_width="match_parent"
        android:layout_marginLeft="30dp"
        android:layout_height="wrap_content"/>
</LinearLayout>

item的布局我就使用了一个自定义的FlowRadioGroup,代码在下面。

FlowRadioGroup:里面注视已经写好了。

public class FlowRadioGroup extends RadioGroup {
    public FlowRadioGroup(Context context) {
        super(context);
    }

    public FlowRadioGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * 储存所有的view 按行记录
     */
    private List<List<View>> mAllViews = new ArrayList<List<View>>();
    /**
     * 记录每一行的高度
     */
    private List<Integer> mLineHeight = new ArrayList<Integer>();
    private String TAG = "TAG";

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 置空 view 容器 和 lineHeight 容器  重新赋值
        //因为OnMeasure方法会走两次,第一次是实例化这个对象的时候高度和宽度都是0
        //之后走了OnSizeChange()方法后 又走了一次OnMeasure,所以要把第一次加进去的数据清空。
        mAllViews.clear();
        mLineHeight.clear();
        //得到上级容器为其推荐的宽高和计算模式
        int specWidthMode = MeasureSpec.getMode(widthMeasureSpec);
        int specHeighMode = MeasureSpec.getMode(heightMeasureSpec);
        int specWidthSize = MeasureSpec.getSize(widthMeasureSpec);
        int specHeighSize = MeasureSpec.getSize(heightMeasureSpec);
        // 计算出所有的 child 的 宽和高
//      measureChildren(specWidthSize, specHeighSize);
        // 记录如果是 warp_content 是设置的宽和高
        int width = 0;
        int height = 0;
        // 得到子view的个数
        int cCount = getChildCount();
        /**
         * 记录每一行的宽度,width不断取最大宽度
         */
        int lineWidth = 0;
        /**
         * 每一行的高度,累加至height
         */
        int lineHeight = 0;

        // 存储每一行所有的childView
        List<View> lineViews = new ArrayList<View>();

        for (int i = 0; i < cCount; i++) {
            // 得到每个子View
            View child = getChildAt(i);
            // 测量每个子View的宽高
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            // 当前子view的lp
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            // 子view的宽和高
            int cWidth = 0;
            int cheight = 0;
            // 当前子 view 实际占的宽
            cWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            // 当前子View 实际占的高
            cheight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            lineHeight=cheight;
            // 需要换行
            if(lineWidth + cWidth > specWidthSize){
                width = Math.max(lineWidth, cWidth);// 取最大值
                lineWidth = cWidth; // 开启新行的时候重新累加width
                // 开启新行时累加 height
//              lineHeight = cheight; // 记录下一行的高度
                mAllViews.add(lineViews);
                mLineHeight.add(cheight);
                lineViews = new ArrayList<>();
                // 换行的时候把该 view 放进 集合里
                lineViews.add(child);// 这个  view(child) 是下一行的第一个view
                height += cheight; //每个View高度是一样的,直接累加
                Log.e("需要换行", "hight--" + height);
                Log.e("onMeasure", "AllViews.size()  --  > " + mAllViews.size());
            }else {
                // 不需要换行
                lineWidth += cWidth;//
                Log.e("不需要换行","hight--"+height);
                // 不需要换行时 把子View add 进集合
                lineViews.add(child);
            }

            if(i == cCount-1){
                // 如果是最后一个view
                width = Math.max(lineWidth, cWidth);
                height += cheight;
                Log.e("最后一个view","hight--"+height);
            }
        }
        // 循环结束后 把最后一行内容add进集合中
        mLineHeight.add(lineHeight); // 记录最后一行
        mAllViews.add(lineViews);
        // MeasureSpec.EXACTLY 表示设置了精确的值
        // 如果 mode 是 MeasureSpec.EXACTLY 时候,则不是 warp_content 用计算来的值,否则则用上级布局分给它的值
        setMeasuredDimension(
                specWidthMode == MeasureSpec.EXACTLY ? specWidthSize : width,
                specHeighMode == MeasureSpec.EXACTLY ? specHeighSize : height
        );
        Log.e("onMeasure", "mAllViews.size() -- > " + mAllViews.size() + "   mLineHeight.size() -- > " + mLineHeight.size() + "Height -- > "+height);
    }

    /**
     * 所有childView的位置的布局
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // 当前行的最大高度
        int lineHeight = 0;
        // 存储每一行所有的childView
        List<View> lineViews = new ArrayList<View>();
        int left = 0;
        int top = 0;
        // 得到总行数
        int lineNums = mAllViews.size();
        for (int i = 0; i < lineNums; i++)
        {
            // 每一行的所有的views
            lineViews = mAllViews.get(i);
            // 当前行的最大高度
            lineHeight = mLineHeight.get(i);

            Log.e("onLayout" , "第" + i + "行 :" + lineViews.size()+"-------lineHeight"+ lineHeight);

            // 遍历当前行所有的View
            for (int j = 0; j < lineViews.size(); j++)
            {
                View child = lineViews.get(j);
                if (child.getVisibility() == View.GONE)
                {
                    continue;
                }
                MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

                //计算childView的left,top,right,bottom
                int lc = left + lp.leftMargin;
                int tc = top + lp.topMargin;
                int rc =lc + child.getMeasuredWidth();
                int bc = tc + child.getMeasuredHeight();

                child.layout(lc, tc, rc, bc);

                left += child.getMeasuredWidth() + lp.rightMargin + lp.leftMargin;
            }
            left = 0;
            top += lineHeight;
        }
        Log.v("onLayout", "onLayout   mAllViews.size() -- > " + mAllViews.size() + "   mLineHeight.size() -- > "+ mLineHeight.size());
    }


    /**
     * 这个一定要设置,否则会包强转错误
     * 设置它支持 marginLayoutParams
     */
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {

        return (LayoutParams) new MarginLayoutParams(getContext(),attrs);
    }
}

三丶Activity

TagActivity 就是我的主Activity,主要是接受adapter回调选中的数据进行操作和获取数据。

public class NewTagActivity extends BaseActivity implements AddlabelView {
    private RecyclerView mTagRecyclerView;
    private AddLabelPresenter presenter;
    private NewTagRecyclerViewAdapter mTagAdapter;


    @Override
    protected int initContentView() {
        return R.layout.new_tag;
    }

    @Override
    protected void setStatus() {

    }

    @Override
    protected String getToolbarTitle() {
        return null;
    }

    @Override
    protected void initUi() {
        mTagRecyclerView = findViewById(R.id.tag_recyclerView);
        findViewById(R.id.save_label).setOnClickListener(this);
        mTagRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mTagRecyclerView.addItemDecoration(new SpaceItemDecoration(10));
        mTagAdapter = new NewTagRecyclerViewAdapter(this);
        mTagRecyclerView.setAdapter(mTagAdapter);

        initData();
    }

    private void initData() {
        presenter = new AddLabelPresenter(this);
       // 这里是adapter定义的回调,会把选中的标签返回到acticity以便进行操作
        presenter.getLabelDataBean(this);
    }


    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.save_label:
              
                saveLabel();
                break;
        }
    }
 // 点击保存后的操作
    
    private void saveLabel() {
        List<ClickLabelDataBean.SubLabelBean> allSelectedSubBean = mTagAdapter.getAllSelectedSubBean();
        Log.e("zjb", "saveLabel: "+allSelectedSubBean.size());
        for (ClickLabelDataBean.SubLabelBean subLabelBean : allSelectedSubBean) {
            Log.e("zjb", "saveLabel: "+subLabelBean.getLabel_name());
        }
    }

      // 这里是数据,我用的是mvp模式,比较懒,也就没有去搞专门的测试数据了。这个不重要
    @Override
    public void showLabelDateBean(List<ClickLabelDataBean> dataBeanList) {
         // 因为跟后台商量好了最后一个会返回其它这个类型,这里也set也是为了标记下当是最后一个集合的时候就是多选
        dataBeanList.get(dataBeanList.size() - 1).setLabel_type(1);
        mTagAdapter.setDatas(dataBeanList);
    }

   
}

四丶最重要的Adapter了

最关键的就是adapter了 不墨迹 我直接上代码。 代码里面有注视,尽力看注释吧。

public class NewTagRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private static final int MAX_MUXSLECTE_LIMIT_SIZE = 5;
    private Context mContext;
    private int [] img = {R.mipmap.excavate_spot,R.mipmap.intention_spot,R.mipmap.other_spot,
            R.mipmap.relationship_spot,R.mipmap.trust_spot};
    private List<ClickLabelDataBean> mLabelBeanList = new ArrayList<>();
    private Map<String, List<ClickLabelDataBean.SubLabelBean>> mAllSelectedSubBean = new HashMap<>();


    public NewTagRecyclerViewAdapter(Context context) {
        this.mContext = context;
    }

    public void addDatas(List<ClickLabelDataBean> datas){
        this.mLabelBeanList.addAll(datas);
        notifyDataSetChanged();
    }

    public void clearDatas(){
        this.mLabelBeanList.clear();
        notifyDataSetChanged();
    }

    public void setDatas(List<ClickLabelDataBean> datas){
        this.mLabelBeanList.clear();
        addDatas(datas);
        Toast.makeText(mContext, datas.size(), Toast.LENGTH_SHORT).show();
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_tag, parent, false);
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        MyViewHolder myViewHolder = (MyViewHolder) holder;
        ClickLabelDataBean subLabelBean = mLabelBeanList.get(position);

        //ui
        myViewHolder.mTvName.setText(subLabelBean.getLabel_name());
        myViewHolder.mIvName.setImageResource(img[position]);

        //暂存选中数据
        List<ClickLabelDataBean.SubLabelBean> eachLineSelectDatas = new ArrayList<>();
        String label_id = subLabelBean.getLabel_id();
        mAllSelectedSubBean.put(label_id, eachLineSelectDatas);

        final List<ClickLabelDataBean.SubLabelBean> subLabel = subLabelBean.getSubLabel();
        if(subLabelBean.getLabel_type() == 0) {// 是单选选项
            handleSubSingleItemClick(myViewHolder.mRadioGroup, eachLineSelectDatas, label_id);
            for (ClickLabelDataBean.SubLabelBean labelBean : subLabel) {
                RadioButton radioButton = new RadioButton(mContext);
                myViewHolder.mRadioGroup.addView(radioButton);
                radioButton.setId(radioButton.hashCode());
                radioButton.setText(labelBean.getLabel_name());
                radioButton.setTag(labelBean);
                if("1".equals(labelBean.getChoose())){
                    myViewHolder.mRadioGroup.check(radioButton.hashCode());
                }
            }
        }else if(subLabelBean.getLabel_type() == 1){ //多选选项
            int size = subLabel.size();
            for (int i = 0; i < size; i++) {
                ClickLabelDataBean.SubLabelBean labelBean = subLabel.get(i);
                if(i == size - 1 ){ //【添加其他标签】
                    TextView textView = new TextView(mContext);
                    textView.setText(labelBean.getLabel_name());
                    textView.setTextSize(20);
                    handlesubAddOtherTagClick(textView);
                    myViewHolder.mRadioGroup.addView(textView);
                }else {
                    CheckBox checkBox = new CheckBox(mContext);
                    handleSubMuxItemClick(checkBox, eachLineSelectDatas, label_id);
                    checkBox.setText(labelBean.getLabel_name());
                    checkBox.setTag(labelBean);
                    if("1".equals(labelBean.getChoose())){
                        checkBox.setChecked(true);
                    }
                    myViewHolder.mRadioGroup.addView(checkBox);
                }

            }
        }

    }

    //处理【添加其他标签】的点击
    private void handlesubAddOtherTagClick(TextView textView) {
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ToastUtil.showToast(textView.getText().toString());
            }
        });
    }

    // 处理多选的点击
    private void handleSubMuxItemClick(CheckBox checkBox, List<ClickLabelDataBean.SubLabelBean> selectSingleDatas, String label_id) {
        checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                ClickLabelDataBean.SubLabelBean subLabelData = (ClickLabelDataBean.SubLabelBean) checkBox.getTag();
                if(isChecked){
                    if(selectSingleDatas.size() >= MAX_MUXSLECTE_LIMIT_SIZE){
                        ToastUtil.showToast(R.string.max_muxselcted_size);
                        checkBox.setChecked(false);
                        return;
                    }
                    selectSingleDatas.add(subLabelData);
                }else {
                    selectSingleDatas.remove(subLabelData);
                }
                mAllSelectedSubBean.put(label_id, selectSingleDatas);
            }
        });

    }

    //处理单选的点击
    private void handleSubSingleItemClick(FlowRadioGroup mRadioGroup, List<ClickLabelDataBean.SubLabelBean> selectMuxDatas, String label_id) {
        mRadioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                RadioButton radioButton = mRadioGroup.findViewById(checkedId);
                ClickLabelDataBean.SubLabelBean subLabelBean = (ClickLabelDataBean.SubLabelBean) radioButton.getTag();
                selectMuxDatas.clear();
                selectMuxDatas.add(subLabelBean);
                mAllSelectedSubBean.put(label_id, selectMuxDatas);
            }
        });
    }

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


    static class MyViewHolder extends RecyclerView.ViewHolder{
        FlowRadioGroup mRadioGroup;
        ImageView mIvName;
        TextView mTvName;

        MyViewHolder(View itemView) {
            super(itemView);
            mRadioGroup = (FlowRadioGroup) itemView.findViewById(R.id.flowRadioGroup);
            mIvName = (ImageView) itemView.findViewById(R.id.im_name);
            mTvName = (TextView) itemView.findViewById(R.id.t_name);
        }
    }

    //提供给外部获取全部选中的结果
    List<ClickLabelDataBean.SubLabelBean> getAllSelectedSubBean() {
        List<ClickLabelDataBean.SubLabelBean> datas = new ArrayList<>();
        Set<Map.Entry<String, List<ClickLabelDataBean.SubLabelBean>>> entries = mAllSelectedSubBean.entrySet();
        for (Map.Entry<String, List<ClickLabelDataBean.SubLabelBean>> entry : entries) {
            datas.addAll(entry.getValue());
        }
        return datas;
    }
}

五丶说点其他的

大概基本就是这样了,博主也是第一次写文章,以后会进行优化。希望大家看到的不喜勿喷。我也只是才做了一年多的小人物。只是想记录自己的一点一滴,以后肯定会有更好的实现方式,但是现在肚子里的墨水太少了。写这篇博客也只是为了提醒自己,记录一下而已。如果大家有更好的实现方式,麻烦大家倾囊相授。

祝大家工作顺利,生活愉快。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值