Android自定义ViewGroup之子控件的自动换行和添加删除

Android 简单实现View自动换行(附源码) hi, 尘少又来了, 这次为大家带来的, 是View可以自动换行的容器。类似于淘宝搜索时的推荐, 首先我不知道淘宝的是否只支持文字, 但是我的是任何View都支持的。 看下效果先: 淘宝: 我的: 废话不多少, 上代码: 1、先把我的自定义控件放到你的项目里 2、Activity的XML布局 如果里边放很多条目的话, 可能要套在ScrollView里, 以防显示不全<S 阅读详情

概述:

常用的布局类型并不能满足所有需求,这时就会用到ViewGroup。

ViewGroup作为一个放置View的容器,并且我们在写布局xml的时候,会告诉容器(凡是以layout为开头的属性,都是为用于告诉容器的),我们的宽度(layout_width)、高度(layout_height)、对齐方式(layout_gravity)等;当然还有margin等;于是乎,ViewGroup需要做的事情是:给childView计算出建议的宽和高和测量模式 ;决定childView的位置;为什么只是建议的宽和高,而不是直接确定呢,别忘了childView宽和高可以设置为wrap_content,这样只有childView才能计算出自己的宽和高。

博客名已经说明了这篇博客要将给出的内容,不用赘述。

先看效果图:
这里写图片描述

代码注释很详细,直接看代码即可,没贴源码,因为这是从一个项目里面抠出来的。

Demo

先写一个自定义LinearLayout,它的功能是自适应子控件:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<code class = "language-java" hljs= "" > public class ItemContainer extends LinearLayout {
     private int width; //组件宽
     private int height; //组件高
     private int childCount;
     private int childMarginLeft = SizeConvert.dip2px(getContext(), 8 ); //子控件相对左边控件的距离
     private int childMarginHorizonTal = SizeConvert.dip2px(getContext(), 10 ); //子控件相对最左、最右的距离
     private int childMarginTop = SizeConvert.dip2px(getContext(), 8 ); //子控件相对顶部控件的距离
     private int childWidth; //子控件宽
     private int childHeight; //子控件高
 
     public ItemContainer(Context context) {
         super (context);
     }
 
     public ItemContainer(Context context, AttributeSet attrs) {
         super (context, attrs);
     }
 
     @Override
     protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) {
         super .onMeasure(widthMeasureSpec, heightMeasureSpec);
         childCount = getChildCount(); //得到子控件数量
         if (childCount> 0 ) {
             childWidth = (width - childMarginLeft * 4 ) / 3 ;
             childHeight = SizeConvert.dip2px(getContext(), 42 ); //给子控件的高度一个定值
             //根据子控件的高和子控件数目得到自身的高
             height = childHeight * ((childCount- 1 )/ 3 + 1 ) + childMarginHorizonTal * 2 + childMarginTop*((childCount- 1 )/ 3 );
             Log.d(childHeight,childHeight+);
         } else {
             //如果木有子控件,自身高度为0,即不显示
             height = 0 ;
         }
         width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
         Log.d(height,height+);
         //根据自身的宽度约束子控件宽度
         measureChildren(widthMeasureSpec, heightMeasureSpec);
         //设置自身宽度
         setMeasuredDimension(width, height);
     }
 
     @Override
     protected void onLayout( boolean changed, int l, int t, int r, int b) {
         /**
          * 遍历所有子控件,并设置它们的位置和大小
          * 每行只能有三个子控件,且高度固定,宽度相同,且每行正好布满
          */
         for ( int i = 0 ; i < childCount; i++) {
             View childView = getChildAt(i); //得到当前子控件
             childView.layout((i% 3 ) * childWidth + (i% 3 + 1 )*childMarginLeft
                     , (i / 3 )*childHeight + childMarginHorizonTal + (i / 3 )*childMarginTop
                     , (i% 3 + 1 ) * childWidth + (i% 3 + 1 )*childMarginLeft
                     , (i / 3 + 1 )*childHeight + childMarginHorizonTal + (i / 3 )*childMarginTop);
         }
     }
 
}</code>

