瀑布流的标签选择器

最近需要做一个类似标签选择的功能,类似饿了么的评价页面的标签选择,没办法,项目需要,代码撸起来

先来看看实现效果:

Lable.gif

最终的效果就是上面那个样子,接下来来看看实现步骤:

既然里面是一个个的标签,那我们就先把标签做出来,我这边是直接继承 RadioButton 来实现这个标签效果的

修改 RadioButton 的样式达到我们想要的效果,代码如下:

class LableItem extends RadioButton {
        public LableItem(Context context) {
            super(context);
            initStyle();
            Click();
        }
        public LableItem(Context context, AttributeSet attrs) {
            super(context, attrs);
            initStyle();
            Click();
        }
        /**
         * 设置标签item的样式 需要修改样式 直接修改drawable文件夹下select文件即可
         */
        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
        public void initStyle() {
            Bitmap bitmap = null;
            setButtonDrawable(new BitmapDrawable(bitmap));
            setBackground(getResources().getDrawable(R.drawable.select));
            setTextSize(textsize);
            setTextColor(no_textcolor);
            setPadding(padding, padding, padding, padding);
            setGravity(Gravity.CENTER);
        }
}

这里我们用到了 selector 文件和两个 shape 文件来更改 RadioButton 的选中和不选中的样式

selector 代码:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true" android:drawable="@drawable/checked"></item>
    <item android:state_checked="false" android:drawable="@drawable/uncheck"></item>
</selector>

未选中时的 shape 代码:

<?xml version="1.0" encoding="utf-8"?>
<shape  xmlns:android="http://schemas.android.com/apk/res/android">
    <corners android:radius="3dp"></corners>
    <solid android:color="#ffffff"></solid>
    <stroke android:color="#bdbdbd" android:width="0.5dp"></stroke>
</shape>

选中时的 shape 代码:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <corners android:radius="3dp"></corners>
    <solid android:color="#fff5e9"></solid>
    <stroke android:color="#ff8977" android:width="0.5dp"></stroke>
</shape>

这样就达到了选中和不选中时的样式改变

然后我们接着给他设置点击事件 让点击选中,再次点击取消选中:

 public void Click() {
            this.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {

                    if (select == 0) {
                        setChecked(true);
                        select = 1;
                        setTextColor(select_textcolor);
                        if (onItemSelectClick != null) {
                            onItemSelectClick.selectclick(getText().toString(), Integer.parseInt(getTag().toString()));
                        }
                    } else if (select == 1) {
                        setChecked(false);
                        select = 0;
                        setTextColor(no_textcolor);
                     
                    }
                }
            });

这样就可以让他点击选中,再次点击取消选中

这样标签我们就做好了,完整的标签代码:

class LableItem extends RadioButton {

        private int select = 0;

        public LableItem(Context context) {
            super(context);
            initStyle();
            Click();
        }

        public LableItem(Context context, AttributeSet attrs) {
            super(context, attrs);
            initStyle();
            Click();
        }

        /**
         * 设置标签item的样式 需要修改样式 直接修改drawable文件夹下select文件即可
         */
        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
        public void initStyle() {
            Bitmap bitmap = null;
            setButtonDrawable(new BitmapDrawable(bitmap));
            setBackground(getResources().getDrawable(R.drawable.select));
            setTextSize(textsize);
            setTextColor(no_textcolor);
            setPadding(padding, padding, padding, padding);
            setGravity(Gravity.CENTER);
        }


        public void Click() {
            this.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (!ismultiple){
                        clearSelect();
                    }

                    if (select == 0) {
                        setChecked(true);
                        select = 1;
                        setTextColor(select_textcolor);
                        if (onItemSelectClick != null) {
                            onItemSelectClick.selectclick(getText().toString(), Integer.parseInt(getTag().toString()));
                        }
                    } else if (select == 1) {
                        setChecked(false);
                        select = 0;
                        setTextColor(no_textcolor);
                        if (onCancelSelectClick != null) {
                            onCancelSelectClick.cancelselectclick(getText().toString(), Integer.parseInt(getTag().toString()));
                        }
                    }
                }
            });
        }

        /**
         * 判断是否选中
         *
         * @return
         */
        public boolean isSelect() {
            return select == 0 ? false : true;
        }

        public void setSelect(int select){
            this.select=select;
        }

    }

