You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使CompositedTransformFollower缩放容器但不缩放装饰边框厚度?

解决Flutter缩放时选框边框厚度保持固定物理像素的问题

核心思路是让边框的绘制抵消部件缩放的影响,或在不受缩放作用的层级绘制边框,以下是几种可行方案:

方案1:通过逆缩放系数调整边框宽度

当部件被缩放k倍时,将边框的逻辑宽度设置为「目标物理像素值 / (设备像素比 × k)」,这样渲染后的实际物理厚度就能保持固定。

实现步骤

  1. 从控制部件缩放的Matrix4变换中提取缩放倍数(如transform.getMaxScaleOnAxis())。
  2. 通过MediaQuery获取设备像素比devicePixelRatio,计算抵消缩放后的逻辑边框宽度:borderWidth = 目标物理像素值 / (devicePixelRatio * 缩放倍数)。
  3. 结合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的影响,边框厚度可直接设置为对应物理像素的逻辑值,无需额外调整。

实现步骤

  1. 用GlobalKey获取目标部件的RenderBox。
  2. 将部件的局部坐标转换为全局坐标,结合缩放后的宽高计算边框的全局Rect。
  3. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 05:56:06