上期回顾

Android高级UI系列教程(一)_我想月薪过万的博客-CSDN博客https://blog.csdn.net/qq_41885673/article/details/121870917

Android两种坐标系

FlowLayout布局效果展示

FlowLayout布局代码解析

根据我们上一节的测量算法分析可得:先测量子View 再测量并保存自己

  • 测量孩子
        int paddingLeft = getPaddingLeft();int paddingRight = getPaddingRight();int paddingTop = getPaddingTop();int paddingBottom = getPaddingBottom();//先度量孩子//第一步:先获取孩子总个数int childCount = getChildCount();//第二步:逐个测量for (int i = 0; i < childCount; i++) {//获取到当前的 子ViewView childView = getChildAt(i);//如果孩子不可见 则取消测量if (childView.getVisibility() != GONE) {LayoutParams childLP = childView.getLayoutParams();//将 LayoutParams 转变为 measureSpecint childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, childLP.width);int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, childLP.height);//获取当前的 子View 的宽高childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);//获取子View的度量宽高int childMeasuredWidth = childView.getMeasuredWidth();int childMeasuredHeight = childView.getMeasuredHeight();}}
  • 计算并设置自己的宽高
    //度量@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//这个不写 会导致 UI 混乱 因为父布局可能多次调用子 view.measureclearMeasureParams();int paddingLeft = getPaddingLeft();int paddingRight = getPaddingRight();int paddingTop = getPaddingTop();int paddingBottom = getPaddingBottom();int selfWidth = MeasureSpec.getSize(widthMeasureSpec); //ViewGroup解析的父亲给我的参考的宽度int selfHeight = MeasureSpec.getSize(heightMeasureSpec); //ViewGroup解析的父亲给我的参考高度List<View> lineViews = new ArrayList<>(); //保存一行中的所有的viewint lineWidthUsed = 0; //记录这行已经使用了多宽的sizeint lineHeight = 0; //一行的行高int parentNeededWidth = 0; //measure过程中 子View要求的父ViewGroup的宽int parentNeededHeight = 0; //measure过程中 子View要求的父ViewGroup的高//先度量孩子//第一步:先获取孩子总个数int childCount = getChildCount();//第二步:逐个测量for (int i = 0; i < childCount; i++) {//获取到当前的 子ViewView childView = getChildAt(i);if (childView.getVisibility() != GONE) {LayoutParams childLP = childView.getLayoutParams();//将 LayoutParams 转变为 measureSpecint childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, childLP.width);int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, childLP.height);//获取当前的 子View 的宽高childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);//获取子View的度量宽高int childMeasuredWidth = childView.getMeasuredWidth();int childMeasuredHeight = childView.getMeasuredHeight();//如果需要换行if (lineWidthUsed + childMeasuredWidth + mHorizontalSpacing > selfWidth) {//一旦换行,我们就可以判断当前行需要的宽和高了,所以此时要记录下来allLines.add(lineViews);lineHeights.add(lineHeight);parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;parentNeededWidth = Math.max(lineWidthUsed + mHorizontalSpacing, parentNeededWidth);lineViews = new ArrayList<>();lineWidthUsed = 0;lineHeight = 0;}//View 是分行Layout的 所以要记录每一行有哪些View,这样可以方便layout布局lineViews.add(childView);//每行都会有自己的宽和高lineWidthUsed = lineWidthUsed + childMeasuredWidth + mHorizontalSpacing;lineHeight = Math.max(lineHeight, childMeasuredHeight);//处理最后一行数据if (i == childCount - 1) {allLines.add(lineViews);lineHeights.add(lineHeight);parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;parentNeededWidth = Math.max(lineWidthUsed + mHorizontalSpacing, parentNeededWidth);}}}//再度量自己,并保存int widthMode = MeasureSpec.getMode(widthMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth : parentNeededWidth;int realHeight = (heightMode == MeasureSpec.EXACTLY) ? selfHeight : parentNeededHeight;//再度量自己,保存setMeasuredDimension(realWidth, realHeight);}
  • 布局代码编写
    //布局@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {int lineCount = allLines.size();int curL = getPaddingLeft();int curT = getPaddingTop();for (int i = 0; i < lineCount; 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 top = curT;int left = curL;//不能使用 view.getWidth() 因为这个方法是 执行onLayout之后才会有值int right = left + view.getMeasuredWidth();int bottom = top + view.getMeasuredHeight();view.layout(left, top, right, bottom);curL = right + mHorizontalSpacing;}curT += lineHeight + mVerticalSpacing;curL = getPaddingLeft();}}
  • 完整代码展示