接下来我们开始做装这个标签的瀑布流,这边我是选择继承的 ViewGroup 然后把标签一个一个添加进去,然后进行计算排序,首先我们把要用到的自定义属性在 attrs 文件中定义好

自定义属性:

<declare-styleable name="Lable">
        <attr name="lable_textSize" format="dimension"></attr> //文字的大小
        <attr name="lable_opttextColor" format="color"></attr> //选中时文字的颜色
        <attr name="lable_notextColor" format="color"></attr> //未选中时文字的颜色
        <attr name="lable_padding" format="integer"></attr> //内边距(int型)
        <attr name="lable_margin" format="integer"></attr> //外边距(int型)
        <attr name="lable_ismultiple" format="boolean"></attr> //是否可多选 false-单选 true-多选
    </declare-styleable>

我这边的自定义属性就暂时只定义了这么多,然后我们在自定义控件里面获取一下它们:

public Lable(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray typedArray=context.obtainStyledAttributes(attrs,R.styleable.Lable);
        textsize=px2dip(typedArray.getDimensionPixelSize(R.styleable.Lable_lable_textSize,dip2px(16)));
        select_textcolor=typedArray.getColor(R.styleable.Lable_lable_opttextColor, Color.BLACK);
        no_textcolor=typedArray.getColor(R.styleable.Lable_lable_notextColor,Color.BLACK);
        padding=typedArray.getInteger(R.styleable.Lable_lable_padding,10);
        margin=typedArray.getInteger(R.styleable.Lable_lable_margin,8);
        ismultiple=typedArray.getBoolean(R.styleable.Lable_lable_ismultiple,true);
    }

接着我们把标签添加进来:

public void initLableItem() {
        if (listcount == null || listcount.size() == 0)
            return;
        for (int i = 0; i < listcount.size(); i++) {
            LableItem lableItem = new LableItem(mContext);
            lableItem.setText(listcount.get(i));
            lableItem.setTag(i + 1);
            addView(lableItem);
        }
    }

这个 listcount 就是一个 List 里面装的就是标签的文字信息,有多少条内容就创建多少个标签并添加到我们的 View当中,添加完成之后,我们就要去在 onMeasure 方法中测量我们控件的宽高,在这里我们用 view.getWidth() 是获取不到我们标签的真实宽高的,获取不到,我们就无法做到瀑布流的效果,那么我们就用Paint 去获取文字宽高进行计算得到标签的真实宽高

我们先初始化一下 Paint :

public void init() {
        paint = new Paint();
        paint.setTextSize(dip2px(textsize));
        scenewidth = getScene(SCENEWIDTH);
    }

然后我们把要用到的计算封装成方法,方便以后调用

获取屏幕的宽高:

 /**
     * 获取屏幕的高宽
     *
     * @param i
     * @return
     */
    public int getScene(int i) {
        WindowManager wm = (WindowManager) getContext()
                .getSystemService(Context.WINDOW_SERVICE);
        int width = wm.getDefaultDisplay().getWidth();
        int height = wm.getDefaultDisplay().getHeight();
        if (i == SCENEWIDTH) {
            return width;
        } else if (i == SCENEHEIGHT) {
            return height;
        }
        return 0;
    }

获取标签的高度:

/**
     * 获取标签的高度
     *
     * @param paint
     * @return
     */
    public int getLableHeight(Paint paint) {
        Paint.FontMetrics fm = paint.getFontMetrics();
        return (int) Math.ceil(fm.descent - fm.ascent) + padding;
    }

获取标签的宽度:

 /**
     * 获取标签的宽度
     *
     * @param paint
     * @param str
     * @return
     */
    public int getLableWidth(Paint paint, String str) {
        int iRet = 0;
        if (str != null && str.length() > 0) {
            int len = str.length();
            float[] widths = new float[len];
            paint.getTextWidths(str, widths);
            for (int j = 0; j < len; j++) {
                iRet += (int) Math.ceil(widths[j]);
            }
        }
        return iRet + padding;
    }

因为我们后面会让他们有外边距和内边距的效果,所以把 padding 直接在这里计算了

