天天看點

安卓标簽LabelsView的簡單使用

前言:可以設定标簽的選中效果。 可以設定标簽的選中類型:不可選中、單選、限數量多選和不限數量多選等, 并支援設定必選項等功能

1、效果圖

安卓标簽LabelsView的簡單使用

2、關鍵代碼:LabelsView.java

import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import word.hello.com.module.R;

/**
 * 标簽清單
 */
public class LabelsView extends ViewGroup implements View.OnClickListener {

    private Context mContext;

    private ColorStateList mTextColor;
    private float mTextSize;
    private Drawable mLabelBg;
    private int mTextPaddingLeft;
    private int mTextPaddingTop;
    private int mTextPaddingRight;
    private int mTextPaddingBottom;
    private int mWordMargin;
    private int mLineMargin;
    private SelectType mSelectType;
    private int mMaxSelect;

    //用于儲存label資料的key
    private static final int KEY_DATA = R.id.labels_data;
    //用于儲存label位置的key
    private static final int KEY_POSITION = R.id.labels_position;

    private ArrayList<Object> mLabels = new ArrayList<>();
    //儲存選中的label的位置
    private ArrayList<Integer> mSelectLabels = new ArrayList<>();

    //儲存必選項。在多選模式下,可以設定必選項,必選項預設選中,不能反選
    private ArrayList<Integer> mCompulsorys = new ArrayList<>();

    private OnLabelClickListener mLabelClickListener;
    private OnLabelSelectChangeListener mLabelSelectChangeListener;

    /**
     * Label的選擇類型
     */
    public enum SelectType {
        //不可選中,也不響應選中事件回調。(預設)
        NONE(1),
        //單選,可以反選。
        SINGLE(2),
        //單選,不可以反選。這種模式下,至少有一個是選中的,預設是第一個
        SINGLE_IRREVOCABLY(3),
        //多選
        MULTI(4);

        int value;

        SelectType(int value) {
            this.value = value;
        }

        static SelectType get(int value) {
            switch (value) {
                case 1:
                    return NONE;
                case 2:
                    return SINGLE;
                case 3:
                    return SINGLE_IRREVOCABLY;
                case 4:
                    return MULTI;
            }
            return NONE;
        }
    }

    public LabelsView(Context context) {
        super(context);
        mContext = context;
    }

