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

今天一步步的写一个标签云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;//遍历所有的子viewfor (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间距开始,到总的heightchild.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;//先清空所有的viewremoveAllViews();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);//添加viewaddView(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);}//是否显示更多itemif (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 不用显示更多itemif (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间距开始,到总的heightchild.layout(totalWidth - childWidth + mItemBorder,totalHeight - childHeight,totalWidth + mItemBorder,totalHeight);} else {//超出部分不显示 ,前面已经加上了,所以这里需要减去totalWidth -= childWidth + mViewBorder;break;}}//更多itemif (mMoreTextTv != null) {//起始位置 从最后一个item+item边距位置x开始 //结束位置 x+moretextWidthmMoreTextTv.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

[github高级控件]带你走近-自定义标签云相关推荐

  1. 十四、windows窗体高级控件

    1 PictureBox控件 PictureBox控件可以显示来自位图.图标或者原文件,以及来自增强的元文件.Jpeg或GIF文件的图形.如果控件不足以显示整幅图像,则捡钱图像以适应控件的大小 usi ...

  2. Android高级控件(二)——SurfaceView实现GIF动画架包,播放GIF动画,自己实现功能的初体现...

    Android高级控件(二)--SurfaceView实现GIF动画架包,播放GIF动画,自己实现功能的初体现 写这个的原因呢,也是因为项目中用到了gif动画,虽然网上有很多的架包可以实现,不过我们还 ...

  3. Android高级控件----AdapterView与Adapter详解

    在J2EE中提供过一种非常好的框架--MVC框架,实现原理:数据模型M(Model)存放数据,利用控制器C(Controller)将数据显示在视图V(View)上.在Android中有这样一种高级控件 ...

  4. 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第八章:高级控件

    本章介绍了App开发常用的一些高级控件用法,主要包括:如何使用下拉框及其适配器.如何使用列表 类视图及其适配器.如何使用翻页类视图及其适配器.如何使用碎片及其适配器等.然后结合本章所学 的知识,演示了 ...

  5. 三、PyQt5高级控件的使用

    (四)PyQt5高级控件的使用 PyQt5中包含了很多用于简化窗口设计的可视化控件,除了常用控件外,还有一些关于进度.展示数据等的高级控件. 本章重点讲解PyQt5程序开发中用到的一些高级控件,主要包 ...

  6. Android高级控件(一)——ListView绑定CheckBox实现全选,增加和删除等功能

    Android高级控件(一)--ListView绑定CheckBox实现全选,增加和删除等功能 这个控件还是挺复杂的,也是项目中应该算是比较常用的了,所以写了一个小Demo来讲讲,主要是自定义adap ...

  7. Android高级控件(四)——VideoView 实现引导页播放视频欢迎效果,超级简单却十分的炫酷

    Android高级控件(四)--VideoView 实现引导页播放视频欢迎效果,超级简单却十分的炫酷 是不是感觉QQ空间什么的每次新版本更新那炫炫的引导页就特别的激动,哈哈,其实他实现起来真的很简单很 ...

  8. Android 高级控件(七)——RecyclerView的方方面面

    Android 高级控件(七)--RecyclerView的方方面面 RecyclerView出来很长时间了,相信大家都已经比较了解了,这里我把知识梳理一下,其实你把他看成一个升级版的ListView ...

  9. Android的高级控件(自动提示文本框与下拉列表)

    一.高级控件与常用控件的区别:是否使用适配器 二.适配器 1.种类 ①.数组适配器 ArrayAdapter       new ArrayAdapter<String>(this,R.l ...

  10. android_高级控件_1

    有蛮久没更新了今天来记录一下android的高级控件 AutoCompleteTextView(自动补全): 开发过web项目的小伙伴们应该知道在web项目中实现自动补全是有多麻烦,在安卓开发中实现自 ...

最新文章

  1. 黑科技,教你用Python打电话,控制手机技术,快来学一下
  2. 基于Springboot实现宠物医院综合管理系统
  3. Qt动态多语言的实现(VS2012开发)
  4. ie9浏览器两个ajax请求同步不兼容_浏览器拦截问题
  5. Yinchuan-B The Great Wall
  6. 使用cpan安装perl模块
  7. linux shell oracle脚本_领导:如何用shell脚本统计Oracle数据库进程明细和存储过程信息...
  8. 工业以太网交换机在实际应用中的优势
  9. adam算法效果差原因_冷库制冷效果差原因
  10. 关系型数据库性能优化总结(转)
  11. Linux下清除DNS缓存
  12. iterator adapter inserter
  13. JSP教程第4讲笔记
  14. python 使用 config 文件
  15. b站投稿 您的稿件未能成功转码。原因:该视频时长不足1秒,请检查视频时长并尝试重新上传。解决办法
  16. excel随机数_excel怎样生成随机数
  17. PostgreSQL 数据库跨版本升级常用方案
  18. ESL-chapter8-EM算法介绍1-混合高斯的例子
  19. 如何在大学里成为IT技术大神?
  20. 【报告分享】2020年中国老年教育市场研究报告-IT桔子(附下载)

热门文章

  1. Activation function in Neural Network
  2. 使用DNSObserver检测DNS安全漏洞
  3. jq 获取父元素html,jq获取父级元素_使用jquery获取父元素或父节点的方法
  4. USACO26 moofest 奶牛集会(归并排序)
  5. 使用opencv的nonfree模块
  6. [LeetCode] Three Sum题解
  7. JavaScript中内存使用规则--堆和栈
  8. 给Chrome和Firefox添加js脚本作为插件的方法
  9. CSS实现文字半透明显示在图片上方法
  10. linux mysql远程连接