我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android中流式布局FlowLayout怎么用

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android中流式布局FlowLayout怎么用

小编给大家分享一下Android中流式布局FlowLayout怎么用,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!

引子

下图是实现效果

Android中流式布局FlowLayout怎么用

自定义View的流程

想想自定义view我们都要做哪些事情

  • 布局,我们要确定view的尺寸以及要摆放的位置,也就是 onMeasure() 、onLayout() 两方法

  • 显示,布局之后是怎么把它显示出来,主要用的是onDraw,可能用到 :canvas paint matrix clip rect animation path(贝塞尔) line

  • 交互,onTouchEvent

本文要做的是流式布局,继承自ViewGroup,主要实现函数是onMeasure() 、onLayout() 。下图是流程图

Android中流式布局FlowLayout怎么用

onMeasure

onMeasure是测量方法,那测量的是什么?我们不是在xml已经写好view的尺寸了吗,为什么还要测量?

有这么几个问题,我们在xml写view的时候确实写了view的width与height,但那玩意是具体的dp吗?我们可能写的是match_parent,可能是wrap_content,可能是权重,可能是具体的长度。对于不是确定值的我们要给它什么尺寸?哪怕写的是确定值就一定能给它吗,如果父布局最大宽度是100dp,子布局写的是200dp咋办?对于多层级的view,我们只是调用本个view的onMeasure方法吗?

以下面的图为栗子

Android中流式布局FlowLayout怎么用

如果上图圈红的View就是我们要自定义的view,我们该怎么测量它?

  • 首先我们要知道它的父布局能给它多大的空间

  • 对于容器类型的view,根据其所有子view需要的空间计算出view所需的尺寸

首先要明确一点:测量是自上而下递归的过程!以FlowLayout的高度举例,它的height要多少合适?根据布局的摆放逐个测量每行的高度得出其所需的height,这个测出的高度再根据父布局给出的参考做计算,最后得到真正的高度。在测量子view的时候,子view可能也是容器,其内部也有很多view,其本身的不确定性需要遍历其子布局,这是一个递归的过程!

下面开始我们的测量过程,假设FlowLayout的父布局是LinearLayout,整体UI布局如下

Android中流式布局FlowLayout怎么用


LinearLayout给它的空间有多大,还记得onMeasure的两个参数嘛,这俩是父布局给的参考值,也是父布局对其约束限制

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

MeasureSpec由两部分构成,高2位表示mode,低30位表示size;父布局给的参考宽高

 int selfWidth = MeasureSpec.getSize(widthMeasureSpec); int selfHeight = MeasureSpec.getSize(heightMeasureSpec);

由此我们可以得到父布局给的空间大小,也就是FlowLayout的最大空间。那我们实际需要多大空间呢,我们需要测量所以的子view。

子view的摆放逻辑:

  • 本行能放下则放到本行,即满足条件 lineUsed + childWidthMeasured + mHorizontalSpacing < selfWidth

  • 本行放不下则另起一行

摆放逻辑有了,怎么测量子view

  • 获得子view的LayoutParams从而获得xml里设置的layout_width与layout_height

  • 调用getChildMeasureSpec方法算出MeasureSpec

  • 子view调用measure方法测量

//获得LayoutParamsLayoutParams childParams = childView.getLayoutParams();//计算measureSpecint childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, parentLeft + parentRight, childParams.width);int childHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, parentTop + parentBottom, childParams.height);//测量childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);

下面是 getChildMeasureSpec 内部实现,以横向尺寸为例

