自定义标签多选更改背景图片

本文介绍如何在项目中创建一个自定义布局,使用CheckBox实现多选标签功能。通过重写ViewGroup的onDraw、onMeasure和onLayout方法,类似流式布局,详细讲解了实体类TagBean、XML布局、背景选中效果、字体选中效果以及在Activity中的应用,提供了一种解决标签布局问题的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在做项目时候遇到了标签布局问题,当时我首选的是用流式布局TagFlowLayout的控件,可是发现不能满足我项目的需求,于是翻看了一下网页写了一个用配合ChekBox来实现多选标签的布局,在这写出来,希望对其他伙伴们有些帮助!
需取效果:
这里写图片描述
这里写图片描述

首先:我们需要一个实体类TagBean,确保有id,和name(显示的内容)


public class TagBean {
    private String id;
    private String name;

    public void setId(String id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }


    public String getId() {
        return id;
    }


    public String getName() {
        return name;
    }

    public TagBean(String id, String name) {
        this.id = id;
        this.name = name;
    }
}

然后就可以写我们的自定义布局了,重写ViewGroup,采用onDraw,onMeasure,onLayout等方法类似于流式布局,熟悉流式布局的小伙伴应该so easy,这里就不说太多了,直接上代码:

public class LabelLayout extends ViewGroup {

    private int mMaxCheckCount = Integer.MAX_VALUE;
    /**
     * 竖直方向间距, default is 8.0dp.
     */
    private int horizontalSpacing;

    /**
     * 水平方向间距, default is 4.0dp.
     */
    private int verticalSpacing;

    //whether or not to draw the divider between labels at horizon.
    private boolean enableDivider = false; //是否允许显示分割线  默认不显示
    private int dividerColor = 0xffECECEC;
    private float dividerHeight;

    private int checkboxLayoutId;

    //nark checked labels.
    private Map<String, Boolean> labelcheckMap;
    //mark the first position in a row.
    private Set<Integer> rowPositons = new HashSet<>();

    //The paint to draw the divider.
    Paint dividerPaint;

    public LabelLayout(Context context) {
        this(context, null);
    }

    public LabelLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LabelLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setWillNotDraw(false);

