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

使用ShaderMask为带阴影文本添加LinearGradient时出现透明边框问题

Flutter 渐变文本阴影出现透明边框的解决方案

我在深色主题下实现带渐变匹配阴影的文本效果,用ShaderMask给Text叠加LinearGradient后,文本周围出现明显的微弱透明边框,预期应该是平滑的阴影过渡。通过Flutter DevTools Inspector追踪发现,这个透明边框源自Text组件本身。试过给RichText加Padding、用MediaQuery.removePadding包裹Text、更换ShaderMask的所有混合模式,都没能解决问题。

参考效果:
带有水平渐变和阴影的文本,文本周围有可见的微弱透明边框

最小复现代码:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          primary: Colors.black54,
          seedColor: Colors.black,
          brightness: Brightness.dark,
        ),
      ),
      home: const MyHomePage(title: 'ShaderMask Shadow'),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.primary,
        title: Text(title),
      ),
      body: Center(
        child: RichText(
          text: TextSpan(
            children: [
              WidgetSpan(
                child: ShaderMask(
                  blendMode: BlendMode.srcATop,
                  shaderCallback: (bounds) =>
                      LinearGradient(
                        colors: [
                          Colors.blue.shade500,
                          const Color.fromARGB(255, 181, 12, 207),
                          Colors.blue.shade500,
                          const Color.fromARGB(255, 181, 12, 207),
                          Colors.blue.shade500,
                        ],
                      ).createShader(
                        Rect.fromLTWH(0, 0, bounds.width, bounds.height),
                      ),
                  child: Text(
                    "THIS IS THE TEXT",
                    style: Theme.of(context).textTheme.headlineLarge!.copyWith(
                      fontSize: 69,
                      shadows: <Shadow>[
                        Shadow(
                          offset: const Offset(0.0, 0.0),
                          blurRadius: 16.0,
                          color: Colors.blue.shade500.withValues(alpha: 0.4),
                        ),
                        Shadow(
                          offset: const Offset(0.0, 0.0),
                          blurRadius: 16.0,
                          color: const Color.fromARGB(255, 181, 12, 207).withValues(alpha: 0.4),
                        ),
                        Shadow(
                          offset: const Offset(0.0, 0.0),
                          blurRadius: 16.0,
                          color: Colors.blue.shade500.withValues(alpha: 0.4),
                        ),
                        Shadow(
                          offset: const Offset(0.0, 0.0),
                          blurRadius: 16.0,
                          color: const Color.fromARGB(255, 181, 12, 207).withValues(alpha: 0.4),
                        ),
                        Shadow(
                          offset: const Offset(0.0, 0.0),
                          blurRadius: 16.0,
                          color: Colors.blue.shade500.withValues(alpha: 0.4),
                        ),
                      ],
                    ),
                    textAlign: TextAlign.center,
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

问题原因

透明边框是因为ShaderMask的裁剪范围仅覆盖Text的原始字形区域,而Text的阴影是绘制在组件布局边界内,ShaderMask的透明区域(未被渐变覆盖的部分)会透出阴影边缘,形成边框感。同时叠加多个同位置阴影也会导致阴影边缘透明度叠加不均,加重这个问题。

解决方案

最稳定的方法是用Stack分离阴影层和渐变文本层,让阴影独立绘制,不受ShaderMask影响:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          primary: Colors.black54,
          seedColor: Colors.black,
          brightness: Brightness.dark,
        ),
      ),
      home: const MyHomePage(title: 'ShaderMask Shadow'),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  Widget build(BuildContext context) {
    final baseTextStyle = Theme.of(context).textTheme.headlineLarge!.copyWith(
          fontSize: 69,
        );
    const targetText = "THIS IS THE TEXT";

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.primary,
        title: Text(title),
      ),
      body: Center(
        child: Stack(
          alignment: Alignment.center,
          children: [
            // 底层:仅显示阴影,文本设为透明
            Text(
              targetText,
              style: baseTextStyle.copyWith(
                color: Colors.transparent,
                shadows: const [
                  Shadow(
                    offset: Offset(0.0, 0.0),
                    blurRadius: 16.0,
                    color: Color.fromARGB(102, 59, 130, 246), // 蓝色alpha=0.4
                  ),
                  Shadow(
                    offset: Offset(0.0, 0.0),
                    blurRadius: 16.0,
                    color: Color.fromARGB(102, 181, 12, 207), // 紫色alpha=0.4
                  ),
                  Shadow(
                    offset: Offset(0.0, 0.0),
                    blurRadius: 16.0,
                    color: Color.fromARGB(102, 59, 130, 246),
                  ),
                  Shadow(
                    offset: Offset(0.0, 0.0),
                    blurRadius: 16.0,
                    color: Color.fromARGB(102, 181, 12, 207),
                  ),
                  Shadow(
                    offset: Offset(0.0, 0.0),
                    blurRadius: 16.0,
                    color: Color.fromARGB(102, 59, 130, 246),
                  ),
                ],
              ),
              textAlign: TextAlign.center,
            ),
            // 上层:仅显示渐变文本,无阴影
            ShaderMask(
              blendMode: BlendMode.srcIn, // 该模式仅在文本不透明区域应用渐变
              shaderCallback: (bounds) => LinearGradient(
                colors: [
                  Colors.blue.shade500,
                  const Color.fromARGB(255, 181, 12, 207),
                  Colors.blue.shade500,
                  const Color.fromARGB(255, 181, 12, 207),
                  Colors.blue.shade500,
                ],
              ).createShader(Rect.fromLTWH(0, 0, bounds.width, bounds.height)),
              child: Text(
                targetText,
                style: baseTextStyle,
                textAlign: TextAlign.center,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

修改说明

  1. 用Stack将阴影和渐变文本分层,阴影层独立绘制,避免ShaderMask的裁剪影响
  2. 把ShaderMask的混合模式改为BlendMode.srcIn,这个模式更适配文本渐变场景,只会在文本的不透明区域渲染渐变,消除多余的透明区域
  3. 阴影层的文本颜色设为透明,仅保留阴影效果,避免和上层渐变文本重叠

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.02 05:57:28