当前位置: 首页 > article >正文

自定义view实现历史记录流式布局

在 Android 开发中,流式布局是一种常见的 UI 设计模式,尤其适用于展示动态内容,如标签、历史记录等。本文将介绍如何通过自定义 View 来实现一个历史记录的流式布局。

使用自定义view实现历史记录流式布局的运行结果图: 

1. 创建自定义 ViewGroup

首先,我们需要创建一个名为 FlowLayout 的类,继承自 ViewGroup。在构造函数中,我们可以初始化一些必要的属性。

public class FlowLayout extends ViewGroup {
    private List<List<View>> allListView = new ArrayList<>(); // 所有View的二维数组
    private List<Integer> allListHeight = new ArrayList<>(); // 每一行的高度
    private int mywidthspacing = 16; // 宽间距
    private int myheightspacing = 8; // 高间距


    public FlowLayout(Context context) {
        super(context);
    } // new

    public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    } // 反射

    public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }// xml

    public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

2. 重写 onMeasure 方法

在 onMeasure 方法中,我们需要测量每个子视图的大小,并计算出 FlowLayout 的最终宽高。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 调用父类的 onMeasure 方法,确保父类的测量逻辑被执行
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
    // 初始化存储所有行的视图和高度的列表,防止 onMeasure 被多次调用时出现数据错误
    allListView = new ArrayList<>();
    allListHeight = new ArrayList<>();
    
    // 获取父视图的宽度和高度
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
    // 初始化总高度和总宽度
    int allheight = 0;
    int allwidth = 0; 
    
    // 获取子视图的数量
    int count = getChildCount();
    
    // 初始化当前行的宽度和高度
    int linewidthuser = 0; 
    int lineHeight = 0;
    
    // 用于存储当前行的视图
    List<View> listview = new ArrayList<>();

    // 遍历所有子视图
    for (int i = 0; i < count; i++) {
        View childView = getChildAt(i); // 获取当前子视图
        LayoutParams childLP = childView.getLayoutParams(); // 获取子视图的布局参数
        
        // 如果子视图可见
        if (childView.getVisibility() != GONE) {
            // 计算子视图的测量规格
            int childwidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingRight(), childLP.width);
            int childheightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTop() + getPaddingBottom(), childLP.height);
            
            // 测量子视图的宽高
            childView.measure(childwidthMeasureSpec, childheightMeasureSpec);
            int childMeasuredWidth = childView.getMeasuredWidth(); // 获取测量后的宽度
            int childMeasuredHeight = childView.getMeasuredHeight(); // 获取测量后的高度


            // 判断当前行是否能放下下一个子视图,特别需要注意加入当前view的Padding的距离
            if (linewidthuser + childMeasuredWidth + mywidthspacing > widthSize - getPaddingLeft() - getPaddingRight()) {
                // 如果放不下,保存当前行的视图和高度
                allListView.add(listview);
                allListHeight.add(lineHeight);

                // 更新总宽度和总高度
                allwidth = Math.max(allwidth, linewidthuser + mywidthspacing);
                allheight = allheight + lineHeight + myheightspacing;

                // 换行,重置当前行的宽高
                linewidthuser = 0;
                lineHeight = 0;
                listview = new ArrayList<>();
                
                // 将当前子视图作为新行的第一个视图
                listview.add(childView);
                linewidthuser += childMeasuredWidth + mywidthspacing; // 更新当前行宽度
                lineHeight = Math.max(childMeasuredHeight, lineHeight); // 更新当前行高度
            } else {
                // 如果可以放下,继续添加到当前行
                listview.add(childView);
                linewidthuser += childMeasuredWidth + mywidthspacing; // 更新当前行宽度
                lineHeight = Math.max(childMeasuredHeight, lineHeight); // 更新当前行高度
                
                // 如果是第一行,更新总宽度和高度
                if (allListView.size() < 1) {
                    allheight = Math.max(childMeasuredHeight, allheight);
                    allwidth = linewidthuser;
                }
            }
            
            // 如果是最后一个子视图,保存当前行的视图和高度
            if (i == count - 1) {
                allListView.add(listview);
                allListHeight.add(childMeasuredHeight);
            }
        }
    }

    // 获取测量模式
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);

    // 根据测量模式来获取需要的宽度和高度
    int realWidthSize = (widthMode == MeasureSpec.EXACTLY) ? widthSize : allwidth + getPaddingLeft() + getPaddingRight();
    int realHeightSize = (heightMode == MeasureSpec.EXACTLY) ? heightSize : allheight + getPaddingBottom() + getPaddingTop();

    // 设置测量后的宽高
    setMeasuredDimension(realWidthSize, realHeightSize);
}

3. 重写 onLayout 方法

