自定义控件之流式布局
程序员文章站
2022-03-27 10:32:46
实现如上效果。实现思路:控件FlowLayout继承自ViewGroup,重写onMeasure[测量]、onLayout[布局]方法。addItem()方法用于增加String类型的list注意事项:1. 测量子View的宽和高时,要先调用measureChild,child.getMeasuredHeight();才能获取到值2.onMeasure中元素换行时的处理。用arr存储每行的第一个元素所对应的index,便于onLayout使用3.onLayout中每行的第一......
实现如上效果。
实现思路:
控件FlowLayout继承自ViewGroup,重写onMeasure[测量]、onLayout[布局]方法。addItem()方法用于增加String类型的list
注意事项:
1. 测量子View的宽和高时,要先调用measureChild,child.getMeasuredHeight();才能获取到值
2. onMeasure中元素换行时的处理。用arr存储每行的第一个元素所对应的index,便于onLayout使用
3. onLayout中每行的第一个元素和后续元素的关系,以及当前行和前一行的关系
4. 可以把distanceV、distanceH作为参数提取出来,更灵活
代码如下:
1.FlowLayout
public class FlowLayout extends ViewGroup {
private static final String TAG = "FlowLayout";
private int paddingT, paddingB, paddingL, paddingR;//上下左右的内边距
private int distanceH, distanceV;//纵、横轴上每个元素中间的间距
private int lineH;//行高
public FlowLayout(Context context) {
this(context, null);
}
public FlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
distanceV = distanceH = 20;
}
private ArrayList<Integer> arr = new ArrayList<>(); //每行中第一个字符串对应的index
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
arr.clear();
paddingL = getPaddingStart();
paddingR = getPaddingEnd();
paddingT = getPaddingTop();
paddingB = getPaddingBottom();
Log.d(TAG, "onMeasure: [" + paddingL + "," + paddingT + "," + paddingR + "," + paddingB+"]");
int w = getMeasuredWidth();
int cnt = getChildCount();
int lineNum = 1;
int sumW = 0;//当前行所占总宽度
for (int i = 0; i < cnt; i++) {
View child = getChildAt(i);
// 测量子View的宽和高:先调用measureChild,child.getMeasuredHeight();才能获取到值
measureChild(child, widthMeasureSpec, heightMeasureSpec);
//LayoutParams lp = child.getLayoutParams();
lineH = child.getMeasuredHeight();
int tempW = child.getMeasuredWidth();
if (0 == i) {//这一行的第一个元素
sumW = paddingL + paddingR + tempW;
arr.add(i);//把第一个元素对应的index存入arr
Log.d(TAG, "onMeasure: line" + lineNum);
} else {
sumW += tempW + distanceH;
}
if (sumW > w) {
sumW = paddingL + paddingR + tempW;
lineNum++;
arr.add(i);//换行时,把该元素对应的index存入arr
Log.d(TAG, "onMeasure: line" + lineNum + ", index = " + i);
}
}
int h = lineH*lineNum + paddingT + paddingB + (lineNum-1)*distanceV;//所有元素所占宽高,包含padding
Log.d(TAG, "onMeasure: w = " + w + ", h = " + h);
setMeasuredDimension(w, h) ;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int cnt = getChildCount();
int curL, curR, curT, curB ,preL;
int preT = 0;
for (int i = 0; i < arr.size(); i++) {
//当前行第一个元素对应的index
int curLineFirstEleIndex = arr.get(i);
//当前行最后一个元素对应的index
boolean lastLine = i+1 >= arr.size();//最后一行
int curLineLastEleIndex = lastLine ? cnt-1 : arr.get(i + 1) - 1;//下一行的前一个元素,为当前行的最后一个元素
Log.d(TAG, "onLayout: line" + i + "=[" + curLineFirstEleIndex + "," + curLineLastEleIndex + "]");
preL = 0;//每进入新的一行,preL要置0,curL才会从最左侧开始计算
curT = (preT == 0) ? paddingT : (preT+distanceV);
curB = curT + lineH;
//针对当前行做处理
for (int j = curLineFirstEleIndex; j <= curLineLastEleIndex; j++){
View child = getChildAt(j);
//若是第一个元素,则其左边距为paddingL;否则为上个元素的左边界加元素横向间距
curL = (preL==0) ? paddingL : (preL + distanceH);
curR = curL + child.getMeasuredWidth();
//curT = paddingT;
//curB = curT + lineH;
child.layout(curL, curT, curR, curB);
preL = curR;//把当前元素的右边界 赋值给 preL
}
preT = curB;//把当前行的底边界 赋值给 preT
}
}
public void addItem(List<String> itemList) {
if (null != itemList && itemList.size() > 0) {
for (int i = 0; i < itemList.size(); i++) {
TextView tv = (TextView)LayoutInflater.from(getContext()).inflate(R.layout.item, null);
tv.setText(itemList.get(i));
addView(tv);
}
}
}
}
2. item.xml
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="test"
android:background="@drawable/text_bg_shape"
android:id="@+id/tv"
android:textSize="25sp"
android:textColor="#000"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:paddingTop="5dp"
android:paddingBottom="5dp"/>
3.text_bg_shape.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="30dp"/>
<solid android:color="#0ee"/>
</shape>
4.activity
<com.lyl.ftest.FlowLayout
android:layout_height="match_parent"
android:layout_width="match_parent"
android:layout_margin="10dp"
android:id="@+id/flow"/>
FlowLayout f = findViewById(R.id.flow);
ArrayList<String> itemList = new ArrayList<>();
itemList.add("你好");
itemList.add("你好你好你好你好");
itemList.add("好好好好");
itemList.add("好好好好12324");
itemList.add("你好12325");
f.addItem(itemList);
本文地址:https://blog.csdn.net/lyl0530/article/details/107206927
上一篇: 小程序轮播图指示点样式修改
推荐阅读
-
Android布局之LinearLayout自定义高亮背景的方法
-
深入理解IOS控件布局之Masonry布局框架
-
Android布局控件之常用linearlayout布局
-
ios的collection控件的自定义布局实现与设计
-
Java Swing组件布局管理器之FlowLayout(流式布局)入门教程
-
Android编程布局控件之AbsoluteLayout用法实例分析
-
WPF自定义控件和样式之自定义按钮(Button)
-
ios的collection控件的自定义布局实现与设计
-
C#设计模式之Template模板方法模式实现ASP.NET自定义控件 密码强度检测功能
-
Android-自定义控件之ListView下拉刷新的实现