    public LabelsView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        getAttrs(context, attrs);
    }

    public LabelsView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        getAttrs(context, attrs);
    }

    private void getAttrs(Context context, AttributeSet attrs) {
        if (attrs != null) {
            TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.labels_view);
            int type = mTypedArray.getInt(R.styleable.labels_view_selectType, 1);
            mSelectType = SelectType.get(type);

            mMaxSelect = mTypedArray.getInteger(R.styleable.labels_view_maxSelect, 0);
            mTextColor = mTypedArray.getColorStateList(R.styleable.labels_view_labelTextColor);
            mTextSize = mTypedArray.getDimension(R.styleable.labels_view_labelTextSize,
                    sp2px(context, 14));
            mTextPaddingLeft = mTypedArray.getDimensionPixelOffset(
                    R.styleable.labels_view_labelTextPaddingLeft, 0);
            mTextPaddingTop = mTypedArray.getDimensionPixelOffset(
                    R.styleable.labels_view_labelTextPaddingTop, 0);
            mTextPaddingRight = mTypedArray.getDimensionPixelOffset(
                    R.styleable.labels_view_labelTextPaddingRight, 0);
            mTextPaddingBottom = mTypedArray.getDimensionPixelOffset(
                    R.styleable.labels_view_labelTextPaddingBottom, 0);
            mLineMargin = mTypedArray.getDimensionPixelOffset(R.styleable.labels_view_lineMargin, 0);
            mWordMargin = mTypedArray.getDimensionPixelOffset(R.styleable.labels_view_wordMargin, 0);
            int labelBgResId = mTypedArray.getResourceId(R.styleable.labels_view_labelBackground, 0);
            mLabelBg = getResources().getDrawable(labelBgResId);
            mTypedArray.recycle();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int count = getChildCount();
        int maxWidth = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();

        int contentHeight = 0; //記錄内容的高度
        int lineWidth = 0; //記錄行的寬度
        int maxLineWidth = 0; //記錄最寬的行寬
        int maxItemHeight = 0; //記錄一行中item高度最大的高度
        boolean begin = true; //是否是行的開頭

        for (int i = 0; i < count; i++) {
            View view = getChildAt(i);
            measureChild(view, widthMeasureSpec, heightMeasureSpec);

            if (!begin) {
                lineWidth += mWordMargin;
            } else {
                begin = false;
            }

            if (maxWidth <= lineWidth + view.getMeasuredWidth()) {
                contentHeight += mLineMargin;
                contentHeight += maxItemHeight;
                maxItemHeight = 0;
                maxLineWidth = Math.max(maxLineWidth, lineWidth);
                lineWidth = 0;
                begin = true;
            }
            maxItemHeight = Math.max(maxItemHeight, view.getMeasuredHeight());

            lineWidth += view.getMeasuredWidth();
        }

        contentHeight += maxItemHeight;
        maxLineWidth = Math.max(maxLineWidth, lineWidth);

        setMeasuredDimension(measureWidth(widthMeasureSpec, maxLineWidth),
                measureHeight(heightMeasureSpec, contentHeight));
    }

    private int measureWidth(int measureSpec, int contentWidth) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            result = contentWidth + getPaddingLeft() + getPaddingRight();
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        result = Math.max(result, getSuggestedMinimumWidth());
        return result;
    }

    private int measureHeight(int measureSpec, int contentHeight) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            result = contentHeight + getPaddingTop() + getPaddingBottom();
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        result = Math.max(result, getSuggestedMinimumHeight());
        return result;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

        int x = getPaddingLeft();
        int y = getPaddingTop();

        int contentWidth = right - left;
        int maxItemHeight = 0;

        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View view = getChildAt(i);

            if (contentWidth < x + view.getMeasuredWidth() + getPaddingRight()) {
                x = getPaddingLeft();
                y += mLineMargin;
                y += maxItemHeight;
                maxItemHeight = 0;
            }
            view.layout(x, y, x + view.getMeasuredWidth(), y + view.getMeasuredHeight());
            x += view.getMeasuredWidth();
            x += mWordMargin;
            maxItemHeight = Math.max(maxItemHeight, view.getMeasuredHeight());
        }
    }

    /*  用于儲存View的資訊的key  */
    private static final String KEY_SUPER_STATE = "key_super_state";
    private static final String KEY_TEXT_COLOR_STATE = "key_text_color_state";
    private static final String KEY_TEXT_SIZE_STATE = "key_text_size_state";
    private static final String KEY_BG_RES_ID_STATE = "key_bg_res_id_state";
    private static final String KEY_PADDING_STATE = "key_padding_state";
    private static final String KEY_WORD_MARGIN_STATE = "key_word_margin_state";
    private static final String KEY_LINE_MARGIN_STATE = "key_line_margin_state";
    private static final String KEY_SELECT_TYPE_STATE = "key_select_type_state";
    private static final String KEY_MAX_SELECT_STATE = "key_max_select_state";
    // 由于新版(1.4.0)的标簽清單允許設定任何類型的資料,而不僅僅是String。并且标簽顯示的内容
    // 最終由LabelTextProvider提供,是以LabelsView不再在onSaveInstanceState()和onRestoreInstanceState()
    // 中儲存和恢複标簽清單的資料。
    private static final String KEY_LABELS_STATE = "key_labels_state";
    private static final String KEY_SELECT_LABELS_STATE = "key_select_labels_state";
    private static final String KEY_COMPULSORY_LABELS_STATE = "key_select_compulsory_state";

    @Override
    protected Parcelable onSaveInstanceState() {

        Bundle bundle = new Bundle();
        //儲存父類的資訊
        bundle.putParcelable(KEY_SUPER_STATE, super.onSaveInstanceState());
        //儲存标簽文字顔色
        if (mTextColor != null) {
            bundle.putParcelable(KEY_TEXT_COLOR_STATE, mTextColor);
        }
        //儲存标簽文字大小
        bundle.putFloat(KEY_TEXT_SIZE_STATE, mTextSize);
        //儲存标簽背景 (由于背景改用Drawable,是以不能自動儲存和恢複)
//        bundle.putInt(KEY_BG_RES_ID_STATE, mLabelBgResId);
        //儲存标簽内邊距
        bundle.putIntArray(KEY_PADDING_STATE, new int[]{mTextPaddingLeft, mTextPaddingTop,
                mTextPaddingRight, mTextPaddingBottom});
        //儲存标簽間隔
        bundle.putInt(KEY_WORD_MARGIN_STATE, mWordMargin);
        //儲存行間隔
        bundle.putInt(KEY_LINE_MARGIN_STATE, mLineMargin);
        //儲存标簽的選擇類型
        bundle.putInt(KEY_SELECT_TYPE_STATE, mSelectType.value);
        //儲存标簽的最大選擇數量
        bundle.putInt(KEY_MAX_SELECT_STATE, mMaxSelect);
        //儲存标簽清單
//        if (!mLabels.isEmpty()) {
//            bundle.putStringArrayList(KEY_LABELS_STATE, mLabels);
//        }
        //儲存已選擇的标簽清單
        if (!mSelectLabels.isEmpty()) {
            bundle.putIntegerArrayList(KEY_SELECT_LABELS_STATE, mSelectLabels);
        }

        //儲存必選項清單
        if (!mCompulsorys.isEmpty()) {
            bundle.putIntegerArrayList(KEY_COMPULSORY_LABELS_STATE, mCompulsorys);
        }

        return bundle;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            //恢複父類資訊
            super.onRestoreInstanceState(bundle.getParcelable(KEY_SUPER_STATE));

            //恢複标簽文字顔色
            ColorStateList color = bundle.getParcelable(KEY_TEXT_COLOR_STATE);
            if (color != null) {
                setLabelTextColor(color);
            }
            //恢複标簽文字大小
            setLabelTextSize(bundle.getFloat(KEY_TEXT_SIZE_STATE, mTextSize));
//            //恢複标簽背景  (由于背景改用Drawable,是以不能自動儲存和恢複)
//            int resId = bundle.getInt(KEY_BG_RES_ID_STATE, mLabelBgResId);
//            if (resId != 0) {
//                setLabelBackgroundResource(resId);
//            }
            //恢複标簽内邊距
            int[] padding = bundle.getIntArray(KEY_PADDING_STATE);
            if (padding != null && padding.length == 4) {
                setLabelTextPadding(padding[0], padding[1], padding[2], padding[3]);
            }
            //恢複标簽間隔
            setWordMargin(bundle.getInt(KEY_WORD_MARGIN_STATE, mWordMargin));
            //恢複行間隔
            setLineMargin(bundle.getInt(KEY_LINE_MARGIN_STATE, mLineMargin));
            //恢複标簽的選擇類型
            setSelectType(SelectType.get(bundle.getInt(KEY_SELECT_TYPE_STATE, mSelectType.value)));
            //恢複标簽的最大選擇數量
            setMaxSelect(bundle.getInt(KEY_MAX_SELECT_STATE, mMaxSelect));
//            //恢複标簽清單
//            ArrayList<String> labels = bundle.getStringArrayList(KEY_LABELS_STATE);
//            if (labels != null && !labels.isEmpty()) {
//                setLabels(labels);
//            }
            //恢複必選項清單
            ArrayList<Integer> compulsory = bundle.getIntegerArrayList(KEY_COMPULSORY_LABELS_STATE);
            if (compulsory != null && !compulsory.isEmpty()) {
                setCompulsorys(compulsory);
            }
            //恢複已選擇的标簽清單
            ArrayList<Integer> selectLabel = bundle.getIntegerArrayList(KEY_SELECT_LABELS_STATE);
            if (selectLabel != null && !selectLabel.isEmpty()) {
                int size = selectLabel.size();
                int[] positions = new int[size];
                for (int i = 0; i < size; i++) {
                    positions[i] = selectLabel.get(i);
                }
                setSelects(positions);
            }
            return;
        }
        super.onRestoreInstanceState(state);
    }

    /**
     * 設定标簽清單
     *
     * @param labels
     */
    public void setLabels(List<String> labels) {
        setLabels(labels, new LabelTextProvider<String>() {
            @Override
            public CharSequence getLabelText(TextView label, int position, String data) {
                return data.trim();
            }
        });
    }

    /**
     * 設定标簽清單,标簽清單的資料可以是任何類型的資料,
     * 它最終顯示的内容由LabelTextProvider根據标簽的資料提供。
     *
     * @param labels
     * @param provider
     * @param <T>
     */
    public <T> void setLabels(List<T> labels, LabelTextProvider<T> provider) {
        //清空原有的标簽
        innerClearAllSelect();
        removeAllViews();
        mLabels.clear();

        if (labels != null) {
            mLabels.addAll(labels);
            int size = labels.size();
            for (int i = 0; i < size; i++) {
                addLabel(labels.get(i), i, provider);
            }
        }

        if (mSelectType == SelectType.SINGLE_IRREVOCABLY) {
            setSelects(0);
        }
    }

    /**
     * 擷取标簽清單
     *
     * @return
     */
    public <T> List<T> getLabels() {
        return (List<T>) mLabels;
    }

    private <T> void addLabel(T data, int position, LabelTextProvider<T> provider) {
        final TextView label = new TextView(mContext);
        label.setPadding(mTextPaddingLeft, mTextPaddingTop, mTextPaddingRight, mTextPaddingBottom);
        label.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
        label.setTextColor(mTextColor != null ? mTextColor : ColorStateList.valueOf(0xFF000000));
        //設定給label的背景(Drawable)是一個Drawable對象的拷貝,
        // 因為如果所有的标簽都共用一個Drawable對象,會引起背景錯亂。
        label.setBackgroundDrawable(mLabelBg.getConstantState().newDrawable());
        //label通過tag儲存自己的資料(data)和位置(position)
        label.setTag(KEY_DATA, data);
        label.setTag(KEY_POSITION, position);
        label.setOnClickListener(this);
        addView(label);
        label.setText(provider.getLabelText(label, position, data));
    }

    @Override
    public void onClick(View v) {
        if (v instanceof TextView) {
            TextView label = (TextView) v;
            if (mSelectType != SelectType.NONE) {
                if (label.isSelected()) {
                    if (mSelectType != SelectType.SINGLE_IRREVOCABLY
                            && !mCompulsorys.contains((Integer) label.getTag(KEY_POSITION))) {
                        setLabelSelect(label, false);
                    }
                } else if (mSelectType == SelectType.SINGLE || mSelectType == SelectType.SINGLE_IRREVOCABLY) {
                    innerClearAllSelect();
                    setLabelSelect(label, true);
                } else if (mSelectType == SelectType.MULTI
                        && (mMaxSelect <= 0 || mMaxSelect > mSelectLabels.size())) {
                    setLabelSelect(label, true);
                }
            }

            if (mLabelClickListener != null) {
                mLabelClickListener.onLabelClick(label, label.getTag(KEY_DATA), (int) label.getTag(KEY_POSITION));
            }
        }
    }

    private void setLabelSelect(TextView label, boolean isSelect) {
        if (label.isSelected() != isSelect) {
            label.setSelected(isSelect);
            if (isSelect) {
                mSelectLabels.add((Integer) label.getTag(KEY_POSITION));
            } else {
                mSelectLabels.remove((Integer) label.getTag(KEY_POSITION));
            }
            if (mLabelSelectChangeListener != null) {
                mLabelSelectChangeListener.onLabelSelectChange(label, label.getTag(KEY_DATA),
                        isSelect, (int) label.getTag(KEY_POSITION));
            }
        }
    }

    /**
     * 取消所有選中的label
     */
    public void clearAllSelect() {
        if (mSelectType != SelectType.SINGLE_IRREVOCABLY) {
            if (mSelectType == SelectType.MULTI && !mCompulsorys.isEmpty()) {
                clearNotCompulsorySelect();
            } else {
                innerClearAllSelect();
            }
        }
    }

    private void innerClearAllSelect() {
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            setLabelSelect((TextView) getChildAt(i), false);
        }
        mSelectLabels.clear();
    }

    private void clearNotCompulsorySelect() {
        int count = getChildCount();
        List<Integer> temps = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            if (!mCompulsorys.contains(i)) {
                setLabelSelect((TextView) getChildAt(i), false);
                temps.add(i);
            }

        }
        mSelectLabels.removeAll(temps);
    }

    /**
     * 設定選中label
     *
     * @param positions
     */
    public void setSelects(List<Integer> positions) {
        if (positions != null) {
            int size = positions.size();
            int[] ps = new int[size];
            for (int i = 0; i < size; i++) {
                ps[i] = positions.get(i);
            }
            setSelects(ps);
        }
    }

    /**
     * 設定選中label
     *
     * @param positions
     */
    public void setSelects(int... positions) {
        if (mSelectType != SelectType.NONE) {
            ArrayList<TextView> selectLabels = new ArrayList<>();
            int count = getChildCount();
            int size = mSelectType == SelectType.SINGLE || mSelectType == SelectType.SINGLE_IRREVOCABLY
                    ? 1 : mMaxSelect;
            for (int p : positions) {
                if (p < count) {
                    TextView label = (TextView) getChildAt(p);
                    if (!selectLabels.contains(label)) {
                        setLabelSelect(label, true);
                        selectLabels.add(label);
                    }
                    if (size > 0 && selectLabels.size() == size) {
                        break;
                    }
                }
            }

            for (int i = 0; i < count; i++) {
                TextView label = (TextView) getChildAt(i);
                if (!selectLabels.contains(label)) {
                    setLabelSelect(label, false);
                }
            }
        }
    }

    /**
     * 設定必選項,隻有在多項模式下,這個方法才有效
     *
     * @param positions
     */
    public void setCompulsorys(List<Integer> positions) {
        if (mSelectType == SelectType.MULTI && positions != null) {
            mCompulsorys.clear();
            mCompulsorys.addAll(positions);
            //必選項發生改變,就要恢複到初始狀态。
            innerClearAllSelect();
            setSelects(positions);
        }
    }

    /**
     * 設定必選項,隻有在多項模式下,這個方法才有效
     *
     * @param positions
     */
    public void setCompulsorys(int... positions) {
        if (mSelectType == SelectType.MULTI && positions != null) {
            List<Integer> ps = new ArrayList<>(positions.length);
            for (int i : positions) {
                ps.add(i);
            }
            setCompulsorys(ps);
        }
    }

    /**
     * 擷取必選項,
     *
     * @return
     */
    public List<Integer> getCompulsorys() {
        return mCompulsorys;
    }

    /**
     * 清空必選項,隻有在多項模式下,這個方法才有效
     */
    public void clearCompulsorys() {
        if (mSelectType == SelectType.MULTI && !mCompulsorys.isEmpty()) {
            mCompulsorys.clear();
            //必選項發生改變,就要恢複到初始狀态。
            innerClearAllSelect();
        }
    }

    /**
     * 擷取選中的label(傳回的是所有選中的标簽的位置)
     *
     * @return
     */
    public List<Integer> getSelectLabels() {
        return mSelectLabels;
    }

    /**
     * 擷取選中的label(傳回的是所頭選中的标簽的資料)
     *
     * @param <T>
     * @return
     */
    public <T> List<T> getSelectLabelDatas() {
        List<T> list = new ArrayList<>();
        int size = mSelectLabels.size();
        for (int i = 0; i < size; i++) {
            View label = getChildAt(mSelectLabels.get(i));
            Object data = label.getTag(KEY_DATA);
            if (data != null) {
                list.add((T) data);
            }
        }
        return list;
    }

    /**
     * 設定标簽背景
     *
     * @param resId
     */
    public void setLabelBackgroundResource(int resId) {
        setLabelBackgroundDrawable(getResources().getDrawable(resId));
    }

    /**
     * 設定标簽背景
     *
     * @param color
     */
    public void setLabelBackgroundColor(int color) {
        setLabelBackgroundDrawable(new ColorDrawable(color));
    }

    /**
     * 設定标簽背景
     *
     * @param drawable
     */
    public void setLabelBackgroundDrawable(Drawable drawable) {
        mLabelBg = drawable;
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            TextView label = (TextView) getChildAt(i);
            label.setBackgroundDrawable(mLabelBg.getConstantState().newDrawable());
        }
    }

    /**
     * 設定标簽内邊距
     *
     * @param left
     * @param top
     * @param right
     * @param bottom
     */
    public void setLabelTextPadding(int left, int top, int right, int bottom) {
        if (mTextPaddingLeft != left || mTextPaddingTop != top
                || mTextPaddingRight != right || mTextPaddingBottom != bottom) {
            mTextPaddingLeft = left;
            mTextPaddingTop = top;
            mTextPaddingRight = right;
            mTextPaddingBottom = bottom;
            int count = getChildCount();
            for (int i = 0; i < count; i++) {
                TextView label = (TextView) getChildAt(i);
                label.setPadding(left, top, right, bottom);
            }
        }
    }

    public int getTextPaddingLeft() {
        return mTextPaddingLeft;
    }

    public int getTextPaddingTop() {
        return mTextPaddingTop;
    }

    public int getTextPaddingRight() {
        return mTextPaddingRight;
    }

    public int getTextPaddingBottom() {
        return mTextPaddingBottom;
    }

    /**
     * 設定标簽的文字大小(機關是px)
     *
     * @param size
     */
    public void setLabelTextSize(float size) {
        if (mTextSize != size) {
            mTextSize = size;
            int count = getChildCount();
            for (int i = 0; i < count; i++) {
                TextView label = (TextView) getChildAt(i);
                label.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
            }
        }
    }

    public float getLabelTextSize() {
        return mTextSize;
    }

    /**
     * 設定标簽的文字顔色
     *
     * @param color
     */
    public void setLabelTextColor(int color) {
        setLabelTextColor(ColorStateList.valueOf(color));
    }

    /**
     * 設定标簽的文字顔色
     *
     * @param color
     */
    public void setLabelTextColor(ColorStateList color) {
        mTextColor = color;
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            TextView label = (TextView) getChildAt(i);
            label.setTextColor(mTextColor != null ? mTextColor : ColorStateList.valueOf(0xFF000000));
        }
    }

    public ColorStateList getLabelTextColor() {
        return mTextColor;
    }

    /**
     * 設定行間隔
     */
    public void setLineMargin(int margin) {
        if (mLineMargin != margin) {
            mLineMargin = margin;
            requestLayout();
        }
    }

    public int getLineMargin() {
        return mLineMargin;
    }

    /**
     * 設定标簽的間隔
     */
    public void setWordMargin(int margin) {
        if (mWordMargin != margin) {
            mWordMargin = margin;
            requestLayout();
        }
    }

    public int getWordMargin() {
        return mWordMargin;
    }

    /**
     * 設定标簽的選擇類型
     *
     * @param selectType
     */
    public void setSelectType(SelectType selectType) {
        if (mSelectType != selectType) {
            mSelectType = selectType;
            //選擇類型發生改變,就要恢複到初始狀态。
            innerClearAllSelect();
            if (mSelectType == SelectType.SINGLE_IRREVOCABLY) {
                setSelects(0);
            }
            if (mSelectType != SelectType.MULTI) {
                mCompulsorys.clear();
            }
        }
    }

    public SelectType getSelectType() {
        return mSelectType;
    }

    /**
     * 設定最大的選擇數量
     *
     * @param maxSelect
     */
    public void setMaxSelect(int maxSelect) {
        if (mMaxSelect != maxSelect) {
            mMaxSelect = maxSelect;
            if (mSelectType == SelectType.MULTI) {
                //最大選擇數量發生改變,就要恢複到初始狀态。
                innerClearAllSelect();
            }
        }
    }

    public int getMaxSelect() {
        return mMaxSelect;
    }

    /**
     * 設定标簽的點選監聽
     *
     * @param l
     */
    public void setOnLabelClickListener(OnLabelClickListener l) {
        mLabelClickListener = l;
    }

    /**
     * 設定标簽的選擇監聽
     *
     * @param l
     */
    public void setOnLabelSelectChangeListener(OnLabelSelectChangeListener l) {
        mLabelSelectChangeListener = l;
    }

    /**
     * sp轉px
     */
    public static int sp2px(Context context, float spVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                spVal, context.getResources().getDisplayMetrics());
    }

    public interface OnLabelClickListener {

        /**
         * @param label    标簽
         * @param data     标簽對應的資料
         * @param position 标簽位置
         */
        void onLabelClick(TextView label, Object data, int position);
    }

    public interface OnLabelSelectChangeListener {

        /**
         * @param label    标簽
         * @param data     标簽對應的資料
         * @param isSelect 是否選中
         * @param position 标簽位置
         */
        void onLabelSelectChange(TextView label, Object data, boolean isSelect, int position);
    }

    /**
     * 給标簽提供最終需要顯示的資料。因為LabelsView的清單可以設定任何類型的資料,而LabelsView裡的每個item的是一
     * 個TextView,隻能顯示CharSequence的資料,是以LabelTextProvider需要根據每個item的資料傳回item最終要顯示
     * 的CharSequence。
     *
     * @param <T>
     */
    public interface LabelTextProvider<T> {

        /**
         * 根據data和position傳回label需要需要顯示的資料。
         *
         * @param label
         * @param position
         * @param data
         * @return
         */
        CharSequence getLabelText(TextView label, int position, T data);
    }

}
           