package com.wust.testalipay;import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;import androidx.annotation.Nullable;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/*** ClassName: FlowLayout <br/>* Description: <br/>* date: 2021/12/14 16:16<br/>** @author yiqi<br />* @QQ 1820762465* @微信 yiqiideallife* @技术交流QQ群 928023749*/
public class FlowLayout extends ViewGroup {private int mHorizontalSpacing = 0;private int mVerticalSpacing = 0;private List<List<View>> allLines = new ArrayList<>(); // 记录所有的行,一行一行的存储,用于layoutList<Integer> lineHeights = new ArrayList<>(); //记录每一行的行高,用于layout// new 一个对象的时候调用public FlowLayout(Context context) {this(context, null);}// 解析 xml 布局得时候调用public FlowLayout(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}// 主题stylepublic FlowLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}// 我们在编写自定义布局得时候 一般重写这三个构造方法即可private void clearMeasureParams() {//这种写法会出现内存抖动//allLines = new ArrayList<>();//lineHeights = new ArrayList<>();allLines.clear();lineHeights.clear();}//度量@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//这个不写 会导致 UI 混乱 因为父布局可能多次调用子 view.measureclearMeasureParams();int paddingLeft = getPaddingLeft();int paddingRight = getPaddingRight();int paddingTop = getPaddingTop();int paddingBottom = getPaddingBottom();int selfWidth = MeasureSpec.getSize(widthMeasureSpec); //ViewGroup解析的父亲给我的参考的宽度int selfHeight = MeasureSpec.getSize(heightMeasureSpec); //ViewGroup解析的父亲给我的参考高度List<View> lineViews = new ArrayList<>(); //保存一行中的所有的viewint lineWidthUsed = 0; //记录这行已经使用了多宽的sizeint lineHeight = 0; //一行的行高int parentNeededWidth = 0; //measure过程中 子View要求的父ViewGroup的宽int parentNeededHeight = 0; //measure过程中 子View要求的父ViewGroup的高//先度量孩子//第一步:先获取孩子总个数int childCount = getChildCount();//第二步:逐个测量for (int i = 0; i < childCount; i++) {//获取到当前的 子ViewView childView = getChildAt(i);if (childView.getVisibility() != GONE) {LayoutParams childLP = childView.getLayoutParams();//将 LayoutParams 转变为 measureSpecint childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, childLP.width);int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, childLP.height);//获取当前的 子View 的宽高childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);//获取子View的度量宽高int childMeasuredWidth = childView.getMeasuredWidth();int childMeasuredHeight = childView.getMeasuredHeight();//如果需要换行if (lineWidthUsed + childMeasuredWidth + mHorizontalSpacing > selfWidth) {//一旦换行,我们就可以判断当前行需要的宽和高了,所以此时要记录下来allLines.add(lineViews);lineHeights.add(lineHeight);parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;parentNeededWidth = Math.max(lineWidthUsed + mHorizontalSpacing, parentNeededWidth);lineViews = new ArrayList<>();lineWidthUsed = 0;lineHeight = 0;}//View 是分行Layout的 所以要记录每一行有哪些View,这样可以方便layout布局lineViews.add(childView);//每行都会有自己的宽和高lineWidthUsed = lineWidthUsed + childMeasuredWidth + mHorizontalSpacing;lineHeight = Math.max(lineHeight, childMeasuredHeight);//处理最后一行数据if (i == childCount - 1) {allLines.add(lineViews);lineHeights.add(lineHeight);parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;parentNeededWidth = Math.max(lineWidthUsed + mHorizontalSpacing, parentNeededWidth);}}}//再度量自己,并保存int widthMode = MeasureSpec.getMode(widthMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth : parentNeededWidth;int realHeight = (heightMode == MeasureSpec.EXACTLY) ? selfHeight : parentNeededHeight;//再度量自己,保存setMeasuredDimension(realWidth, realHeight);}//布局@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {int lineCount = allLines.size();int curL = getPaddingLeft();int curT = getPaddingTop();for (int i = 0; i < lineCount; 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 top = curT;int left = curL;//不能使用 view.getWidth() 因为这个方法是 执行onLayout之后才会有值int right = left + view.getMeasuredWidth();int bottom = top + view.getMeasuredHeight();view.layout(left, top, right, bottom);curL = right + mHorizontalSpacing;}curT += lineHeight + mVerticalSpacing;curL = getPaddingLeft();}}
}

易错点提示

1、onMeasure()可能会执行多次,所以得在 onMeasure() 方法中 初始化清空  allLines 和 lineHeights ,要不然你会发现布局会向下偏移。同时,初始化清空最好使用 clear() 方法,如果使用 new ArrayList<>() 就会导致内存抖动。

2、最后一行的处理不要忘记,处理逻辑如下:

                //处理最后一行数据if (i == childCount - 1) {allLines.add(lineViews);lineHeights.add(lineHeight);parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;parentNeededWidth = Math.max(lineWidthUsed + mHorizontalSpacing, parentNeededWidth);}

