欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

自定义控件之流式布局

程序员文章站 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