[github高级控件]带你走近->自定义标签云

来源:互联网 发布:流量互换源码 编辑:程序博客网 时间:2024/06/16 02:12

在项目中经常遇到一些标签云的效果,比如城市的选择,景点类型选择,酒店房型选择 这些常见使用标签云效果就比较好了。
这里写图片描述

这里写图片描述

今天一步步的写一个标签云view,借鉴于github上TagCloudView

1.准备工作

attr.xml

<resources>    <declare-styleable name="TagTextViewStyle">        <attr name="t_textSize" format="dimension"/>        <attr name="t_textColor" format="color"/>        <attr name="t_itemBorder" format="dimension"/>        <attr name="t_viewBorder" format="dimension"/>        <attr name="t_tagBackground" format="reference"/>        <attr name="t_singleLine" format="boolean"/>        <attr name="t_imageWidth" format="dimension"/>        <attr name="t_imageHeight" format="dimension"/>        <attr name="t_rightArrow" format="integer"/>        <attr name="t_showArrow" format="boolean"/>        <attr name="t_showMore" format="boolean"/>        <attr name="t_moreTextStr" format="string"/>        <attr name="t_moreTextWidth" format="dimension"/>    </declare-styleable></resources>

然后定义一个TagTextView,继承与ViewGroup,接下来就从attr中获取这些属性

 TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.TagTextViewStyle, defStyleAttr, defStyleAttr);        mTextSize = typedArray.getDimensionPixelSize(R.styleable.TagTextViewStyle_t_textSize, 12);    //更多属性自行实现...

2.重写onMeasure

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        //1,计算子view        //2,计算tag view 实际需要高度        //3,根据高度设置    }


计算子view

MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求。一个MeasureSpec由size和mode组成,它有三种模式:UNSPECIFIED(未指定),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小;EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;AT_MOST(至多),子元素至多达到指定大小的值

这里不指定具体的mode和size作特殊处理,只是使用到size和mode

      //计算 ViewGroup 上级容器为其推荐的宽高        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        mWidthSize = MeasureSpec.getSize(widthMeasureSpec);        mHeightSize = MeasureSpec.getSize(heightMeasureSpec);        // 计算出所有的childView的宽和高          measureChildren(widthMeasureSpec, heightMeasureSpec);

measureChildren一行代码就计算了所以的childview的宽,高,这里不具体分析,可以自行查看源码分析measureChildren原理。


计算TagTextView的高度并确定item的位置

如果所有标签没有超过1行,那么TagTextView的高度就是item的高度;如果超过一行,那么高度h=item高度*行数+所有间距

