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

Flutter中如何在Stack组件实现局部穿透显示下层Widget?

需求可实现,正确方案如下

核心思路

你的问题出在单独的透明绘制无法影响上层全屏Widget的显示——之前的TransparentCenterArea只是在自己的小区域内画了透明圆,并没有修改上层Widget(如Widget2、Widget3)的像素。正确的做法是利用**图层混合模式(BlendMode.clear)**在目标Widget上挖除指定圆形区域,结合嵌套Stack结构让遮罩直接作用于需要穿透的Widget。

完整实现代码

import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(
    home: TransparentAreaDemo(),
  ));
}

class TransparentAreaDemo extends StatefulWidget {
  const TransparentAreaDemo({super.key});

  @override
  State<TransparentAreaDemo> createState() => _TransparentAreaDemoState();
}

class _TransparentAreaDemoState extends State<TransparentAreaDemo> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _radiusAnimation;

  @override
  void initState() {
    super.initState();
    // 初始化动画控制器,实现半径从50到150的往复动画
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    )..repeat(reverse: true);
    _radiusAnimation = Tween<double>(begin: 50, end: 150).animate(_controller);
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('穿透区域示例')),
      body: Stack(
        children: [
          // 最底层Widget
          Widget1(),
          // 第二层Widget + 穿透遮罩(带动画)
          Stack(
            children: [
              Widget2(),
              AnimatedBuilder(
                animation: _radiusAnimation,
                builder: (context, child) {
                  return TransparentCenterArea(radius: _radiusAnimation.value);
                },
              ),
            ],
          ),
          // 最上层Widget + 固定半径穿透遮罩
          Stack(
            children: [
              Widget3(),
              TransparentCenterArea(radius: 100),
            ],
          ),
        ],
      ),
    );
  }
}

class TransparentCenterArea extends StatelessWidget {
  final double radius;

  const TransparentCenterArea({super.key, required this.radius});

  @override
  Widget build(BuildContext context) {
    // 全屏布局,确保遮罩覆盖目标Widget
    return SizedBox.expand(
      child: CustomPaint(
        painter: _TransparentAreaPainter(radius: radius),
      ),
    );
  }
}

class _TransparentAreaPainter extends CustomPainter {
  final double radius;

  _TransparentAreaPainter({required this.radius});

  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    
    // 保存图层,是BlendMode.clear生效的关键
    final layerBounds = Rect.fromLTWH(0, 0, size.width, size.height);
    canvas.saveLayer(layerBounds, Paint());

    // 用BlendMode.clear清除圆形区域的像素,露出下层内容
    final clearPaint = Paint()..blendMode = BlendMode.clear;
    canvas.drawCircle(center, radius, clearPaint);

    canvas.restore();
  }

  @override
  bool shouldRepaint(_TransparentAreaPainter oldDelegate) {
    // 半径变化时触发重绘,支持动画
    return oldDelegate.radius != radius;
  }
}

class Widget1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue,
      child: const Center(child: Text('Widget 1', style: TextStyle(color: Colors.white, fontSize: 24))),
    );
  }
}

class Widget2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.green,
      child: const Center(child: Text('Widget 2', style: TextStyle(color: Colors.white, fontSize: 24))),
    );
  }
}

class Widget3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.orange,
      child: const Center(child: Text('Widget 3', style: TextStyle(color: Colors.white, fontSize: 24))),
    );
  }
}

关键细节说明

  1. 嵌套Stack结构:每个需要穿透的全屏Widget必须和TransparentCenterArea放在同一个子Stack中,确保遮罩直接覆盖目标Widget,而不是作为独立元素存在于主Stack。
  2. 图层保存(saveLayer):必须调用canvas.saveLayer才能让BlendMode.clear正确作用于下层Widget的像素,否则混合模式可能不生效。
  3. 动画支持:通过AnimatedBuilder监听动画值变化,触发CustomPainter重绘,实现圆形区域的平滑展开/收缩效果。
  4. 通用适用性:即使目标Widget是复杂内容(非纯色背景),该方案依然有效,因为BlendMode.clear是直接清除像素,与下层内容无关。

内容的提问来源于stack exchange,提问作者MetaStack

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 20:06:01