前言:

前面几篇讲了自定义控件绘制原理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 LayoutParams generateDefaultLayoutParams() {
return new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
} /**
* 负责设置子控件的测量模式和大小 根据所有子控件设置自己的宽和高
*/
@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
ViewGroup.LayoutParams lp = child.getLayoutParams();
// 当前子空间实际占据的宽度
int childWidth = child.getMeasuredWidth() + childHorizontalSpace;
// 当前子空间实际占据的高度
int childHeight = child.getMeasuredHeight() + childVerticalSpace; if (lp != null && lp instanceof MarginLayoutParams) {
MarginLayoutParams params = (MarginLayoutParams) lp;
childWidth += params.leftMargin + params.rightMargin;
childHeight += params.topMargin + params.bottomMargin;
} /**
* 如果加入当前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; }
}

TagsLayout.java

总结:

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

Android自定义控件之自定义ViewGroup实现标签云的更多相关文章

  1. Android自定义控件之自定义组合控件

    前言: 前两篇介绍了自定义控件的基础原理Android自定义控件之基本原理(一).自定义属性Android自定义控件之自定义属性(二).今天重点介绍一下如何通过自定义组合控件来提高布局的复用,降低开发 ...

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

    前言: 前两篇介绍了自定义控件的基础原理Android自定义控件之基本原理(一).自定义属性Android自定义控件之自定义属性(二).今天重点介绍一下如何通过自定义组合控件来提高布局的复用,降低开发 ...

  3. Android 自定义控件之继承ViewGroup创建新容器

    欢迎大家来学习本节内容,前几节我们已经学习了其他几种自定义控件,分别是Andriod 自定义控件之音频条及 Andriod 自定义控件之创建可以复用的组合控件还没有学习的同学请先去学习下,因为本节将使 ...

  4. android自定义控件(3)-自定义当前按钮属性

    那么还是针对我们之前写的自定义控件:开关按钮为例来说,在之前的基础上,我们来看看有哪些属性是可以自定义的:按钮的背景图片,按钮的滑块图片,和按钮的状态(是开还是关),实际上都应该是可以在xml文件中直 ...

  5. android自定义控件(4)-自定义水波纹效果

    一.实现单击出现水波纹单圈效果: 照例来说,还是一个自定义控件,观察这个效果,发现应该需要重写onTouchEvent和onDraw方法,通过在onTouchEvent中获取触摸的坐标,然后以这个坐标 ...

  6. android自定义控件(五) 自定义组合控件

    转自http://www.cnblogs.com/hdjjun/archive/2011/10/12/2209467.html 代码为自己编写 目标:实现textview和ImageButton组合, ...

  7. 老猪带你玩转android自定义控件二——自定义索引栏listview

    带索引栏的listview,在android开发非常普遍,方便用户进行字母索引,就像微信通讯录这样: 今天,我们就从零到一实现这个具有索引栏的listview. 怎么实现这个控件了,我们应当梳理出一个 ...

  8. 玩转android自定义控件二——自定义索引栏listview

    带索引栏的listview,在android开发非常普遍,方便用户进行字母索引,就像微信通讯录这样: 今天,我们就从零到一实现这个具有索引栏的listview. 怎么实现这个控件了,我们应当梳理出一个 ...

  9. Android动画效果之自定义ViewGroup添加布局动画

    前言: 前面几篇文章介绍了补间动画.逐帧动画.属性动画,大部分都是针对View来实现的动画,那么该如何为了一个ViewGroup添加动画呢?今天结合自定义ViewGroup来学习一下布局动画.本文将通 ...

随机推荐

  1. 浅谈 jQuery 核心架构设计

    jQuery对于大家而言并不陌生,因此关于它是什么以及它的作用,在这里我就不多言了,而本篇文章的目的是想通过对源码简单的分析来讨论 jQuery 的核心架构设计,以及jQuery 是如何利用javas ...

  2. ASP.NET Aries 入门开发教程5:自定义列表页工具栏区

    前言: 抓紧时间,继续写教程,因为发现用户期待的内容,都在业务处理那一块. 不得不继续勤劳了. 这节主要介绍工具栏区的玩法. 工具栏的默认介绍: 工具栏默认包括5个按钮,根据不同的权限决定显示: 添加 ...

  3. POCO Controller 你这么厉害,ASP.NET vNext 知道吗?

    写在前面 阅读目录: POCO 是什么? 为什么会有 POJO? POJO 的意义 POJO 与 PO.VO 的区别 POJO 的扩展 POCO VS DTO Controller 是什么? 关于 P ...

  4. 【原】AFNetworking源码阅读(五)

    [原]AFNetworking源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中提及到了Multipart Request的构建方法- [AFHTTP ...

  5. Nhibernate的Session管理

    参考:http://www.cnblogs.com/renrenqq/archive/2006/08/04/467688.html 但这个方法还不能解决Session缓存问题,由于创建Session需 ...

  6. spring源码分析之context

    重点类: 1.ApplicationContext是核心接口,它为一个应用提供了环境配置.当应用在运行时ApplicationContext是只读的,但你可以在该接口的实现中来支持reload功能. ...

  7. 马里奥AI实现方式探索 ——神经网络+增强学习

    [TOC] 马里奥AI实现方式探索 --神经网络+增强学习 儿时我们都曾有过一个经典游戏的体验,就是马里奥(顶蘑菇^v^),这次里约奥运会闭幕式,日本作为2020年东京奥运会的东道主,安倍最后也已经典 ...

  8. Jquery(1)

    鼠标点击事件: <input type="button" value="测试" onclick="test()" /> < ...

  9. 通过微信小程序看前端

    前言 2016年9月22日凌晨,微信官方通过“微信公开课”公众号发布了关于微信小程序(微信应用号)的内测通知.整个朋友圈瞬间便像炸开了锅似的,各种揣测.介绍性文章在一夜里诞生.而真正收到内测邀请的公众 ...

  10. 浅谈Java的throw与throws

    转载:http://blog.csdn.net/luoweifu/article/details/10721543 我进行了一些加工,不是本人原创但比原博主要更完善~ 浅谈Java异常 以前虽然知道一 ...