接着我们真正的在 onMeasure 里面来测量控件的宽高:

/**
     * 控件的宽高测量
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width = 0;
        int height = getLableHeight(paint) + margin + padding;

        int widths = 0;

        if (listcount.size() == 0) {
            width = 0;
            height = 0;

        } else {
            for (int i = 0; i < listcount.size(); i++) {
                int itemWidth = getLableWidth(paint, listcount.get(i));
                if (widths + itemWidth > scenewidth) { //判断有没有换行
                    height += getLableHeight(paint) + margin + padding;
                    widths = margin;
                    isbr = false; //换行标识
                }
                if (isbr) {
                    width += itemWidth + margin;//如果没有换行 就按实际的宽度去测量
                } else {
                    width = widthSize; //只要换行了 就证明宽度是充满的
                }
                widths += itemWidth + margin;
            }
            height += margin;//在底部加上一个外边距
            if (isbr)
                width += margin;//这是为了在没有换行的情况下 最后在加上一个外边距
        }
        setMeasuredDimension(width, height);
    }

宽高测量好之后我们就要去在 onLayout 里面安排标签的位置:

/**
     * 安排摆放位置
     *
     * @param changed
     * @param l
     * @param t
     * @param r
     * @param b
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int count = getChildCount();

        int width = margin;
        int height = margin;

        if (count == 0) {
            width = 0;
            height = 0;
        }

        lableItems.clear();

        // 获取标签高度
        int itemheight = getLableHeight(paint);
        for (int i = 0; i < count; i++) {
            LableItem lableItem = (LableItem) getChildAt(i);
            lableItems.add(lableItem);
            // 获取标签宽度
            int itemWidth = getLableWidth(paint, listcount.get(i))+margin;

            if (width + itemWidth > scenewidth) { 
            //如果item的宽度加上下一个item的宽度大于屏幕宽度的话 那么这个时候就要换行了
                height += itemheight + margin + padding;
                width = margin;
            }
            lableItem.layout(width, height, width + itemWidth, height + itemheight + padding);
            width += itemWidth + margin;
        }
    }

这样就实现了瀑布流的效果,然后我们再给他加上一些供使用者调用的方法:

获取选中的内容字符串:

 public List<String> getSelectContent() {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < lableItems.size(); i++) {
            if (lableItems.get(i).isSelect()) {
                list.add(lableItems.get(i).getText().toString());
            }
        }
        return list;
    }

取消全部选中:

 public void clearSelect(){

       for (int i=0;i<getChildCount();i++){
           LableItem lableItem= (LableItem) getChildAt(i);
           if (lableItem.isSelect()){
               lableItem.setChecked(false);
               lableItem.setTextColor(no_textcolor);
               lableItem.setSelect(0);
               if (onCancelAllSelectListener!=null){
                   onCancelAllSelectListener.cancelalllistener();
               }
           }
       }
    }

然后添加对外部使用的点击事件的回调等,就大功告成了!

全部代码如下:

package com.example.yinshuai.thelabel;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.RadioButton;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * Created by 尹帅 on 2017/8/7.
 *
 * 瀑布流的标签选择
 *
 * 作者:尹帅 1317972280@qq.com
 */
public class Lable extends ViewGroup {
    private final static int SCENEWIDTH = 1;
    private final static int SCENEHEIGHT = 2;
    private float textsize = 0;
    private int select_textcolor=0;
    private int no_textcolor=0;
    private int padding = 0;
    private int margin = 0;
    private boolean ismultiple=true;

    private int scenewidth;
    private boolean isbr = true;
    private Paint paint;
    private List<String> listcount = new ArrayList<>();
    private Context mContext;
    private List<LableItem> lableItems = new ArrayList<>();

    public OnItemSelectClickListener onItemSelectClick;
    public OnCancelSelectClickListener onCancelSelectClick;
    public OnCancelAllSelectListener onCancelAllSelectListener;

    public Lable(Context context) {
        super(context);
        mContext = context;
        init();
    }

