【Android】图解View的工作流程原理

news2025/1/17 1:49:01

文章目录

  • 入口
    • DecorView如何加载到Window中
    • MeasureSpec
  • Measure
    • View的测量
    • ViewGroup的测量
  • Layout
    • View的`layout()`
  • Draw
    • 1、绘制背景
    • 3、绘制View内容
    • 4、绘制子View
    • 6、绘制装饰

在这里插入图片描述

入口

DecorView如何加载到Window中

在这里插入图片描述


MeasureSpec

该类是View的内部类,封装View的规格尺寸。
在这里插入图片描述
他就是一个32位的int值,高2为代表 specMode(测量模式),低30位代表specSize(测量大小)
specMode:UNSPECIFIED AT_MOST EXACTLY

对于每个View都有对应的MeasureSpec,在测量流程中,通过makeMeasureSpec() 来保存宽和高,通过
getMode()getSize() 得到模式和宽高
MeasureSpec自身的布局参数和父容器的测量规格共同影响

那么顶层View的DecorView没有父容器,怎么得到测量规格呢?
通过getRootMeasureSpec()

/**
 * 根据窗口大小和根视图尺寸,获取根视图的MeasureSpec
 *
 * @param windowSize    窗口大小
 * @param rootDimension 根视图尺寸
 * @return 根视图的MeasureSpec
 */
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {
        case ViewGroup.LayoutParams.MATCH_PARENT:
            // 如果根视图尺寸为MATCH_PARENT(即填充父窗口),窗口无法调整大小。
            // 强制根视图尺寸为窗口大小,使用MeasureSpec.EXACTLY模式。
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // 如果根视图尺寸为WRAP_CONTENT(即自适应内容),窗口可以调整大小。
            // 设置根视图最大尺寸为窗口大小,使用MeasureSpec.AT_MOST模式。
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // 如果根视图尺寸为具体的数值,窗口希望有确定的大小。
            // 强制根视图尺寸为指定的大小,使用MeasureSpec.EXACTLY模式。
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
    }
    return measureSpec;
}


Measure

在某些极端情况下,系统可能需要多次measure才能确定最终的测量宽/高

在这里插入图片描述


View的测量

在这里插入图片描述

ViewGroup的测量

ViewGroup没有onMeasure(),用measureChildren()去递归调用子元素的测量方法measureChild()
在这里插入图片描述


Layout

View的layout()

在这里插入图片描述

在这里插入图片描述


Draw

在这里插入图片描述

官方文档阐述为:

  1. 如果需要,则绘制背景
  2. 保存当前canvas层(可以不执行)
  3. 绘制View的内容
  4. 绘制子View
  5. 如果需要,则绘制View的褪色边缘,类似于阴影效果(可以不执行)
  6. 绘制装饰,例如滚动条
  7. 如果有必要,绘制默认的焦点高亮显示(可以不执行)

1、绘制背景

调用View的drawBackground()来执行

/**
 * Draws the background onto the specified canvas.
 *
 * @param canvas Canvas on which to draw the background
 */
@UnsupportedAppUsage
private void drawBackground(Canvas canvas) {
    final Drawable background = mBackground; // 获取背景Drawable对象
    if (background == null) { // 如果背景Drawable为null
        return; // 直接返回,不进行绘制
    }
    setBackgroundBounds(); // 设置背景Drawable的边界矩形
    ...
    final int scrollX = mScrollX; // 获取View的当前水平滚动偏移量
    final int scrollY = mScrollY; // 获取View的当前垂直滚动偏移量
    if ((scrollX | scrollY) == 0) { // 如果水平和垂直滚动偏移量都为0
        background.draw(canvas); // 直接绘制背景Drawable在画布上
    } else { // 如果有滚动偏移量
        canvas.translate(scrollX, scrollY); // 将画布平移至滚动偏移量的位置
        background.draw(canvas); // 绘制背景Drawable在平移后的画布上
        canvas.translate(-scrollX, -scrollY); // 恢复画布的原始位置
    }
}

3、绘制View内容

onDraw() 需要去自己进行重写实现

4、绘制子View

dispathchDraw() 需要去自己进行重写实现

ViewGroup进行了重写:

@Override
protected void dispatchDraw(Canvas canvas) {
    ...
    for (int i = 0; i < childrenCount; i++) { // 遍历子View
        while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) { // 如果当前索引为临时索引
        
            final View transientChild = mTransientViews.get(transientIndex);
            
            if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || 
                transientChild.getAnimation() != null) { // 如果临时子View可见或者临时子View有动画
                more |= drawChild(canvas, transientChild, drawingTime); // 在画布上绘制临时子View,并返回是否还有更多绘制
            }
            
            transientIndex++; // 增加临时索引
            
            if (transientIndex >= transientCount) { // 如果临时索引超过了临时子View的数量
                transientIndex = -1; // 重置临时索引
            }
        }

        final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); // 获取并验证预排序的子View索引
        final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); // 根据索引找到对应View
        
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { // 如果子View可见或者有动画
            more |= drawChild(canvas, child, drawingTime); // 在画布上绘制子View,并返回是否还有更多绘制
        }
    }
    ...
}

