Compose 自定义 - 绘制阶段 Draw

跟我一起使用 compose 做一个跨平台的黑白棋游戏(2)界面布局 自此,咱们的界面布局就算完成了,虽然现在看起来可能简陋了点,但是现在还只是在验证可行性,等所有代码写完,我们再进行亿点点优化,就会丰富好看多了。对了,项目源码我将在这系列文章完结,也就是项目真正写完的时候上传到 Github,到时会在文中附上链接的。 阅读详情

官方界面

一、概念

所有的绘制操作都是通过调整像素大小来执行的。若要确保项目在不同的设备密度和屏幕尺寸上都能采用一致的尺寸,请务必使用 .toPx() 对 dp 进行转换或者采用小数尺寸。 

fun Dp.toPx(): Float = value * density
val width = 50.dp.toPx()

1.1 混合模式 BlendMode

参考文章

不指定任何模式,绘制两个图案,后面的会叠加在前面之上,通过 BlendMode 可以对重叠部分的效果进行设置。源图指的是即将绘制的图像,目标图则是已存在的图像。

1.1.1 Alpha 合成

Src只显示源图,删除目标图。
SrcOver(默认)都显示,源图绘制在目标图上。
SrcIn只显示重叠部分,重叠部分来自源图。
SrcAtop只显示目标图,但重叠部分来自源图。
Dst只显示目标图,丢弃源图的绘制。
DstOver都显示,但重叠部分来自目标图。
DstIn只显示重叠部分,重叠部分来自目标图。
DstAtop只显示源图,但重叠部分来自目标图。
Clear都不显示,删除目标图,丢弃源图绘制。
SrcOut只显示源图,但重叠部分不显示。
DstOut只显示目标图,但重叠部分不显示。
Xor都显示,但不显示重叠部分。

1.1.2 颜色混合

Darken

通过从每个颜色通道中选择最低值来合成源图像和目标图像。

输出图像的不透明度的计算方法与SrcOver相同。

Lighten

通过选择每个颜色通道中的最高值来合成源图像和目标图像。

输出图像的透明度计算方式与 SrcOver 模式相同。

Multiply

(api29+)

将源图像和目标图像的每个分量(包括 Alpha 通道)进行相乘。

此操作只能产生相同或更暗的颜色(乘以白色 1.0 保持不变,乘以黑色 0.0 结果为黑色)。

由于 Alpha 通道也会被相乘,若任一图像中的像素完全透明(透明度 0.0),则输出图像中对应像素也将完全透明。这与 DstIn 模式相似,但区别在于此处颜色通道也参与了运算。

若需要仅对颜色通道进行乘法运算而保留 Alpha 通道不变,请考虑使用 Modulate 模式。

Screen

对源图像和目标图像的分量取反后进行相乘,再将结果取反。

这里的"分量取反"意味着:完全饱和的通道(不透明白色)被视为 0.0,而通常被视为 0.0 的值(黑色、透明)则被视为 1.0。

此模式本质上与 Modulate 混合模式相同,区别在于乘法运算前对颜色值进行取反,渲染前再将结果取反。

此操作只能产生相同或更亮的颜色(乘以黑色 1.0 保持不变,乘以白色 0.0 结果为白色)。类似地,在 Alpha 通道中,它只能使像素变得更不透明。

其效果类似于两台投影仪同时将图像投射到同一屏幕上时产生的叠加效果。

Overlay

对源图像和目标图像的分量进行乘法运算,但调整过程会倾向于保留目标图像的特征。

具体计算规则如下:

若目标值较小,则将其与源值直接相乘。

若源值较小,则先对源值和目标值取反,再将两个取反后的值相乘,最后对结果取反。

这里的"分量取反"意味着:完全饱和的通道(不透明白色)被视为 0.0,而通常被视为 0.0 的值(黑色、透明)则被视为 1.0。

Plus

将源图像和目标图像的每个分量进行相加。

任一图像中像素的透明度会降低该图像对输出像素的贡献,其效果相当于该图像中该像素的颜色变暗。

Modulate


将源图像和目标图像的颜色分量进行相乘。

此操作只能产生相同或更暗的颜色(乘以白色 1.0 保持不变,乘以黑色 0.0 结果为黑色)。

当合成两个不透明图像时,其效果类似于将两张幻灯片重叠在投影仪上。

若需要同时相乘 Alpha 通道,请考虑使用 Multiply 模式。

ColorDodge

(api29+)

用源图像的逆分量除目标图像。

"分量取反"意味着:完全饱和的通道(不透明白色)被视为 0.0,而通常被视为 0.0 的值(黑色、透明)则被视为 1.0。

ColorBurn

(api29+)

用源图像除目标图像的逆分量,再将结果取反。

"分量取反"意味着:完全饱和的通道(不透明白色)被视为 0.0,而通常被视为 0.0 的值(黑色、透明)则被视为 1.0。

Hardlight

(api29+)

对源图像和目标图像的分量进行乘法运算,但调整过程会倾向于保留源图像的特征。

具体计算规则如下:

若源值较小,则将其与目标值直接相乘。

若目标值较小,则先对目标值和源值取反,再将两个取反后的值相乘,最后对结果取反。

这里的"分量取反"意味着:完全饱和的通道(不透明白色)被视为 0.0,而通常被视为 0.0 的值(黑色、透明)则被视为 1.0。

Softlight

(api29+)

对于低于 0.5 的源值采用 ColorDodge 模式,对于高于 0.5 的源值采用 ColorBurn 模式。

此效果与 Overlay 相似,但结果更为柔和。

Difference

(api29+)

对每个通道执行较大值减去较小值的运算。

合成黑色无效果;合成白色则会反转另一图像的颜色。

输出图像的透明度计算方式与 SrcOver 模式相同。
此效果与 Exclusion 相似,但效果更为强烈。

Exclusion

(api29+)

从两图像之和中减去两图像乘积的两倍。

合成黑色无效果;合成白色则会反转另一图像的颜色。

输出图像的透明度计算方式与 SrcOver 模式相同。
此效果与 Difference 相似,但效果更为柔和。

Hue

(api29+)

采用源图像的色相,以及目标图像的饱和度和亮度。其效果相当于使用源图像对目标图像进行着色处理。

输出图像的透明度计算方式与 SrcOver 模式相同。源图像中完全透明的区域,其色相会从目标图像中获取。

