Flutter设置Text的height为1时ShaderMask渐变失效问题
解决Flutter中Text设置height后ShaderMask渐变覆盖不全的问题
问题复现
当为Text组件设置TextStyle(height: 1)并通过ShaderMask添加渐变效果时,字母的下伸部分(如"y"的下端)会出现黑色缺口,渐变未覆盖该区域;未指定height时则正常。
未指定height效果:
设置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
相关产品推荐
相关产品推荐

