Android自定义控件ViewGroup实现标签云(四)

前言:

前面几篇讲了自定义控件绘制原理Android自定义控件基本原理详解(一) ,Android自定义控件之自定义属性(二) ,Android自定义控件之自定义组合控件(三) ,常言道:“好记性不如烂笔头,光说不练假把式!!!”,作为一名学渣就是因为没有遵循这句名言才沦落于此,所以要谨遵教诲,注重理论与实践相结合,今天通过自定义ViewGroup来实现一下项目中用到的标签云。

需求背景:

公司需要实现一个知识点的标签显示,每个标签的长度未知,如下图所示

基本绘制流程:

绘制原理这里不再介绍大致介绍下绘制流程
 •构造函数获取自定义属性
 •onMeasure()方法,测量子控件的大小
 •onLayout()方法,对子控件进行布局

1.)自定义属性

<declare-styleable name="TagsLayout"> <attr name="tagVerticalSpace" format="dimension" /> <attr name="tagHorizontalSpace" format="dimension" /> </declare-styleable>

2.)构造函数中获取自定义属性值

private int childHorizontalSpace; private int childVerticalSpace; public TagsLayout(Context context, AttributeSet attrs) { super(context, attrs); TypedArray attrArray = context.obtainStyledAttributes(attrs, R.styleable.TagsLayout); if (attrArray != null) { childHorizontalSpace = attrArray.getDimensionPixelSize(R.styleable.TagsLayout_tagHorizontalSpace, 0); childVerticalSpace = attrArray.getDimensionPixelSize(R.styleable.TagsLayout_tagVerticalSpace, 0); attrArray.recycle(); } }

3.)onMeasure函数测量子控件大小,然后设置当前控件大小

/** * 负责设置子控件的测量模式和大小 根据所有子控件设置自己的宽和高 */ @Override protected void onMeasure(int widthMeasureSpec, int 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); // 如果是warp_content情况下,记录宽和高 int width = 0; int height = 0; /** * 记录每一行的宽度,width不断取最大宽度 */ int lineWidth = 0; /** * 每一行的高度,累加至height */ int lineHeight = 0; int count = getChildCount(); int left = getPaddingLeft(); int top = getPaddingTop(); // 遍历每个子元素 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

/** * 记录子控件的坐标 */ public class Location { public Location(int left, int top, int right, int bottom) { this.left = left; this.top = top; this.right = right; this.bottom = bottom; } public int left; public int top; public int right; public int bottom; }

4.)onLayout函数对所有子控件重新布局

@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); if (child.getVisibility() == GONE) continue; Location location = (Location) child.getTag(); child.layout(location.left, location.top, location.right, location.bottom); } }

这里直接遍历所有子控件调用子控件的layout函数进行布局。

如何使用:
 1).布局问自己中直接引用

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:lee="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.whoislcj.views.TagsLayout android:id="@+id/image_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp" lee:tagHorizontalSpace="10dp" lee:tagVerticalSpace="10dp" /> </LinearLayout>

2).代码添加标签

TagsLayout imageViewGroup = (TagsLayout) findViewById(R.id.image_layout); ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); String[] string={"从我写代码那天起,我就没有打算写代码","从我写代码那天起","我就没有打算写代码","没打算","写代码"}; for (int i = 0; i < 5; i++) { TextView textView = new TextView(this); textView.setText(string[i]); textView.setTextColor(Color.WHITE); textView.setBackgroundResource(R.drawable.round_square_blue); imageViewGroup.addView(textView, lp); }

具体效果

3.)最后附上TagsLayout全部代码

