自定义控件之TagGroup

来源:互联网 发布:淘宝客户运营平台在哪 编辑:程序博客网 时间:2024/06/05 16:35

转载请注明出处:http://blog.csdn.net/crazy1235/article/details/74907150


标签瀑布流布局!

实现方式有很多种。

  • 继承LinearLayout

  • 继承ViewGroup

  • 继承别的布局…


继承LinearLayout

继承LinearLayout相对来说,实现比较简单!不需要自己处理onMeasure() 和 onLayout() 函数!

整个布局设置成 LinearLayout.VERTICAL 排列模式!

然后每一行作为一个子LinearLayout,通过 addView(LinearLayout)进去!子LinearLayout是 LinearLayout.HORIZONTAL 排列模式!

当一行addView(tag)的时候超过了最外层的宽度,则需要另起一行!


继承ViewGroup

继承ViewGroup的情况,比较麻烦。需要自己处理onMeasure()和onLayout() !

自定义一套TagGroup 需要注意的地方:

  • 标签的margin值

  • 标签的padding值

  • TagGroup的padding值

  • 标签的行间距

  • 标签的列间距

  • 字体大小

  • 字体颜色

  • 标签按下的状态

  • 选中标签

  • ……


SuperTagGroup

首先是,属性定义:

    <declare-styleable name="SuperTagGroup">        <attr name="horizontal_spacing" format="dimension" />        <attr name="vertical_spacing" format="dimension" />        <attr name="tag_horizontal_padding" format="dimension" />        <attr name="tag_vertical_padding" format="dimension" />        <attr name="tag_text_size" format="dimension" />        <attr name="tag_text_color" format="color" />        <attr name="tag_corner_radius" format="dimension" />        <attr name="tag_border_width" format="dimension" />        <attr name="tag_border_color" format="color" />        <attr name="tag_border_checked_color" format="color" />        <attr name="tag_bg_color" format="color" />        <attr name="tag_bg_checked_color" format="color" />        <attr name="tag_bg_drawable" format="reference" />        <attr name="max_selected_count" format="integer" />    </declare-styleable>

然后在构造函数中,读取出这些属性

public SuperTagGroup(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SuperTagGroup, defStyleAttr, R.style.SuperTagGroup);        horizontalSpace = ta.getDimension(R.styleable.SuperTagGroup_horizontal_spacing, 0);        verticalSpace = ta.getDimension(R.styleable.SuperTagGroup_vertical_spacing, 0);        horizontalPadding = (int) ta.getDimension(R.styleable.SuperTagGroup_tag_horizontal_padding, 0);        verticalPadding = (int) ta.getDimension(R.styleable.SuperTagGroup_tag_vertical_padding, 0);        textSize = ta.getDimension(R.styleable.SuperTagGroup_tag_text_size, 0);        textColor = ta.getColor(R.styleable.SuperTagGroup_tag_text_color, 0);        cornerRadius = ta.getDimension(R.styleable.SuperTagGroup_tag_corner_radius, 0);        borderWidth = ta.getDimension(R.styleable.SuperTagGroup_tag_border_width, 0);        borderColor = ta.getColor(R.styleable.SuperTagGroup_tag_border_color, 0);        tagBorderCheckedColor = ta.getColor(R.styleable.SuperTagGroup_tag_border_checked_color, 0);        tagBgColor = ta.getColor(R.styleable.SuperTagGroup_tag_bg_color, 0);        tagBgCheckedColor = ta.getColor(R.styleable.SuperTagGroup_tag_bg_checked_color, 0);        tagBgDrawable = ta.getResourceId(R.styleable.SuperTagGroup_tag_bg_drawable, 0);        maxSelectedNum = ta.getInt(R.styleable.SuperTagGroup_max_s    ta.recycle();        init();    }

接着就是重要的onMeasure() 函数

用来对自身布局和子view进行测量,得到测量宽高来进行设置

主要就是针对每个子view进行测量,注意过程中要加上我们设置的padding值,和spacing值!

针对各个子view测量完毕之后,再加上布局的padding值,即可得到布局的宽高!

@Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        Log.d("SuperTagGroup", "onMeasure");        // width size & mode        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        // height size & mode        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        // padding        int paddingLeft = getPaddingLeft();        int paddingTop = getPaddingTop();        int paddingRight = getPaddingRight();        int paddingBottom = getPaddingBottom();        // the width & height final result        int resultWidth = 0;        int resultHeight = 0;        int lineWidth = 0;        int lineHeight = 0;        for (int i = 0; i < getChildCount(); i++) {            View child = getChildAt(i);            if (child.getVisibility() == View.GONE) {                continue;            }            if (!(child instanceof SuperTagView)) {                throw new IllegalStateException("SuperTagGroup can only has SuperTagView child");            }            // measure child            measureChild(child, widthMeasureSpec, heightMeasureSpec);            // 这里要记得加上子view的margin值//            MarginLayoutParams childLayoutParams = (MarginLayoutParams) child.getLayoutParams();            int childWidth = child.getMeasuredWidth();            int childHeight = child.getMeasuredHeight();            lineHeight = Math.max(childHeight, lineHeight);            if (lineWidth + childWidth > widthSize - paddingLeft - paddingRight) { // 需要换一行                resultWidth = Math.max(resultWidth, lineWidth); // 每一行都进行比较,最终得到最宽的值                resultHeight += verticalSpace + lineHeight;                lineWidth = (int) (childWidth + horizontalSpace); // 新的一行的宽度                lineHeight = childHeight; // 新的一行的高度            } else {                // 当前行的宽度                lineWidth += childWidth + horizontalSpace;                // 当前行最大的高度                lineHeight = Math.max(lineHeight, childHeight);            }            // 最后一个, 需要再次比较宽            if (i == getChildCount() - 1) {                resultWidth = Math.max(resultWidth, lineWidth);            }        }        resultWidth += paddingRight + paddingLeft;        // 布局最终的高度        resultHeight += lineHeight + paddingBottom + paddingTop;        setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : resultWidth, heightMode == MeasureSpec.EXACTLY ? heightSize : resultHeight);    }

onLayout() 是用来确定各个子view的四个点的位置

@Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        // padding        int paddingLeft = getPaddingLeft();        int paddingTop = getPaddingTop();        int paddingRight = getPaddingRight();        int lineHeight = 0;        // 子view的左侧和顶部位置        int childLeft = paddingLeft;        int childTop = paddingTop;        for (int i = 0; i < getChildCount(); i++) {            View child = getChildAt(i);            if (child.getVisibility() == View.GONE) {                continue;            }            // 找到最后一个append tag            if (((SuperTagView) child).isAppendTag()) {                if (appendTagIndex != -1 && latestAppendTagView != null) {                    latestAppendTagView.setAppendTag(false);                }                appendTagIndex = i;                ((SuperTagView) child).setAppendTag(true);                latestAppendTagView = (SuperTagView) child;            }            int childWidth = child.getMeasuredWidth();            int childHeight = child.getMeasuredHeight();            lineHeight = Math.max(lineHeight, childHeight);            if (childLeft + childWidth + paddingRight > getWidth()) { // 需要换行                childLeft = paddingLeft;                childTop += lineHeight + verticalSpace;                lineHeight = childHeight;            }            // 布局            child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);            // 计算下一个子view X的位置            childLeft += childWidth + horizontalSpace;        }    }

SuperTagView

针对SuperTagGroup设计了这样一个自定义子view。可以直接在xml布局中添加tag,当然也可以动态的添加删除tag!

还是首先来看 属性定义。

<declare-styleable name="SuperTagView">        <attr name="is_append_tag" format="boolean" />        <attr name="horizontal_padding" format="dimension" />        <attr name="vertical_padding" format="dimension" />        <attr name="corner_radius" format="dimension" />        <attr name="border_width" format="dimension" />        <attr name="border_color" format="color" />        <attr name="border_checked_color" format="color" />        <attr name="bg_color" format="color" />        <attr name="bg_checked_color" format="color" />    </declare-styleable>

然后在构造函数中读取属性

