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

Flutter设置Text的height为1时ShaderMask渐变失效问题

解决Flutter中Text设置height后ShaderMask渐变覆盖不全的问题

问题复现

当为Text组件设置TextStyle(height: 1)并通过ShaderMask添加渐变效果时,字母的下伸部分(如"y"的下端)会出现黑色缺口,渐变未覆盖该区域;未指定height时则正常。

未指定height效果:
未指定height效果

设置height: 1时效果:
设置height为1效果

原代码示例:

import 'package:flutter/material.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(body: Body()),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    const style = TextStyle(
      fontSize: 48,
      fontWeight: FontWeight.bold,
      color: Colors.black,
      height: 1,
    );
    return Center(
      child: ShaderMask(
        blendMode: BlendMode.srcIn,
        shaderCallback: (bounds) => _gradient.createShader(bounds),
        child: const Text('Why?', style: style),
      ),
    );
  }
}

const _gradient = LinearGradient(colors: [Colors.red, Colors.red]);

原因分析

TextStyle.height是行高系数,设置为1时,Text的布局高度等于fontSize,但部分字形(如y、g、p)的下伸/上伸部分会超出这个布局高度范围。而ShaderMask的shaderCallback传入的bounds是Text的布局矩形,并非实际字形的绘制范围,导致超出布局的字形部分无法被渐变覆盖,显示Text的原始颜色(黑色)。

解决方案

方法1:Padding+Transform快速适配

给Text添加底部padding扩展ShaderMask的范围,再通过Transform.translate抵消padding对视觉行高的影响,确保渐变覆盖完整字形:

@override
Widget build(BuildContext context) {
  const style = TextStyle(
    fontSize: 48,
    fontWeight: FontWeight.bold,
    color: Colors.black,
    height: 1,
  );
  return Center(
    child: ShaderMask(
      blendMode: BlendMode.srcIn,
      shaderCallback: (bounds) => _gradient.createShader(bounds),
      child: Transform.translate(
        offset: const Offset(0, -4), // 向上偏移抵消padding高度
        child: Padding(
          padding: const EdgeInsets.only(bottom: 8.0), // 扩展底部范围,值根据字体调整
          child: const Text('Why?', style: style),
        ),
      ),
    ),
  );
}

方法2:自定义组件适配任意height值

通过TextPainter计算字形的实际边界,动态调整Shader的范围,彻底解决不同height值下的覆盖问题:

import 'package:flutter/material.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(body: Body()),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Center(
      child: GradientText(
        'Why?',
        style: const TextStyle(
          fontSize: 48,
          fontWeight: FontWeight.bold,
          color: Colors.black,
          height: 1,
        ),
        gradient: const LinearGradient(colors: [Colors.red, Colors.red]),
      ),
    );
  }
}

class GradientText extends StatelessWidget {
  const GradientText(
    this.text, {
    super.key,
    required this.style,
    required this.gradient,
  });

  final String text;
  final TextStyle style;
  final Gradient gradient;

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final textPainter = TextPainter(
          text: TextSpan(text: text, style: style),
          textDirection: TextDirection.ltr,
        )..layout(maxWidth: constraints.maxWidth);

        // 计算所有字形的合并边界
        final glyphBounds = textPainter.computeMetrics().fold(
          Rect.zero,
          (previous, metric) => previous.union(metric.boundingBox),
        );

        return ShaderMask(
          blendMode: BlendMode.srcIn,
          shaderCallback: (bounds) {
            // 调整Shader范围,使其覆盖完整字形
            final adjustedBounds = bounds
                .translate(0, bounds.center.dy - glyphBounds.center.dy)
                .expandToInclude(glyphBounds);
            return gradient.createShader(adjustedBounds);
          },
          child: Text(text, style: style),
        );
      },
    );
  }
}

这个自定义组件会自动计算字形的实际绘制范围,确保渐变完全覆盖所有字符,不受TextStyle.height的影响。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 17:42:10