Android自定义ViewGroup实现选择面板
作者:猪飞啦~ 发布时间:2022-11-02 09:20:36
标签:Android,ViewGroup,自定义,选择面板
背景
在做社交类平台开发的小伙伴都躲不开选择社交个性标签的业务需求,那么实现这个UI效果我想大伙第一时间想到的必定是RecycleView或GridView,其实这两者都可以实现需求,但我们的标签长度是不固定的,有可能是4个字符也有可能是10个字符,这时使用这两者就很能实现根据每个标签的宽度来自适应换行显示,那么这时就离不开自定义ViewGroup
效果
至于我这里的效果为什么不根据字体的数量进行自适应宽度的问题,是因为我这边的产品要求每行显示四个且宽高一致,所以我在每个item外面加了一层RelativeLayout,需要自适应宽度的朋友可以在创建item时不要在item外面多加一层
思路
1,我们先把每一行的标签看作一个对象
2,在onMeasure()方法中获取ViewGroup宽度,减去padding值便是ViewGroup的可用宽度
3,获取所有的子View进行遍历,创建一个对象来存储每一行的标签view,每次添加一个标签view先判断剩余空间能否存放得下这个标签view,如果不能则换行
4,在onLayout()方法中进行布局,循环子view并调用其layout()方法进行布局,每布局一个子view就计算出下一个子view的x坐标,y坐标
完整代码
/**
* create by lijianhui
* on 2022-7-6
* <p>
* description: 自定义标签选择面板
*/
public class TagSelectView extends ViewGroup implements View.OnClickListener {
private int mMaxWidth;
private int mHorizontalSpace = DensityUtil.dp2px(5);
private int mVerticalSpace = DensityUtil.dp2px(10);
private List<RowTag> mRows = new ArrayList<>();
private TagClickCallback mTagClickCallback;
private int mTitleHeight;
private boolean mUpdateTabState = true;
public TagSelectView(@NonNull Context context) {
super(context);
}
public TagSelectView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public TagSelectView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 测量宽高
*/
@SuppressLint("DrawAllocation")
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mRows.clear();
// 获取总宽度
int width = MeasureSpec.getSize(widthMeasureSpec);
mMaxWidth = width - getPaddingStart() - getPaddingEnd();
//测量子view
int childCount = this.getChildCount();
RowTag currentLine = null;
for (int i = mTitleHeight > 0 ? 1 : 0; i < childCount; i++) {
View childView = getChildAt(i);
childView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
childView.setOnClickListener(this);
if (currentLine == null) {
currentLine = new RowTag(mMaxWidth, mHorizontalSpace);
currentLine.addTagView(childView);
mRows.add(currentLine);
} else {
if (currentLine.canAddView(childView)) {
currentLine.addTagView(childView);
} else {
currentLine = new RowTag(mMaxWidth, mHorizontalSpace);
currentLine.addTagView(childView);
mRows.add(currentLine);
}
}
}
//测量自己
int height = getPaddingTop() + getPaddingBottom();
for (int i = 0; i < mRows.size(); i++) {
height += mRows.get(i).mHeight;
}
height += (mRows.size() - 1) * mVerticalSpace;
height += mTitleHeight;
setMeasuredDimension(width, height);
}
/**
* 布局子view
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
left = getPaddingStart();
top = getPaddingTop() + mTitleHeight;
for (int i = 0; i < mRows.size(); i++) {
// 获取行
RowTag line = mRows.get(i);
// 管理
line.layoutView(top, left);
// 更新高度
top += line.mHeight;
if (i != mRows.size() - 1) {
top += mVerticalSpace;
}
}
}
/**
* 添加标题
*
* @param view 标题
*/
public void addTitleView(View view) {
view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
mTitleHeight = view.getMeasuredHeight() + DensityUtil.dp2px(15);
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
addView(view);
}
/**
* 标签被点击
*
* @param v 点击的标签
*/
@Override
public void onClick(View v) {
if (mUpdateTabState) {
v.setSelected(!v.isSelected());
}
if (mTagClickCallback != null) {
mTagClickCallback.tagClick(v);
}
}
/**
* 设置标签点击回调接口
*
* @param tagClickCallback 点击回调接口
*/
public void setTagClickCallback(TagClickCallback tagClickCallback) {
mTagClickCallback = tagClickCallback;
}
/**
* 设置点击表情是否改变状态
*
* @param updateTabState true:改变
*/
public void setUpdateTabState(boolean updateTabState) {
this.mUpdateTabState = updateTabState;
}
/**
* 设置水平间距
*
* @param horizontalSpace 间距
*/
public void setHorizontalSpace(int horizontalSpace) {
this.mHorizontalSpace = horizontalSpace;
}
/**
* 标签点击回调接口
*/
public interface TagClickCallback {
void tagClick(View view);
}
/**
* 每一行的数据
*/
private static class RowTag {
private final int mMaxWidth;
private final int mHorizontalSpace;
private final List<View> mTagViews;
private int mUsedWidth;
private int mHeight;
public RowTag(int maxWidth, int horizontalSpace) {
this.mMaxWidth = maxWidth;
this.mHorizontalSpace = horizontalSpace;
this.mTagViews = new ArrayList<>();
}
/**
* 添加标签
*
* @param view 标签view
*/
public void addTagView(View view) {
int childWidth = view.getMeasuredWidth();
int childHeight = view.getMeasuredHeight();
if (mTagViews.size() == 0) {
if (childWidth > mMaxWidth) {
mUsedWidth = mMaxWidth;
} else {
mUsedWidth = childWidth + mHorizontalSpace;
}
mHeight = childHeight;
} else {
mUsedWidth += childWidth + mHorizontalSpace;
mHeight = Math.max(childHeight, mHeight);
}
mTagViews.add(view);
}
/**
* 判断是否可添加
*
* @param view 要添加的标签view
* @return 如果剩余宽度可以装下要添加的标签view返回true
*/
public boolean canAddView(View view) {
if (mTagViews.size() == 0) {
return true;
}
return view.getMeasuredWidth() <= (mMaxWidth - mUsedWidth - mHorizontalSpace);
}
/**
* 布局标签
*
* @param t 头坐标
* @param l 左坐标
*/
public void layoutView(int t, int l) {
int avg = 0;
if (mTagViews.size() > 1) {
avg = (mMaxWidth - mUsedWidth) / (mTagViews.size() - 1);
}
for (View view : mTagViews) {
// 获取宽高 如需填充空余空间:measuredWidth = view.getMeasuredWidth() + avg
int measuredWidth = view.getMeasuredWidth();
int measuredHeight = view.getMeasuredHeight();
// 重新测量
view.measure(MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY));
// 重新获取宽度值
measuredWidth = view.getMeasuredWidth();
int top = t;
int left = l;
int right = measuredWidth + left;
int bottom = measuredHeight + top;
// 指定位置
view.layout(left, top, right, bottom);
// 更新坐标
l += measuredWidth + mHorizontalSpace;
}
}
}
}
使用
/**
* 构建标签
*
* @param tagName 标签名称
*/
private void buildTagView(String tagName, boolean social) {
RelativeLayout relativeLayout = new RelativeLayout(getContext());
SuperTextView superTextView = new SuperTextView(getContext());
superTextView.setGravity(Gravity.CENTER);
superTextView.setText(tagName.length() > 5 ? tagName.substring(0, 5) : tagName);
superTextView.setSolid(social ? Color.parseColor("#A68CFF") : Color.TRANSPARENT);
if (!social) {
superTextView.setStrokeColor(Color.parseColor("#EDEDED"));
superTextView.setStrokeWidth(DensityUtil.dp2px(1));
}
superTextView.setTextColor(social ? Color.WHITE : Color.parseColor("#727272"));
superTextView.setTextSize(11);
superTextView.setCorner(DensityUtil.dp2px(14));
superTextView.setOnClickListener(this);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(DensityUtil.dp2px(70), DensityUtil.dp2px(28));
relativeLayout.addView(superTextView, params);
mBinding.tagSelectView.addView(relativeLayout);
}
来源:https://blog.csdn.net/qq_42359647/article/details/125849918