主活动完成的功能就是上面贴图演示的功能,让两个自定义ViewGroup能够添加删除子控件,子控件是在代码中动态搭建的,下面会给出方法:
活动:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<code class = "language-java" hljs= "" > public class ItemOperateActivity extends BaseActivity {
     private LinearLayout mContentNoItem;
     private LinearLayout mContentItemRemove;
     private ItemContainer mItemContainerAdd;
     private ItemContainer mItemContainerRemove;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
         setContentView(R.layout.activity_item_operate);
         mContentNoItem = (LinearLayout) findViewById(R.id.linearlayout_attention_null);
         mContentItemRemove = (LinearLayout) findViewById(R.id.linearlayout_remove);
         mItemContainerAdd = (ItemContainer) findViewById(R.id.item_container_add);
         mItemContainerRemove = (ItemContainer) findViewById(R.id.item_container_remove);
 
         initItems( new String[]{随时定位, 客户拜访}, new String[]{新增客户, 客户总量});
 
     }
 
     /**
      * 添加条目时需要调用的方法
      * @param name
      */
     private void addItem(String name) {
         mContentNoItem.setVisibility(View.GONE);
         Button button = new Button(getApplicationContext());
         ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT
                 , 0 );
         button.setLayoutParams(params);
         button.setText(name);
         button.setGravity(Gravity.CENTER);
         button.setBackgroundResource(R.drawable.item_select_bg);
         button.setTextSize( 13 );
         button.setTextColor(Color.argb( 255 , 47 , 79 , 79 )); //铅灰色
         button.setOnClickListener( new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 mItemContainerAdd.removeView(v);
                 removeItem(((Button) v).getText().toString());
                 if (mItemContainerAdd.getChildCount()== 0 ) {
                     mContentNoItem.setVisibility(View.VISIBLE);
                 }
             }
 
         });
         mItemContainerAdd.addView(button);
 
     }
 
     /**
      * 删除条目时需要调用的方法
      * @param name
      */
     private void removeItem(String name) {
         mContentItemRemove.setVisibility(View.VISIBLE);
         Button button = new Button(getApplicationContext());
         ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT
                 , 0 );
         button.setLayoutParams(params);
         button.setText(name);
         button.setGravity(Gravity.CENTER);
         button.setBackgroundResource(R.drawable.item_select_bg_below);
         button.setTextSize( 13 );
         button.setTextColor(Color.argb( 255 , 47 , 79 , 79 )); //铅灰色
         button.setOnClickListener( new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 /**
                  * 点击按钮时,删除
                  */
                 mItemContainerRemove.removeView(v);
                 addItem(((Button) v).getText().toString());
 
                 if (mItemContainerRemove.getChildCount() == 0 ) {
                     mContentItemRemove.setVisibility(View.GONE);
                 }
             }
         });
         mItemContainerRemove.addView(button);
     }
 
     /**
      * 初始化子控件
      * @param itemsAdd 已添加的子控件名数组
      * @param itemsRemove 可添加的子控件名数组
      */
     private void initItems(String[] itemsAdd, String[] itemsRemove) {
         for (String itemAdd : itemsAdd) {
             addItem(itemAdd);
         }
         for (String itemRemove : itemsRemove) {
             removeItem(itemRemove);
         }
     }
}</code>