//以横向尺寸为例,第一个参数是父布局给的spec,第二个参数是扣除自己使用的尺寸,第三个是layoutParams    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {        int specMode = MeasureSpec.getMode(spec);        int specSize = MeasureSpec.getSize(spec);        int size = Math.max(0, specSize - padding);        int resultSize = 0;        int resultMode = 0;        switch (specMode) {        // Parent has imposed an exact size on us        //老王的钱是确定的,小王有三种可能        case MeasureSpec.EXACTLY:            if (childDimension >= 0) {                resultSize = childDimension;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.MATCH_PARENT) {                // Child wants to be our size. So be it.                resultSize = size;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size. It can't be                // bigger than us.                resultSize = size;                resultMode = MeasureSpec.AT_MOST;            }            break;        // Parent has imposed a maximum size on us        //老王的钱最多有多少,小王有三种可能        case MeasureSpec.AT_MOST:            if (childDimension >= 0) {                // Child wants a specific size... so be it                resultSize = childDimension;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.MATCH_PARENT) {                // Child wants to be our size, but our size is not fixed.                // Constrain child to not be bigger than us.                resultSize = size;                resultMode = MeasureSpec.AT_MOST;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size. It can't be                // bigger than us.                resultSize = size;                resultMode = MeasureSpec.AT_MOST;            }            break;        // Parent asked to see how big we want to be        //老王的钱不确定,小王有三种可能        case MeasureSpec.UNSPECIFIED:            if (childDimension >= 0) {                // Child wants a specific size... let him have it                resultSize = childDimension;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.MATCH_PARENT) {                // Child wants to be our size... find out how big it should                // be                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;                resultMode = MeasureSpec.UNSPECIFIED;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size.... find out how                // big it should be                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;                resultMode = MeasureSpec.UNSPECIFIED;            }            break;        }        //noinspection ResourceType        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);    }

上面的算法其实很简单,根据父布局给的mode和size结合自身的尺寸算出自己的mode和size,具体规则如下

Android中流式布局FlowLayout怎么用

算法的实现是父布局有三种可能,子布局三种可能,总共9种可能。就像下面的小王想跟老王借钱买房,有几种可能?

Android中流式布局FlowLayout怎么用

测量完子view后怎么确定布局的大小?

  • 测量所有行,得到最大的值作为布局的width

  • 测量所有行的高度,高度的总和是布局的height

  • 调用 setMeasuredDimension 函数设置最终的尺寸

onLayout

基于测量工作我们基本确定了所有子view的摆放位置,这阶段要做的就是把所有的view摆放上去,调用子view的layout函数即可

具体代码实现