猜你喜欢
- 以下内容来自 * ,关于静态类型检查和动态类型检查的解释:•静态类型检查:基于程序的源代码来验证类型安全的过程;•动态类型检查:在程序运行
- 本文实例讲述了Java文件操作工具类fileUtil。分享给大家供大家参考,具体如下:package com.gcloud.common;i
- 目录一、前言(1)Timer(2)DelayedQueue 延迟队列(3)ScheduledThreadPoolExecutor(4)Sch
- 看看效果图:我们项目中头像显示一般都是圆形的,但是有时候不排除各种样式(不一定是个规则的形状),比如 上次UI给了我一个 圆形下面少了一块。
- MQ:消息队列/消息中间件/消息代理,产品有很多,ActiveMQ RabbitMQ RocketMQ Kafka1、Stream解决的痛点
- 写了一个人民币小写转大写的方法,Java版本,思路很简单,没有测出什么Bug,有bug欢迎反馈public class RMBChange
- JVM管理两种类型的内存,堆和非堆。堆是给开发人员用的上面说的就是,是在JVM启动时创建;非堆是留给JVM自己用的,用来存放类的信息的。它和
- Java GC 机制与内存分配策略详解收集算法是内存回收的方 * ,垃圾收集器是内存回收的具体实现自动内存管理解决的是:给对象分配内存 以及
- Java怎么自动添加重写的toString方法,这里我们将给大家介绍详细的解决方法。首先,添加一个任意的类,具体的类型没有要求,然后在主程序
- 1、来源random.nextInt() 为 java.util.Random类中的方法; Math.random() 为 java.lan
- 本文介绍了Java实现动态获取图片验证码的示例代码,分享给大家,具体如下:import javax.imageio.ImageIO;impo
- 避免多线程同时读写共享数据在实际开发中,难免会遇到多线程读写共享数据的需求。比如在某个业务处理时,先获取共享数据(比如是一个计数),再利用共
- 本篇介绍我们如何利用selenium 来操作各种页面元素阅读目录链接(link)输入框 textbox按钮(Button)下拉选择框(Sel
- 一、观察者模式基本概况1.概念观察者模式(Observer Design Pattern)也被称为发布订阅模式(Publish-Subcri
- 本文实例讲述了Android单选按钮对话框用法。分享给大家供大家参考。具体如下:main.xml布局文件<?xml version=&
- 以下弹出框是框的实现,放入到SWT项目下就可运行。1.提示框MessageBox mb = new MessageBox(shell,SWT
- Spring整合mybatis的mapper生成过程mapperScannerConfigurer实现了BeandifinitionRegi
- MD5加密在我们的程序中,不管是什么,都会有安全问题,今天就说的是MD5加密的方法MD5是哈希算法,也就是 从明文A到密文B很容易,但是从密
- 什么是构建生命周期构建生命周期是一组阶段的序列(sequence of phases),这些构建生命周期中的每一个由构建阶段的不同列表定义,
- 删除以逗号隔开的字符串中某一个值例如要删除 “1,2,3,4” 中的 2,返回 &ldquo