[github高级控件]带你走近->自定义标签云
来源:互联网 发布:流量互换源码 编辑:程序博客网 时间:2024/06/16 08:38
在项目中经常遇到一些标签云的效果,比如城市的选择,景点类型选择,酒店房型选择 这些常见使用标签云效果就比较好了。
今天一步步的写一个标签云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
- [github高级控件]带你走近->自定义标签云
- [github高级控件] 带你走近 -> CircleIndicator指示器原点动画切换
- 带你走近AngularJS - 创建自定义指令
- 带你走近AngularJS - 创建自定义指令
- 带你走近AngularJS - 基本功能介绍
- 带你走近AngularJS - 体验指令实例
- 带你走近AngularJS - 基本功能介绍
- 带你走近AngularJS - 体验指令实例
- Toast的高级自定义方式-循序渐进带你了解toast
- 带你阅读dubbo源码之自定义标签(二)
- 自定义控件之带标签的文本编辑框(不带命名空间)
- 带你玩转Github
- 安卓自定义标签云控件
- JSP自定义标签学习(高级)
- JSP自定义标签学习(高级)
- 带属性的自定义标签
- 带属性的自定义标签
- 自定义标签(带属性)
- 《The two second acdvantage》读书笔记
- [嵌入式]Cortex-A8处理器编程(上)
- jq框架封装学习笔记2-选择模块
- oracle学习之:解锁用户
- 单例模式——懒汉式和饿汉式
- [github高级控件]带你走近->自定义标签云
- JQuery中的一些重要方法
- jq框架封装学习笔记3-封装select引擎
- IDEA的安装
- jq框架封装学习笔记4-DOM操作模块
- Spark系列修炼---入门笔记25
- submit汉化 亲测可用
- Ubuntu系统安装matlab教程
- caffe之小小的CNN网络跑起来