        labelcheckMap = new HashMap<>();

        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LabelLayout, defStyleAttr, R.style.LabelLayoutDefault);
        try {
            horizontalSpacing = (int) a.getDimension(R.styleable.LabelLayout_label_horizontalSpacing, dp2px(8.0f));
            verticalSpacing = (int) a.getDimension(R.styleable.LabelLayout_label_verticalSpacing, dp2px(4.0f));

            checkboxLayoutId = a.getResourceId(R.styleable.LabelLayout_label_checkboxLayout, R.layout.view_label_common);
            enableDivider = a.getBoolean(R.styleable.LabelLayout_label_enableDivider, false);
            dividerHeight = a.getDimension(R.styleable.LabelLayout_label_dividerHeight, dp2px(2));
            dividerColor = a.getColor(R.styleable.LabelLayout_label_dividerColor, 0xffECECEC);
        } finally {
            a.recycle();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (enableDivider) {
            if (dividerPaint == null) {
                dividerPaint = new Paint();
                dividerPaint.setAntiAlias(true);
                dividerPaint.setColor(dividerColor);
                dividerPaint.setStyle(Paint.Style.FILL);
            }

            for (Integer top : rowPositons) {
                if (top != 0) {
                    //draw lines between labels.
                    canvas.drawRect(0, top - dividerHeight / 2, getMeasuredWidth(), top + dividerHeight / 2, dividerPaint);
                }
            }
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        measureChildren(widthMeasureSpec, heightMeasureSpec);

        int width = 0;
        int height = 0;

        int row = 0; // The row counter.
        int rowWidth = 0; // Calc the current row width.
        int rowMaxHeight = 0; // Calc the max tag height, in current row.

        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();

            if (child.getVisibility() != GONE) {
                rowWidth += childWidth;
                if (rowWidth > widthSize) { // Next line.
                    rowWidth = childWidth; // The next row width.
                    height += rowMaxHeight + verticalSpacing;
                    rowMaxHeight = childHeight; // The next row max height.
                    row++;
                } else { // This line.
                    rowMaxHeight = Math.max(rowMaxHeight, childHeight);
                }
                rowWidth += horizontalSpacing;
            }
//            System.out.println("measured height:" + height);
            rowPositons.add(height - verticalSpacing / 2);
        }
        // Account for the last row height.
        height += rowMaxHeight;

        // Account for the padding too.
        height += getPaddingTop() + getPaddingBottom();

        // If the tags grouped in one row, set the width to wrap the tags.
        if (row == 0) {
            width = rowWidth;
            width += getPaddingLeft() + getPaddingRight();
        } else {// If the tags grouped exceed one line, set the width to match the parent.
            width = widthSize;
        }

        setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width,
                heightMode == MeasureSpec.EXACTLY ? heightSize : height);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int parentLeft = getPaddingLeft();
        final int parentRight = r - l - getPaddingRight();
        final int parentTop = getPaddingTop();
        final int parentBottom = b - t - getPaddingBottom();

        int childLeft = parentLeft;
        int childTop = parentTop;

        int rowMaxHeight = 0;

        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            final int width = child.getMeasuredWidth();
            final int height = child.getMeasuredHeight();

            if (child.getVisibility() != GONE) {
                if (childLeft + width > parentRight) { // Next line
                    childLeft = parentLeft;
                    childTop += rowMaxHeight + verticalSpacing;
                    rowMaxHeight = height;
                } else {
                    rowMaxHeight = Math.max(rowMaxHeight, height);
                }
                child.layout(childLeft, childTop, childLeft + width, childTop + height);

                childLeft += width + horizontalSpacing;
            }
        }
    }

    /**
     * set the default labels that you wanna to add into       LabelLayout.
     *
     * @param labels A collection contains objects that implement ILabel interface.
     */
    public void setLabels(List<TagBean> labels) {
        labelcheckMap.clear();
        removeAllViews();
        if (labels == null || labels.size() == 0) return;
        for (final TagBean label : labels) {
            final CheckBox tagView = (CheckBox) View.inflate(getContext(), checkboxLayoutId, null);
            tagView.setText(label.getName());
            addView(tagView);
            tagView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                    if (isChecked) {//选中时添加到map
                        if (labelcheckMap.size() > mMaxCheckCount - 1) {
                            if (checkListener != null) {
                                checkListener.onBeyondMaxCheckCount();

                            }
                            tagView.setChecked(false);
                        } else {
                            labelcheckMap.put(label.getId(), true);
                            if (checkListener != null) {
                                checkListener.onCheckChanged(label, true);
                                Drawable drawable = getResources().getDrawable(R.mipmap.select_tag);
                                tagView.setCompoundDrawablePadding(10);
                                drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
  //设置选中头部图片
                               tagView.setCompoundDrawables(drawable, null, null, null);
                            }
                        }
                    } else {//否则及时清理map
                        if (labelcheckMap.containsKey(label.getId())) {
                            labelcheckMap.remove(label.getId());
                            if (checkListener != null) {
                                checkListener.onCheckChanged(label, false);
 //设置未选中头部图片
                                tagView.setCompoundDrawables(null, null, null, null);
                            }
                        }
                    }
                }
            });
        }
    }

    /**
     * set the maximum numbers of checked labels.
     */
    public void setMaxCheckCount(int count) {
        mMaxCheckCount = count;
    }

    /**
     * Get the current selected tag number.
     */
    public int getCheckedLabelsCount() {
        int count = 0;
        for (Map.Entry<String, Boolean> m : labelcheckMap.entrySet()) {
            if (m.getValue()) {
                count++;
            }
        }
        return count;
    }

    public List<String> getCheckedLabelIds() {
        List<String> chechedLabelIds = new ArrayList<>();
        for (Map.Entry<String, Boolean> m : labelcheckMap.entrySet()) {
            if (m.getValue()) {
                chechedLabelIds.add(m.getKey());
            }
        }
        return chechedLabelIds;
    }

    /**
     * To serialize checked-label ids as json, make benefit for use.
     *
     * @return json string
     */
    public String getCheckedIdsAsJson() {
        List<String> chechedId = new ArrayList<>();
        for (Map.Entry<String, Boolean> m : labelcheckMap.entrySet()) {
            if (m.getValue()) {
                chechedId.add(m.getKey());
            }
        }
        return new JSONArray(chechedId).toString();
    }


    private float dp2px(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
                getResources().getDisplayMetrics());
    }
 //监听方法
    private OnCheckChangeListener checkListener;

    public interface OnCheckChangeListener {
        void onCheckChanged(TagBean label, boolean isChecked);

        void onBeyondMaxCheckCount();
    }

    public void setOnCheckChangedListener(OnCheckChangeListener checkListener) {
        this.checkListener = checkListener;
    }