最后调用了drawChild()方法,而该方法其实返回的是child的draw()方法,即View的draw():

/**
 * This method is called by ViewGroup.drawChild() to have each child view draw itself.
 *
 * This is where the View specializes rendering behavior based on layer type,
 * and hardware acceleration.
 */
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        ...
    if (!drawingWithDrawingCache) { // 1. 没有使用绘制缓存
        if (drawingWithRenderNode) { // 使用RenderNode进行绘制
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            ((RecordingCanvas) canvas).drawRenderNode(renderNode);
        } else {
            // 对于没有背景的布局,快速路径
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { // 子View标记为不需要被绘制
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                dispatchDraw(canvas); // 调用dispatchDraw()方法进行绘制
            } else {
                draw(canvas); // 调用draw()方法进行绘制
            }
        }
    } else if (cache != null) { // 2. 存在绘制缓存
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
        if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
            // 没有图层画笔,使用临时画笔绘制位图
            ...
        } else {
            // 使用图层画笔绘制位图,合并两个透明度,并恢复
            ...
        }
    }
        ...
    return more; // 返回是否还有更多需要绘制的内容
}

在这里插入图片描述

6、绘制装饰

View的DrawForeground()

/**
 * 绘制视图的前景内容。
 *
 * <p>前景内容可以包括滚动条、前景绘制或其他视图特定的装饰。前景绘制在主视图内容之上。</p>
 *
 * @param canvas 用于绘制的画布
 */
public void onDrawForeground(Canvas canvas) {
    onDrawScrollIndicators(canvas); // 调用绘制滚动指示器的方法
    onDrawScrollBars(canvas); // 调用绘制滚动条的方法

    final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
    if (foreground != null) {
        // 如果存在前景就绘制
        ...
        foreground.draw(canvas);
    }
}

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

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

相关文章

下一个电商风口会是什么呢?揭秘电商新蓝海,我来告诉你答案!

大家好&#xff0c;我是电商花花。 直播电商还能持续多久呢&#xff1f; 这个电商项目你肯定没听过&#xff0c;叫视频号小店&#xff0c;只有电商老炮才知道&#xff0c;这是2024年乃至今后3年的风口。 我做电商这么多年&#xff0c;在视频号小店刚出台就开始研究视频号&am…