布局:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<code class = "language-xml" hljs= "" ><!--?xml version= 1.0 encoding=utf- 8 ?-->
<linearlayout android:layout_height= "match_parent" android:layout_width= "match_parent" android:orientation= "vertical" xmlns:android= "http://schemas.android.com/apk/res/android" >
 
     <relativelayout android:background= "@drawable/item_bg" android:layout_height= "60dp" android:layout_width= "match_parent" >
         <linearlayout android:gravity= "center" android:id= "@+id/linearlayout_back" android:layout_alignparentleft= "true" android:layout_centervertical= "true" android:layout_height= "wrap_content" android:layout_marginleft= "5dp" android:layout_width= "wrap_content" android:orientation= "horizontal" >
             <imagebutton android:background= "@drawable/btn_back/" android:layout_height= "wrap_content" android:layout_width= "wrap_content" >
 
             <textview android:layout_height= "wrap_content" android:layout_width= "wrap_content" android:text= "报表" android:textsize= "@dimen/head_left_text_size/" >
         </textview></imagebutton></linearlayout>
 
         <textview android:layout_centerinparent= "true" android:layout_height= "wrap_content" android:layout_width= "wrap_content" android:text= "订阅" android:textsize= "@dimen/head_center_text_size/" >
 
     </textview></relativelayout>
 
     <linearlayout android:background= "@drawable/item_bg" android:layout_height= "wrap_content" android:layout_width= "match_parent" android:orientation= "horizontal" android:paddingbottom= "12dp" android:paddingleft= "8dp" android:paddingtop= "12dp" >
 
         <textview android:layout_height= "wrap_content" android:layout_width= "wrap_content" android:text= "已添加" android:textsize= "16dp/" >
 
         <textview android:layout_height= "wrap_content" android:layout_width= "wrap_content" android:text= "(点击删除)" android:textsize= "13dp/" >
     </textview></textview></linearlayout>
 
     <com.test.shiweiwei.myproject.selfish_view.self_defined_view_group.itemcontainer android:background= "@drawable/item_bg" android:id= "@+id/item_container_add" android:layout_height= "wrap_content" android:layout_width= "match_parent" >
 
     </com.test.shiweiwei.myproject.selfish_view.self_defined_view_group.itemcontainer>
 
     <linearlayout android:background= "@drawable/item_bg" android:gravity= "center" android:id= "@+id/linearlayout_attention_null" android:layout_height= "wrap_content" android:layout_width= "match_parent" android:orientation= "vertical" android:paddingbottom= "35dp" android:paddingtop= "35dp" android:visibility= "gone" >
         <imageview android:layout_height= "wrap_content" android:layout_width= "wrap_content" android:src= "@mipmap/attendance_null/" >
         <textview android:layout_height= "wrap_content" android:layout_width= "wrap_content" android:text= "无报表信息/" android:textsize= "13dp" >
     </textview></imageview></linearlayout>
 
     <imageview android:layout_height= "wrap_content/" android:layout_width= "match_parent" >
 
     <linearlayout android:id= "@+id/linearlayout_remove" android:layout_height= "wrap_content" android:layout_width= "match_parent" android:orientation= "vertical" >
         <linearlayout android:background= "@drawable/item_bg" android:layout_height= "wrap_content" android:layout_margintop= "@dimen/first_page_item_margin_top" android:layout_width= "match_parent" android:orientation= "horizontal" android:paddingbottom= "12dp" android:paddingleft= "8dp" android:paddingtop= "12dp" >
 
             <textview android:layout_height= "wrap_content" android:layout_width= "wrap_content" android:text= "可添加" android:textsize= "16dp/" >
 
         </textview></linearlayout>
 
         <com.test.shiweiwei.myproject.selfish_view.self_defined_view_group.itemcontainer android:background= "@drawable/item_bg" android:id= "@+id/item_container_remove" android:layout_height= "wrap_content" android:layout_width= "match_parent" >
 
         </com.test.shiweiwei.myproject.selfish_view.self_defined_view_group.itemcontainer>
     </linearlayout>
 
</imageview></linearlayout></code>

drawable下的文件:
item_bg:

?
1
2
3
4
5
<code class = "language-xml" hljs= "" ><!--?xml version= 1.0 encoding=utf- 8 ?-->
<shape xmlns:android= "http://schemas.android.com/apk/res/android" >
     <solid android:color= "@color/white" ></solid>
     <stroke android:color= "@color/stroke_vertical" android:width= "0.3dp" ></stroke>
</shape></code>

item_select_bg:

?
1
2
3
4
5
6
7
<code class = "language-xml" hljs= "" ><!--?xml version= 1.0 encoding=utf- 8 ?-->
<shape xmlns:android= "http://schemas.android.com/apk/res/android" >
     <solid android:color= "@color/white" ></solid>
     <stroke android:color= "@color/dark_brow" android:width= "0.3dp" ></stroke>
     <corners android:radius= "4dp" ></corners>
     <padding android:bottom= "12dp" android:top= "12dp" ></padding>
