Android Jetpack Compose基础之组件的帧渲染

Android Jetpack Compose基础之组件的帧渲染

  • 组合
  • 布局
      • LayoutModifier
        • 示例
      • LayoutCompsable
        • 示例
  • 绘制
      • Canvas
      • DrawModifier
        • DrawModifier-drawWithContent
          • 示例
        • DrawModifier-drawBehind
          • 源码
          • 示例
        • DrawModifier-drawWithCache
          • 源码
          • 示例
  • 拓展Modifier.graphicsLayer

Android View 系统,它有 3 个主要阶段:测量、布局和绘制,而Compose和它很相似,它的渲染流程分为组合、布局、绘制这三个阶段。
在这里插入图片描述

组合:执行Composable函数体,生成LayoutNode视图树

布局:该阶段包含两个步骤:测量和放置。对于布局树中的每个节点LayoutNode,布局元素都会根据 2D 坐标来测量宽高尺寸并放置自己及其所有子元素。

绘制:将所有的LayoutNode实际绘制到屏幕上

组合

组合阶段主要是生成并维护LayoutNode视图树,当我门在Activity中使用setContent时,会开始首次组合,此时会执行代码块中设计的所有Composable函数体,生成与之对应的LayoutNode视图树(具体过程可见《Android Jetpack Compose基础之Compose视图结构》)

在Compose中如果某个Compsable依赖了某个可变状态,该状态发生更新时,会触发当前Composable重新进行组合阶段,即重组,具体重组内容详见《Android Jetpack Compose基础之生命周期-重组》

布局

在Compose中,每个LayoutNode都会根据来自父LayoutNode的布局约束来进行自我测量(类似传统View中的MeasureSpec)。布局约束中包含了父LayoutNode允许子LayoutNode的最大宽高和最小宽高,当父LayoutNode希望子LayoutNode测量的宽高为具体值时,约束中宽高的最大值和最小值是一致的
注意:LayoutNode不允许被多次测量。
步骤:
测量子节点:节点会测量其子节点(如果存在)
确定自己的大小:节点根据这些测量结果来决定自己的大小。
放置子节点:每个子节点根据节点自身的位置进行放置。

LayoutModifier

作用是用来修饰LayoutNode的宽高与原有内容在新宽高下摆放的位置,其具体调用调用方法如下

fun Modifier.layout(
    measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
) = this then LayoutElement(measure)