IDEA删除块注释(文档注释)/**

1. 问题缘由 在使用java写LeetCode的时候&#xff0c;有些题会有一些封装好的类&#xff0c;在本地编写代码的时候&#xff0c;如果没有定义好这些类&#xff0c;就会爆红&#xff0c;看着很难受&#xff0c;此时可以把官方定义好的类拿过来&#xff0c;但是这些类是有块注释的…

电商-广告投放效果分析(KMeans聚类、数据分析-pyhton数据分析

电商-广告投放效果分析&#xff08;KMeans聚类、数据分析&#xff09; 文章目录 电商-广告投放效果分析&#xff08;KMeans聚类、数据分析&#xff09;项目介绍数据数据维度概况数据13个维度介绍 导入库&#xff0c;加载数据数据审查相关性分析数据处理建立模型聚类结果特征分析…

基于单片机电容介电常数测量显示系统设计

**单片机设计介绍&#xff0c;基于单片机电容介电常数测量显示系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机电容介电常数测量显示系统的设计&#xff0c;是一个集成了电子技术、单片机编程、电容测量以及显…

uni-app如何实现高性能

这篇文章主要讲解uni-app如何实现高性能的问题&#xff1f; 什么是uni-app&#xff1f; 简单说一下什么是uni-app&#xff0c;uni-app是继承自vue.js&#xff0c;对vue做了轻度定制&#xff0c;并且实现了完整的组件化开发&#xff0c;并且支持多端发布的一种架构&#xff0c…

【Python】 小顶堆:困难 Leetcode 23. 合并 K 个升序链表 -- Python中heapq对于自定义数据类型的比较

描述 给你一个链表数组&#xff0c;每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中&#xff0c;返回合并后的链表。 示例 1&#xff1a; 输入&#xff1a;lists [[1,4,5],[1,3,4],[2,6]] 输出&#xff1a;[1,1,2,3,4,4,5,6] 代码 代码1 由于可能存在相同…

HTML——5.表单、框架、颜色

一、表单 HTML 表单用于在网页中收集用户输入的数据&#xff0c;例如登录信息、搜索查询等。HTML 提供了一系列的表单元素&#xff0c;允许用户输入文本、选择选项、提交数据等。 <!DOCTYPE html> <html lang"en"> <head> <meta charset&q…

我的系统设计方法论

上一篇文章简述了我的日常需求分析方法&#xff0c;需求分析完成之后就进入系统设计阶段&#xff0c;真正的工时估算和开发测试计划&#xff0c;都是在做了一定的系统设计之后才能做好的。 不想做工时估算和做到哪里是哪里&#xff0c;是一个态度问题。工时估算困难&#xff0c…

数据结构篇:深度剖析LSM及与B+树优劣势分析

本文旨在探讨LSM的特性及其在实际应用场景中的作用&#xff0c;同时对其与B树进行比较&#xff0c;以帮助更好地理解和运用这两种数据结构。 什么是LSM&#xff08;Log-structured Merge-tree&#xff09; 全称 Log-Structured Merge-Tree 日志结构合并树&#xff0c;但不是树…

5G随身wifi真实测评!飞猫5g随身wifi怎么样?飞猫5GVS格行5G随身wifi哪款网速快?5G随身wifi推荐品牌第一名!

飞猫5G随身wifi&#xff1a; 产品外观&#xff1a;黑色大气外观&#xff0c;净重175g&#xff0c;屏幕有信号和指示灯。 产品性能&#xff1a;采用展锐芯片。6根LDS天线&#xff0c;网速100-200mbps&#xff0c;网络延迟10-20ms&#xff0c;2.4G/5G双频可选&#xff0c;超稳定…

【gurobi】python调用gurobi创建范围约束

1.python调用gurobi创建范围约束 要在Python中使用Gurobi创建范围约束&#xff0c;您可以使用 addConstr() 方法&#xff0c;该方法允许您指定约束条件的下限和上限。下面是一个示例&#xff0c;演示了如何创建一个范围约束&#xff1a; 假设您有一个变量 x&#xff0c;您想要…

软考114-上午题-【计算机网络】-路由

一、路由 二、真题 真题1&#xff1a; 真题2&#xff1a; 真题3&#xff1a; 真题4&#xff1a; 真题5&#xff1a; 路由协议实际上是一种在路由器之间交换路由信息的协议。 路由协议让路由器了解整个网络的拓扑结构&#xff0c;包括哪些网络是直接相连的&#xff0c;哪些网络…

数据库重点知识(个人整理笔记)

目录 1. 索引是什么&#xff1f; 1.1. 索引的基本原理 2. 索引有哪些优缺点&#xff1f; 3. MySQL有哪几种索引类型&#xff1f; 4. mysql聚簇和非聚簇索引的区别 5. 非聚簇索引一定会回表查询吗&#xff1f; 6. 讲一讲前缀索引&#xff1f; 7. 为什么索引结构默认使用B…

练习题(2024/4/6)

1最接近的三数之和 给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数&#xff0c;使它们的和与 target 最接近。 返回这三个数的和。 假定每组输入只存在恰好一个解。 示例 1&#xff1a; 输入&#xff1a;nums [-1,2,1,-4], target …

AI Agents产品图谱+网站合集

这个网站收集了市面受欢迎的项目&#xff0c;包括开源项目和闭源项目以及公司 地址&#xff1a;通过浏览列表中的AI代理项目和公司&#xff0c;社区里的创业者可以了解当前市场上的主要玩家和他们的产品特点&#xff0c;进行市场趋势分析和竞争分析。

了解这些技术:Flutter应用顺利登陆iOS平台的步骤与方法

引言 &#x1f680; Flutter作为一种跨平台的移动应用程序开发框架&#xff0c;为开发者提供了便利&#xff0c;使他们能够通过单一的代码库构建出高性能、高保真度的应用程序&#xff0c;同时支持Android和iOS两个平台。然而&#xff0c;完成Flutter应用程序的开发只是第一步…

LC 226.翻转二叉树

226. 翻转二叉树 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a; root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1] 示例 2&#xff1a; 输入&#xff1a; root [2,1,3] 输出&#xff1a…

VMware-16.0配置虚拟机网络模式

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、为什么要配置网络&#xff1f;二、配置步骤1.检查VMware服务2.进入配置页面3.添加网络模式1.Bridge2.NAT3.Host-only 4.DHCP租约5.静态IP 三、使用总结 前言…

ARM、X86、RISC-V三分天下

引入&#xff1a; 简单的介绍一下X86、ARM、RISC-V三种cpu架构的区别和应用场景。 目录 简单概念讲解 1. X86架构 2. ARM架构 3. RISC-V架构 应用场景 X86、ARM和RISC-V是三种不同的CPU架构&#xff0c;它们在设计理念、指令集和应用场景上有一些区别。 简单概念讲解 1. X…

计算机语言 之【C++】入门级知识讲解(命名空间,C++输入输出,缺省参数,函数重载,引用,内敛函数,auto关键字,for循环,指针空值nullptr)

三点睡六点起&#xff0c;阎王夸我好身体 不到三点我不睡&#xff0c;太平间里抢C位 一、命名空间 1.命名空间的作用 2.命名空间定义 3.命名空间使用 二、C的输入输出 1.输入输出说明介绍 2.std命名空间的使用惯例 三、缺省参数 1.缺省参数概念 2.缺省参数分类 四、…