范围:组件编写、渲染调整与颜色动画所需的颜色工具与缩放绘制工具。涉及
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.WHITE | 0xFFFFFFFF | 不透明白 |
Colors.WHITE_50 | 0x80FFFFFF | 50% 白(前景淡字) |
Colors.WHITE_20 | 0x33FFFFFF | 20% 白(分隔线弱化) |
Colors.BLACK_50 | 0x80000000 | 50% 黑(遮罩) |
Colors.BLACK_75 | 0xC0000000 | 75% 黑(重遮罩) |
Colors.BLUE | 0xFF3B82F6 | 强调蓝(Fluent 主色) |
Colors.BLUE_50 | 0x803B82F6 | 50% 强调蓝(选中底色) |
Colors.BLUE_DARK | 0xFF1D4ED8 | 深强调蓝(按下态) |
Colors.GRAY_30 | 0x4D808080 | 30% 灰(禁用描边) |
Colors.GRAY_50 | 0x80808080 | 50% 灰(禁用文字) |
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_SEMI | 0x80000000 | 半透明黑遮罩(模态底) |
UIRenderUtil.COLOR_BLACK_75 | 0xC0000000 | 75% 黑遮罩 |
UIRenderUtil.COLOR_BLACK_50 | 0x80000000 | 50% 黑遮罩(与 COLOR_BLACK_SEMI 同值) |
UIRenderUtil.COLOR_BLACK_25 | 0x40000000 | 25% 黑遮罩(悬停底) |
UIRenderUtil.COLOR_BLACK_12 | 0x1F000000 | 12% 黑遮罩(弱悬停底) |
UIRenderUtil.COLOR_BLUE_ACCENT | 0xFF3B82F6 | 强调蓝(焦点环/指示条) |
UIRenderUtil.COLOR_BLUE_ACCENT_SEMI | 0x803B82F6 | 半透明强调蓝 |
UIRenderUtil.COLOR_BLUE_DARK | 0xFF1D4ED8 | 深强调蓝(按下) |
UIRenderUtil.COLOR_BLUE_LIGHT | 0xFF60A5FA | 亮强调蓝 |
UIRenderUtil.COLOR_BLUE_HOVER | 0xFF2563EB | 悬停强调蓝 |
UIRenderUtil.COLOR_WHITE | 0xFFFFFFFF | 不透明白 |
UIRenderUtil.COLOR_WHITE_SEMI | 0x80FFFFFF | 半透明白 |
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任何阶段调用它们都必须得到相同结果。- 颜色一律
intARGB:不要改成 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的关闭按钮在绘制、悬停、点击三处都依赖它。