自定义ViewGroup实现流式布局

news2024/12/28 5:55:13

目录

1、View的绘制流程

2、自定义ViewGroup构造函数的作用

3、onMeasure 方法

3.1、View的度量方式

3.2、onMeasure方法参数的介绍

3.3、自定义ViewGroup onMeasure 方法的实现

4、onLayout方法

5、onDraw方法

6、自定义View的生命周期

7、自定义流式布局的实现

扩展:


1、View的绘制流程

从Activity启动开始,了解View的绘制流程。
图1:Activity启动的流程。
图2:Activity启动的调用流程
图3:显示了View与Window的逻辑结构。
图3中的ViewRootImpl是在图2中的Activity的attach中生成的,是在Activity的onCreate生命周期之前,ViewRootImpl负责处理View 的事件和逻辑。
图4:ViewRootImpl的方法的调用流程
        通过ViewRootImpl的performTraversals方法看出View的绘制流程 mearsure、layout、draw,分别对应View及ViewGroup的onMeasure、onLayout和onDraw方法。
图5:Activity在onCreate时使用setContentView 给Activity设置具体的ContentView。

2、自定义ViewGroup构造函数的作用

        构造函数用于创建对象,自定义View或ViewGroup会有三个不同用途的构造函数,分别用于
new Object()创建对象、xml解析生成对象和自定义。
/**
* 通过 new FlowLayout() 代码创建
* @param context
*/
public FlowLayout(Context context) {
    super(context);
}


/**
* 通过xml解析创建
* @param context
* @param attrs
*/
public FlowLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
}


/**
* 自定义View 默认的属性以及自定义的属性
* @param context
* @param attrs
* @param defStyleAttr
*/
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

3、onMeasure 方法

        可参照LinearLayout或其他布局代码中onMeasure 的具体实现。

3.1、View的度量方式

        View的层级结构如上图,在measure时,采用深度递归自上而下进行遍历,在ViewGroup中触发子View的度量。

3.2、onMeasure方法参数的介绍

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
        onMeasure方法的参数有2个,分别是widthMeasureSpec和heightMeasureSpec,两个参数分别是 父布局根据父布局的宽高、父布局的padding、当前控件的layout_width或layout_height属性值、当前控件的margin属性值生成的包含width和height相关的mode以及宽高值。
        以ViewGroup的width为例,layout_width有三种数值,match_parent、wrap_content和固定值。
        当layout_width为match_parent时,分配给的宽度是父布局的宽度。
        当layout_width为wrap_content时,因还未知具体需要多大的宽度,所以分配的宽度是父布局的宽度。
        当layout_width为固定值时,分配的宽度是固定值。
综上,度量最终计算的宽高只会比参数中给定的宽高小或者相等。

3.3、自定义ViewGroup onMeasure 方法的实现

    在自定义ViewGroup时需要实现onMeasure方法,否则layout_width和layout_height属性无效,该ViewGroup的宽高为父控件的宽高。
    
    在重写onMeasure方法时,该方法需要实现的功能如下:
  • 度量子View(深度递归,因为ViewGroup不是叶子节点,一定要度量子View的宽高)
  • 根据子View度量后的宽高计算ViewGroup的宽高
注意
  • 在度量子View的宽高时要考虑ViewGroup的padding和子View的margin值。
  • 在计算ViewGroup的宽高时,要使用子View度量后的宽高,(ViewGroup分配给子View的宽高可能会大,需要子View执行onMeasure方法后确定自己的宽高)
  • 在计算ViewGroup的宽高时要加入ViewGroup的padding值。

4、onLayout方法

    在onLayout方法中使用相对父控件的相对布局,而不是相对Android屏幕坐标系的绝对布局,在实现onLayout时只考虑与父控件的关系即可,最终在View绘制时,会将相对位置转换为绝对位置,此工作是由WMS来实现的。
注意:
  • 在自定义ViewGroup的 onLayout中,需考虑ViewGroup的padding和子View的margin值
  • 在自定义View中不用实现onLayout。

5、onDraw方法

    ViewGroup的作用在于布局子View,如果需要边框和背景的绘制,在onDraw中实现,如不需要,可不实现。

6、自定义View的生命周期

    View的度量、布局和绘制会有多次,但是View的初始化只有一次。

7、自定义流式布局的实现

package com.liluj.androidui.custom;


import static com.scwang.smartrefresh.layout.util.SmartUtil.dp2px;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import com.liluj.androidui.utils.UnitConvertUtils;
import java.util.ArrayList;
import java.util.List;


/**
* 自定义流式布局
*/
public class FlowLayout extends ViewGroup {
    //水平间隔
    private int horizontalSpace = dp2px(20);
    //垂直间隔
    private int veticalSpace = dp2px(20);