對應style:

<!--标簽清單樣式-->
    <declare-styleable name="labels_view">
        <attr name="selectType" format="enum">
            <enum name="NONE" value="1" />
            <enum name="SINGLE" value="2" />
            <enum name="SINGLE_IRREVOCABLY" value="3" />
            <enum name="MULTI" value="4" />
        </attr>
        <attr name="maxSelect" format="integer" />
        <attr name="labelTextColor" format="reference" />
        <attr name="labelTextSize" format="dimension" />
        <attr name="labelTextPaddingLeft" format="dimension" />
        <attr name="labelTextPaddingTop" format="dimension" />
        <attr name="labelTextPaddingRight" format="dimension" />
        <attr name="labelTextPaddingBottom" format="dimension" />
        <attr name="lineMargin" format="dimension" />
        <attr name="wordMargin" format="dimension" />
        <attr name="labelBackground" format="reference" />
    </declare-styleable>
           

3、使用方法

public class LabelsViewActivity extends AppCompatActivity implements View.OnClickListener {

    private LabelsView labelsView;
    private TextView text1;
    private List<String> stringList = new ArrayList<>();
    private List<LabelsDto> listData = new ArrayList<>();

    private String[] labelsData = {"咖啡", "飲食", "男裝", "女裝", "眼鏡", "内衣配飾", "母嬰",
            "鞋靴", "運動", "箱包"};


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_labels_view);
        labelsView = this.findViewById(R.id.labelsView);
        text1 = this.findViewById(R.id.text1);
        findViewById(R.id.button1).setOnClickListener(this);
        findViewById(R.id.button2).setOnClickListener(this);
        findViewById(R.id.button3).setOnClickListener(this);
        findViewById(R.id.button4).setOnClickListener(this);

        initData();
    }

    /**
     * 初始化資料源
     */
    private void initData() {
        //①:普通String類型
        for (int i = 0; i < labelsData.length; i++) {
            stringList.add(labelsData[i]);
        }
        //②:标簽帶ID類型等
        //标題和id
        listData.add(new LabelsDto("近視眼鏡", "1"));
        listData.add(new LabelsDto("精品男裝", "2"));
        listData.add(new LabelsDto("女裙", "3"));
        listData.add(new LabelsDto("男鞋", "4"));
        listData.add(new LabelsDto("筆記本", "5"));
        listData.add(new LabelsDto("生活用品", "6"));
        listData.add(new LabelsDto("廚房家具", "7"));
        listData.add(new LabelsDto("3D數位", "8"));
        //第一步:設定資料源
        labelsView.setLabels(listData, new LabelsView.LabelTextProvider<LabelsDto>() {
            @Override
            public CharSequence getLabelText(TextView label, int position, LabelsDto data) {
                return data.getLabelTitle();根據data和position傳回label需要顯示的資料。
            }
        });
        //第二步:點選事件
        labelsView.setOnLabelClickListener(new LabelsView.OnLabelClickListener() {
            @Override
            public void onLabelClick(TextView label, Object data, int position) {
                //label是被點選的标簽(例:label.getText().toString()),
                // data是标簽所對應的資料,position是标簽的位置。
                text1.setText("點選的标題:" + listData.get(position).getLabelTitle()
                        + "\n點選的id:" + listData.get(position).getLabelId());
            }
        });
        //第三步:标簽選中事件
        labelsView.setOnLabelSelectChangeListener(new LabelsView.OnLabelSelectChangeListener() {
            @Override
            public void onLabelSelectChange(TextView label, Object data, boolean isSelect, int position) {
                //label是被選中的标簽,data是标簽所對應的資料,isSelect是是否選中,position是标簽的位置
            }
        });
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.button1:
                labelsView.setSelectType(LabelsView.SelectType.NONE);
                break;
            case R.id.button2:
                labelsView.setSelectType(LabelsView.SelectType.SINGLE);
                break;
            case R.id.button3:
                labelsView.setSelectType(LabelsView.SelectType.SINGLE_IRREVOCABLY);
                break;
            case R.id.button4:
                labelsView.setSelectType(LabelsView.SelectType.MULTI);
                break;
        }
    }
}
           