private data class LayoutElement(
    val measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
) : ModifierNodeElement<LayoutModifierImpl>() {
    override fun create() = LayoutModifierImpl(measure)

    override fun update(node: LayoutModifierImpl) {
        node.measureBlock = measure
    }

    override fun InspectorInfo.inspectableProperties() {
        name = "layout"
        properties["measure"] = measure
    }
    
interface Measurable : IntrinsicMeasurable {
    /**
     * Measures the layout with [constraints], returning a [Placeable] layout that has its new
     * size. A [Measurable] can only be measured once inside a layout pass.
     */
    fun measure(constraints: Constraints): Placeable
}

Measurable:表示被修饰的LayoutNode的测量句柄,通过内部的measure方法完成LayoutNode的测量。
constraints:表示来自父LayoutNode的布局约束

示例

使用LayoutModifier实现Text顶部到文本基线的高度
请添加图片描述


 Column(modifier = Modifier
                   .fillMaxSize()
                   .verticalScroll(scrollState)
            ) {
                Text(
                    text = "Text Sample",
                    modifier = Modifier
                        .background(Color.Cyan)
                        .firstBaselineToTop(40.dp)
                )
       		}
       		
fun Modifier.firstBaselineToTop(top: Dp) = Modifier.layout { measurable, constraints ->
    //将父LayoutNode的布局约束,直接传入Measure中,直接提供给被修饰的LayoutNode进行测量,测量的结果包装在Placeable示例中进行返回
    val placeable = measurable.measure(constraints)
    //确认组件时存在内容基线的
    check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
    //获取基线高度
    val firstBaseline = placeable[FirstBaseline]
    //应摆放的顶部高度=所设置的顶部到基线的高度-实际组件内容顶部到基线的高度
    val placeableY = top.roundToPx() - firstBaseline
    //该组件占有的高度=应摆放的顶部高度+实际内容的高度
    val height = placeable.height + placeableY
    layout(placeable.width, height) {
        //指定原有应该绘制的内容在新的高宽下摆放的相对位置
        placeable.placeRelative(0, placeableY)
    }
}

LayoutCompsable

LayoutModifier可以类比于定制具体View,如果需要定制ViewGoup,就需要使用LayoutCompsable了,它的源码如下

@UiComposable
@Composable
inline fun Layout(
    content: @Composable @UiComposable () -> Unit,//我们声明的子组件信息
    modifier: Modifier = Modifier,//外部传入的修饰符
    measurePolicy: MeasurePolicy//表示测量策略,
) {
    val compositeKeyHash = currentCompositeKeyHash
    val localMap = currentComposer.currentCompositionLocalMap
    ReusableComposeNode<ComposeUiNode, Applier<Any>>(
        factory = ComposeUiNode.Constructor,
        update = {
            set(measurePolicy, SetMeasurePolicy)
            set(localMap, SetResolvedCompositionLocals)
            @OptIn(ExperimentalComposeUiApi::class)
            set(compositeKeyHash, SetCompositeKeyHash)
        },
        skippableUpdate = materializerOf(modifier),
        content = content
    )
}
示例

粗略仿照column,它本身的源码如下

@Composable
inline fun Column(
    modifier: Modifier = Modifier,
    verticalArrangement: Arrangement.Vertical = Arrangement.Top,
    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
    content: @Composable ColumnScope.() -> Unit
) {
    val measurePolicy = columnMeasurePolicy(verticalArrangement, horizontalAlignment)
    Layout(
        content = { ColumnScopeInstance.content() },
        measurePolicy = measurePolicy,
        modifier = modifier
    )
}

自定义CustomColumnLayout主要是为了直观看出测量高宽的过程,运行效果自行脑补,编码过程如下。

 CustomColumnLayout {
                    Text(text = "CustomColumnLayout", modifier = Modifier.background(Color.Cyan))
                    Text(text = "CustomColumnLayout2", modifier = Modifier.background(Color.Cyan))
                }
@Composable
fun CustomColumnLayout(
    modifier: Modifier = Modifier,
    // 此处可添加自定义的参数
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurable, constraints ->
        val placeables = measurable.map { measurable ->
            //测量每个子组件
            measurable.measure(constraints)
        }
        var yPosition = 0
        layout(constraints.maxWidth, 2000) {
            placeables.forEach { placeable ->
                placeable.placeRelative(x = 0, y = yPosition)
                yPosition += placeable.height
            }
        }
    }
}

绘制

绘制就是将所有的LayoutNode实际绘制到屏幕之上咯

Canvas

@Composable
fun Canvas(modifier: Modifier, onDraw: DrawScope.() -> Unit) =
    Spacer(modifier.drawBehind(onDraw))

@Composable
@NonRestartableComposable
fun Spacer(modifier: Modifier) {
    Layout(measurePolicy = SpacerMeasurePolicy, modifier = modifier)
}

DrawScope 作用域中,compose提供了基础的绘制API
请添加图片描述

drawLine:绘制线
drawRect:绘制矩形
drawImage:绘制图片
drawRoundRect:绘制圆觉矩形
drawCircle:绘制圆
drawOval:绘制椭圆
drawArc:绘制弧线
drawPath:绘制路径
drawPoints:绘制点

DrawModifier

DrawModifier-drawWithContent

可以允许在绘制时自定义绘制层级Z轴的层级请添加图片描述
,方法需要传入ContentDrawScope作用域lambda,而它ContentDrawScope是继承自DrawScope,最终通过drawContent()方法绘制组件本身的内容

fun Modifier.drawWithContent(
    onDraw: ContentDrawScope.() -> Unit
): Modifier = this then DrawWithContentElement(onDraw)

interface ContentDrawScope : DrawScope {
    /**
     * Causes child drawing operations to run during the `onPaint` lambda.
     */
    fun drawContent()
}

private data class DrawWithContentElement(
    val onDraw: ContentDrawScope.() -> Unit
) : ModifierNodeElement<DrawWithContentModifier>() {
    override fun create() = DrawWithContentModifier(onDraw)

    override fun update(node: DrawWithContentModifier) {
        node.onDraw = onDraw
    }

    override fun InspectorInfo.inspectableProperties() {
        name = "drawWithContent"
        properties["onDraw"] = onDraw
    }
}

示例

绘制如下效果
请添加图片描述