public class TagsLayout extends ViewGroup { private int childHorizontalSpace; private int childVerticalSpace; public TagsLayout(Context context, AttributeSet attrs) { super(context, attrs); TypedArray attrArray = context.obtainStyledAttributes(attrs, R.styleable.TagsLayout); if (attrArray != null) { childHorizontalSpace = attrArray.getDimensionPixelSize(R.styleable.TagsLayout_tagHorizontalSpace, 0); childVerticalSpace = attrArray.getDimensionPixelSize(R.styleable.TagsLayout_tagVerticalSpace, 0); attrArray.recycle(); } } /** * 负责设置子控件的测量模式和大小 根据所有子控件设置自己的宽和高 */ @Override protected void onMeasure(int widthMeasureSpec, int 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); // 如果是warp_content情况下,记录宽和高 int width = 0; int height = 0; /** * 记录每一行的宽度,width不断取最大宽度 */ int lineWidth = 0; /** * 每一行的高度,累加至height */ int lineHeight = 0; int count = getChildCount(); int left = getPaddingLeft(); int top = getPaddingTop(); // 遍历每个子元素 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); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); if (child.getVisibility() == GONE) continue; Location location = (Location) child.getTag(); child.layout(location.left, location.top, location.right, location.bottom); } } /** * 记录子控件的坐标 */ public class Location { public Location(int left, int top, int right, int bottom) { this.left = left; this.top = top; this.right = right; this.bottom = bottom; } public int left; public int top; public int right; public int bottom; } }

总结:
至此有关简单的自定义控件已经介绍的差不多了,项目中很复杂的控件现在涉及的比较少,以后用到之后再做记录。

时间: 2024-07-30 14:29:04

Android自定义控件ViewGroup实现标签云(四)的相关文章

Android自定义控件ViewGroup实现标签云(四)_Android

前言: 前面几篇讲了自定义控件绘制原理Android自定义控件基本原理详解(一) ,Android自定义控件之自定义属性(二) ,Android自定义控件之自定义组合控件(三) ,常言道:"好记性不如烂笔头,光说不练假把式!!!",作为一名学渣就是因为没有遵循这句名言才沦落于此,所以要谨遵教诲,注重理论与实践相结合,今天通过自定义ViewGroup来实现一下项目中用到的标签云. 需求背景: 公司需要实现一个知识点的标签显示,每个标签的长度未知,如下图所示   基本绘制流程:  绘制原理

Android自定义ViewGroup实现标签流容器FlowLayout_Android

本篇文章讲的是Android 自定义ViewGroup之实现标签流式布局-FlowLayout,开发中我们会经常需要实现类似于热门标签等自动换行的流式布局的功能,网上也有很多这样的FlowLayout,但不影响我对其的学习.和往常一样,主要还是想总结一下自定义ViewGroup的开发过程以及一些需要注意的地方. 按照惯例,我们先来看看效果图 一.写代码之前,有几个是问题是我们先要弄清楚的: 1.什么是ViewGroup:从名字上来看,它可以被翻译为控件组,言外之意是ViewGroup内部包含了许

Android自定义ViewGroup实现标签浮动效果_Android

前面在学习鸿洋大神的一些自定义的View文章,看到了自定义ViewGroup实现浮动标签,初步看了下他的思路以及结合自己的思路完成了自己的浮动标签的自定义ViewGroup.目前实现的可以动态添加标签.可点击.效果图如下: 1.思路  首先在onMeasure方法中测量ViewGroup的宽和高,重点是处理当我们自定义的ViewGroup设置为wrap_content的情况下,如何去测量其大小的问题.当我们自定义的ViewGroup设置为wrap_content时,我们需要让子View先去测量自

Android自定义ViewGroup实现标签浮动效果

前面在学习鸿洋大神的一些自定义的View文章,看到了自定义ViewGroup实现浮动标签,初步看了下他的思路以及结合自己的思路完成了自己的浮动标签的自定义ViewGroup.目前实现的可以动态添加标签.可点击.效果图如下: 1.思路  首先在onMeasure方法中测量ViewGroup的宽和高,重点是处理当我们自定义的ViewGroup设置为wrap_content的情况下,如何去测量其大小的问题.当我们自定义的ViewGroup设置为wrap_content时,我们需要让子View先去测量自

Android自定义控件——开源组件SlidingMenu的项目集成

转载请注明出处:http://blog.csdn.net/allen315410/article/details/39611355        在实际项目开发中,定制一个菜单,能让用户得到更好的用户体验,诚然菜单的样式各种各样,但是有一种菜单--滑动菜单,是被众多应用广泛使用的.关于这种滑动菜单的实现,我在前面的博文中也介绍了如何自定义去实现,请参考Android自定义控件--侧滑菜单,这篇博文描述的是如何从无到有创建一个侧滑菜单的控件,里面的代码不多,但是处理的逻辑和各种效果比较复杂,如果稍

Android自定义控件案例汇总1(菜单、popupwindow、viewpager)_Android

自定义控件是根据自己的需要自己来编写控件.安卓自带的控件有时候无法满足你的需求,这种时候,我们只能去自己去实现适合项目的控件.同时,安卓也允许你去继承已经存在的控件或者实现你自己的控件以便优化界面和创造更加丰富的用户体验.在平常的项目中,我们 人为的把自定义控件分为两种:一种是组合方式实现.一种是通过继承view或viewgroup及其子类实现.两者都可以实现我们想要的效果,因此,我们可以根据自己的需求,选择合适的方案.本文以案例的形式来显示几种较为常见的自定义控件. 案例一 优酷菜单: 功能介

Android自定义控件之自定义组合控件(三)_Android

前言: 前两篇介绍了自定义控件的基础原理Android自定义控件基本原理详解(一).Android自定义控件之自定义属性(二).今天重点介绍一下如何通过自定义组合控件来提高布局的复用,降低开发成本,以及维护成本. 使用自定义组合控件的好处?我们在项目开发中经常会遇见很多相似或者相同的布局,比如APP的标题栏,我们从三种方式实现标题栏来对比自定义组件带来的好处,毕竟好的东西还是以提高开发效率,降低开发成本为导向的. 1.)第一种方式:直接在每个xml布局中写相同的标题栏布局代码  <?xml ve

Android自定义控件案例汇总2(自定义开关、下拉刷新、侧滑菜单)_Android

案例四 自定义开关: 功能介绍:本案例实现的功能是创建一个自定义的开关,可以自行决定开关的背景.当滑动开关时,开关的滑块可跟随手指移动.当手指松开后,滑块根据开关的状态,滑到最右边或者滑到最左边,同时保存开关的状态,将开关的状态回调给调用者.当然,上述功能系统给定的switch控件也可以实现. 实现步骤:         1. 写一个类继承view,重写两个参数的构造方法.在构造方法中指定工作空间,通过attrs.getAttributeResourceValue方法将java代码中的属性值和x

Android自定义控件之自定义组合控件(三)

前言: 前两篇介绍了自定义控件的基础原理Android自定义控件基本原理详解(一).Android自定义控件之自定义属性(二).今天重点介绍一下如何通过自定义组合控件来提高布局的复用,降低开发成本,以及维护成本. 使用自定义组合控件的好处? 我们在项目开发中经常会遇见很多相似或者相同的布局,比如APP的标题栏,我们从三种方式实现标题栏来对比自定义组件带来的好处,毕竟好的东西还是以提高开发效率,降低开发成本为导向的.  1.)第一种方式:直接在每个xml布局中写相同的标题栏布局代码 <?xml v