上面是布局类代码我全展示出来,
还有就是xml文件的代码:
ChekBox的:

<?xml version="1.0" encoding="utf-8"?>
<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="32dp"
    android:layout_margin="10dp"
    android:background="@drawable/label_selector"
    android:button="@null"
    android:paddingBottom="4dp"
    android:paddingLeft="13dp"
    android:paddingRight="13dp"
    android:paddingTop="4dp"
    android:text="hello"
    android:textColor="@drawable/label_gray_selector"
    android:textSize="16sp" />

背景选中效果代码:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true">
        <shape android:shape="rectangle">
            <corners android:radius="6dp" />
            <solid android:color="#fff" />
        </shape>
    </item>
    <item>
        <shape android:shape="rectangle">
            <corners android:radius="6dp" />
            <stroke android:width="1dp" android:color="#fff" />
        </shape>
    </item>
</selector>

字体选中效果:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="#FF9400" android:state_checked="true" />
    <item android:color="#434343" />
</selector>

主布局 :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.l.tag_label.MainActivity">

   <com.example.l.tag_label.LabelLayout
       android:id="@+id/label_me"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:layout_margin="16dp"
       >
   </com.example.l.tag_label.LabelLayout>
   <Button
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:onClick="bt"
       android:text="点击"/>
</LinearLayout>

然后就是activity中的代码了,
首先添加数据:

  private ArrayList<TagBean> initList() {
        ArrayList<TagBean> list = new ArrayList<>();
        for (int i = 0; i < 8; i++) {
            TagBean bean = new TagBean(i + "", "hello" + i);
            list.add(bean);
        }
        return list;
    }

然后再把数据传过去,

 tab.setLabels(list);//  设置数据
        tab.setMaxCheckCount(4);//  设置最大选中数量
        //  点击选中取消监听
        tab.setOnCheckChangedListener(new LabelLayout.OnCheckChangeListener() {
            @Override
            public void onCheckChanged(TagBean label, boolean isChecked) {
                String name = label.getName();
                Toast.makeText(MainActivity.this, isChecked + "==" + name, Toast.LENGTH_SHORT).show();
                Log.d("dd", name + "");
                //  在这块可以自己写个集合储存一下
                if (isChecked)
                    name_liat.add(name);
                else
                    name_liat.remove(name);

            }

            @Override
            public void onBeyondMaxCheckCount() {

            }
        });

最后执行自己的button按钮监听方法就可以把集合里面的东西拿出来了。

public void bt(View view) {
        for (int i = 0; i <name_liat.size() ; i++) {
            Log.d("ee", name_liat.get(i).toString() + "");
        }

    }

写的够详细,简单了!如果感觉有帮助,请点赞!!^_^
效果图:这里写图片描述
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值