← 返回索引

范围:组件编写、渲染调整与颜色动画所需的颜色工具与缩放绘制工具。涉及 Colors、UIRenderUtil 各自提供的功能、lerp/darken/alpha 在边界输入下的行为、TextDrawUtil 对缩放矩阵的集中封装、这些方法的改动波及面。

前置:元素系统 中 Element 的渲染流程。


Colors 配色工具类

Colors 是一个不可实例化的静态工具类(final + 私有构造),为组件提供不依赖渲染上下文的 ARGB 颜色计算与常用语义色常量。颜色一律以 int(ARGB,最高字节为 alpha)表示,这一点全框架通用。

lerp(a, b, t) 四通道线性插值

Colors.lerp 对 alpha、R、G、B 四个通道分别做线性插值(Math.round 取整),t = 0 返回 a,t = 1 返回 b。

  • 边界:lerp 不钳制 t。t 超出 [0, 1] 时插值结果经 & 0xFF 掩码写回,即越界值不是饱和截断,而是回绕(例如算出 256 会变成 0)。需要钳制语义时由调用方夹紧,或改用 darken(其内部先钳制 t)。
  • 适用场景:组件做悬停色、按下色、主题过渡色时,把当前色与目标色传入 lerp,进度由 Animator.get() 提供。

darken(color, t) 向黑加深

Colors.darken 先把 t 钳到 [0, 1],再执行 lerp(color, 0xFF000000, t),即把颜色向不透明黑插值;t 越大颜色越深。

  • javadoc 与实现的差异(以代码为准):darken 的 javadoc 写着「向黑色加深(保留透明度不变)」,但实现把整个颜色(含 alpha 通道)向 0xFF000000 插值,因此对半透明颜色调用 darken 后,alpha 实际会升高(更不透明),t = 1 时完全不透明。依赖「半透明保持不变」的调用方应先确认输入是否带透明度,或在调用后自行写回原 alpha。

常用颜色常量

组件默认配色优先引用这些语义常量,避免各组件直接书写 ARGB 字面量:

常量值含义
Colors.WHITE0xFFFFFFFF不透明白
Colors.WHITE_500x80FFFFFF50% 白(前景淡字)
Colors.WHITE_200x33FFFFFF20% 白(分隔线弱化)
Colors.BLACK_500x8000000050% 黑(遮罩)
Colors.BLACK_750xC000000075% 黑(重遮罩)
Colors.BLUE0xFF3B82F6强调蓝(Fluent 主色)
Colors.BLUE_500x803B82F650% 强调蓝(选中底色)
Colors.BLUE_DARK0xFF1D4ED8深强调蓝(按下态)
Colors.GRAY_300x4D80808030% 灰(禁用描边)
Colors.GRAY_500x8080808050% 灰(禁用文字)

UIRenderUtil 渲染遮罩与叠色工具

UIRenderUtil 同样是 final + 私有构造的静态类,用于渲染期最常见的临时降低透明度与近似色叠层需求,并提供渲染遮罩常用常量。

alpha(color, alpha) 透明度系数缩放

UIRenderUtil.alpha 把颜色已有的 alpha 乘以系数 alpha(四舍五入后写回),RGB 不变。

  • 边界:这是乘性缩放,不是「绝对设置为 alpha」。对 0xFFFFFFFF 传 0.5f 得到 50% 白,对本身只有 50% alpha 的颜色再传 0.5f 会降到约 25%。alpha 超过 1 或为负时结果同样经写回高位字节截断,调用方应自行保证落在 [0, 1]。
  • 适用场景:禁用态整体压暗、滚动时头部渐隐等需要在现有颜色上继续降低不透明度的场景。

lerpColor(from, to, t) 四通道插值

UIRenderUtil.lerpColor 与 Colors.lerp 一样对四通道做 Math.round 线性插值,功能等价(重复 API)。

  • 分工建议:新代码统一使用 Colors.lerp,因为其位于 Colors,与语义常量、darken 集中在一处,颜色语义更集中;lerpColor 供既有渲染调用使用。两者的差异仅为归属类不同,数学行为一致。

常用遮罩常量

这些常量供遮罩层、悬浮底、高亮底直接取用,避免在渲染代码中重复构造 alpha 值:

常量值用途
UIRenderUtil.COLOR_BLACK_SEMI0x80000000半透明黑遮罩(模态底)
UIRenderUtil.COLOR_BLACK_750xC000000075% 黑遮罩
UIRenderUtil.COLOR_BLACK_500x8000000050% 黑遮罩(与 COLOR_BLACK_SEMI 同值)
UIRenderUtil.COLOR_BLACK_250x4000000025% 黑遮罩(悬停底)
UIRenderUtil.COLOR_BLACK_120x1F00000012% 黑遮罩(弱悬停底)
UIRenderUtil.COLOR_BLUE_ACCENT0xFF3B82F6强调蓝(焦点环/指示条)
UIRenderUtil.COLOR_BLUE_ACCENT_SEMI0x803B82F6半透明强调蓝
UIRenderUtil.COLOR_BLUE_DARK0xFF1D4ED8深强调蓝(按下)
UIRenderUtil.COLOR_BLUE_LIGHT0xFF60A5FA亮强调蓝
UIRenderUtil.COLOR_BLUE_HOVER0xFF2563EB悬停强调蓝
UIRenderUtil.COLOR_WHITE0xFFFFFFFF不透明白
UIRenderUtil.COLOR_WHITE_SEMI0x80FFFFFF半透明白