这里实现2中模式:多行模式和单行模式
先介绍怎么实现多行模式

 private int getMultiTotalHeight(int totalWidth, int totalHeight) {        int childWidth;        int childHeight;    //遍历所有的子view        for (int i = 0; i < getChildCount(); i++) {            View child = getChildAt(i);            childWidth = child.getMeasuredWidth();            childHeight = child.getMeasuredHeight();            //总的宽度 = 所有子view宽+item间距            totalWidth += childWidth + mItemBorder;            //设置第一行高度 因为layout t = totalHeight - childHeight            //要让totalHeight - childHeight不为负数 所以先设置一行高度            if (i == 0) {                totalHeight = childHeight + mItemBorder;            }            //2种情况所有item宽度大于viewgroup宽度,这时候需要换行;不大于viewgroup宽度,继续            //所有的totalWidth=item宽度+之间的边距,加上左边第一个item与viewGroup边距mViewBorder,加上右边mItemBorder边距,大于viewGroup的宽,需要换行            if (totalWidth + mItemBorder + mViewBorder > mWidthSize) {                totalWidth = mItemBorder;//换行设置间距                //高度 = 原高度 + item高度 + item间距                totalHeight += childHeight + mItemBorder;                //layout确定子view在view group中的位置                child.layout(                        totalWidth + mViewBorder,                        totalHeight - childHeight,                        totalWidth + childWidth + mViewBorder,                        totalHeight);                totalWidth += childWidth;            } else {                //横排:起始 间隔viewboder距离开始,到总的width(items+item间距)+view间距                //竖排:起始 离顶部viewboder间距开始,到总的height                child.layout(totalWidth - childWidth + mViewBorder,                        totalHeight - childHeight,                        totalWidth + mViewBorder,                        totalHeight);            }        }        return totalHeight + mItemBorder;    }

设置高度

 //子view计算的宽,高度去决定测量的宽高值  setMeasuredDimension(mWidthSize, heightMode == MeasureSpec.EXACTLY ? mHeightSize : totalHeight);

做完上面的步骤,view的测量,布局都已经完成,接下来只需要把item view放进来。

添加item到ViewGroup

 public void setTags(List<String> tagDatas) {        if (tagDatas == null) {            return;        }        mTags = tagDatas;        //先清空所有的view        removeAllViews();        String tag;        for (int i = 0; i < mTags.size(); i++) {            tag = tagDatas.get(i);            //加载布局文件            TextView tagView = (TextView) mInflater.inflate(R.layout.layout_item, null);            tagView.setText(tag);            tagView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);            tagView.setBackgroundResource(mTagBackground);            tagView.setTextColor(mTextColor);            LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);            tagView.setLayoutParams(params);          //添加view            addView(tagView);          }    //刷新        postInvalidate();    }

多行显示tag就在这里写完了,运行,看下标签云的效果

写完多行模式,接下来写单行模式,我们会问,多行模式下不超过viewgroup宽度不就是单行吗,为什么还要定义单行模式?
这里的单行模式是指不管有多少item都值显示单行,超出的item不显示,用”…”代替。

单行模式与多行模式基本类似,主要区别在于获取totalHeight和addView部分。接下来对这两个部分进行实现。

单行模式实现

初始化单行模式view

 private void initSingleLineView(int widthMeasureSpec, int heightMeasureSpec) {     //判断是否是单行模式        if (!mSingleLine) {            return;        }        //是否显示向右箭头        if (mShowArrow) {            mArrowIv = new ImageView(getContext());            mArrowIv.setImageResource(mArrowResId);            mArrowIv.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));            mArrowIv.setScaleType(ImageView.ScaleType.CENTER_INSIDE);            measureChild(mArrowIv, widthMeasureSpec, heightMeasureSpec);            mArrowIconWidth = mArrowIv.getMeasuredWidth();            mImageHeight = mArrowIv.getMeasuredHeight();            addView(mArrowIv);        }    //是否显示更多item        if (mShowMore) {            mMoreTextTv = (TextView) mInflater.inflate(R.layout.layout_item, null);            mMoreTextTv.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);            mMoreTextTv.setTextColor(mTextColor);            mMoreTextTv.setBackgroundResource(mTagBackground);            @SuppressLint("DrawAllocation")            LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);            mMoreTextTv.setLayoutParams(layoutParams);            mMoreTextTv.setText(mMoreTextStr == null || mMoreTextStr.equals("") ? "..." : mMoreTextStr);            measureChild(mMoreTextTv, widthMeasureSpec, heightMeasureSpec);            mMoreTextWidth = mMoreTextTv.getMeasuredWidth();            addView(mMoreTextTv);        }    }

计算单行模式高度及确定位置2

单行模式看起来代码比较多,其实并不复杂,只是比多行模式多了一个arrow icon和查看更多item

这里写图片描述

private int getSingleTotalHeight(int totalWidth, int totalHeight) {        int childWidth;        int childHeight = 0;        totalWidth += mViewBorder;//设置左边距    //上图红色边框的总宽度        int textTotalWidth = getTextTotalWidth();        //items 总宽度小于 viewgroup - 箭头图片width 不用显示更多item        if (textTotalWidth < mWidthSize - mArrowIconWidth) {            mMoreTextStr = null;            mMoreTextWidth = 0;        }        //计算item中宽度并确定布局        for (int i = 0; i < getChildCount(); i++) {            View child = getChildAt(i);            childWidth = child.getMeasuredWidth();            childHeight = child.getMeasuredHeight();            if (i == 0) {                totalWidth += childWidth;                totalHeight = childHeight + mViewBorder;            } else {                totalWidth += childWidth + mItemBorder;            }    //左边距+items宽+item边距+更多item宽+arrow icon宽+右边距 < viewgroup宽            if (mViewBorder + totalWidth + mItemBorder +   mMoreTextWidth + mArrowIconWidth+ mViewBorder  < mWidthSize) {              //横排:起始 间隔mItemBorder距离开始,到总的width(items+item间距)+mItemBorder间距                //竖排:起始 离顶部viewboder间距开始,到总的height                child.layout(                        totalWidth - childWidth + mItemBorder,                        totalHeight - childHeight,                        totalWidth + mItemBorder,                        totalHeight);            } else {            //超出部分不显示 ,前面已经加上了,所以这里需要减去                totalWidth -= childWidth + mViewBorder;                break;            }        }        //更多item        if (mMoreTextTv != null) {            //起始位置 从最后一个item+item边距位置x开始             //结束位置 x+moretextWidth            mMoreTextTv.layout(                    mViewBorder+ totalWidth + mItemBorder,                    totalHeight - childHeight,                    totalWidth + mViewBorder + mItemBorder + mMoreTextWidth,                    totalHeight);        }        totalHeight += mViewBorder;        //箭头 与上面类似        if (mArrowIv != null) {            mArrowIv.layout(                    mWidthSize - mArrowIconWidth - mViewBorder,                    (totalHeight - mImageHeight) / 2,                    mWidthSize - mViewBorder,                    (totalHeight - mImageHeight) / 2 + mImageHeight);        }        return totalHeight;    }

这样单行模式也就完成了,剩下的就是加入监听事件,优化等,功能扩展,这里不多介绍

完整demo下载地址:TagTextView

0 0
原创粉丝点击