4.3 自定义控件 之 继承ViewGroup实现标签云
2017-09-22 11:32
393 查看
点此进入:从零快速构建APP系列目录导图
点此进入:UI编程系列目录导图
点此进入:四大组件系列目录导图
点此进入:数据网络和线程系列目录导图
实现一个知识点的标签显示,每个标签的长度未知,如下图所示:
本篇的控件涉及到的内容比较多,所以先介绍下View的绘制流程、相关回调方法等,避免后面用到的时候不知道什么意思。
一、View绘制流程
1、mesarue() 测量过程
主要作用:为整个 View 树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth),每个 View 的控件的实际宽高都是由父视图和本身视图决定的。具体的调用链如下:ViewRoot 根对象的属性 mView(其类型一般为 ViewGroup 类型)调用 measure()方法去计算 View 树的大小,回调 View/ViewGroup 对象的 onMeasure() 方法,该方法实现的功能如下:
1、设置本 View 视图的最终大小,该功能的实现通过调用 setMeasuredDimension()方法去设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth)。
2 、如果该 View 对象是个 ViewGroup 类型,需要重写该 onMeasure() 方法,对其子视图进行遍历的measure() 过 程 。 对 每 个 子 视 图 的 measure() 过 程 , 是 通 过 调 用 父 类 ViewGroup.java 类 里 的measureChildWithMargins() 方法去实现,该方法内部只是简单地调用了 View 对象的 measure() 方法。
2、layout() 布局过程
主要作用:为将整个根据子视图的大小以及布局参数将 View 树放到合适的位置上。具体的调用链如下:
1、layout 方法会设置该 View 视图位于父视图的坐标轴,即 mLeft,mTop,mLeft,mBottom(调用setFrame()函数去实现)接下来回调 onLayout()方法(如果该 View 是 ViewGroup 对象,需要实现该方法,对每个子视图进行布局)。
2、如果该 View 是个 ViewGroup 类型,需要遍历每个子视图 childView,调用该子视图的 layout() 方法去设置它的坐标值。
3、draw()绘图过程
由 ViewRoot 对象的 performTraversals() 方法调用 draw() 方法发起绘制该 View 树,值得注意的是每次发起绘图时,并不会重新绘制每个 View 树的视图,而只会重新绘制那些“需要重绘”的视图,View 类内部变量包含了一个标志位 DRAWN,当该视图需要重绘时,就会为该 View 添加该标志位。
调用流程 :
1 、绘制该 View 的背景
2 、为显示渐变框做一些准备操作(大多数情况下,不需要改渐变框)
3、调用 onDraw() 方法绘制视图本身(每个 View 都需要重载该方法,ViewGroup 不需要实现该方法)
4、调用 dispatchDraw() 方法绘制子视图(如果该 View 类型不为 ViewGroup,即不包含子视图,不需要重载该方法)
值得说明的是,ViewGroup 类已经为我们重写了 dispatchDraw() 的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。
另外,关于 invalidate() 方法的介绍,大家可以参照这篇:Android中View绘制流程以及invalidate()等相关方法分析
二、自定义标签云类的实现
1、自定义属性
<declare-styleable name="TagsLayout"> <attr name="tagVerticalSpace" format="dimension" /> <attr name="tagHorizontalSpace" format="dimension" /> </declare-styleable>
2、构造函数中获取自定义属性值
public TagsLayout(Context context) { super(context); mContext = context; init(); } public TagsLayout(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; mAttributeSet = attrs; init(); } public TagsLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mContext = context; mAttributeSet = attrs; init(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public TagsLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); mContext = context; mAttributeSet = attrs; init(); } private void init() { TypedArray attrArray = mContext.obtainStyledAttributes(mAttributeSet, R.styleable.TagsLayout); if (attrArray != null) { mChildHorizontalSpace = attrArray.getDimensionPixelSize(R.styleable.TagsLayout_tagHorizontalSpace, 0); mChildVerticalSpace = attrArray.getDimensionPixelSize(R.styleable.TagsLayout_tagVerticalSpace, 0); attrArray.recycle(); } }
3、onMeasure函数测量子控件大小,然后设置当前控件大小
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 获得它的父容器为它设置的测量模式和大小 int sizeWidth = MeasureSpec.getSize(widthMeasureSpec); int sizeHeight = MeasureSpec.getSize(heightMeasureSpec); int modeWidth = MeasureSpec.getMode(widthMeasureSpec); int modeHeight = MeasureSpec.getMode(heightMeasureSpec); // 遍历每个子元素 for (int i = 0; i < count; i++) { View child = getChildAt(i); if (child.getVisibility() == GONE) continue; // 测量每一个child的宽和高 measureChild(child, widthMeasureSpec, heightMeasureSpec); // 得到child的lp MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); // 当前子空间实际占据的宽度 int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + childHorizontalSpace; // 当前子空间实际占据的高度 int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin + childVerticalSpace; /** * 如果加入当前child,则超出最大宽度,则的到目前最大宽度给width,类加height 然后开启新行 */ if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight()) { width = Math.max(lineWidth, childWidth);// 取最大的 lineWidth = childWidth; // 重新开启新行,开始记录 // 叠加当前高度, height += lineHeight; // 开启记录下一行的高度 lineHeight = childHeight; child.setTag(new Location(left, top + height, childWidth + left - childHorizontalSpace, height + child.getMeasuredHeight() + top)); } else {// 否则累加值lineWidth,lineHeight取最大高度 child.setTag(new Location(lineWidth + left, top + height, lineWidth + childWidth - childHorizontalSpace + left, height + child.getMeasuredHeight() + top)); lineWidth += childWidth; lineHeight = Math.max(lineHeight, childHeight); } } width = Math.max(width, lineWidth) + getPaddingLeft() + getPaddingRight(); height += lineHeight; sizeHeight += getPaddingTop() + getPaddingBottom(); height += getPaddingTop() + getPaddingBottom(); setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ? sizeWidth : width, (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight : height); }
通过遍历所有子控件调用measureChild函数获取每个子控件的大小,然后通过宽度叠加判断是否换行,叠加控件的高度,同时记录下当前子控件的坐标,这里记录坐标引用了自己写的一个内部类Location.java。
4、onLayout函数对所有子控件重新布局
private class Location { private int left; private int top; private int right; private int bottom; private Location(int left, int top, int right, int bottom) { this.left = left; this.top = top; this.right = right; this.bottom = bottom; } }
三、自定义标签云的使用
1、在布局文件中直接引用
<com.wgh.willflowcloudtag.TagsLayout android:id="@+id/tagsLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal"> </com.wgh.willflowcloudtag.TagsLayout>
2、在MainActivity中用代码添加标签
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TagsLayout tagsLayout = (TagsLayout) findViewById(R.id.tagsLayout); ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); String[] string={"锄禾日当午","汗滴禾下土", "metacognition", "WillFlow","谁知盘中餐","粒粒皆辛苦"}; for (int i = 0; i < string.length; i++) { TextView textView = new TextView(this); textView.setText(string[i]); textView.setTextColor(getResources().getColor(R.color.colorAccent)); textView.setBackgroundResource(R.drawable.a); tagsLayout.addView(textView, lp); } }
至此有关简单的自定义控件已经介绍的差不多了,项目中很复杂的控件现在涉及的比较少,以后用到之后再做记录。
最后,给大家介绍一款开源3D标签云:3D标签云
点此进入:GitHub开源项目“爱阅”。“爱阅”专注于收集优质的文章、站点、教程,与大家共分享。下面是“爱阅”的效果图:
联系方式:
简书:WillFlowCSDN:WillFlow
微信公众号:WillFlow
相关文章推荐
- Android自定义控件之自定义ViewGroup实现标签云
- Android自定义控件之自定义ViewGroup实现标签云(四)
- 继承已有ViewGroup实现自定义控件、touch事件的传递
- Android自定义控件之自定义ViewGroup实现标签云
- android自定义控件系列教程----继承ViewGroup实现带阻力效果的可回弹的SrollView
- android继承View实现复杂的自定义控件(1)
- Android自定义控件3----继承ViewGroup自定义和系统一样的ViewPager
- android 继承ViewGroup实现自定义布局
- Android单独继承View类来实现自定义控件
- Anroid自定义控件/继承自ViewGroup的刷新控件——LinearLayout
- 基于ViewGroup实现自动换行标签控件
- 自定义ViewGroup--浮动标签的实现
- 自定义View继承ViewGroup自定义属性,实现水平垂直阶梯的排列
- Android自定义控件系列六:自定义ViewGroup(一)实现ViewPager效果
- 【代码】Android 单独继承View类来实现自定义控件
- 自定义ViewGroup实现标签云效果。
- 自定义控件(视图)28期笔记09:自定义视图之继承自ViewGroup(仿ViewPager效果案例)
- Android继承自View和ViewGroup,自定义控件
- 利用热门标签布局,实现单选列表(实现ViewGroup多行显示单选功能)
- ViewPager + Fragment+radiogroup实现滑动标签页