@Composable
![请添加图片描述](https://img-blog.csdnimg.cn/direct/9f19af3b2c77460099ad488e7c03dbd5.png)
fun BaseDrawWithContent() {
    var pointerOffset by remember {
        mutableStateOf(Offset(0f, 0f))
    }
    Column(modifier = Modifier
        .fillMaxWidth()
        .height(200.dp)
        .pointerInput(key1 = "dragging") {
            detectDragGestures { change, dragAmount ->
                pointerOffset += dragAmount
            }
        }
        .onSizeChanged {
            pointerOffset = Offset(it.width / 2f, it.height / 2f)
        }
        .drawWithContent {
            drawContent()
            drawRect(
                Brush.radialGradient(listOf(Color.Transparent, Color.Black), center = pointerOffset, radius = 100.dp.toPx())
            )
        }
    ) {
        Text(
            text = "drawWithContent", modifier = Modifier
                .fillMaxSize()
                .background(color = Color.Cyan)
        )
    }
}

DrawModifier-drawBehind

在绘制组件拓展的内容,在绘制组件本身,用作自定义组件背景

源码

定制的绘制逻辑onDraw最终被传入DrawBackgroundModifier的主构造函数中,在draw()方法中先绘制自定义的内容onDraw,然后在绘制组件内容drawContent

fun Modifier.drawBehind(
    onDraw: DrawScope.() -> Unit
) = this then DrawBehindElement(onDraw)

private data class DrawBehindElement(
    val onDraw: DrawScope.() -> Unit
) : ModifierNodeElement<DrawBackgroundModifier>() {
    override fun create() = DrawBackgroundModifier(onDraw)

    override fun update(node: DrawBackgroundModifier) {
        node.onDraw = onDraw
    }

    override fun InspectorInfo.inspectableProperties() {
        name = "drawBehind"
        properties["onDraw"] = onDraw
    }
}

internal class DrawBackgroundModifier(
    var onDraw: DrawScope.() -> Unit
) : Modifier.Node(), DrawModifierNode {

    override fun ContentDrawScope.draw() {
        onDraw()
        drawContent()
    }
}
示例

给文本绘制一个bg

@Composable
fun BaseDrawBehind() {
    Text(text = "DrawBehind", modifier = Modifier.drawBehind {
        drawRoundRect(Color.DarkGray, cornerRadius = CornerRadius(10.dp.toPx()))
    })
}
DrawModifier-drawWithCache

背景:在drawScop中绘制时,绘制一些有关对象时,如ImageBitmap,Paint、Path时,当组件发生重绘时,由于drawScop会反复执行,会使其中声明的对象发生频繁创建,
作用:drawWithCache会缓存在其中创建的对象。只要绘制区域的大小不变,或者读取的任何状态对象都未发生变化,对象就会被缓存。此修饰符有助于改进绘制调用的性能,因为它不必对绘制时创建的对象(例如:Brush, Shader, Path 等)进行重新分配。
注意:请仅在创建必须缓存的对象时才使用 Modifier.drawWithCache。如果在无需缓存对象时使用此修饰符,可能会导致不必要的 lambda 分配。

源码

方法需要传入CacheDrawScope作用域的lambda并返回DrawResult

fun Modifier.drawWithCache(
    onBuildDrawCache: CacheDrawScope.() -> DrawResult
) = this then DrawWithCacheElement(onBuildDrawCache)

private data class DrawWithCacheElement(
    val onBuildDrawCache: CacheDrawScope.() -> DrawResult
) : ModifierNodeElement<CacheDrawModifierNodeImpl>() {
    override fun create(): CacheDrawModifierNodeImpl {
        return CacheDrawModifierNodeImpl(CacheDrawScope(), onBuildDrawCache)
    }

    override fun update(node: CacheDrawModifierNodeImpl) {
        node.block = onBuildDrawCache
    }

    override fun InspectorInfo.inspectableProperties() {
        name = "drawWithCache"
        properties["onBuildDrawCache"] = onBuildDrawCache
    }
}

fun CacheDrawModifierNode(
    onBuildDrawCache: CacheDrawScope.() -> DrawResult
): CacheDrawModifierNode {
    return CacheDrawModifierNodeImpl(CacheDrawScope(), onBuildDrawCache)
}
示例
@Composable
fun BaseDrawWithCache() {
    Text(text = "drawWithCache", modifier = Modifier.drawWithCache {
        val brush = Brush.linearGradient(listOf(Color.Red, Color.Green))
        onDrawBehind {
            drawRoundRect(brush, cornerRadius = CornerRadius(10.dp.toPx()))
        }
    })
}

拓展Modifier.graphicsLayer

可实现对图层的缩放、平移、旋转、裁剪、透明度等功能
使用方式

@Composable
fun BaseGraphicsLayer() {
    var progressX by remember {
        mutableStateOf(0.1f)
    }
    Slider(value = progressX, onValueChange = { progressX = it }, valueRange = 0f..2f)
    var progressY by remember {
        mutableStateOf(0.1f)
    }
    Slider(value = progressY, onValueChange = { progressY = it }, valueRange = 0f..2f)
    Image(
        painter = painterResource(id = R.mipmap.btn_shara), contentDescription = "",
        modifier = Modifier.graphicsLayer(
            scaleX = progressX,
            scaleY = progressY
        )
    )
}

————note end————

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

6-191 拓扑排序

一项工程由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其他子任务后才能执行。例如,下图表示了一项工程若干子任务之间的先后关系。 编写函数输出所有子任务的拓扑序列。 函数接口定义: Status Push_SeqStack(SeqStack &s, ElemType x)//入栈,x入到…

Polar 2024春季个人挑战赛 Jay17 WP

Polar 2024春季个人挑战赛 Rank&#xff1a;7 【WEB】机器人 开题 起手敏感文件robots.txt 【WEB】PHP反序列化初试 最简单的php反序列化 POC&#xff1a; <?php class Easy{public $name;public function __wakeup(){echo $this->name;} } class Evil{public $evi…

2、Jenkins持续集成-gitlab安装和源码上传

文章目录 1、Gitlab代码托管服务器安装2、源代码上传托管 环境&资源准备 统一采用VMware中安装CentOS7&#xff0c;安装教程&#xff0c;统一设置静态IP资源包都存在于我的资源里面 资源版本&位置 名称机器IP软件代码托管服务器192.168.2.100Gitlab-12.4.2持续集成服…

idm下载器 idm网络加速器 Internet Download Manager(IDM)免激活不弹窗版

idm下载器官方版英文名为Internet Download Manager。是一款界面简单、体积小巧下载速度飞快的下载工具&#xff0c;支持断点续传、多个任务同时下载&#xff0c;同时还支持限速下载&#xff0c;在自义文件类型里还可以自由设置下载的文件类型。 一、IDM软件安装 Internet Dow…

Linux安装Nginx及配置TCP负载均衡

目录 1、安装编译工具及库文件2、下载解压Nginx压缩包3、Ngnix配置Tcp负载均衡4、配置Ngnix的文件5、Nginx启动 1、安装编译工具及库文件 yum -y install make zlib zlib-devel gcc-c libtool openssl openssl-devel pcre-devel2、下载解压Nginx压缩包 wget https://nginx.o…

Docker 安装 Tomcat

目录 1. 总体步骤 2.安装tomcat 2.1 搜索 2.2 拉取 2.3 查看是否拉取到镜像 2.4 运行镜像 2.5 访问tomcat首页 1. 总体步骤 搜索镜像拉取镜像查看镜像启动镜像停止容器移除容器 2.安装tomcat 2.1 搜索 2.2 拉取 docker pull tomcat 2.3 查看是否拉取到镜像 docker …

连接医患的桥梁:深入了解互联网医院APP的开发与优化

当下&#xff0c;互联网技术的不断发展&#xff0c;越来越多的医院和医疗机构开始关注并投入到互联网医院APP的开发与优化中。接下来&#xff0c;小编将与大家共同探讨互联网医院APP的开发与优化。 一、互联网医院APP的开发原则 &#xff08;1&#xff09;用户体验至上 界面设…

卷积篇 | YOLOv8改进之主干网络中引入可变形卷积DConv

前言:Hello大家好,我是小哥谈。可变形卷积模块是一种改进的卷积操作,它可以更好地适应物体的形状和尺寸,提高模型的鲁棒性。可变形卷积模块的实现方式是在标准卷积操作中增加一个偏移量offset,使卷积核能够在训练过程中扩展到更大的范围,从而实现对尺度、长宽比和旋转等各…

git基础-查看提交历史

查看提交历史 在创建了多个提交之后&#xff0c;或者如果克隆了一个具有现有提交历史的存储库&#xff0c;可能会想要回顾一下发生了什么。最基本和强大的工具就是 git log 命令。 运行下git log查看下输出状态 默认情况下&#xff0c;不带任何参数运行 git log 命令会以逆时…

拌合楼管理系统(八) c#海康威视摄像头车牌识别

前言: c#调用海康威视SDK实现车牌识别 原本以为海康威视sdk的Demo里面没有车牌识别的实例,后来发现自己肤浅了,官方是有提供的,只是车牌识别是通过安防布警的方式实现的.程序主动监听,触发告警后获取到车牌信息. 一、接口调用的流程&#xff1a; 首先初始化sdk -> 开…

袁志佳:前端全栈工程师精英班

教程介绍 本套课程涵盖大前端的全部领域&#xff0c;并对传统的Web前端全栈深入教学。如利用前端知识开发 AI、VR、AR、iOS、Android、PC、Server、智能硬件。 同时我们将核心打造 JavaScript语言新发展、Vue源码分析、前端持续集成方案汇总、MV*框架深度分析 、前端图形学、N…

亚稳态及其解决办法

异步电路 亚稳态 亚稳态亚稳态的产生原因什么是同步异步信号怎么消除亚稳态 亚稳态 在数字电路中&#xff0c;每一位数据不是1&#xff08;高电平&#xff09;就是0&#xff08;低电平&#xff09;。当然对于具体的电路来说&#xff0c;并非1&#xff08;高电平&#xff09;就是…

elementary OS7 Ubuntu 22.04中硬盘挂载报错

elementary OS7 Ubuntu 22.04中硬盘挂载报错 背景目标思路解决方法 背景 上周末安装elementaryos7的过程中将windows10的引导文件搞丢了&#xff0c;这两天准备修复一下&#xff0c;保险期间将固态硬盘上的文件备份到移动硬盘上&#xff0c;备份过程中出现报错的问题&#xff…

2024常用Web支付开发讲解教程

课程介绍 本教程为web支付开发&#xff0c;讲解了最常用的两钟支付&#xff1a;支付宝支付和微信支付&#xff0c;服务器配置和API对接&#xff0c;学完本课程可以学会微信支付、和支付宝支付开发。 学习资料 链接&#xff1a;https://pan.baidu.com/s/19WarLP2dO4dFvNbIHLU…

C++类的6个默认成员函数(构造)

C类和对象基础-CSDN博客https://blog.csdn.net/lh11223326/article/details/136834917?spm1001.2014.3001.5501 目录 1.构造函数 概念 特性 2.析构函数 概念 特性 3.拷贝构造函数 概念 特征 4.赋值运算符重载&#xff08;构造实现&#xff09; 运算符重载 赋值运算…

【常见BUG系列】Java 编程中的 NoSuchFieldError 异常:原因与解决方法

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

云原生(五)、Docker-Swarm集群

基础环境说明 1、环境准备 1、启动4台服务器&#xff08;在同一个网段内&#xff09;。 2、重命名4台服务器&#xff0c;方便区分。 hostnamectl set-hostname swarm1 reboot安装docker。参考文章&#xff1a;云原生&#xff08;二&#xff09;、Docker基础 2、DockerSwarm…

Autosar Crypto Interface学习笔记

文章目录 前言Functional specificationError classificationError detection API specificationType DefinitionsFunction definitionsGeneral APICryIf_InitCryIf_GetVersionInfo Job Processing InterfaceCryIf_ProcessJobDispatch Key IDs匹配KeyId Job Cancellation Inter…

springcloud-Nacos 更强大的注册中心组件

Nacos 实际上从设计思想来说 Eureka 和 nacos 是一样的。 后者是Alibaba推出的 一款更强大 功能更丰富的注册中心 你可以理解为Eureka的高配版 技多不压身既然了解了 Eureka, nacos也来学习一下吧&#xff01; 安装 首先nacos不像eureka 直接pom里面引个依赖就搞定了&#…

odoo17开发教程(17):美化UI界面

从业务角度来看&#xff0c;我们的房地产模块现在很有意义。我们创建了特定的视图&#xff0c;添加了几个操作按钮和约束条件。不过&#xff0c;我们的用户界面还有些粗糙。我们希望在列表视图中添加一些颜色&#xff0c;并使一些字段和按钮有条件地消失。例如&#xff0c;&quo…
最新文章