在 onLayout 方法中,我们需要根据测量的结果来布局每个子视图的位置。

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    // 获取当前视图的左、上、右、下边距
    int curLeft = getPaddingLeft(); // 当前行的起始左边距
    int curTop = getPaddingTop(); // 当前行的起始上边距

    // 遍历所有行
    for (int i = 0; i < allListView.size(); i++) { // 遍历每一行
        curLeft = getPaddingLeft(); // 每行开始时重置左边距

        // 遍历当前行的所有视图
        for (int j = 0; j < allListView.get(i).size(); j++) { // 遍历当前行的每个视图
            View view = allListView.get(i).get(j); // 获取当前视图

            // 定义视图的布局边界
            int left, top, right, bottom;
            left = curLeft; // 当前视图的左边界
            top = curTop; // 当前视图的上边界
            right = left + view.getMeasuredWidth(); // 当前视图的右边界
            bottom = top + view.getMeasuredHeight(); // 当前视图的下边界
            
            // 布局当前视图
            view.layout(left, top, right, bottom);
            
            // 更新当前行的左边距,为下一个视图留出空间
            curLeft = right + mywidthspacing; // 加上视图的宽度和水平间距
        }

        // 更新当前行的顶部边距,为下一行留出空间
        curTop = curTop + allListHeight.get(i) + myheightspacing; // 加上当前行的高度和垂直间距

        // 重置左边距,以便下一行的布局
        curLeft = getPaddingLeft(); // 每行开始时重置左边距
    }
}

4.使用

在布局文件中,注意把com.example.note换成自己的包名:

<com.example.note.FlowLayout
            android:id="@+id/flowLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="16dp" />

这里就自己加的几个数据在Activity中运行测试:



public class TestActivity extends AppCompatActivity {
    private FlowLayout flowLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        flowLayout = findViewById(R.id.flowLayout);

        ArrayList<String> list = new ArrayList<>();
        list.add("你好");
        list.add("我是谁");
        list.add("历史记录");
        list.add("搜索我的物品A");
        list.add("搜索我的物品B");
        list.add("搜索我的物品C");
        list.add("搜索我的物品D");
        list.add("你好呀");
        list.add("自定义View");
        list.add("流式布局实现啦");

        // 添加文字到 FlowLayout
        for (int i = 0; i < list.size(); i++) {
            final TextView textView = new TextView(this);
            textView.setText(list.get(i));

            GradientDrawable drawable = new GradientDrawable();
            drawable.setShape(GradientDrawable.RECTANGLE);
            drawable.setColor(Color.GRAY); // 设置背景颜色为灰色
            drawable.setCornerRadius(26f); // 设置圆角半径
            textView.setBackground(drawable);
            textView.setTextSize(20f);
            textView.setTextColor(Color.WHITE); // 设置字体颜色为白色
            textView.setPadding(16, 8, 16, 8); // 设置内边距

            textView.setOnClickListener(v -> {
                // 处理按钮点击事件
            });

            flowLayout.addView(textView);
        }
    }
}


http://www.kler.cn/a/378161.html

相关文章:

  • V900新功能-电脑不在旁边,通过手机给PLC远程调试网关配置WIFI联网
  • 如何根据一系列提交文件,匹配对应的git提交记录?用ai
  • 链接数据Linked Data的深层解读
  • sh cmake-linux.sh -- --skip-license --prefix = $MY_INSTALL_DIR
  • Ubuntu Netlink 套接字使用介绍
  • TCP与UDP的端口连通性
  • 5G基础知识
  • 【51 Pandas+Pyecharts | 深圳市共享单车数据分析可视化】
  • Java爬虫:京东商品SKU信息的“窃听风云”
  • 消息中间件类型介绍
  • 共创一带一路经济体,土耳其海外媒体软文发稿 - 媒体宣发报道
  • nodejs入门教程9:nodejs Buffer
  • Vue学习笔记(十一)
  • Unity的gRPC使用之实现客户端
  • 基于统计方法的语言模型
  • kubesphere jenkins自动重定向 http://ks-apiserver:30880/oauth/authorize
  • 开源库 FloatingActionButton
  • new/delete和malloc()/free()的区别及其使用
  • 无人机航拍铁路障碍物识别图像分割系统:创新焦点发布
  • 将分类标签转换为模型可以处理的数值格式
  • Android 蓝牙连接 HID 设备
  • 【RAG】自动化RAG框架-“AutoML风”卷到了RAG?
  • 基于Android13源码分析Launcher启动
  • java多线程编程(二)一一>线程安全问题, 单例模式, 解决程线程安全问题的措施
  • FRAMES数据集:由谷歌和哈佛大学 联合创建一个综合评估数据集,目的测试检索增强生成系统在事实性、检索准确性和推理方面的能力
  • .card ~ img { width: 100%; height: 100%; object-fit: cover; }