對應實體類:

public class LabelsDto {

    private String labelTitle;//标簽标題
    private String labelId;//标簽id

    public String getLabelTitle() {
        return labelTitle;
    }

    public void setLabelTitle(String labelTitle) {
        this.labelTitle = labelTitle;
    }

    public String getLabelId() {
        return labelId;
    }

    public void setLabelId(String labelId) {
        this.labelId = labelId;
    }

    public LabelsDto(String labelTitle, String labelId) {
        this.labelTitle = labelTitle;
        this.labelId = labelId;
    }
}

           

對應布局檔案:

<?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"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal">

        <Button
            android:id="@+id/button1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="不可選中" />

        <Button
            android:id="@+id/button2"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="單選,可以反選" />

        <Button
            android:id="@+id/button3"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="單選,不可以反選" />

        <Button
            android:id="@+id/button4"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="多選" />
    </LinearLayout>

    <word.hello.com.module.view.LabelsView
        android:id="@+id/labelsView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="15dp"
        app:labelBackground="@drawable/label_shape"
        app:labelTextColor="@drawable/label_text"
        app:labelTextPaddingBottom="5dp"
        app:labelTextPaddingLeft="10dp"
        app:labelTextPaddingRight="10dp"
        app:labelTextPaddingTop="5dp"
        app:labelTextSize="16sp"
        app:lineMargin="10dp"
        app:maxSelect="3"
        app:selectType="MULTI"
        app:wordMargin="10dp" />

    <TextView
        android:id="@+id/text1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

           