Saturation

(api29+)

采用源图像的饱和度,以及目标图像的色相和亮度。

输出图像的透明度计算方式与 SrcOver 模式相同。源图像中完全透明的区域,其饱和度会从目标图像中获取。

Color

(api29+)

采用源图像的色相和饱和度,以及目标图像的亮度。其效果相当于使用源图像对目标图像进行着色处理。

输出图像的透明度计算方式与 SrcOver 模式相同。源图像中完全透明的区域,其色相和饱和度会从目标图像中获取。

Luminosity

(api29+)

采用源图像的亮度,以及目标图像的色相和饱和度。

输出图像的透明度计算方式与 SrcOver 模式相同。源图像中完全透明的区域,其亮度会从目标图像中获取。

1.2 颜色滤镜 ColorFilter

tint()

fun tint(

    color: Color,

    blendMode: BlendMode = BlendMode.SrcIn

): ColorFilter

染色。滤镜作为源图,背景作为目标图,一起参与混合模式。

colorMatrix()

fun colorMatrix(colorMatrix: ColorMatrix): ColorFilter

创建一个通过 4x5 颜色矩阵转换颜色的滤镜。此滤镜可用于调整像素饱和度、执行 YUV 到 RGB 的色彩空间转换等操作。

lighting()

fun lighting(multiply: Color, add: Color): ColorFilter

创建一个可用于模拟简单光照效果滤镜。光照 ColorFilter 由两个参数定义:一个用于乘以源颜色,另一个用于加到源颜色。

1.3 坐标系区别

        当内容进入 Popup、自定义 Layout 或 Canvas,它只是进入了一个新的节点或容器,local 原点重新归零。进入哪个节点,就以哪个节点的左上角为局部原点。所以同一个锚点中心,在 window 中可能是 x = 620px,换到 Popup local 后可能只剩 x = 180px。

        root 与 window 在简单全屏页面里可能数值相近,但不能因此当成同一个概念。系统栏、Dialog、Popup、嵌入式 Compose 容器或不同平台窗口都可能让两者出现偏移。

        跨容器对齐时,先把数据统一到 window,进入目标容器后,再减去目标容器的 window 原点,得到它的 local 坐标。

  • local:当前节点自身。
  • root:当前 Compose UI树的根布局。
  • window:当前承载界面的窗口。
常见API结果相对谁适合解决什么
positionInParent()直接父布局普通父子对齐
positionInRoot()UI树根布局同一根布局内跨层比较
positionInWindow()当前定位窗口Popup、平台窗口内定位
boundsInWindow()当前定位窗口直接获得节点窗口边界

二、画布 DrawScope

绘制都是在 DrawScope 中发生的,可以理解为画布,提供了绘制所需的上下文信息属性,封装了一系列的绘制API。

属性说明
size可以拿到可以拿画布的宽高(size.width、size.height)。
center可以拿到画布中心点坐标。
drawContext存储了以下信息:绘制尺寸size、封装的canvas、用来旋转缩放移动的transform,而通过 canvas.nativeCanvas 就能获取具体平台的实现,即可以调用 Android 原生的 Canvas 来实现更多需求。

2.1 通过 Modifier 修饰符

在已有的组件之上或之下绘制。

.drawBehind()

fun Modifier.drawBehind(
    onDraw: DrawScope.() -> Unit
)

在被修饰项的后面绘制内容(底层是先绘制 Lambda 再绘制被修饰项,后绘制的会显示在上面),适合绘制背景边框。

.drawWithContent()

fun Modifier.drawWithContent(
    onDraw: ContentDrawScope.() -> Unit
)

可以掌控绘制顺序,手动调用 drawContent() 绘制被修饰项,后绘制的会显示在上面。

.drawWithCache()

fun Modifier.drawWithCache(
    onBuildDrawCache: CacheDrawScope.() -> DrawResult
)

当绘制复杂效果时,不希望因为重组而重新创建 Lambda 中用于绘制的实例如 Bush、Path 等,这可能会产生内存抖动,还可以避免重复的昂贵计算。在 Lambada 中调用 onDrawWithContent()、onDrawBehind() 就类似于上面两个修饰符的功能。

 

@Composable
fun Demo() {
    Row(
        modifier = Modifier.size(150.dp),
        horizontalArrangement = Arrangement.Center,
        verticalAlignment = Alignment.CenterVertically
    ) {
        Image(
            painterResource(id = R.drawable.logo_wechat_square),
            contentDescription = null,
            modifier = Modifier
                .size(50.dp)
                .drawWithContent {
                    drawContent()
                    drawRedDot()    //后绘制的会显示在上面
                }
        )
        Image(
            painterResource(id = R.drawable.logo_wechat_square),
            contentDescription = null,
            modifier = Modifier.padding(start = 10.dp).size(50.dp).drawBehind {
                drawRedDot()
            }
        )
    }
}

fun DrawScope.drawRedDot() {
    drawCircle(
        color = Color.Red,
        radius = 18F,
        center = Offset(drawContext.size.width, 0f)
    )
}

2.2 通过 Canvas() 可组合项

是一个独立组件,在 DrawScope 作用域中调用各种函数绘制。Compose 作为跨平台 UI 框架,所使用的 Canvas() 函数只是一个封装,最终还是调用具体平台即 Android 原生的 Canvas。

fun Canvas(

        modifier: Modifier,

        onDraw: DrawScope.() -> Unit

) = Spacer(modifier.drawBehind(onDraw))

发现该方法只是一个封装,真正绘制的是调用 drawBehind()。绘制内容是显示在 Spacer 下面的,由于 Spacer 是透明的,因此我们所绘制内容得以全部显示。

@Composable
fun Demo() {
    Canvas(
        modifier = Modifier.size(400.dp).background(Color.White)
    ) {
        //通过 size 可以拿到可以拿画布的宽高
        val width = size.width
        val height = size.height
        //通过 center 可以拿到画布中心点坐标
        val center = center
        //调用各种方法绘制
        drawRect(
            color = Color.Blue,
            size = Size(width,height)
        )
    }
}

四、笔刷 Brush

官方页面

主要用于绘制渐变色,需要纯色填充使用 SolidColor。很多 API 都提供了 Color 或 Brush 两套重载,能用 Color 表达清楚时不需要强行换成 Brush,不要把 Brush 当成更高级的 Color,Color 解决“是什么颜色”,Brush 解决“区域如何被填充”。一般先用 Color 建立清晰层级,只有当单色表达不够时再引入 Brush。