    public Lable(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;

        TypedArray typedArray=context.obtainStyledAttributes(attrs,R.styleable.Lable);
        textsize=px2dip(typedArray.getDimensionPixelSize(R.styleable.Lable_lable_textSize,dip2px(16)));
        select_textcolor=typedArray.getColor(R.styleable.Lable_lable_opttextColor, Color.BLACK);
        no_textcolor=typedArray.getColor(R.styleable.Lable_lable_notextColor,Color.BLACK);
        padding=typedArray.getInteger(R.styleable.Lable_lable_padding,10);
        margin=typedArray.getInteger(R.styleable.Lable_lable_margin,8);
        ismultiple=typedArray.getBoolean(R.styleable.Lable_lable_ismultiple,true);
        init();
    }

    /**
     * 对外提供选中监听的接口
     */
    public interface OnItemSelectClickListener {
        void selectclick(String text, int position);
    }

    public void setOnItemSelectClickListener(OnItemSelectClickListener onItemClick) {
        this.onItemSelectClick = onItemClick;
    }

    /**
     * 对外提供取消item选中的接口
     */
    public interface OnCancelSelectClickListener {
        void cancelselectclick(String text, int position);
    }

    public void setOnCancelSelectClickListener(OnCancelSelectClickListener onCancelSelectClick) {
        this.onCancelSelectClick = onCancelSelectClick;
    }

    public interface OnCancelAllSelectListener{
        void cancelalllistener();
    }

    public void setOnCancelAllSelectListener(OnCancelAllSelectListener l){
         onCancelAllSelectListener=l;
    }

    /**
     * 初始化
     */
    public void initLableItem() {
        if (listcount == null || listcount.size() == 0)
            return;
        for (int i = 0; i < listcount.size(); i++) {
            LableItem lableItem = new LableItem(mContext);
            lableItem.setText(listcount.get(i));
            lableItem.setTag(i + 1);
            addView(lableItem);
        }
    }

    public void init() {
        paint = new Paint();
        paint.setTextSize(dip2px(textsize));
        scenewidth = getScene(SCENEWIDTH);
    }

    /**
     * 设置数据
     * @param data
     */
    public void setDataList(List<String> data) {
        this.listcount.addAll(data);
        initLableItem();
    }