</shape><!--?xml version= 1.0 encoding=utf- 8 ?--></code>

item_select_bg_below:

?
1
2
3
4
5
6
<code class = "language-xml" hljs= "" ><!--?xml version= 1.0 encoding=utf- 8 ?-->
<shape xmlns:android= "http://schemas.android.com/apk/res/android" >
     <solid android:color= "@color/white" ></solid>
     <stroke android:color= "@color/stroke_vertical" android:width= "0.3dp" ></stroke>
     <corners android:radius= "4dp" ></corners>
  </shape></code>
Android 自定义View 自动换行布局 前言 最近工作中有了新需求,就是新增用户的兴趣爱好标签,对于标签要求就是需要一行一行的摆放,空间不足的时候则跳至下一行进行摆放。看了网上这样类似的UI实现五花八门的,但是可定制化比较差,所以就自己动手简单的实现了一个可定制化UI的标签控件,方便以后复用。 话不多说先上一个效果图~ 如何使用 1.首先需要下载源码-&amp;amp;gt;源码地址,可以直接对项目进行依赖,或者将源码... 阅读详情

相关推荐

AutoFlowLayout:多功能流式布局与网格布局控件

近期工作需要用到流式布局,网上也有很多关于这方面的资料。发现流式布局与网格布局的自定义很有意思,是学习自定义控件的一个很好的方式,所以就撸了个几百行代码的控件,既实用又具有学习价值。 一、AutoFlowLayout应用场景流式布局,在很多标签类的场景中可以用的;而网格布局在分类中以及自拍九宫格等场景很常见。如下所示: 如此使用频繁而又实现简单的控件,怎能不自己撸一个呢?控件,还是定制的好啊。

LRH0211的博客 898

Android 自动换行自定义ViewGrop