Color 更适合Brush 更适合
普通文字少量标题文字渐变
普通背景渐变背景
边框、分隔线自绘图形填充
错误、成功、警告状态色图表柱体、环形进度

linearGradient

线性渐变

fun linearGradient(
        colors: List<Color>,        //渐变颜色
        start: Offset = Offset.Zero,        //开始的位置
        end: Offset = Offset.Infinite,        //结束的位置
        tileMode: TileMode = TileMode.Clamp        //重复模式
): Brush

水平渐变和垂直渐变底层就是调用的线性渐变。

适合表达方向感。比如顶部工具栏、侧边栏选中项、图表柱体、状态概览区域。它不适合用在大量正文背景上,因为连续渐变会降低长时间阅读的稳定性。

horizontalGradient

水平方向渐变

fun horizontalGradient(
        colors: List<Color>,
        startX: Float = 0.0f,
        endX: Float = Float.POSITIVE_INFINITY,
        tileMode: TileMode = TileMode.Clamp
): Brush

verticalGradient

垂直方向渐变

fun verticalGradient(
        colors: List<Color>,
        startY: Float = 0.0f,
        endY: Float = Float.POSITIVE_INFINITY,
        tileMode: TileMode = TileMode.Clamp
): Brush

radialGradient

放射渐变

fun radialGradient(
        colors: List<Color>,
        center: Offset = Offset.Unspecified,        //中心位置
        radius: Float = Float.POSITIVE_INFINITY,        //半径
        tileMode: TileMode = TileMode.Clamp
): Brush

在真实界面里,径向渐变要比线性渐变更克制。它很容易变成纯装饰。如果它不能帮助用户理解状态、焦点或层级,通常就不应该出现。

sweepGradient

扫描渐变

fun sweepGradient(
        colors: List<Color>,
        center: Offset = Offset.Unspecified
): Brush

更适合圆形进度、仪表盘、环形图这类围绕中心绘制的组件。

SolidColor

纯色填充

class SolidColor(

        val value: Color

) : Brush(), Interpolatable

val solidBrush = SolidColor(Color(0xFF2563EB))

在只接收 Brush 的地方,SolidColor 就有用。

网格渐变见下方七章。

Compose v1.12 引入,创建多个点实现自然的颜色渐变。

4.2.1 使用 colorStop 更改颜色分布

自定义颜色在渐变中的显示方式,可以调整每种颜色的 colorStop 值,0 ~ 1 之间的小数。

val colorStops = arrayOf(
    0.0f to Color.Yellow,
    0.2f to Color.Red,
    1f to Color.Blue
)
Box(modifier = Modifier
        .requiredSize(200.dp)
        .background(Brush.horizontalGradient(colorStops = colorStops))
)

 4.2.2 使用 TileMode 让图案重复显示

当未指定 Brush 的开始位置 start 和结束位置 end 时,默认会填满整个区域,只有在区域 > Brush 时 TileMode 才会在渐变中平铺。以下举例 HorizontalGradient 的效果。

TileMode.Repeated将区域剩余空间绘制为重复的顺序颜色。
TileMode.Mirror将区域剩余空间绘制为重复的反转颜色。
TileMode.Clamp将区域剩余空间绘制为结束颜色。
TileMode.Decal将区域剩余空间绘制为透明色。(仅适用于 API 31 及更高版本。可使用 TileMode.isSupported() 确定设备是否支持 TileMode。如果使用了不受支持的 TileMode,系统会应用默认的 TileMode.Clamp。)

 4.2.3 更改 Brush 大小

当知道绘制区域大小时(如在 DrawScope 中通过 size 获取)可以按照 TileMode 方式平铺,在不知道的情况下(如将 Brush 分配给文字)可以扩展 Shader 重写 createShader() 函数利用绘制区域大小 size 形参。对于 radialGradient 如果未指定中心位置 center 和半径radius,渐变将占据整个 DrawScope 但是是以宽高较小的那边为直径,此时自定义大小会获得更好的效果(发散到屏幕外边去)。

val listColors = listOf(Color.Yellow, Color.Red, Color.Blue)
val customBrush = remember {
    object : ShaderBrush() {
        override fun createShader(size: Size): Shader {
            return LinearGradientShader(
                colors = listColors,
                from = Offset.Zero,
                to = Offset(size.width / 4f, 0f),
                tileMode = TileMode.Mirror
            )
        }
    }
}
Box(
    modifier = Modifier
        .requiredSize(200.dp)
        .background(customBrush)
)

4.2.4 使用图片作为 Brush

如需使用 ImageBitmap 作为 Brush,请以 ImageBitmap 的形式加载相应图片,然后创建 ImageShader Brush。可以应用于一下几种类型的绘制:背景、文字、画布。

val imageBrush =
    ShaderBrush(ImageShader(ImageBitmap.imageResource(id = R.drawable.dog)))

//用于 background
Box(
    modifier = Modifier
        .requiredSize(200.dp)
        .background(imageBrush)
)

//用于 TextStyle
Text(
    text = "Hello Android!",
    style = TextStyle(
        brush = imageBrush,
        fontWeight = FontWeight.ExtraBold,
        fontSize = 36.sp
    )
)

//用于 DrawScope#drawCircle()
Canvas(onDraw = {
    drawCircle(imageBrush)
}, modifier = Modifier.size(200.dp))

六、绘制

6.1 形状

6.1.1 画线 drawLine()