    /**
     * 控件的宽高测量
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width = 0;
        int height = getLableHeight(paint) + margin + padding;

        int widths = 0;

        if (listcount.size() == 0) {
            width = 0;
            height = 0;

        } else {
            for (int i = 0; i < listcount.size(); i++) {
                int itemWidth = getLableWidth(paint, listcount.get(i));
                if (widths + itemWidth > scenewidth) { //判断有没有换行
                    height += getLableHeight(paint) + margin + padding;
                    widths = margin;
                    isbr = false; //换行标识
                }
                if (isbr) {
                    width += itemWidth + margin;//如果没有换行 就按实际的宽度去测量
                } else {
                    width = widthSize; //只要换行了 就证明宽度是充满的
                }
                widths += itemWidth + margin;
            }
            height += margin;//在底部加上一个外边距
            if (isbr)
                width += margin;//这是为了在没有换行的情况下 最后在加上一个外边距
        }
        setMeasuredDimension(width, height);
    }

    /**
     * 安排摆放位置
     *
     * @param changed
     * @param l
     * @param t
     * @param r
     * @param b
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int count = getChildCount();

        int width = margin;
        int height = margin;

        if (count == 0) {
            width = 0;
            height = 0;
        }

        lableItems.clear();

        // 获取标签高度
        int itemheight = getLableHeight(paint);
        for (int i = 0; i < count; i++) {
            LableItem lableItem = (LableItem) getChildAt(i);
            lableItems.add(lableItem);
            // 获取标签宽度
            int itemWidth = getLableWidth(paint, listcount.get(i))+margin;

            if (width + itemWidth > scenewidth) { //如果item的宽度加上下一个item的宽度大于屏幕宽度的话 那么这个时候就要换行了
                height += itemheight + margin + padding;
                width = margin;
            }
            lableItem.layout(width, height, width + itemWidth, height + itemheight + padding);
            width += itemWidth + margin;
        }
    }


    /**
     * 获取选中的内容字符串
     */
    public List<String> getSelectContent() {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < lableItems.size(); i++) {
            if (lableItems.get(i).isSelect()) {
                list.add(lableItems.get(i).getText().toString());
            }
        }
        return list;
    }

    /**
     * 取消全部选中
     */
    public void clearSelect(){

       for (int i=0;i<getChildCount();i++){
           LableItem lableItem= (LableItem) getChildAt(i);
           if (lableItem.isSelect()){
               lableItem.setChecked(false);
               lableItem.setTextColor(no_textcolor);
               lableItem.setSelect(0);
               if (onCancelAllSelectListener!=null){
                   onCancelAllSelectListener.cancelalllistener();
               }
           }
       }
    }

    /**
     * 获取标签的高度
     *
     * @param paint
     * @return
     */
    public int getLableHeight(Paint paint) {
        Paint.FontMetrics fm = paint.getFontMetrics();
        return (int) Math.ceil(fm.descent - fm.ascent) + padding;
    }

    /**
     * 获取标签的宽度
     *
     * @param paint
     * @param str
     * @return
     */
    public int getLableWidth(Paint paint, String str) {
        int iRet = 0;
        if (str != null && str.length() > 0) {
            int len = str.length();
            float[] widths = new float[len];
            paint.getTextWidths(str, widths);
            for (int j = 0; j < len; j++) {
                iRet += (int) Math.ceil(widths[j]);
            }
        }
        return iRet + padding;
    }


    /**
     * 标签的Item
     */
    class LableItem extends RadioButton {

        private int select = 0;

        public LableItem(Context context) {
            super(context);
            initStyle();
            Click();
        }

        public LableItem(Context context, AttributeSet attrs) {
            super(context, attrs);
            initStyle();
            Click();
        }

        /**
         * 设置标签item的样式 需要修改样式 直接修改drawable文件夹下select文件即可
         */
        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
        public void initStyle() {
            Bitmap bitmap = null;
            setButtonDrawable(new BitmapDrawable(bitmap));
            setBackground(getResources().getDrawable(R.drawable.select));
            setTextSize(textsize);
            setTextColor(no_textcolor);
            setPadding(padding, padding, padding, padding);
            setGravity(Gravity.CENTER);
        }


        public void Click() {
            this.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (!ismultiple){
                        clearSelect();
                    }

                    if (select == 0) {
                        setChecked(true);
                        select = 1;
                        setTextColor(select_textcolor);
                        if (onItemSelectClick != null) {
                            onItemSelectClick.selectclick(getText().toString(), Integer.parseInt(getTag().toString()));
                        }
                    } else if (select == 1) {
                        setChecked(false);
                        select = 0;
                        setTextColor(no_textcolor);
                        if (onCancelSelectClick != null) {
                            onCancelSelectClick.cancelselectclick(getText().toString(), Integer.parseInt(getTag().toString()));
                        }
                    }
                }
            });
        }

        /**
         * 判断是否选中
         *
         * @return
         */
        public boolean isSelect() {
            return select == 0 ? false : true;
        }

        public void setSelect(int select){
            this.select=select;
        }

    }


    /**
     * 获取屏幕的高宽
     *
     * @param i
     * @return
     */
    public int getScene(int i) {
        WindowManager wm = (WindowManager) getContext()
                .getSystemService(Context.WINDOW_SERVICE);
        int width = wm.getDefaultDisplay().getWidth();
        int height = wm.getDefaultDisplay().getHeight();
        if (i == SCENEWIDTH) {
            return width;
        } else if (i == SCENEHEIGHT) {
            return height;
        }
        return 0;
    }


    /**
     * dp-->px
     *
     * @param dipValue
     * @return
     */
    private int dip2px(float dipValue) {
        final float scale = mContext.getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    /**
     * sp转px
     *
     * @return
     */

    public int sp2px(float spVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spVal, mContext.getResources().getDisplayMetrics());
    }

    /**
     * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
     */
    public int px2dip( float pxValue) {
        final float scale = mContext.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

    /**
     * 将px值转换为sp值,保证文字大小不变
     *
     * @param pxValue
     * @return
     */
    public  int px2sp( float pxValue) {
        final float fontScale = mContext.getResources().getDisplayMetrics().scaledDensity;
        return (int) (pxValue / fontScale + 0.5f);
    }
}

项目 GitHub 地址:Lable
个人博客地址:小白的博客

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值