如何使CompositedTransformFollower缩放容器但不缩放装饰边框厚度?
解决Flutter缩放时选框边框厚度保持固定物理像素的问题
核心思路是让边框的绘制抵消部件缩放的影响,或在不受缩放作用的层级绘制边框,以下是几种可行方案:
方案1:通过逆缩放系数调整边框宽度
当部件被缩放k倍时,将边框的逻辑宽度设置为「目标物理像素值 / (设备像素比 × k)」,这样渲染后的实际物理厚度就能保持固定。
实现步骤
- 从控制部件缩放的
Matrix4变换中提取缩放倍数(如transform.getMaxScaleOnAxis())。 - 通过
MediaQuery获取设备像素比devicePixelRatio,计算抵消缩放后的逻辑边框宽度:borderWidth = 目标物理像素值 / (devicePixelRatio * 缩放倍数)。 - 结合
CompositedTransformFollower获取部件位置,用CustomPaint绘制边框。
代码示例
class FixedThicknessMarquee extends StatelessWidget { final GlobalKey targetKey; final double targetPhysicalPixelThickness; final Matrix4 transform; const FixedThicknessMarquee({ super.key, required this.targetKey, required this.targetPhysicalPixelThickness, required this.transform, }); @override Widget build(BuildContext context) { final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; final scale = transform.getMaxScaleOnAxis(); final borderWidth = targetPhysicalPixelThickness / (devicePixelRatio * scale); return CompositedTransformFollower( link: CompositedTransformTarget( key: targetKey, child: const SizedBox.shrink(), ), child: CustomPaint( painter: _MarqueePainter( borderWidth: borderWidth, color: Colors.blue, ), child: SizedBox( width: scale * (targetKey.currentContext?.size?.width ?? 0), height: scale * (targetKey.currentContext?.size?.height ?? 0), ), ), ); } } class _MarqueePainter extends CustomPainter { final double borderWidth; final Color color; _MarqueePainter({required this.borderWidth, required this.color}); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = color ..style = PaintingStyle.stroke ..strokeWidth = borderWidth; canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint); } @override bool shouldRepaint(covariant _MarqueePainter oldDelegate) { return oldDelegate.borderWidth != borderWidth || oldDelegate.color != color; } }
方案2:在独立Overlay层绘制边框(不跟随部件缩放)
直接在Overlay中根据部件的全局位置和缩放后的尺寸绘制边框,由于Overlay层不受部件transform的影响,边框厚度可直接设置为对应物理像素的逻辑值,无需额外调整。
实现步骤
- 用
GlobalKey获取目标部件的RenderBox。 - 将部件的局部坐标转换为全局坐标,结合缩放后的宽高计算边框的全局Rect。
- 在OverlayEntry中绘制边框,边框宽度设为
目标物理像素值 / devicePixelRatio(转换为逻辑像素)。
代码示例
void showFixedThicknessMarquee(GlobalKey targetKey, double physicalThickness) { final renderBox = targetKey.currentContext?.findRenderObject() as RenderBox?; if (renderBox == null) return; final overlay = Overlay.of(targetKey.currentContext!); final devicePixelRatio = MediaQuery.of(targetKey.currentContext!).devicePixelRatio; final borderWidth = physicalThickness / devicePixelRatio; final globalPosition = renderBox.localToGlobal(Offset.zero); final scale = renderBox.transform.getMaxScaleOnAxis(); final scaledSize = renderBox.size * scale; final entry = OverlayEntry( builder: (context) => Positioned( left: globalPosition.dx, top: globalPosition.dy, width: scaledSize.width, height: scaledSize.height, child: CustomPaint( painter: _MarqueePainter( borderWidth: borderWidth, color: Colors.blue, ), ), ), ); overlay.insert(entry); }
方案3:画布逆变换绘制边框
在绘制边框前,对画布应用部件缩放的逆变换,绘制完成后恢复画布状态,这样边框的宽度不会被部件的transform缩放影响。
代码示例片段
@override void paint(Canvas canvas, Size size) { final scale = transform.getMaxScaleOnAxis(); final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; final borderWidth = targetPhysicalPixelThickness / devicePixelRatio; canvas.save(); canvas.scale(1 / scale); final paint = Paint() ..color = Colors.blue ..style = PaintingStyle.stroke ..strokeWidth = borderWidth; // 绘制时需乘以缩放倍数,保证边框位置和尺寸匹配部件 canvas.drawRect(Rect.fromLTWH(0, 0, size.width * scale, size.height * scale), paint); canvas.restore(); }
内容的提问来源于stack exchange,提问作者Adam
相关产品推荐
相关产品推荐