TextDrawUtil 缩放绘制工具

TextDrawUtil(final + 私有构造的静态类)将「缩放渲染矩阵 + 绘制 + 还原」这一固定流程集中封装。

缩放绘制的实现位置

MC 的 GuiGraphicsExtractor.text(...) 不接受缩放参数,outline(...) 也固定画 1px 且不区分边。需要更大字号或按比例变化的描边宽度时,唯一途径是修改渲染矩阵,该矩阵来自 e.pose() 返回的 org.joml.Matrix3x2fStack(JOML 二维仿射变换栈)。

TextDrawUtil 先调用 pose().pushMatrix() 压入一层矩阵,调用 translate(x, y) 平移到目标左上角,调用 scale(s, s) 缩放,在局部坐标 (0, 0) 绘制文字,最后调用 popMatrix() 恢复原矩阵。

  • 顺序不可颠倒:translate 与 scale 都是世界系变换。先缩放再平移会使缩放围绕原点偏移。
  • push/pop 必须成对:漏掉 popMatrix() 会使后续所有绘制一并缩放。

API

方法说明
draw(e, font, text, x, y, color, scale)按 (x, y) 为左上角锚点画一行字。text 有 String 与 Component 两枚重载
drawScaledRect(e, x, y, w, h, color, scale)把 (x, y, w, h) 这块矩形按 scale 放大,用于「边框按比例粗细」
scaledWidth(font, text, scale)缩放后字形实际占的宽
scaledHeight(font, scale)缩放后字形实际占的高(一行字高即 font.lineHeight × scale)
  • 边界:scale <= 0 或 == 1 时直接调用原生 e.text(...),不操作矩阵。scaledWidth/scaledHeight 对 scale <= 0 按 1 计算,font/text 为 null 时返回 0。
  • 命中框应使用 scaledWidth/scaledHeight:缩放后字形的屏幕占位改变,使用未缩放的 font.width(...) / font.lineHeight 计算命中框会使点击区与绘制结果错位。绘制、悬停、点击三处应共用同一套取值。

用法

  • MessageFlyout 的关闭按钮:CLOSE_SCALE = 2.0f,绘制调用 TextDrawUtil.draw(...),closeGlyphX() 与命中框使用 scaledWidth/scaledHeight;定位用的内缩量由 closeInset() 按「单行居中所需」算出,距上与距右取同一个值。
  • SelectDropdown 的左侧边框:outline(...) 无法分侧也无法设宽度,所以上/下/右三条边用 fill 画满 1px,左边用 drawScaledRect(..., LEFT_BORDER_SCALE) 画半宽(LEFT_BORDER_SCALE = 0.5f)。

不变量

  • Colors 与 UIRenderUtil 必须保持可静态调用:两者都是 final + 私有构造的纯函数/常量容器,不得引入实例状态或渲染上下文依赖,组件在 measure/extractRenderState 任何阶段调用它们都必须得到相同结果。
  • 颜色一律 int ARGB:不要改成 record 或 float[4] 之类的返回类型,全框架的 fill/border/插值调用点都以 int 为契约。
  • 不要顺带修改 darken 的 alpha 行为:javadoc 与实现不一致是已知问题;若要改语义(真的保留 alpha),属于行为变更,需先排查所有 darken 调用方(见下)并同步更新 javadoc 与文档。
  • TextDrawUtil 的 pushMatrix() / popMatrix() 必须成对:不成对会污染同一帧后续所有绘制。改动这个类时优先保证配对,不要为了省事把 pop 挪进条件分支。
  • 命中框的尺寸必须与缩放一致:缩放绘制与缩放后的命中框是一对,只改其中一个会让点击区和视觉错位。

连带影响

  • 改 Colors.lerp 签名(增参、换类型)会波及至少 6 处调用:Button、ItemView、SelectBox、Toggle 等组件的颜色过渡都在直接调用它。
  • 改 darken 的钳制或 alpha 行为会波及所有用它做按下/禁用色的组件,半透明输入的颜色会随之变化。
  • 改 UIRenderUtil.alpha 的乘性语义(比如改成绝对赋值)会波及所有禁用压暗、渐隐类渲染调用。
  • 改任一常量的值会同时改变引用该常量的全部组件观感;换值前先 grep 引用点。
  • 改 TextDrawUtil.scaledWidth/scaledHeight 的算法会波及所有缩放绘制的命中框;MessageFlyout 的关闭按钮在绘制、悬停、点击三处都依赖它。

相关文档

  • 动画系统:Animator.get() 的进度传入 Colors.lerp 即得到颜色动画
  • 元素系统:Element 基类与渲染约定(颜色工具本身不在 Element 中,而由 Colors 与 UIRenderUtil 提供)
  • 自绘扩展:CustomDrawUtil、GradientPainter 负责画 fill 与 text 之外的形状,另有独立的 ui.render 包