自定义控件之TagGroup
转载请注明出处:http://blog.csdn.net/crazy1235/article/details/74907150
标签瀑布流布局!
实现方式有很多种。
继承LinearLayout
继承ViewGroup
继承别的布局…
继承LinearLayout
继承LinearLayout相对来说,实现比较简单!不需要自己处理onMeasure() 和 onLayout() 函数!
整个布局设置成 LinearLayout.VERTICAL 排列模式!
然后每一行作为一个子LinearLayout,通过 addView(LinearLayout)进去!子LinearLayout是 LinearLayout.HORIZONTAL 排列模式!
当一行addView(tag)的时候超过了最外层的宽度,则需要另起一行!
继承ViewGroup
继承ViewGroup的情况,比较麻烦。需要自己处理onMeasure()和onLayout() !
自定义一套TagGroup 需要注意的地方:
标签的margin值
标签的padding值
TagGroup的padding值
标签的行间距
标签的列间距
字体大小
字体颜色
标签按下的状态
选中标签
……
SuperTagGroup
首先是,属性定义:
<declare-styleable name="SuperTagGroup">
<attr name="horizontal_spacing" format="dimension" />
<attr name="vertical_spacing" format="dimension" />
<attr name="tag_horizontal_padding" format="dimension" />
<attr name="tag_vertical_padding" format="dimension" />
<attr name="tag_text_size" format="dimension" />
<attr name="tag_text_color" format="color" />
<attr name="tag_corner_radius" format="dimension" />
<attr name="tag_border_width" format="dimension" />
<attr name="tag_border_color" format="color" />
<attr name="tag_border_checked_color" format="color" />
<attr name="tag_bg_color" format="color" />
<attr name="tag_bg_checked_color" format="color" />
<attr name="tag_bg_drawable" format="reference" />
<attr name="max_selected_count" format="integer" />
</declare-styleable>
然后在构造函数中,读取出这些属性
public SuperTagGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SuperTagGroup, defStyleAttr, R.style.SuperTagGroup);
horizontalSpace = ta.getDimension(R.styleable.SuperTagGroup_horizontal_spacing, 0);
verticalSpace = ta.getDimension(R.styleable.SuperTagGroup_vertical_spacing, 0);
horizontalPadding = (int) ta.getDimension(R.styleable.SuperTagGroup_tag_horizontal_padding, 0);
verticalPadding = (int) ta.getDimension(R.styleable.SuperTagGroup_tag_vertical_padding, 0);
textSize = ta.getDimension(R.styleable.SuperTagGroup_tag_text_size, 0);
textColor = ta.getColor(R.styleable.SuperTagGroup_tag_text_color, 0);
cornerRadius = ta.getDimension(R.styleable.SuperTagGroup_tag_corner_radius, 0);
borderWidth = ta.getDimension(R.styleable.SuperTagGroup_tag_border_width, 0);
borderColor = ta.getColor(R.styleable.SuperTagGroup_tag_border_color, 0);
tagBorderCheckedColor = ta.getColor(R.styleable.SuperTagGroup_tag_border_checked_color, 0);
tagBgColor = ta.getColor(R.styleable.SuperTagGroup_tag_bg_color, 0);
tagBgCheckedColor = ta.getColor(R.styleable.SuperTagGroup_tag_bg_checked_color, 0);
tagBgDrawable = ta.getResourceId(R.styleable.SuperTagGroup_tag_bg_drawable, 0);
maxSelectedNum = ta.getInt(R.styleable.SuperTagGroup_max_s ta.recycle();
init();
}
接着就是重要的onMeasure() 函数
用来对自身布局和子view进行测量,得到测量宽高来进行设置
主要就是针对每个子view进行测量,注意过程中要加上我们设置的padding值,和spacing值!
针对各个子view测量完毕之后,再加上布局的padding值,即可得到布局的宽高!
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.d("SuperTagGroup", "onMeasure");
// width size & mode
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
// height size & mode
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
// padding
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();
// the width & height final result
int resultWidth = 0;
int resultHeight = 0;
int lineWidth = 0;
int lineHeight = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
if (!(child instanceof SuperTagView)) {
throw new IllegalStateException("SuperTagGroup can only has SuperTagView child");
}
// measure child
measureChild(child, widthMeasureSpec, heightMeasureSpec);
// 这里要记得加上子view的margin值
// MarginLayoutParams childLayoutParams = (MarginLayoutParams) child.getLayoutParams();
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
lineHeight = Math.max(childHeight, lineHeight);
if (lineWidth + childWidth > widthSize - paddingLeft - paddingRight) { // 需要换一行
resultWidth = Math.max(resultWidth, lineWidth); // 每一行都进行比较,最终得到最宽的值
resultHeight += verticalSpace + lineHeight;
lineWidth = (int) (childWidth + horizontalSpace); // 新的一行的宽度
lineHeight = childHeight; // 新的一行的高度
} else {
// 当前行的宽度
lineWidth += childWidth + horizontalSpace;
// 当前行最大的高度
lineHeight = Math.max(lineHeight, childHeight);
}
// 最后一个, 需要再次比较宽
if (i == getChildCount() - 1) {
resultWidth = Math.max(resultWidth, lineWidth);
}
}
resultWidth += paddingRight + paddingLeft;
// 布局最终的高度
resultHeight += lineHeight + paddingBottom + paddingTop;
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : resultWidth, heightMode == MeasureSpec.EXACTLY ? heightSize : resultHeight);
}
onLayout() 是用来确定各个子view的四个点的位置
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// padding
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight();
int lineHeight = 0;
// 子view的左侧和顶部位置
int childLeft = paddingLeft;
int childTop = paddingTop;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
// 找到最后一个append tag
if (((SuperTagView) child).isAppendTag()) {
if (appendTagIndex != -1 && latestAppendTagView != null) {
latestAppendTagView.setAppendTag(false);
}
appendTagIndex = i;
((SuperTagView) child).setAppendTag(true);
latestAppendTagView = (SuperTagView) child;
}
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
lineHeight = Math.max(lineHeight, childHeight);
if (childLeft + childWidth + paddingRight > getWidth()) { // 需要换行
childLeft = paddingLeft;
childTop += lineHeight + verticalSpace;
lineHeight = childHeight;
}
// 布局
child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
// 计算下一个子view X的位置
childLeft += childWidth + horizontalSpace;
}
}
SuperTagView
针对SuperTagGroup设计了这样一个自定义子view。可以直接在xml布局中添加tag,当然也可以动态的添加删除tag!
还是首先来看 属性定义。
<declare-styleable name="SuperTagView">
<attr name="is_append_tag" format="boolean" />
<attr name="horizontal_padding" format="dimension" />
<attr name="vertical_padding" format="dimension" />
<attr name="corner_radius" format="dimension" />
<attr name="border_width" format="dimension" />
<attr name="border_color" format="color" />
<attr name="border_checked_color" format="color" />
<attr name="bg_color" format="color" />
<attr name="bg_checked_color" format="color" />
</declare-styleable>
然后在构造函数中读取属性
public SuperTagView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SuperTagView, defStyleAttr, R.style.SuperTagGroup_TagView);
isAppendTag = ta.getBoolean(R.styleable.SuperTagView_is_append_tag, false);
horizontalPadding = (int) ta.getDimension(R.styleable.SuperTagView_horizontal_padding, SuperTagUtil.dp2px(context, SuperTagUtil.DEFAULT_HORIZONTAL_PADDING));
verticalPadding = (int) ta.getDimension(R.styleable.SuperTagView_vertical_padding, SuperTagUtil.dp2px(context, SuperTagUtil.DEFAULT_VERTICAL_PADDING));
cornerRadius = ta.getDimension(R.styleable.SuperTagView_corner_radius, 0);
borderWidth = ta.getDimension(R.styleable.SuperTagView_border_width, 0);
borderColor = ta.getColor(R.styleable.SuperTagView_border_color, SuperTagUtil.DEFAULT_TAG_BORDER_COLOR);
bgColor = ta.getColor(R.styleable.SuperTagView_bg_color, SuperTagUtil.DEFAULT_TAG_BG_COLOR);
borderCheckedColor = ta.getColor(R.styleable.SuperTagView_border_checked_color, 0);
bgCheckedColor = ta.getColor(R.styleable.SuperTagView_bg_checked_color, 0);
ta.recycle();
init();
}
这里需要注意,不仅要绘制背景色还要绘制边框。还要考虑到选中标签的效果!
所以使用了 StateListDrawable
根据背景色,边框色,选中的颜色来创建StateListDrawable作为背景
private Drawable generateBackgroundDrawable() {
StateListDrawable stateListDrawable = new StateListDrawable();
stateListDrawable.addState(CHECK_STATE, new TagBgDrawable(bgCheckedColor, bgRectF, borderCheckedColor, borderRectF, borderWidth, cornerRadius));
stateListDrawable.addState(new int[]{}, new TagBgDrawable(bgColor, bgRectF, borderColor, borderRectF, borderWidth, cornerRadius));
return stateListDrawable;
}
@Override
protected int[] onCreateDrawableState(int extraSpace) {
int[] states = super.onCreateDrawableState(extraSpace + CHECK_STATE.length);
if (isChecked()) {
mergeDrawableStates(states, CHECK_STATE);
}
return states;
}
@Override
public void setChecked(boolean checked) {
if (this.isChecked != checked) {
this.isChecked = checked;
refreshDrawableState();
}
}
@Override
public boolean isChecked() {
return isChecked;
}
@Override
public void toggle() {
setChecked(!isChecked);
}
效果图
- 动态添加删除tag
- 最多选中一个tag
- 最多选中5个tag
- 不限选中个数
- 嵌套ScrollView的使用情况
引用开源库
compile 'com.jacksen:supertaggroup:1.0'
gradle 4.0 版本之后,使用下面依赖方式
implementation 'com.jacksen:supertaggroup:1.0'
源码
https://github.com/crazy1235/SuperTagGroup
欢迎star
参考
自定义控件之TagGroup的更多相关文章
- android自定义控件一站式入门
自定义控件 Android系统提供了一系列UI相关的类来帮助我们构造app的界面,以及完成交互的处理. 一般的,所有可以在窗口中被展示的UI对象类型,最终都是继承自View的类,这包括展示最终内容的非 ...
- ASP.NET MVC学习之母版页和自定义控件的使用
一.母板页_Layout.cshtml类似于传统WebForm中的.master文件,起到页面整体框架重用的目地1.母板页代码预览 <!DOCTYPE html> <html> ...
- C# 自定义控件VS用户控件
1 自定义控件与用户控件区别 WinForm中, 用户控件(User Control):继承自 UserControl,主要用于开发 Container 控件,Container控件可以添加其他Con ...
- 自定义控件之 圆形 / 圆角 ImageView
一.问题在哪里? 问题来源于app开发中一个很常见的场景——用户头像要展示成圆的: 二.怎么搞? 机智的我,第一想法就是,切一张中间圆形透明.四周与底色相同.尺寸与头像相同的蒙板图片,盖在 ...
- 如何开发FineReport的自定义控件?
FineReport作为插件化开发的报表软件,有些特殊需求的功能需要自己开发,开发的插件包帆软官方有提提供,可以去帆软论坛上找,本文将主要介绍如何开发一个自定义控件,这里讲讲方法论. 第一步:实例化一 ...
- WPF自定义控件第二 - 转盘按钮控件
继之前那个控件,又做了一个原理差不多的控件.这个控件主要模仿百度贴吧WP版帖子浏览界面左下角那个弹出的按钮盘.希望对大家有帮助. 这个控件和之前的也差不多,为了不让大家白看,文章最后发干货. 由于这个 ...
- 【Win 10应用开发】AdaptiveTrigger在自定义控件中是可以触发的
前些天,看到有网友给我留言,说AdaptiveTrigger在自定义控件(模板化控件)中不能触发.因为当时我正在写其他的代码,就没有去做实验来验证,于是我就给这位网友提了使用GotoVisualSta ...
- WPF自定义控件与样式(3)-TextBox & RichTextBox & PasswordBox样式、水印、Label标签、功能扩展
一.前言.预览 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要是对文本 ...
- Android自定义控件之自定义ViewGroup实现标签云
前言: 前面几篇讲了自定义控件绘制原理Android自定义控件之基本原理(一),自定义属性Android自定义控件之自定义属性(二),自定义组合控件Android自定义控件之自定义组合控件(三),常言 ...
随机推荐
- smarty内置函数
1.{append} 追加 2.{assign} 赋值 3.{block} 块 4.{call} 调用 5.{capture}捕获 6.{config_load}用来从配置文件中加载config变 ...
- LeetCode:路径总和II【113】
LeetCode:路径总和II[113] 题目描述 给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径. 说明: 叶子节点是指没有子节点的节点. 示例:给定如下二叉树, ...
- JavaScript:隐藏Url中的参数
<script type="text/javascript"> function submitForm(url, data) { var eleForm = docum ...
- 使用 getopt 处理命令行长参数
getopt命令并不是bash的内建命令,它是由util-linux包提供的外部命令. getopt 与 getopts 的区别 getopts 是 shell 内建命令, getopt 是一个独立外 ...
- [转]hadoop2.x常用端口及定义方法
端口 Hadoop集群的各部分一般都会使用到多个端口,有些是daemon之间进行交互之用,有些是用于RPC访问以及HTTP访问.而随着Hadoop周边组件的增多,完全记不住哪个端口对应哪个应用,特收集 ...
- dbml 注意事项
1,修改dbml中的字段,需要修改2个地方
- Samba 3.6.9 安装、管理
Samba简介 Samba服务类似于windows上的共享功能,可以实现linux上共享文件,windows上访问,当然在linux上可以访问到.是一种在局域网上共享文件和打印机的一种通信协议,它为局 ...
- Android中Fragment的Hide和Show
我们都知道,Fragment动态添加的时候我们可以使用FragmentTransaction的add和replace方法,replace方法就等效于对这个Fragment先执行remove(),再执行 ...
- Pandas级联
Pandas提供了各种工具(功能),可以轻松地将Series,DataFrame和Panel对象组合在一起. pd.concat(objs,axis=0,join='outer',join_axes= ...
- Java多线程 - 控制线程
join线程 在某个线程的执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的线程完成为止. join()方法有三种重载形式: join():等待被join的线程执 ...