public class FlowLayout extends ViewGroup {    private int mHorizontalSpacing = dp2px(16); //每个item横向间距    private int mVerticalSpacing = dp2px(8); //每个item横向间距    //  记录所有的行    private List<List<View>> allLines = new ArrayList<>();    //  记录所有的行高    private List<Integer> lineHeights = new ArrayList<>();        public FlowLayout(Context context) {        super(context);    }        public FlowLayout(Context context, AttributeSet attrs) {        super(context, attrs);    }        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 clearMeasureParams() {        //  不断创建回收会造成内存抖动,clear即可        allLines.clear();        lineHeights.clear();    }        @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        clearMeasureParams();        //  先测量孩子        int childCount = getChildCount();        int parentTop = getPaddingTop();        int parentLeft = getPaddingLeft();        int parentRight = getPaddingRight();        int parentBottom = getPaddingBottom();        //  爷爷给的参考值        int selfWidth = MeasureSpec.getSize(widthMeasureSpec);        int selfHeight = MeasureSpec.getSize(heightMeasureSpec);        //  保存一行所有的 view        List<View> lineViews = new ArrayList<>();        //  记录这行已使用多宽 size        int lineWidthUsed = 0;        //  一行的高        int lineHeight = 0;        //  measure过程中,子view要求的父布局宽高        int parentNeedWidth = 0;        int parentNeedHeight = 0;        for (int i = 0; i < childCount; i++) {            View childView = getChildAt(i);            LayoutParams childParams = childView.getLayoutParams();            //  将LayoutParams转为measureSpec                        int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, parentLeft + parentRight, childParams.width);            int childHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, parentTop + parentBottom, childParams.height);            childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);            //  获取子View测量的宽高            int childMeasuredWidth = childView.getMeasuredWidth();            int childMeasuredHeight = childView.getMeasuredHeight();            //  需要换行            if (childMeasuredWidth + lineWidthUsed + mHorizontalSpacing > selfWidth) {                //  换行时确定当前需要的宽高                parentNeedHeight = parentNeedHeight + lineHeight + mVerticalSpacing;                parentNeedWidth = Math.max(parentNeedWidth, lineWidthUsed + mHorizontalSpacing);                //  存储每一行的数据 !!! 最后一行会被漏掉                allLines.add(lineViews);                lineHeights.add(lineHeight);                //  数据清空                lineViews = new ArrayList<>();                lineWidthUsed = 0;                lineHeight = 0;            }            lineViews.add(childView);            lineWidthUsed = lineWidthUsed + childMeasuredWidth + mHorizontalSpacing;            lineHeight = Math.max(lineHeight, childMeasuredHeight);            //处理最后一行数据            if (i == childCount - 1) {                allLines.add(lineViews);                lineHeights.add(lineHeight);                parentNeedHeight = parentNeedHeight + lineHeight + mVerticalSpacing;                parentNeedWidth = Math.max(parentNeedWidth, lineWidthUsed + mHorizontalSpacing);            }        }        //  测量完孩子后再测量自己        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        //  如果父布局给的是确切的值,测量子view则变得毫无意义        int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth : parentNeedWidth;        int realHeight = (heightMode == MeasureSpec.EXACTLY) ? selfHeight : parentNeedHeight;        setMeasuredDimension(realWidth, realHeight);    }        @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        int currentL = getPaddingLeft();        int currentT = getPaddingTop();        for (int i = 0; i < allLines.size(); i++) {            List<View> lineViews = allLines.get(i);            int lineHeight = lineHeights.get(i);            for (int j = 0; j < lineViews.size(); j++) {                View view = lineViews.get(j);                int left = currentL;                int top = currentT;                //  此处为什么不用 int right = view.getWidth(); getWidth是调用完onLayout才有的                int right = left + view.getMeasuredWidth();                int bottom = top + view.getMeasuredHeight();                //  子view位置摆放                view.layout(left, top, right, bottom);                currentL = right + mHorizontalSpacing;            }            currentT = currentT + lineHeight + mVerticalSpacing;            currentL = getPaddingLeft();        }    }    public static int dp2px(int dp) {        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics());    }}

实现效果如文章开头

FlowLayout 的 onMeasure方法是什么时候被调用的

FlowLayout的onMeasure是在上面什么调用的,肯定是在其父布局做测量递归的时候调用的。比如FlowLayout的父布局是LinearLayout,咱们去LinearLayout中找实现
LinearLayout.onMeasure()

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {   if (mOrientation == VERTICAL) {        measureVertical(widthMeasureSpec, heightMeasureSpec);    } else {        measureHorizontal(widthMeasureSpec, heightMeasureSpec);    }}void measureVertical(widthMeasureSpec, heightMeasureSpec){//获取子view 的 LayoutParams final LayoutParams lp = (LayoutParams) child.getLayoutParams();......//开始测量measureChildBeforeLayout(child, i, widthMeasureSpec, 0,heightMeasureSpec, usedHeight);}void measureChildBeforeLayout(View child, int childIndex,int widthMeasureSpec, int totalWidth, int heightMeasureSpec,int totalHeight) {        measureChildWithMargins(child, widthMeasureSpec, totalWidth, heightMeasureSpec, totalHeight);    }protected void measureChildWithMargins(View child,        int parentWidthMeasureSpec, int widthUsed,        int parentHeightMeasureSpec, int heightUsed) {            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();//去除自己的使用,padding、margin剩下的再给子view    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin                    + widthUsed, lp.width);    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin                    + heightUsed, lp.height);//此处子view调用其测量函数,也就是FlowLayout的测量    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}

一些其他概念

MeasureSpec 是什么

自定义view常用的一个属性MeasureSpec,是View的内部类,封装了对子View的布局要求,由尺寸和模式组成。由于int类型由32位构成,所以他用高2位表示 Mode,低30位表示Size。

MeasureMode有三种 00 01 11

  • UNSPECIFIED:不对View大小做限制,系统使用

  • EXACTLY:确切的大小,如100dp

  • AT_MOST:大小不可超过某值,如:matchParent,最大不能超过父布局

LayoutParams 与 MeasureSpec 的关系

我们在xml写的键值对是不能直接转化为具体的dp的,根据父布局给的尺寸与模式计算出自己的MeasureSpec,通过不断的递归测量,得到最后的测量值。LayoutParams.width获取的就是xml里写的或许是match_parent,或许是wrap_content,这些是不能直接用的,根据父布局给出的参考值再通过测量子布局的尺寸最后才能得到一个具体的测量值

onLayout为什么不用 int right = view.getWidth() 而用 getMeasuredWidth

这要对整个流程有完整的理解才能回答,getWidth 是在 onLayout 调用后才有的值,getMeasuredWidth在测量后有值

看完了这篇文章,相信你对“Android中流式布局FlowLayout怎么用”有了一定的了解,如果想了解更多相关知识,欢迎关注编程网行业资讯频道,感谢各位的阅读!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android中流式布局FlowLayout怎么用

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android中流式布局FlowLayout怎么用

小编给大家分享一下Android中流式布局FlowLayout怎么用,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!引子下图是实现效果自定义View的流程想想自定义view我们都要做哪些事情布局,我们要确定view的尺寸以及
2023-06-25

Java GUI流式布局管理器FlowLayout怎么用

本文小编为大家详细介绍“Java GUI流式布局管理器FlowLayout怎么用”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java GUI流式布局管理器FlowLayout怎么用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一
2023-06-30

Android 实现FlowLayout流式布局(热门标签)

先上效果图:接着看代码实现:public class FlowLayout extends ViewGroup {protected DataSetObserver mDataSetObserver;private FlowBaseAdap
2022-06-06

Android自定义ViewGroup之实现FlowLayout流式布局

整理总结自鸿洋的博客:http://blog.csdn.net/lmj623565791/article/details/38352503/ 一、FlowLayout介绍 所谓FlowLayout,就是控件根据ViewGroup的宽,自
2022-06-06

Android 自定义流式布局FlowLayout 自己造的轮子真香!

效果图二话不说,先上效果图原创文章 25获赞 45访问量 10万+关注私信展开阅读全文作者:A类函数
2022-06-06

android应用中怎么利用onLayout()实现一个流式布局

这期内容当中小编将会给大家带来有关android应用中怎么利用onLayout()实现一个流式布局,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。在onLayout方法中有四个参数,我画了一个简单的图来分清
2023-05-31

Flexbox+ReclyclerView怎么实现流式布局

本篇内容主要讲解“Flexbox+ReclyclerView怎么实现流式布局”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Flexbox+ReclyclerView怎么实现流式布局”吧!效果:m
2023-06-25

小程序中怎么实现瀑布流布局

这篇文章将为大家详细讲解有关小程序中怎么实现瀑布流布局,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。widthFix:宽度不变,高度自动变化,保持原图宽高比不变。配合column-count
2023-06-19

Android Layout布局属性怎么用

Android的布局属性可以通过在XML布局文件中使用属性来设置。这些属性用于调整布局元素的位置、大小、外观等。以下是一些常用的Android布局属性及其用法:android:layout_width和android:layout_hei
2023-10-23

怎么用JS实现网页瀑布流布局

这篇文章主要介绍怎么用JS实现网页瀑布流布局,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!JS是什么JS是JavaScript的简称,它是一种直译式的脚本语言,其解释器被称为JavaScript引擎,是浏览器的一部分
2023-06-14

怎么在Flutter中嵌套Android布局

小编给大家分享一下怎么在Flutter中嵌套Android布局,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!效果本文具体demo效果如下开发1.首先创建flutter项目,在项目中定义好flutter需要展示布局: @o
2023-06-15

android中预定义的布局方式是什么

Android中预定义的布局方式有以下几种:1. 线性布局(LinearLayout):按照水平或垂直方向排列子视图。2. 相对布局(RelativeLayout):子视图根据相对位置进行布局,可以根据父视图或其他子视图的位置进行定位。3.
2023-08-18

怎么在css3中利用column实现卡片瀑布流布局

这篇文章将为大家详细讲解有关怎么在css3中利用column实现卡片瀑布流布局,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。相关属性column-count:想实现的列数,我这里只需要2列c
2023-06-08

Android怎么调用外部xml布局

要调用外部的xml布局,可以使用LayoutInflater来动态加载布局文件。具体步骤如下:在你想要调用外部xml布局的Activity或Fragment中,创建一个LayoutInflater对象:LayoutInflater inf
2023-10-24

android预加载布局怎么使用

在Android中,可以使用以下方法来预加载布局:1. 使用`LayoutInflater`类的`inflate()`方法手动加载布局文件:```javaLayoutInflater inflater = LayoutInflater.fr
2023-10-09

使用CSS3怎么实现一个瀑布流布局

使用CSS3怎么实现一个瀑布流布局?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。掌握点:1、column-count 把div中的文本分为多少列2、column-width 规
2023-06-08

Android中怎么利用组合控件复用布局

本篇文章为大家展示了Android中怎么利用组合控件复用布局,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。首先,我们需要写出布局文件layout_custom_titlebar.xml。
2023-05-30

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录