    private List<List<View>> lineViewList; //记录每行的View
    private List<Integer> heightList; //记录每列的高度。

    /**
     * 通过 new FlowLayout() 代码创建
     * @param context
     */
    public FlowLayout(Context context) {
        super(context);
    }


    /**
     * 通过xml解析创建
     * @param context
     * @param attrs
     */
    public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }


    /**
     * 自定义View 默认的属性以及自定义的属性
     * @param context
     * @param attrs
     * @param defStyleAttr
     */
    public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


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




    private void initMeasure() {
        /**
         * 此两个参数是为了给Layout布局用的,界面不只绘制一次,会重新绘制,所以在每次执行onMeasure时要重新初始化,而不是在创建FlowLayout时初始化
         */
        lineViewList = new ArrayList();
        heightList = new ArrayList<>();
    }


    /**
     * 依据子View的宽高 确定自己的宽高
     * 注意:
     * 1、margin值在父控件中处理
     * 1.1 当前ViewGroup的margin值在父控件已处理
     * 1.2 在度量和布局子View时要考虑margin值,否则子View的margin属性无用(此例子没处理子View的margin值)
     * 2、padding值包含在ViewGroup以及View的宽高
     * 2.1 在ViewGroup和View的 onMeasure、onLayout、onDraw时要考虑自身的padding值
     * @param widthMeasureSpec horizontal space requirements as imposed by the parent.
     *                         The requirements are encoded with
     *                         {@link android.view.View.MeasureSpec}.
     * @param heightMeasureSpec vertical space requirements as imposed by the parent.
     *                         The requirements are encoded with
     *                         {@link android.view.View.MeasureSpec}.
     *
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);


        initMeasure();


        int count = getChildCount();


        List<View> lineView = new ArrayList<>();
        int lineUsedWidth = 0; //当前行已使用的宽度
        int lineHeight = 0; //当前行的高度
        int mWidth = 0;  //view的宽度
        int mHeight = 0; //view的高度


        /**
         * 父控件根据自己的宽高以及当前View的LayoutParam属性(宽、高、margin)分配给当前View的宽高,固定值就是固定值,
         * match_parent和wrap_content为父控件的宽度,
         */
        int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
        //子View可使用的最大宽度,需减去ViewGroup的paddingLeft和paddingRight
        int childrenCanUseWidth = maxWidth - getPaddingLeft()-getPaddingRight();
        int maxHeight = MeasureSpec.getSize(heightMeasureSpec);


        /**
         * 度量子View并计算ViewGroup属性为wrap_content时实际的宽高
         */
        for(int i = 0;i<count;i++){
            View view = getChildAt(i);
            /**
             * 根据当前View的MeasureSpec属性、padding值和子View的LayoutParams值(android:layout_width和android:layout_height的属性值)来给子View分配空间
             */
            int measureSpecWidth = getChildMeasureSpec(widthMeasureSpec,getPaddingLeft()+getPaddingRight(),view.getLayoutParams().width);
            int measureSpecHeight = getChildMeasureSpec(heightMeasureSpec,getPaddingTop()+getPaddingBottom(),view.getLayoutParams().height);
            /**
             * 触发子View度量自己的空间,子View度量自己的大小后校正子View的大小
             */
            view.measure(measureSpecWidth,measureSpecHeight);


            int measuredWidth = view.getMeasuredWidth();
            int measuredHeight = view.getMeasuredHeight();


            //换行
            if(lineUsedWidth + measuredWidth > childrenCanUseWidth){
                lineViewList.add(lineView);
                heightList.add(lineHeight);


                mWidth = Math.max(mWidth,lineUsedWidth);
                mHeight = mHeight + lineHeight + veticalSpace;


                lineUsedWidth = 0;
                lineHeight = 0;
                lineView = new ArrayList<>();
            }


            lineView.add(view);
            lineUsedWidth = lineUsedWidth + measuredWidth + horizontalSpace;
            lineHeight = Math.max(lineHeight, measuredHeight);


            /**
             * 添加上最后一行的宽高
             */
            if(i == count -1){
                lineViewList.add(lineView);
                heightList.add(lineHeight);
                mWidth = Math.max(mWidth,lineUsedWidth);
                mHeight = mHeight + lineHeight;
            }
        }


        //ViewGroup的宽高加上padding值
        mWidth = mWidth+getPaddingLeft()+getPaddingRight();
        mHeight = mHeight+getPaddingTop()+getPaddingBottom();


        /**
         * 获取当前View 宽高的mode
         */
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);


        /**
         * 当View的宽高为固定值时,使用固定值,此处固定值只会比按照wrap_content计算出来的值大,不会小,与父控件分配给当前View的宽高值有关。
         * 可参见ViewGroup的getChildMeasureSpec方法
         */
        int realWidth = (widthMode == MeasureSpec.EXACTLY) ? maxWidth:mWidth;
        int realHeight = (heightMode == MeasureSpec.EXACTLY) ? maxHeight:mHeight;


        setMeasuredDimension(realWidth,realHeight);
    }




    /**
     * 布局子View,布局子View时使用相对坐标(相对父View进行布局)而不是绝对坐标(Window坐标系)
     * @param changed This is a new size or position for this view
     * @param l Left position, relative to parent
     * @param t Top position, relative to parent
     * @param r Right position, relative to parent
     * @param b Bottom position, relative to parent
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int curL = getPaddingLeft();
        int curT = getPaddingTop();
        for(int i = 0;i< lineViewList.size();i++){
            List<View> lineView = lineViewList.get(i);
            for(int j = 0;j<lineView.size();j++){
                View view = lineView.get(j);
                int left = curL;
                int top = curT;
                int right = curL + view.getMeasuredWidth();
                int bottom = curT + view.getMeasuredHeight();
                view.layout(left,top,right,bottom);
                curL = right + horizontalSpace;
            }
            curL = getPaddingLeft();
            curT = curT + heightList.get(i)+veticalSpace;
        }


    }
}

效果图:

扩展:

  • 将xml解析为UI
        例如:LinearLayout标签解析为LinearLayout 对象,layout_width,layout_height、layout_weight会生成对应的LayoutParam。
        MeasureSpec用于表示View以及ViewGroup的宽高
xml解析采用反射实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/596716.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用VBA在单元格中快速插入Unicode符号

Unicode 符号 Unicode 符号在实际工作中有着广泛的应用&#xff0c;比如用于制作邮件签名、文章排版、演示文稿制作等等。在 Excel 表格中&#xff0c;插入符号可以让表格的排版更加美观&#xff0c;同时也能够帮助用户更清晰地表达意思。 Dingbats Dingbats是一个包含装饰符…

龙蜥白皮书精选:开源 RISC-V 技术支持软硬件全栈平台

文/RISC-V ARCH SIG 01 背景介绍 RISC-V 是一套开源指令集为拥抱更加开放的芯片生态&#xff0c;指令集标准和扩展标准采用全球共享共治的模式&#xff0c;于 Arm 和 x86 有很大不同&#xff0c;因此也受到了业内人士的普遍关注&#xff0c;再未来有着很大应用潜力。龙蜥社区…

奇富科技语音论文入选国际顶会INTERSPEECH 2023

近日&#xff0c;奇富科技机器人团队论文《Eden-TTS&#xff1a;一种简单高效的非自回归“端到端可微分”神经网络的语音合成架构》&#xff08;Eden-TTS: A Simple and Efficient Parallel Text-to-speech Architecture with Collaborative Duration-alignment Learning&#…

【LeetCode】HOT 100(3)

题单介绍&#xff1a; 精选 100 道力扣&#xff08;LeetCode&#xff09;上最热门的题目&#xff0c;适合初识算法与数据结构的新手和想要在短时间内高效提升的人&#xff0c;熟练掌握这 100 道题&#xff0c;你就已经具备了在代码世界通行的基本能力。 目录 题单介绍&#…

1.Hive系列之简介

1. Hive简介 1.1 Hive是什么 Hive是一个基于Hadoop的数据仓库工具&#xff0c;它提供了类似于SQL的查询语言HiveQL&#xff0c;以及用于将查询转换为MapReduce任务的引擎。Hive的主要目的是使数据分析师和开发人员能够轻松地查询和分析存储在Hadoop集群中的数据&#xff0c;而…

如果我们使用大字符串作为 MySQL 索引键会发生什么

背景 正如我之前的文章里所解释的那样&#xff0c;B树的深度决定了MySQL在仅考虑使用索引的情况下的最坏查询性能。在SSD的帮助下&#xff0c;拥有一个比4层更深的B树应该是可以接受的。 那么下一个问题是&#xff1a;性能可能有多糟糕&#xff1f;在这里&#xff0c;我有意设…

elasticsearch中文分词使用以及疑问

最近用到elasticsearch作为知识库底层搜索引擎&#xff0c;开发反馈中文查询有问题&#xff0c;所以引用ik分词解决此问题。 一、安装 根据自己的版本找到github仓库下载&#xff0c;我此处使用为7.9.3版本 v7.9.3 Releases medcl/elasticsearch-analysis-ik GitHub 解压到…

git pull 和push讲解:016

pull 和push大致流程&#xff1a;(将远程仓库同步到本地仓库)>(在本地仓库修改并提交)>(推送修改内容到远程仓库) 1. 首先创建一个文件夹&#xff0c; 打开Git Bash终端&#xff0c; cd到这个文件夹内 2. 将(远程仓库)的克隆到这个文件夹内&#xff1a;git clone 远程仓库…

史上最难HelloWorld

文章目录 TomcatServlet创建一个项目引入依赖创建目录编写代码打包部署验证 优化一下打包和部署-Smart TomcatServlet常见的问题 Tomcat Tomcat就是一个HTTP服务器&#xff0c;HTTP协议是前后端交互的桥梁&#xff0c;前端就是浏览器&#xff0c;后端就是一个HTTP服务器&#…

设计模式之~访问者模式

简述&#xff1a; 访问者模式表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。 访问者模式(Vistor Pattern)是一种将数据结构与数据操作分离的的设计模式。 结构图&#xff1a; 实例&#xff1a; 示例代码&#x…

Linux系统下C语言的编程技巧

Linux系统能够为人们提供更加安全实用的效果,保证计算机系统能够稳定的运行。利用Linux系统下首先要进行C语言的编程,掌握编程的技巧能够更好的发挥计算机的作用。如何掌握Linux系统下计算机C语言的编程技巧是计算机发展的关键要素。本文对Linux系统下计算机C语言的编程技巧进行…

【复习笔记】FreeRTOS(三)任务挂起和恢复

本文是FreeRTOS复习笔记的第三节&#xff0c;任务挂起和恢复&#xff0c;使用的开发板是stm32f407VET6&#xff0c;创建两个任务&#xff0c;task1负责闪烁LED&#xff0c;task2负责按键控制&#xff0c;当按键按下时控制任务挂起&#xff0c;按键再次按下恢复任务&#xff0c;…

【数据结构】经典排序法

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;那个传说中的man的主页 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;题目大解析2 目录 &#x1f449;&#x1f3fb; 直接插入排序&#x1f449;&#x1f3fb; 选择排序&…

微信支付(小程序)-java

微信支付开发者文档微信支付是腾讯公司的支付业务品牌&#xff0c;微信支付提供公众号支付、APP支付、扫码支付、刷卡支付等支付方式。微信支付结合微信公众账号&#xff0c;全面打通O2O生活消费领域&#xff0c;提供专业的互联网行业解决方案&#xff0c;微信支付支持微信红包…

访问github网络问题解决

查看wsl可用镜像列表时产生如下访问github的网络问题 C:\Users\jiangcheng> wsl --list --online 无法从“https://raw.githubusercontent.com/microsoft/WSL/master/distributions/DistributionInfo.json”中提取列表分发。无法与服务器建立连接 Error code: Wsl/WININET_…

hive任务reduce步骤卡在99%原因及解决

我们在写sql的时候经常发现读取数据不多&#xff0c;但是代码运行时间异常长的情况&#xff0c;这通常是发生了数据倾斜现象。数据倾斜现象本质上是因为数据中的key分布不均匀&#xff0c;大量的数据集中到了一台或者几台机器上计算&#xff0c;这些数据的计算速度远远低于平均…

Vulkan Tutorial 9 模型加载Mipmaps

目录 28 加载模型 Sample mesh 加载顶点和索引 ​编辑 顶点去重 28 加载模型 我们将使用tinyobjloader库来从OBJ文件中加载顶点和面。它的速度很快&#xff0c;而且很容易集成&#xff0c;因为它是一个像stb_image一样的单文件库。将包含tiny_obj_loader.h的目录添加到Add…

ChatGPT国内镜像站

免费国内镜像推荐&#xff08;超稳定&#xff09; 下面为大家收集了目前国内最稳定流畅的ChatGPT镜像网站 目录 机器人 博弈ai 泰cool辣 道合顺 二狗问答 核桃 WOChat GPT中文站 TomChat 利用ChatGPTMindShow三分钟生成PPT ChatGPT国内镜像是啥 ChatGPT 镜像是指…

Xpdf 阅读器源码编译后查看文件中文乱码问题解决

经查阅&#xff0c;是由于缺少中文字体包&#xff1a; 第一步&#xff1a;下载所需要的字体包 下载https://dl.xpdfreader.com/xpdf-t1fonts.tar.gz 包含下载中文字体包&#xff08;非嵌入字体&#xff09; http://ftp.gnu.org/gnu/non-gnu/chinese-fonts-truetype/gkai00mp…

pytorch-简单回归问题-手写数字识别

pytorch-简单回归问题-手写数字识别 线性回归添加噪声简单例子分类问题引入-手写数字识别数据集 训练推导手写数字识别1加载数据集编写网络训练网络计算正确率 线性回归添加噪声 使用均方差损失函数来衡量损失 简单例子 通过最小化损失函数&#xff0c;求解出参数w b 下图表示…