public SuperTagView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SuperTagView, defStyleAttr, R.style.SuperTagGroup_TagView);        isAppendTag = ta.getBoolean(R.styleable.SuperTagView_is_append_tag, false);        horizontalPadding = (int) ta.getDimension(R.styleable.SuperTagView_horizontal_padding, SuperTagUtil.dp2px(context, SuperTagUtil.DEFAULT_HORIZONTAL_PADDING));        verticalPadding = (int) ta.getDimension(R.styleable.SuperTagView_vertical_padding, SuperTagUtil.dp2px(context, SuperTagUtil.DEFAULT_VERTICAL_PADDING));        cornerRadius = ta.getDimension(R.styleable.SuperTagView_corner_radius, 0);        borderWidth = ta.getDimension(R.styleable.SuperTagView_border_width, 0);        borderColor = ta.getColor(R.styleable.SuperTagView_border_color, SuperTagUtil.DEFAULT_TAG_BORDER_COLOR);        bgColor = ta.getColor(R.styleable.SuperTagView_bg_color, SuperTagUtil.DEFAULT_TAG_BG_COLOR);        borderCheckedColor = ta.getColor(R.styleable.SuperTagView_border_checked_color, 0);        bgCheckedColor = ta.getColor(R.styleable.SuperTagView_bg_checked_color, 0);        ta.recycle();        init();    }

这里需要注意,不仅要绘制背景色还要绘制边框。还要考虑到选中标签的效果!

所以使用了 StateListDrawable

根据背景色,边框色,选中的颜色来创建StateListDrawable作为背景

private Drawable generateBackgroundDrawable() {        StateListDrawable stateListDrawable = new StateListDrawable();        stateListDrawable.addState(CHECK_STATE, new TagBgDrawable(bgCheckedColor, bgRectF, borderCheckedColor, borderRectF, borderWidth, cornerRadius));        stateListDrawable.addState(new int[]{}, new TagBgDrawable(bgColor, bgRectF, borderColor, borderRectF, borderWidth, cornerRadius));        return stateListDrawable;    }
@Override    protected int[] onCreateDrawableState(int extraSpace) {        int[] states = super.onCreateDrawableState(extraSpace + CHECK_STATE.length);        if (isChecked()) {            mergeDrawableStates(states, CHECK_STATE);        }        return states;    }    @Override    public void setChecked(boolean checked) {        if (this.isChecked != checked) {            this.isChecked = checked;            refreshDrawableState();        }    }    @Override    public boolean isChecked() {        return isChecked;    }    @Override    public void toggle() {        setChecked(!isChecked);    }

效果图


  • 动态添加删除tag

  • 最多选中一个tag

  • 最多选中5个tag

  • 不限选中个数

  • 嵌套ScrollView的使用情况


引用开源库

compile 'com.jacksen:supertaggroup:1.0' 

gradle 4.0 版本之后,使用下面依赖方式

implementation 'com.jacksen:supertaggroup:1.0'

源码

https://github.com/crazy1235/SuperTagGroup

欢迎star


参考

https://github.com/zhuanghongji/FlowLayout

https://github.com/PepernotenX/FlowLayout

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 快捷方式在根目录找不到了怎么办 神秘海域4卡bug了怎么办 地下城老是闪退怎么办 强制关机后电脑打不开了怎么办 文明5地中海的海军怎么办 文明5被贸易禁运怎么办 文明5海里的食物怎么办 文明5遗址没了怎么办 ⅰpad屏幕动不了怎么办 苹果6plus满了怎么办 cf的fps低怎么办win7 游戏倒闭冲的钱怎么办 一闭眼就做噩梦怎么办 吃鸡游戏上瘾了怎么办 使命召唤7很卡怎么办 w10升级系统卡死怎么办 答题卡写错位置怎么办 高考答错区域该怎么办 荒野行动画面中间有条横怎么办 荒野行动pc闪退怎么办 幽灵行动荒野子弹没了怎么办 看门狗2枪没子弹怎么办 爱奇艺不小心删除了本地视频怎么办 80岁老太太就爱闹肚子怎么办? 皇牌空战5弹药不够怎么办 辐射4玩着头晕怎么办 官司打赢了法院不给钱怎么办 电脑玩dnf太卡怎么办 soul被禁止私聊怎么办 刺激战场空投挂树上怎么办 由于经济原因心态不好怎么办 公司经济不好不裁员怎么办 家里经济不好没有钱怎么办 银行柜员找不到工作怎么办 在球队中打替补怎么办 大学生毕业后找不到工作怎么办 30岁不敢换工作怎么办 投完简历没回复怎么办 工业废气一年总量超标怎么办 安监局行政处罚没能力交怎么办 被社会淘汰的人怎么办