fun drawLine(
        color: Color,
        start: Offset,        //起点偏移
        end: Offset,        //终点偏移
        strokeWidth: Float = Stroke.HairlineWidth,        //描边宽度(线宽)
        cap: StrokeCap = Stroke.DefaultCap,        //线两头形状
        pathEffect: PathEffect? = null,        //效果
        alpha: Float = 1.0f,        //范围0~1
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

cap

线条两头的形状

StrokeCap.Butt 平的(默认)
StrokeCap.Square 也是平的但是长一截
StrokeCap.Round 圆的

pathEffect

线条效果

PathEffect.cornerPathEffect(radius: Float)

将线段之间的锐角替换为指定半径的圆角 radius是半径

PathEffect.dashPathEffect(intervals: FloatArray, phase: Float = 0f)

将形状绘制为具有给定间隔的一系列破折号。比如虚线 例如interval={20,5},第一个参数表示虚线的长度是20,5是虚线之间的间隔是5. phase 偏移

PathEffect.chainPathEffect(outer: PathEffect, inner: PathEffect)

创建一个PathEffect,将内部效果应用于路径,然后应用外部效果

PathEffect.stampedPathEffect(shape: Path, advance: Float, phase: Float,style: StampedPathEffectStyle)

用path表示的指定形状冲压绘制的路径.  shape要踩踏的路径,advance 每个冲压形状之间的前进间距, phase 在压印第一个形状之前要偏移的相位量, style如何在每个位置转换形状,因为它是冲压. style有三种取值 StampedPathEffectStyle.Translate 平移 ,StampedPathEffectStyle.Rotate 旋转,StampedPathEffectStyle.Morph 变形

6.1.2 画矩形 drawRect()

fun drawRect(
        color: Color,
        topLeft: Offset = Offset.Zero,        //左上角偏移
        size: Size = this.size.offsetSize(topLeft),
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,        //Fill填充、Stoke描边
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

6.1.3 画圆角矩形 drawRoundRect()

fun drawRoundRect(
        color: Color,
        topLeft: Offset = Offset.Zero,
        size: Size = this.size.offsetSize(topLeft),
        cornerRadius: CornerRadius = CornerRadius.Zero,        //圆角半径
        style: DrawStyle = Fill,        //Fill填充、Stoke描边
        alpha: Float = 1.0f,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

6.1.4 画圆形 drawCircle()

fun drawCircle(
        color: Color,
        radius: Float = size.minDimension / 2.0f,        //半径
        center: Offset = this.center,        //圆心偏移
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,        //Fill填充、Stoke描边
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

6.1.5 画椭圆形 drawOval()

fun drawOval(
        color: Color,
        topLeft: Offset = Offset.Zero,        //左上角偏移
        size: Size = this.size.offsetSize(topLeft),        //传入宽高 Size(200f, 100f)
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,        //Fill填充、Stoke描边
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

6.1.6 画弧度跟扇形 drawArc()

fun drawArc(
        color: Color,
        startAngle: Float,        //起始点角度
        sweepAngle: Float,        //扫过的角度
        useCenter: Boolean,        //是否连接中心(true扇形、false弧形)
        topLeft: Offset = Offset.Zero,        //左上角偏移
        size: Size = this.size.offsetSize(topLeft),        //传入宽高 Size(200f, 100f)
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,        //Fill填充、Stoke描边(弧形填充就是半圆)
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

6.1.7 画点 drawPoints()

fun drawPoints(
        points: List<Offset>,        //点坐标的集合
        pointMode: PointMode,        //如何绘制点
        color: Color,
        strokeWidth: Float = Stroke.HairlineWidth,        //描边宽度(线宽)
        cap: StrokeCap = StrokeCap.Butt,        //线两头形状
        pathEffect: PathEffect? = null,
        alpha: Float = 1.0f,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

PointMode

如何绘制点

PointMode.Points 分别画点
PointMode.Lines 画线(点集合两两组合划线,奇数的话最后一个不管)
PointMode.Polygon 画多边形(最后一个点偏移要跟第一个一样才闭口,否则只是连接各点)

6.2 路径 drawPath()

对于不规则形状,点会非常多需要频繁调用画线方法,画路径方法只需要调用一次。

fun drawPath(
        path: Path,
        color: Color,
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,        //Fill填充、Stoke描边(弧形填充就是半圆)
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)
// 创建路径(围绕组件一圈)
val path = Path().apply {
    moveTo(0F, 0F)  // 左上
    lineTo(size.width, 0F)  // 右上
    lineTo(size.width, size.height) // 右下
    lineTo(0F, size.height) //左下
    close()
}

6.3 图片 drawImage()

fun drawImage(
        image: ImageBitmap,        //源图像
        topLeft: Offset = Offset.Zero,        //左上角偏移
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,        //Fill填充、Stoke描边
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)
fun drawImage(
        image: ImageBitmap,
        srcOffset: IntOffset = IntOffset.Zero,        //源图像的偏移
        srcSize: IntSize = IntSize(image.width, image.height),        //源图像大小
        dstOffset: IntOffset = IntOffset.Zero,        //绘制图像的偏移
        dstSize: IntSize = srcSize,        //绘制图像的大小
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,        //Fill填充、Stoke描边
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

6.4 文本 drawText()

drawText()fun DrawScope.drawText(
    textMeasurer: TextMeasurer,
    text: AnnotatedString,
    topLeft: Offset = Offset.Zero,
    style: TextStyle = TextStyle.Default,
    overflow: TextOverflow = TextOverflow.Clip,
    softWrap: Boolean = true,
    maxLines: Int = Int.MAX_VALUE,
    placeholders: List<AnnotatedString.Range<Placeholder>> = listOf(),
    size: Size = Size.Unspecified,
    blendMode: BlendMode = DrawScope.DefaultBlendMode,
)
fun DrawScope.drawText(
    textMeasurer: TextMeasurer,
    text: String,
    topLeft: Offset = Offset.Zero,
    style: TextStyle = TextStyle.Default,
    overflow: TextOverflow = TextOverflow.Clip,
    softWrap: Boolean = true,
    maxLines: Int = Int.MAX_VALUE,
    size: Size = Size.Unspecified,
    blendMode: BlendMode = DrawScope.DefaultBlendMode,
)
val textMeasurer = rememberTextMeasurer()

Canvas(modifier = Modifier.fillMaxSize()) {
    drawText(textMeasurer, "Hello")
}

6.5 用Canvas对象绘制 drawIntoCanvas()

可以将已有的 View 项目中的绘制逻辑搬过来用。

inline fun DrawScope.drawIntoCanvas(block: (Canvas) -> Unit)
drawIntoCanvas { canvas ->
    val paint = Paint()    //注意这里画笔的导包不要是Compose的
    paint.color = Color.Red
    paint.strokeWidth = 10f
    canvas.drawLine(p1= Offset(50f,50f),p2= Offset(200f,200f),paint = paint)
}

6.6 画布操作

这些操作函数会先保存当前画布状态→执行 Lambda→然后恢复,这比 View 中的 canvas.save() / canvas.restore() 更安全不会导致状态栈溢出。

6.6.1 平移 translate()、inset()

translate()

inline fun DrawScope.translate(
    left: Float = 0.0f,
    top: Float = 0.0f,
    block: DrawScope.() -> Unit
) {
    drawContext.transform.translate(left, top)
    block()
    drawContext.transform.translate(-left, -top)
}

平移左上角,整体比例不变。

inset()

inline fun DrawScope.inset(

    inset: Float,

    block: DrawScope.() -> Unit

)

inline fun DrawScope.inset(
    horizontal: Float = 0.0f,
    vertical: Float = 0.0f,
    block: DrawScope.() -> Unit,
)
inline fun DrawScope.inset(
    left: Float,
    top: Float,
    right: Float,
    bottom: Float,
    block: DrawScope.() -> Unit,
)

@Composable
fun Demo2() {
    Canvas(
        modifier = Modifier.size(200.dp).background(Color.Blue)
    ) {
        val halfSize = size / 2F
        //正常画
        drawRect(
            color = Color.Green,
            size = halfSize
        )
        //平移后画
        translate(left = 20F, top = 20F) {
            drawRect(
                color = Color.Red,
                size = halfSize
            )
        }
    }
}

6.6.2 缩放 scale()

scale()

inline fun DrawScope.scale(
    scale: Float,        //统一设置横向和纵向缩放大小
    pivot: Offset = center,        //以该坐标应用缩放,默认为组件中心。
    block: DrawScope.() -> Unit
)

inline fun DrawScope.scale(
    scaleX: Float,        //横向缩放大小
    scaleY: Float,        //纵向缩放大小
    pivot: Offset = center,        //中心点偏移
    block: DrawScope.() -> Unit
)

6.6.3 旋转 rotate()、rotateRad()

rotate()

inline fun DrawScope.rotate(
    degrees: Float,        //顺时针旋转的度数
    pivot: Offset = center,        //以该坐标应用旋转,默认为组件中心。
    block: DrawScope.() -> Unit
)

旋转了多少角度。

rotateRad()

inline fun DrawScope.rotateRad(
    radians: Float,        //顺时针旋转的弧度
    pivot: Offset = center,                ///以该坐标应用旋转,默认为组件中心。
    block: DrawScope.() -> Unit
)

旋转了多少弧度。

6.6.4 裁剪 clipRect()、clipPath()

clipRect()

inline fun DrawScope.clipRect(
    left: Float = 0.0f,
    top: Float = 0.0f,
    right: Float = size.width,
    bottom: Float = size.height,
    clipOp: ClipOp = ClipOp.Intersect,
    block: DrawScope.() -> Unit
)

裁剪矩形区域,绘制在裁剪好的矩形区域内。ClipOp.Difference从当前剪辑中减去提供的矩形。

clipPath()

inline fun DrawScope.clipPath(
    path: Path,
    clipOp: ClipOp = ClipOp.Intersect,
    block: DrawScope.() -> Unit
)

裁剪路径。

6.6.5 组合多种操作 withTransform()

inline fun DrawScope.withTransform(
    transformBlock: DrawTransform.() -> Unit,
    drawBlock: DrawScope.() -> Unit
)

DrawTransform

作用域中依然可以调用 size 和 center

平移

fun translate(left: Float = 0.0f, top: Float = 0.0f)

fun inset(left: Float, top: Float, right: Float, bottom: Float)

缩放fun scale(scaleX: Float, scaleY: Float, pivot: Offset = center)
旋转fun rotate(degrees: Float, pivot: Offset = center)
裁剪

fun clipRect(
        left: Float = 0.0f,
        top: Float = 0.0f,
        right: Float = size.width,
        bottom: Float = size.height,
        clipOp: ClipOp = ClipOp.Intersect
)

fun clipPath(path: Path, clipOp: ClipOp = ClipOp.Intersect)

七、网格渐变 MeshGradientPainter

官方页面

网格维度网格沿垂直轴和水平轴拆分为多个块。rows 和 columns 的网格包含 (rows+1)×(columns+1) 个顶点。例如,1x1 网格由 4 个顶点组成,形成一个面片。
归一化坐标所有顶点位置都使用归一化坐标系,其中 (0f, 0f) 表示绘制边界的左上角,(1f, 1f) 表示绘制边界的右下角。
Bezier控制点(切线)每个顶点最多包含四个可选的 Bezier 控制点。这些正切指定了相邻顶点之间的边曲率。如果您使用 Offset.Unspecified,Compose 会推断出切线,以确保补丁之间的平滑过渡。由 4 个顶点及其控制点形成的每个网格单元都会生成一个贝塞尔曲面片。
颜色插值框架会计算主要顶点之间的颜色。将 hasBicubicColor 设置为 true 可实现 Catmull-Rom 插值,从而实现更平滑的颜色过渡;设置为 false 可实现双线性插值。

7.1 简单实现

指定其维度,并在配置块内使用 setVertex 函数来定位角点并为其分配颜色。

val rows = 1
val columns = 1

val gradientPainter = remember {
    MeshGradientPainter(rows, columns) {
        // Parameters: row, column, position, color
        setVertex(0, 0, Offset(0f, 0f), Color.Red)     // 左上
        setVertex(0, 1, Offset(1f, 0f), Color.Blue)    // 右上
        setVertex(1, 0, Offset(0f, 1f), Color.Green)   // 左下
        setVertex(1, 1, Offset(1f, 1f), Color.Yellow)  // 右下
    }
}

Box(
    modifier = modifier
        .aspectRatio(16/9f)
        .fillMaxWidth()
        .paint(gradientPainter)
)

7.2 使用贝塞尔控制点

默认情况下,网格生成器会处理复杂的计算,以保持网格过渡平滑。不过,如果您想有选择地推拉或急剧收缩某些颜色部分,可以明确自定义任何单个顶点的切线。控制偏移量是相对于宿主顶点的位置来衡量的。如需完全停用切线推理,并改用完全笔直的线性边界,请为顶点控制点分配 Offset.Zero 的偏移量。

val customTangentPainter = remember {
    MeshGradientPainter(rows = 1, columns = 1) {
        // 调整左上角顶点,使其向右和向下向外弯曲
        setVertex(
            row = 0,
            column = 0,
            position = Offset(0f, 0f),
            color = Color.Magenta,
            rightControlPoint = Offset(0.4f, 0.1f),
            bottomControlPoint = Offset(0.1f, 0.4f)
        )
        // 其他点可以保持未指定,以使用默认推断的备用切线
        setVertex(0, 1, Offset(1f, 0f), Color.Cyan)
        setVertex(1, 0, Offset(0f, 1f), Color.Blue)
        setVertex(1, 1, Offset(1f, 1f), Color.Black)
    }
}
Box(
    modifier = modifier
        .aspectRatio(16/9f)
        .fillMaxWidth()
        .paint(customTangentPainter)
)

7.3 高级实现

3x3 网格,这意味着需要指定 16 个点,并使用不同的偏移量设置中间点。

val points = remember {
    listOf(
        Offset(0.0f, 0.0f), Offset(0.3f, 0.0f), Offset(0.7f, 0.0f), Offset(1.0f, 0.0f),
        Offset(0.0f, 0.3f), Offset(0.2f, 0.4f), Offset(0.7f, 0.2f), Offset(1.0f, 0.3f),
        Offset(0.0f, 0.7f), Offset(0.3f, 0.8f), Offset(0.7f, 0.6f), Offset(1.0f, 0.7f),
        Offset(0.0f, 1.0f), Offset(0.3f, 1.0f), Offset(0.7f, 1.0f), Offset(1.0f, 1.0f)
    )
}

val gradientPainter = remember {
    MeshGradientPainter(rows = 3, columns = 3) {
        // Row 0
        setVertex(0, 0, points[0], yellow)
        setVertex(0, 1, points[1], orange)
        setVertex(0, 2, points[2], yellow)
        setVertex(0, 3, points[3], purple)

        // Row 1
        setVertex(1, 0, points[4], pink)
        setVertex(1, 1, points[5], yellow)
        setVertex(1, 2, points[6], pink)
        setVertex(1, 3, points[7], purple)

        // Row 2
        setVertex(2, 0, points[8], indigo)
        setVertex(2, 1, points[9], pink)
        setVertex(2, 2, points[10], purple)
        setVertex(2, 3, points[11], indigo)

        // Row 3
        setVertex(3, 0, points[12], purple)
        setVertex(3, 1, points[13], indigo)
        setVertex(3, 2, points[14], pink)
        setVertex(3, 3, points[15], yellow)
    }
}

Box(
    modifier = modifier.padding(32.dp)
        .aspectRatio(16 / 9f)
        .fillMaxWidth()
        .paint(gradientPainter)
        // ...
)

7.4 以动画方式呈现

由于 MeshGradientPainter 的 block lambda 参数是在 DrawScope 中执行的,因此它可以读取和观察可变状态。您可以随时间推移为位置或颜色添加动画效果,而无需重新分配着色器或位图。

val infiniteTransition = rememberInfiniteTransition(label = "meshMovement")
val animatedOffset by infiniteTransition.animateFloat(
    initialValue = -0.1f,
    targetValue = 0.1f,
    animationSpec = infiniteRepeatable(
        animation = tween(2500, easing = LinearEasing),
        repeatMode = RepeatMode.Reverse
    ),
    label = "offset"
)

val coral = Color(255, 90, 90)
val peach = Color(255, 139, 90)
val amber = Color(255, 169, 90)
val sunshine = Color(255, 212, 90)
val indigo = Color(0xFF5856D6)
val pink = Color(0xFFFF2D55)


val gradientPainter = remember {
    MeshGradientPainter(rows = 3, columns = 3) {
        // Row 0
        setVertex(0, 0, Offset(0.0f, 0.0f), indigo)
        setVertex(0, 1, Offset(0.3f, 0.0f), peach)
        setVertex(0, 2, Offset(0.7f, 0.0f), amber)
        setVertex(0, 3, Offset(1.0f, 0.0f), sunshine)
        // Row 1
        setVertex(1, 0, Offset(0.0f, 0.3f), pink)
        setVertex(1, 1, Offset(0.2f, 0.4f) + Offset(animatedOffset, animatedOffset), coral)
        setVertex(1, 2, Offset(0.7f, 0.2f) + Offset(animatedOffset, animatedOffset), peach)
        setVertex(1, 3, Offset(1.0f, 0.3f), indigo)

        // Row 2
        setVertex(2, 0, Offset(0.0f, 0.7f), coral)
        setVertex(2, 1, Offset(0.3f, 0.8f) + Offset(animatedOffset, 0f), pink)
        setVertex(2, 2, Offset(0.7f, 0.6f) + Offset(animatedOffset, 0f), sunshine)
        setVertex(2, 3, Offset(1.0f, 0.7f), amber)

        // Row 3
        setVertex(3, 0, Offset(0.0f, 1.0f), sunshine)
        setVertex(3, 1, Offset(0.3f, 1.0f), amber)
        setVertex(3, 2, Offset(0.7f, 1.0f), pink)
        setVertex(3, 3, Offset(1.0f, 1.0f), indigo)
    }
}


Box(
    modifier = modifier.padding(32.dp)
        .safeContentPadding()
        .aspectRatio(16 / 9f)
        .fillMaxWidth()
        .paint(gradientPainter)
)

给 compose draw 绘制的非规则图形添加点击监听 上一节简单介绍了怎么给 composable 添加监听,那么问题来了,怎么给我们自己绘制的内容添加呢?答案是,一样的,但是也不完全一样。怎么监听按钮点击?要知道,绘制的作用域是DrawScope而不是Compose了。并且,在DrawScope中也没有提供任何触摸相关的 API。如果想要监听触摸事件,我们只能在compose作用域中的 Modifier 中添加。你可能会说,那还不简单,那我们直接给提供了DrawScope的 composable 加上监听不就得了?就像这样") }) { 阅读详情

相关推荐

Compose_20--画布(Canvas)

如果我们想自定义图形的话,就要使用Canvas,因为Canvas是自定义图形的核心可组合项。在Canvas中,可用空间大小可用通过modifier来设置,例如设置Canvas的可用空间大小为填充其父元素: Canvas(modifier = Modifier.fillMaxSize()) { } Canvas自动提供了一个维护自身状态且限定了作用域的绘图环境--DrawScope,其提供了一些有用的字段: drawContext:包含创建图形环境所需依赖项的当前DrawContext; cent

weixin_42404361的博客 2704

Compose中Canvas绘制

用Compose中的Canvas绘制了一个Android机器人。

juer2017的博客 941

Compose Canvas基础(1) drawxxx()绘制方法

阅读本文需要一定compose基础,如果没有请移步Jetpack Compose入门详解(实时更新)本文介绍Compose Canvas基础,介绍Canvas可组合项依赖的原理和绘制相关的内置api。

我怀里的猫的博客 1379

Compose实用知识

本文主要讲解一些安卓中实用的compose知识,长期更新 防快速点击 说明 防快速点击主要使用 Modifier 操作符来实现,代码如下 代码 @Composable fun Modifier.avoidRepeatclickable(millis: Long = 500, onClick: () -> Unit): Modifier { var timeStamp by remember { mutableStateOf(0L) } return cli.

ymeddmn的博客 3118

Compose 阶段(Phases)详解

Compose 将 UI 构建分为三个独立阶段:组合(决定显示什么)、布局(决定位置大小)和绘制(实际渲染)。这种分离带来性能优势,每个阶段可独立优化且能智能跳过未变化部分。组合阶段执行 Composable 函数创建 UI 树,布局阶段处理测量和放置,绘制阶段操作 Canvas。与传统 View 系统耦合的三阶段相比,Compose 的分离架构支持更高效的重组和并行执行。优化技巧包括使用 remember 减少不必要重组,以及按需更新特定阶段。

HeDongDong的博客 741

ArcGIS License Server点击启动没反应,许可管理器无法启动怎么办

4 当我们再打开ArcGIS License Server Administrator的时候。3 在【服务】中找到【ArcGIS License Manager】双击。无法启动ArcGIS License Server的情况。在使用ArcGIS的过程中,我们经常会遇到。专注GIS领域知识分享,交流行业软件技巧。欢迎关注,不定期分享GIS各种问题和技巧。1 右击我的电脑——【管理】服务状态——启动,确定即可。启动类型选择——自动。

GIS蒲公英的博客 2万+

Android自定义View绘制流程与实战案例

在Android开发中,自定义View是实现复杂UI效果的核心技术。其底层原理基于View系统的三大绘制阶段:测量(Measure)、布局(Layout)和绘制(Draw)。测量阶段通过MeasureSpec确定View尺寸,布局阶段处理位置信息,绘制阶段则利用Canvas和Paint进行视觉呈现。这种机制赋予了开发者极高的灵活性,能够实现各种独特的UI组件,如星级评分控件、自定义进度条等。通过合理运用硬件加速、对象复用等技术,可以显著提升绘制性能。自定义View广泛应用于需要特殊视觉效果、高性能动画或独特

weixin_29183637的博客 186

DEMATEL-ISM模型的Python实现——方法介绍以及代码复现

本文参考文献与网络资料,对DEMATEL-ISM方法做了介绍何步骤描述,并给出Python代码进行了计算和绘图。

cros1的博客 2万+

Jetpack Compose 的阶段

前言 Compose 会通过几个不同的“阶段”来渲染帧。如果我们观察一下 Android View 系统,就会发现它有 3 个主要阶段:测量、布局和绘制。Compose 和它非常相似,但开头多了一个叫做“组合”的重要阶段。 帧的三个阶段 组合(Composition) 布局(测量和放置)(Layout) 绘制 (Drawing) compose 关键词 幂等:输入计算出相同结果的工作 单向数据流:数据能够沿一个方向(从组合到布局,再到绘制)生成帧. 【有特例】 智能重组: Compose 会跟踪不同阶段中

Android 工程化与移动架构实践 3595

RK3576的ISP和VPSS图像处理能力实测:对比海思方案能做什么?

本文深度评测了RK3576芯片的ISP和VPSS图像处理能力,并与海思方案进行对比。测试显示,RK3576在MIPI接口灵活性、多路视频支持、HDR动态范围和降噪效果等方面表现优异,特别适合智能视觉和安防监控应用。通过实测数据展示了其在低照度、高动态范围等复杂场景下的卓越性能。

weixin_42713608的博客 261

ROS自定义地图实战:从CAD到导航的完整流程

本文详细介绍了如何将CAD图纸或手绘草图高效转换为ROS机器人可直接使用的导航地图。通过解析PGM与YAML标准格式,提供从图像处理、尺度校准到集成AMCL与move_base导航栈的完整实战流程,帮助开发者跳过繁琐的SLAM建图,实现快速、精准的自定义地图部署。

weixin_29083475的博客 496

【Android】Compose绘制系统 VS 传统View绘制系统

init();// 大量初始化代码...@Override// 复杂的测量逻辑@Override// 复杂的绘制逻辑// 直接的绘制代码,无需管理生命周期特性传统View系统Compose系统设计范式命令式声明式架构基础继承 + 面向对象组合 + 函数式状态管理分散在各个View中集中管理,响应式更新机制手动自动智能重组性能特点可能多次测量,GC压力大单次测量,内存友好自定义复杂度高,需要重写多个方法低,直接绘制学习曲线平缓但繁琐陡峭但高效未来方向。

沐怡旸的专栏 1296

DP3(3D Diffusion Policy)算法详解:从点云感知到扩散动作生成

DP3 的全称是:DP3 = 3D Diffusion Policy\boxed{\text{DP3 = 3D Diffusion Policy}}DP3 = 3D Diffusion Policy​对应论文题目是:3D Diffusion Policy: Generalizable Visuomotor Policy Learning via Simple 3D Representations该工作发表于 Robotics: Science and Systems(RSS)2024。DP3 是一种面向机器

u013250861的博客 350

Next AI Draw.io 如何用 Docker Compose 离线部署内网环境并自托管 draw.io

Next AI Draw.io 默认通过 `embed.diagrams.net` 加载 draw.io 编辑器。当部署目标是无外网的内网环境、或该地址被网络策略屏蔽时,浏览器会解析失败(文档中记录的现象是提示"找不到 embed.diagrams.net 的服务器 IP 地址")。官方给出的离线方案是:用 Docker Compose 自托管一个 `jgraph/drawio` 镜像,并通过构建

gitblog_00078的博客 755

FreeRTOS项目:智能手表(1);

从 “最底层启动”→“系统基础配置”→“硬件驱动”→“中间件能力封装”→“上层业务功能”,分层递进、从基础到应用,就能逐步把整个项目的逻辑串联起来~

weixin_46847454的博客 1446

【BlueDroid】蓝牙音乐协议分析之A2DP和AVRCP连接流程(超详细)

AVCTP协议描述了蓝牙设备间Audio/Video的控制信号交换的格式和机制,他是一个总体的协议,具体的控制信息由其指定的协议(AVRCP)实现,AVCTP协议本身只指定控制Command和Response的总体的格式;这个过程中对应了Event中的btavrcp_play_status_changed_callback和btavrcp_play_position_changed_callback;上述为AVCTP-Control建立L2CAP连接的Command,对应H4的Sent (0x00);

风云说通信 7314

使用 Docker 部署 next-ai-draw-io:从单容器快速启动到离线私有化部署

本文以 [docs/cn/docker.md](https://link.gitcode.com/i/137f4f797b10bb533828f247e1d49438) 为主线,讲解如何用 Docker 在本地或服务器上运行 next-ai-draw-io(一个集成 AI 与 draw.io 图表的 Next.js 应用):包括最简的 `docker run` 启动命令、基于 `env.examp

gitblog_00118的博客 837

YOLO11改进-模块-引入多尺度差异融合模块MDFM

MDFM主要用于融合双时相图像特征并生成带有丰富上下文信息的差异特征,其过程包含以下步骤:1. 特征提取与差异特征生成首先从双时相图像中提取特征f1和f2,然后将f1和f2进行像素级别的相减,再对相减结果取绝对值,最后通过一个3x3的卷积操作得到Di。2. 多尺度特征融合(MSFF)接着利用多尺度特征学习机制来增强特征融合效果。该机制通过不同核大小的卷积构建多尺度融合过程,具体是通过一个名为 MSFF 单元来实现的。

qq_64693987的博客 4379

Compose自定义Indication:从机制到可复用的点击反馈实现

在Jetpack Compose中,默认的点击反馈是Material Ripple效果,但实际设计稿往往需要光晕、扫描线等定制视觉。Indication作为Compose提供的交互状态驱动绘制机制,通过IndicationNodeFactory与Node节点,将交互监听、动画策略和绘制逻辑解耦。了解这一原理后,开发者可以自定义Indication来实现按压扩散光晕等反馈,且不影响组件重组性能。基于Modifier.clickable的interactionSource与indication参数,可将自定义反

weixin_35750483的博客 112

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

Android View绘制流程

导入: compile 'com.android.support:percent:26.+'(同上面的V7版本号)

斗锋在干嘛的博客 1165

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

第二章:UI 开发——View 系统与 Jetpack Compose

本文摘要: 本章系统讲解Android UI开发的两大技术体系:传统View/ViewGroup和现代Jetpack Compose。第一部分详细分析View的三大绘制流程(measure/layout/draw)实现原理,包括MeasureSpec机制、自定义View的测量与绘制优化技巧。第二部分深入Jetpack Compose核心机制,涵盖布局系统、动画、手势处理、性能优化以及与View系统的互操作。通过代码示例展示了如何实现自定义属性和高效绘制,对比了两种UI架构的差异与适用场景,为开发者提供全面的

liuyinghui523的专栏 410

DriverStore Explorer:三步工作流释放 Windows 驱动仓库的 GB 级空间

DriverStore Explorer(RAPR)是一款面向 Windows 驱动仓库管理与 Windows 驱动清理的开源图形化工具。它将 Driver Store 中的每个驱动包列表化呈现,附带版本、大小、日期与关联设备等元数据,支持一键标记旧驱动、批量删除旧驱动与驱动导出备份,把持续堆叠的 FileRepository 变成可清理的清单,回收被历史版本驱动占据的 C 盘空间。 ## 问题

gitblog_01086的博客 411

Next AI Draw.io 离线部署完全指南:使用自托管 draw.io 替换 embed.diagrams.net

本指南面向需要在隔离网络或对公网访问受限的环境中部署 Next AI Draw.io 的场景。核心思路是通过自托管(Self-host)draw.io 实例替换默认依赖的 `embed.diagrams.net`,并以 Docker Compose 一键编排两个服务。读完你将掌握 `NEXT_PUBLIC_DRAWIO_BASE_URL` 这一构建时变量的原理、正确的镜像构建参数写法,以及浏览器可

gitblog_00441的博客 271

计算机网络自顶向下方法编程作业五:基于 Python 原始套接字实现 ICMP Ping 程序(完整代码与原理详解)

本文对应《计算机网络——自顶向下方法(第 6 版)》第四章末的套接字编程作业 5:用 Python 自己实现一个 ICMP Ping 程序。文章以作业原文翻译为骨架,结合本仓库中给出的完整实现源码([source/Ping.py](https://link.gitcode.com/i/a0af5c5b5b4d44d232e715d34eef40d2))与官方参考实现([Resource/7th-P

gitblog_00853的博客 181

Jetpack Compose Modifier完全解析:从链式原理到性能优化

声明式UI已成为现代移动开发的主流范式,Jetpack Compose作为Android新一代UI工具包,其Modifier机制是构建界面的核心基石。Modifier并非简单的属性设置器,而是一条有序的链表结构,深刻影响着测量、布局、绘制、手势交互与语义信息。理解链式顺序的洋葱模型,能够避免点击区域错位、背景覆盖异常等典型问题。通过自定义layout、drawWithContent与Modifier.Node,开发者可以高效复用UI逻辑并优化性能。本文结合工程实践,系统拆解Modifier的运作原理与封装技

weixin_33971721的博客 267

Compose Multiplatform图表性能优化:KoalaPlot渲染原理与最佳实践

KoalaPlot是一个基于Compose Multiplatform的图表绘制库,采用Kotlin语言开发,专为跨平台应用提供高效的图表渲染解决方案。本文将深入解析KoalaPlot的渲染原理,并分享提升图表性能的实用技巧,帮助开发者在保持视觉效果的同时优化应用响应速度。 ## 一、KoalaPlot渲染架构解析 KoalaPlot采用分层渲染架构,核心组件包括数据模型、布局计算和Canva

gitblog_00520的博客 411
上一篇: View 自定义 - 概览
下一篇: Compose 组件 - 下拉刷新 pullRefresh()、PullToRefreshBox()
Jomurphys
博客等级 码龄9年 1768粉丝 261原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值