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

news2024/12/29 7:53:31

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.coloradmin.cn/o/1540912.html

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

相关文章

0基础 三个月掌握C语言(13)-下

数据在内存中的存储 浮点数在内存中的存储 常见的浮点数&#xff1a;3.141592、1E10等 浮点数家族包括&#xff1a;float、double、long double类型 浮点数表示的范围&#xff1a;在float.h中定义 练习 关于&#xff08;float*)&n&#xff1a; &n&#xff1a;这是一…

基于SSM的宠物领养平台的设计与实现

基于SSM的宠物领养平台的设计与实现 获取源码——》公主号&#xff1a;计算机专业毕设大全 获取源码——》公主号&#xff1a;计算机专业毕设大全

Three.js 中的 OrbitControls 是一个用于控制相机围绕目标旋转以及缩放、平移等操作的控制器。

demo案例 Three.js 中的 OrbitControls 是一个用于控制相机围绕目标旋转以及缩放、平移等操作的控制器。下面是它的详细讲解&#xff1a; 构造函数: OrbitControls(object: Camera, domElement?: HTMLElement)object&#xff1a;THREE.Camera 实例&#xff0c;控制器将围绕…

LibFuzzer 基本使用

文章目录 前言环境搭建基础使用编写 fuzz target编译链接demo 测试 && 输出日志分析心脏滴血漏洞测试 提高代码覆盖率和测试速度指定种子语料库多核并行 Fuzz使用字典 参考 前言 相较于 AFL 来说&#xff0c;LibFuzzer 在单个进程内完成模糊测试&#xff0c;以此来避免…

Nacos部署(一)Linux部署Nacos2.3.x单机环境

&#x1f60a; 作者&#xff1a; 一恍过去 &#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390 &#x1f38a; 社区&#xff1a; Java技术栈交流 &#x1f389; 主题&#xff1a; Nacos部署&#xff08;一&#xff09;Linux部署Nacos2.3.x单机环境 ⏱️…

【NC20313】仪仗队

题目 仪仗队 欧拉函数&#xff0c;找规律 思路 这好像是一道非常简单的找规律问题&#xff0c;所以你从 1 1 1 开始枚举&#xff0c;计算出当 N i Ni Ni 时的结果 a n s i ans_i ansi​&#xff0c;所以你得出了以下结果&#xff1a; Nans10233549513621725837 令人失望…

招聘自媒体编辑岗位的人才测评方案

人才测评工具在招聘入职的方案&#xff0c;在线工具网根据自媒体岗位的特性和需求来分析&#xff0c;并制定自媒体主编的测评方案。 自媒体作为互联网时代的产物&#xff0c;自然也为我们带来了很多的福利&#xff0c;例如&#xff1a;海量的信息、快捷的传媒方式&#xff0c;那…

学习次模函数-第2章 定义

纵观本专著&#xff0c;我们认为及其幂集&#xff08;即&#xff0c; 所有子集的集合&#xff09;&#xff0c;其基数为。我们也考虑一个实值集函数&#xff0c;使得。 与凸函数的一般约定相反&#xff08;见附录A&#xff09;&#xff0c;我们不允许函数有无穷大的值。 次模分…

一文搞懂数据链路层

数据链路层 1. 简介2. MAC3. 以太网 1. 简介 &#xff08;1&#xff09;概念 链路(link)是一条无源的点到点的物理线路段&#xff0c;中间没有任何其他的交换结点。 数据链路(data link) 除了物理线路&#xff08;双绞线电缆、同轴电缆、光线等介质&#xff09;外&#xff0…

Java获取方法参数名称方案||SpringBoot配置顺序注解

一: Java获取方法参数名称的方法 普盲: getDeclaredMethods与getMethods的的区别 1、getMethods返回一个包含某些 Method 对象的数组&#xff0c;这些对象反映此 Class 对象所表示的类或接口的公共 member 方法。 2、getDeclaredMethods返回 Method 对象的一个数组&#xff0c…

STM32+ESP8266水墨屏天气时钟:简易多级菜单(数组查表法)

项目背景 本次的水墨屏幕项目需要做一个多级菜单的显示&#xff0c;所以写出来一起学习&#xff0c;本篇文章不单单适合于水墨屏&#xff0c;像0.96OLED屏幕也适用&#xff0c;区别就是修改显示函数。 设计思路 多级菜单的实现&#xff0c;一般有两种实现的方法 1.通过双向…

HTTPS协议的工作原理:保护网络通信的安全盾牌

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

力扣每日一题 2024/3/24 零钱兑换

题目描述 用例说明 思路讲解 动态规划五步法 第一步确定dp数组的含义&#xff1a;dp[i]为凑到金额为i所用最少的硬币数量 第二步确定动态规划方程&#xff1a;凑足金额为j-coins[i]所需最少的硬币个数为dp[j-coins[i]]&#xff0c;那凑足金额为j所用的最少硬币数为dp[j-coin…

【C语言】C语言实现扫雷三子棋

主页&#xff1a;醋溜马桶圈-CSDN博客 专栏&#xff1a;C语言_醋溜马桶圈的博客-CSDN博客 gitee&#xff1a;mnxcc (mnxcc) - Gitee.com 目录 1.C语言实现三子棋 1.1 整体思路 1.2 游戏菜单的创建 1.3 游戏主体的实现 1.3.1 棋盘的初始化 1.3.2 打印棋盘 1.3.3 玩家下…

3分钟搞懂示波器测原副边波形

大家好&#xff0c;我是砖一。 今天分享一下如何用示波器测试原副边的波形&#xff0c;验证电源设计规格准确性。 一&#xff0c;试验目的 假设我们现在拿到的样品是属于开关电源类型的。 1&#xff0c;我们对于电源工程师设计出的一个开关电源样品测试原副边波形&#xff…

手撕算法-爬楼梯

描述 分析 一维动态规划。dp[i] dp[i-1] dp[i-2]; 初始状态&#xff1a;d[0] 0, dp[1] 1, dp[2] 2; 返回值:dp[n]; 代码 class Solution {public int climbStairs(int n) {if (n < 2)return n;int[] dp new int[n 1];dp[1] 1;dp[2] 2;for (int i 3; i < …

matlab批量读取目录下的文件的方法

批量处理可以提高效率&#xff0c;这里提供一个可以批量读取nc文件的代码&#xff1a; address C:\Users\Hello World!!\DESKTOP\TerraClimate_ppt\; % Get the list of files udir address; form *.nc; % Get the list of station names files GetFiles(udir,form); [n,p…

MySQL--select count(*)、count(1)、count(列名) 的区别你知道吗?

MySQL select count(*)、count(1)、count(列名) 的区别&#xff1f; 这里我们先给出正确结论&#xff1a; count(*)&#xff0c;包含了所有的列&#xff0c;会计算所有的行数&#xff0c;在统计结果时候&#xff0c;不会忽略列值为空的情况。count(1)&#xff0c;忽略所有的列…

IIS7/iis8/iis10安装II6兼容模块 以windows2022为例

因安全狗的提示 安全狗防护引|擎安装失败 可能原因是: IIS7及以上版本末安装1IS6兼容模块! .所以操作解决 如下. 在开始菜单中,找到服务器管理器.找到下图的IIS,右键添加角色和功能,找到web服务器的管理工具选项,iis6管理兼容性 打钩并安装. 如下图

C++ 实现BSTree

目录 BSTree.h 框架 insert insertR find findR erase eraseR InOrder 拷贝构造 赋值重载 BSTree.h 框架 template<class K> struct BSTreeNode {BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;BSTreeNode(const K& key):_left(null…