注意:word.hello.com.module.view.LabelsView(路徑更改)

對應兩個drawable:label_shape、label_text

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- 标簽選中時的背景 -->
    <item android:state_selected="true">
        <shape>
            <corners android:radius="10dp" />
            <solid android:color="@color/home_color" />
        </shape>
    </item>

    <!-- 标簽的正常背景 -->
    <item>
        <shape>
            <stroke android:width="1dp" android:color="@color/home_color" />
            <corners android:radius="10dp" />
            <solid android:color="@color/white_bg_color" />
        </shape>
    </item>

</selector>
           
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- 标簽選中時的字型顔色 -->
    <item android:color="@color/white_bg_color" android:state_selected="true" />
    <!-- 标簽未選中時的字型顔色 -->
    <item android:color="@color/home_color" />

</selector>
           

顔色值:

<color name="white_bg_color">#ffffff</color>
    <color name="home_color">#8F40C0</color>
           

String values中添加:

<item name="labels_data" type="id" />
    <item name="labels_position" type="id" />
           

4、常用方法屬性圖:

界面描述 方法名
标簽選中顔色(可單獨設定一種顔色) labelTextColor
标簽的背景 labelBackground
字型大小 labelTextSize
标簽下邊距 labelTextPaddingBottom
标簽左邊距 labelTextPaddingLeft
标簽右邊距 labelTextPaddingRight
标簽上邊距 labelTextPaddingTop
行間距 lineMargin
标簽間隔 wordMargin
标簽的選擇類型 有單選(可反選)、單選(不可反選)、多選、不可選四種類型) selectType
标簽的最大選擇數量,(多選的時候才有用),0為不限數量 maxSelect

注意:以上均可在代碼中動态進行設定

常用方法 方法名
設定選中标簽①(可以多個) labelsView.setSelects(1,4,7);
設定選中标簽② List integers=new ArrayList<>(); integers.add(1);integers.add(4);integers.add(7);labelsView.setSelects(integers);
擷取選中的标簽(傳回的是所有選中的标簽的下标) labelsView.getSelectLabels();
擷取選中的label(傳回的是所有選中的标簽的資料) labelsView.getSelectLabelDatas();
取消所有選中的标簽 labelsView.clearAllSelect();
設定必選項①,(模式必須為多項模式) labelsView.setCompulsorys(1,4,7);
設定必選項② 方法同标簽②一緻
清空必選項(模式必須為多項模式) labelsView.clearCompulsorys();

注意:所有的set方法都有對應的get方法

最後附上原github.位址:https://github.com/donkingliang/LabelsView