Android高级UI系列教程(二)相关推荐

  1. android 属性动画高级,Android高级UI开发(二十五)属性动画实战案例之流浪大师与乔帮主...

    在上一篇文章里我们介绍了属性动画的基础知识,今天我们综合运用属性动画的知识来完成一个动画案例.首先,看一下这个动画效果: 1.  分析这个动画案例 第一个动画(浏览大师的动画)是:当点击顶部" ...

  2. Android 高级UI解密 (三) :Canvas裁剪 与 二维、三维Camera几何变换(图层Layer原理)

    Android的绘图机制是核心内容之一,无论是什么样的功能最终都是以图像的形式呈现给用户.因此掌握Android的绘图技巧,有助于Android理解层次的提高,在面对产品经理提出的idea时也更有底气 ...

  3. 黄聪:Microsoft Enterprise Library 5.0 系列教程(二) Cryptography Application Block (高级)

    原文:黄聪:Microsoft Enterprise Library 5.0 系列教程(二) Cryptography Application Block (高级) 本章介绍的是企业库加密应用程序模块 ...

  4. Android 高级UI解密 (二) :Paint滤镜 与 颜色过滤(矩阵变换)

    若是曾经查看过系统UI的源码, 会发现其中使用了一些渲染效果,例如将图片加上黑白.怀旧的效果,生活中常用的逆天美颜相机,其中的原理就是使用了滤镜效果.颜色通道过滤.若还要深究其原理组成,便涉及到了高等 ...

  5. Android 高级UI解密 (四) :花式玩转贝塞尔曲线(波浪、轨迹变换动画)

    讲解此UI系列必然少不了一个奇妙数学曲线-–贝塞尔曲线,它目前运用于App的范围是在太广了,最初的QQ气泡拖拽,到个人界面的波浪效果.Loading波浪效果,甚至于轨迹变化的动画都可以依赖贝塞尔曲线完 ...

  6. 你连《Android高级UI与FrameWork源码》都搞不懂学什么Android?还敢面试阿里P7!

    Android高级UI与FrameWork源码 重要性? 这块知识是现今使用者最多的,我们称之为Android2013~2016年的技术,但是,即使是这样的技术,Android开发者也往往因为网上Co ...

  7. Lance老师UI系列教程第三课-QQ登录注册界面的实现(android-2012最新版)

    分类: android UI教程2012-08-06 22:37 3731人阅读 评论(6) 收藏 举报 uiandroidqqlayoutbutton UI系列教程第三课:腾讯登录注册界面的实现 今 ...

  8. QT5系列教程二---基于qcustomplot的QT5 GUI串口收发绘图软件实现

    QT5系列教程二---基于qcustomplot的QT5 GUI串口收发绘图软件实现 结构 UI部分 代码部分 step1:实现串口数据接受 串口接受数据格式 在`.pro`文件中添加`serialp ...

  9. 以太坊构建DApps系列教程(二):构建TNS代币

    在本系列关于使用以太坊构建DApps教程的第1部分中,我们引导大家做了两个版本的本地区块链进行开发:一个Ganache版本和一个完整的私有PoA版本. 在这一部分中,我们将深入研究并构建我们的TNS代 ...

最新文章

  1. 解决fstream不能打开带有中文路径文件的问题
  2. Ansible-playbook yum安装nginx1.20.1
  3. socket 套接字的基本概念
  4. 配置ip yum 单用户 救援 运行级别
  5. 【渝粤教育】电大中专会计电算化作业 题库
  6. 基于 Docker 打造前端持续集成开发环境
  7. 由openSession、getCurrentSession和HibernateDaoSupport浅谈Spring对事物的支持
  8. 信息学奥赛一本通 1021:打印字符 | OpenJudge NOI 1.2 08
  9. Palindrome DP
  10. redis 参数配置总结
  11. 报表自动化就是连接数据库?错,它打开了数据仓库的大门
  12. 教之初在线计算机考题,教之初计算机考试系统
  13. 如何使用Bartender标签打印软件批量打印构件二维码标签?
  14. 浅谈 css的zoom属性
  15. android 安全加固总结报告,Android应用本地代码的安全加固及安全性评估
  16. (ROS)Moveit编程示例
  17. ivew 的offset用法
  18. 个人java后端详细学习路线(0→1)
  19. b站网页版改html,网页版b站怎么设置弹幕?网页bilibili怎么设置停止播放和调倍速?...
  20. android外卖实验报告,基于Android平台的外卖app设计与实现.doc

热门文章

  1. 【鸿蒙学院】鸿蒙App开发直播学员提问与回答
  2. 认识越南语的发音体系
  3. 从实战学习微信小程序-电商星星评分功能(五)
  4. 用pdfminer把PDF文件转化为文本文件
  5. 关于NC6.X企业报表取不了数的问题及其解决方法。
  6. 为什么互联网需要采用去中心化结构?
  7. TAM: TEMPORAL ADAPTIVE MODULE FOR VIDEO RECOGNITION ∗
  8. 【PWM】从stm32到pwm到OLED屏幕调光到晚上不要玩手机
  9. Windows 10创建用户
  10. python实战应用讲解-【numpy科学计算】scikits-statsmodels模块(附python示例代码)