项目中需要添加子View后在宽度不够的时候能够自动换行,自己写了个简单的自定义View public class AutoLineLayout extends ViewGroup { private int rowHeight = 100; private boolean centerInRow = true; public AutoLineLayout(Context

wsdaijianjun的专栏 660

Android自动换行标签控件

使用RelativeLayout实现自动换行标签控件。

android 多个view自动换行

重绘groupview来实现多个textview或button的自动换行

alvinhuai的专栏 4851

android自定义控件实现自动换行效果

自定义ViewGroup 实现类似于电商平台上的商品属性显示效果

Android自定View流式布局根据文字数量换行

主要为大家详细介绍了Android自定View流式布局,根据文字数量换行,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

Android 自定义ViewGroup之实现FlowLayout-标签流容器

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

Angelica 7781

最全卷积总结篇(分组、深度可分离、空洞、转置等)

分组卷积(Group Convolution)是一种卷积操作方式,它最早在AlexNet中引入,并在深度学习中被广泛使用,尤其是在高效模型设计(如MobileNet和ResNeXt)中。分组卷积通过将输入特征通道分为若干组,并在每组内单独进行卷积操作,可以减少计算复杂度和参数量,同时有效利用模型的结构特性。分组卷积的工作原理通道分组:输入特征图的通道图为C_in,输出特征图的通道数为C_out,将每组的C_in和C_out分为G组,每组的通道数为C_in/G和C_out/G。组内卷积。

qq_44815135的博客 4220

Android自动换行控件开发与优化实践

自动换行布局是Android UI开发中的常见需求,尤其在标签云、动态筛选等场景中至关重要。其核心原理基于ViewGroup的测量(onMeasure)和布局(onLayout)机制,通过智能计算子视图尺寸和屏幕可用宽度实现自适应排列。相比系统原生方案,优化后的自动换行控件在性能(如减少40%布局时间)和功能(支持动态间距调整、三种动画模式)方面具有显著优势。该技术特别适合电商App的商品标签、社交媒体的兴趣标签等需要动态展示多标签的场景。通过合理运用贪心算法和缓存机制,开发者可以构建出既高效又灵活的自动换

weixin_42527401的博客 281

Wider Face+YOLOV7人脸检测

使用Wider Face数据集训练YOLOV7

chen_znn的博客 8410

std::map看来可以被淘汰了?可以用vector来替代,而且更好

vector的优势在于内存连续,可以先std::sort,然后std::lower_bound,或者std::binary_search。而std::map,insert的时候,需要new出内存。

tumu_C的博客 1020

android开发之添加标签与删除标签

老样子,先上效果图标签容器这个在github上已经有很多人实现了,既然有轮子那为什么还要重复制造轮子呢? 本人比较懒,直接参考了https://github.com/XuYongJun96/FlowLayout 在它的基础上扩展了添加标签与删除标签的功能。思路既然是标签容器,那当然就用到了ViewGroup了。 重写onMeasure与onLayout方法。 在onMeasure计算每个标签

江城北望 2301

避坑指南:ZYNQ MPSOC QSPI固化时MT25QU256闪存的特殊配置

本文针对Xilinx ZYNQ UltraScale+ MPSoC平台,详细解析了使用Vitis工具将BOOT.bin固化至Micron MT25QU256ABA QSPI NOR Flash时的特殊配置与避坑指南。重点阐述了Init文件的真实作用、电压与频率的精确匹配策略,以及针对该闪存型号的初始化序列,帮助工程师解决烧录失败、启动异常等常见问题,确保系统可靠启动。

weixin_29175469的博客 272

android动态添加网格布局,AutoFlowLayout:多功能流式布局与网格布局控件

近期工作需要用到流式布局,网上也有很多关于这方面的资料。发现流式布局与网格布局的自定义很有意思,是学习自定义控件的一个很好的方式,所以就撸了个几百行代码的控件,既实用又具有学习价值。一、AutoFlowLayout应用场景流式布局,在很多标签类的场景中可以用的;而网格布局在分类中以及自拍九宫格等场景很常见。如下所示:如此使用频繁而又实现简单的控件,怎能不自己撸一个呢?控件,还是定制的好啊。二、Au...

weixin_36039434的博客 809

Aspose Java 25.10 版本深度破解:基于字节码修改的实战指南

本文提供了Aspose Java 25.10版本基于字节码修改的实战指南。详细介绍了如何通过Javassist工具逆向分析并修改Aspose Cells、Words、Slides及PDF组件的许可证验证逻辑,从而实现绕过限制,并完成最终的打包与功能验证。

stone的博客 1364

智能水下机器人设计: 基于MATLAB的水下机器人动力学建模和控制系统设计,包括水下环境感知、路径规划和任务执行等

智能水下机器人是指能够在水下自主完成各种任务的机器人,例如探测、勘察、救援等。它具有广阔的应用前景,可用于海洋环境监测、水下考古、海上作业等领域。基于MATLAB的水下机器人设计可以为科研人员和工程师提供强大的建模和仿真工具,帮助他们设计更智能、更高效的水下机器人。智能水下机器人是未来海洋科技发展的重要方向,它将为海洋科学研究、水下考古和海上作业等领域带来新的机遇。MATLAB是一种功能强大的数学和科学计算软件,它可以为智能水下机器人设计提供强大的工具和支持。

走向CTO的路上... 965

自定义流式布局+添加+清空

定义的流式布局的类(FlowView) import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.ViewGroup; //------- public class FlowView extends ViewGroup ...

weixin_44349501的博客 343

Z-Image Turbo随机性增强插件的下载教程及使用方法 z-image生成图像太相似怎么办 Z-Image Turbo 整合包下载

Z-Image Turbo 是出了名的“seed 不敏感”,非常难做多样化图像创作。ComfyUi-ConditioningNoiseInjection 插件通过在conditioning(提示词编码)阶段加入可控噪声,成功解决了 Turbo 随机性不足的问题。它不仅能让 Turbo 产生更丰富的构图差异,还能保持主题稳定,是目前我测试过最有效的 Turbo 随机性增强工具。

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 1990
上一篇: Android自定义组件之自动换行View - 儒雅小生
下一篇: 使用极光推送实现分组发送和服务端集成
codepython
博客等级 码龄20年 125粉丝 6原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值