Flutter ShaderMask Localization: Gradient Effects and Visual Masking for Multilingual Apps
ShaderMask applies shader-based visual effects to its child, enabling gradient fades, text effects, image masking, and dynamic visual treatments. When combined with localization, ShaderMask creates stunning fade effects, gradient text, and direction-aware visual polish that adapts to different languages and reading directions. This guide covers comprehensive strategies for localizing ShaderMask widgets in Flutter multilingual applications.
Understanding ShaderMask Localization
ShaderMask widgets require localization for:
- Gradient text: Direction-aware gradient fills for headings
- Fade effects: Content fading respecting reading direction
- List overlays: Scroll fade indicators for long content
- Image effects: Gradient overlays with localized captions
- Interactive elements: State-based visual feedback
- Loading states: Shimmer and skeleton loading effects
Basic ShaderMask with Localized Content
Start with a simple gradient text example:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedShaderMaskDemo extends StatelessWidget {
const LocalizedShaderMaskDemo({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final isRtl = Directionality.of(context) == TextDirection.rtl;
return Scaffold(
appBar: AppBar(title: Text(l10n.shaderMaskDemoTitle)),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Semantics(
label: l10n.gradientTextAccessibility(l10n.heroHeadline),
child: ShaderMask(
shaderCallback: (bounds) {
return LinearGradient(
begin: isRtl ? Alignment.centerRight : Alignment.centerLeft,
end: isRtl ? Alignment.centerLeft : Alignment.centerRight,
colors: [
Theme.of(context).colorScheme.primary,
Theme.of(context).colorScheme.tertiary,
],
).createShader(bounds);
},
child: Text(
l10n.heroHeadline,
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
const SizedBox(height: 16),
Text(
l10n.heroDescription,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 32),
ShaderMask(
shaderCallback: (bounds) {
return LinearGradient(
begin: isRtl ? Alignment.centerRight : Alignment.centerLeft,
end: isRtl ? Alignment.centerLeft : Alignment.centerRight,
colors: [
const Color(0xFF6366F1),
const Color(0xFFEC4899),
],
).createShader(bounds);
},
child: ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 32,
vertical: 16,
),
),
child: Text(
l10n.getStartedButton,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
),
);
}
}
ARB File Structure for ShaderMask
{
"shaderMaskDemoTitle": "Gradient Effects",
"@shaderMaskDemoTitle": {
"description": "Title for shader mask demo page"
},
"heroHeadline": "Transform Your Ideas",
"heroDescription": "Beautiful gradient effects that adapt to any language direction",
"getStartedButton": "Get Started",
"gradientTextAccessibility": "Headline: {text}"
}
Fade Edge List
Create a list with fading edges that respect RTL:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedFadeEdgeList extends StatelessWidget {
final List<String> items;
const LocalizedFadeEdgeList({
super.key,
required this.items,
});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.fadeListTitle)),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Text(
l10n.fadeListDescription,
style: Theme.of(context).textTheme.bodyLarge,
),
),
Expanded(
child: ShaderMask(
shaderCallback: (bounds) {
return const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.transparent,
Colors.white,
Colors.white,
Colors.transparent,
],
stops: [0.0, 0.05, 0.95, 1.0],
).createShader(bounds);
},
blendMode: BlendMode.dstIn,
child: ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 16),
itemCount: items.length,
itemBuilder: (context, index) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
child: ListTile(
leading: CircleAvatar(
child: Text('${index + 1}'),
),
title: Text(items[index]),
subtitle: Text(l10n.listItemSubtitle(index + 1)),
trailing: const Icon(Icons.chevron_right),
onTap: () {},
),
);
},
),
),
),
],
),
);
}
}
class HorizontalFadeList extends StatelessWidget {
const HorizontalFadeList({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final isRtl = Directionality.of(context) == TextDirection.rtl;
return Scaffold(
appBar: AppBar(title: Text(l10n.horizontalFadeListTitle)),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Text(
l10n.categoriesLabel,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
),
SizedBox(
height: 120,
child: ShaderMask(
shaderCallback: (bounds) {
return LinearGradient(
begin: isRtl ? Alignment.centerRight : Alignment.centerLeft,
end: isRtl ? Alignment.centerLeft : Alignment.centerRight,
colors: const [
Colors.transparent,
Colors.white,
Colors.white,
Colors.transparent,
],
stops: const [0.0, 0.05, 0.95, 1.0],
).createShader(bounds);
},
blendMode: BlendMode.dstIn,
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 24),
itemCount: 10,
itemBuilder: (context, index) {
return Container(
width: 100,
margin: EdgeInsetsDirectional.only(
end: 12,
),
child: Card(
child: InkWell(
onTap: () {},
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
_getCategoryIcon(index),
size: 32,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(height: 8),
Text(
l10n.categoryName(index + 1),
style: Theme.of(context).textTheme.labelMedium,
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
),
),
);
},
),
),
),
],
),
);
}
IconData _getCategoryIcon(int index) {
const icons = [
Icons.home,
Icons.work,
Icons.school,
Icons.sports,
Icons.music_note,
Icons.movie,
Icons.restaurant,
Icons.shopping_bag,
Icons.travel_explore,
Icons.health_and_safety,
];
return icons[index % icons.length];
}
}
Gradient Card Header
Create cards with gradient header effects:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedGradientCard extends StatelessWidget {
final String title;
final String subtitle;
final String content;
final IconData icon;
final List<Color> gradientColors;
final VoidCallback? onTap;
const LocalizedGradientCard({
super.key,
required this.title,
required this.subtitle,
required this.content,
required this.icon,
this.gradientColors = const [Color(0xFF6366F1), Color(0xFF8B5CF6)],
this.onTap,
});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final isRtl = Directionality.of(context) == TextDirection.rtl;
return Semantics(
label: l10n.gradientCardAccessibility(title, subtitle),
button: onTap != null,
child: Card(
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onTap,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Gradient header with icon
ShaderMask(
shaderCallback: (bounds) {
return LinearGradient(
begin: isRtl ? Alignment.topRight : Alignment.topLeft,
end: isRtl ? Alignment.bottomLeft : Alignment.bottomRight,
colors: gradientColors,
).createShader(bounds);
},
blendMode: BlendMode.srcATop,
child: Container(
height: 100,
color: Colors.white,
child: Stack(
children: [
// Decorative pattern
Positioned(
right: isRtl ? null : -20,
left: isRtl ? -20 : null,
top: -20,
child: Opacity(
opacity: 0.3,
child: Icon(
icon,
size: 120,
color: Colors.white,
),
),
),
// Title and subtitle
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
title,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
subtitle,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.white.withOpacity(0.9),
),
),
],
),
),
],
),
),
),
// Content area
Padding(
padding: const EdgeInsets.all(16),
child: Text(
content,
style: Theme.of(context).textTheme.bodyMedium,
),
),
// Action row
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {},
child: Text(l10n.learnMoreButton),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: onTap,
child: Text(l10n.actionButton),
),
],
),
),
],
),
),
),
);
}
}
class GradientCardDemo extends StatelessWidget {
const GradientCardDemo({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.gradientCardDemoTitle)),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
LocalizedGradientCard(
icon: Icons.analytics,
title: l10n.analyticsCardTitle,
subtitle: l10n.analyticsCardSubtitle,
content: l10n.analyticsCardContent,
gradientColors: const [Color(0xFF6366F1), Color(0xFF8B5CF6)],
onTap: () {},
),
const SizedBox(height: 16),
LocalizedGradientCard(
icon: Icons.security,
title: l10n.securityCardTitle,
subtitle: l10n.securityCardSubtitle,
content: l10n.securityCardContent,
gradientColors: const [Color(0xFF059669), Color(0xFF34D399)],
onTap: () {},
),
const SizedBox(height: 16),
LocalizedGradientCard(
icon: Icons.speed,
title: l10n.performanceCardTitle,
subtitle: l10n.performanceCardSubtitle,
content: l10n.performanceCardContent,
gradientColors: const [Color(0xFFDC2626), Color(0xFFF87171)],
onTap: () {},
),
],
),
);
}
}
Shimmer Loading Effect
Create a localized shimmer loading skeleton:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedShimmerLoading extends StatefulWidget {
final bool isLoading;
final Widget child;
const LocalizedShimmerLoading({
super.key,
required this.isLoading,
required this.child,
});
@override
State<LocalizedShimmerLoading> createState() => _LocalizedShimmerLoadingState();
}
class _LocalizedShimmerLoadingState extends State<LocalizedShimmerLoading>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1500),
)..repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final isRtl = Directionality.of(context) == TextDirection.rtl;
if (!widget.isLoading) {
return widget.child;
}
return Semantics(
label: l10n.shimmerLoadingAccessibility,
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return ShaderMask(
shaderCallback: (bounds) {
final position = _controller.value * 2 - 1;
return LinearGradient(
begin: isRtl ? Alignment.centerRight : Alignment.centerLeft,
end: isRtl ? Alignment.centerLeft : Alignment.centerRight,
colors: [
Colors.grey.shade300,
Colors.grey.shade100,
Colors.grey.shade300,
],
stops: [
(position - 0.3).clamp(0.0, 1.0),
position.clamp(0.0, 1.0),
(position + 0.3).clamp(0.0, 1.0),
],
).createShader(bounds);
},
blendMode: BlendMode.srcATop,
child: child,
);
},
child: widget.child,
),
);
}
}
class ShimmerPlaceholder extends StatelessWidget {
final double width;
final double height;
final BorderRadius? borderRadius;
const ShimmerPlaceholder({
super.key,
required this.width,
required this.height,
this.borderRadius,
});
@override
Widget build(BuildContext context) {
return Container(
width: width,
height: height,
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: borderRadius ?? BorderRadius.circular(8),
),
);
}
}
class ShimmerListItem extends StatelessWidget {
const ShimmerListItem({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
const ShimmerPlaceholder(
width: 60,
height: 60,
borderRadius: BorderRadius.all(Radius.circular(30)),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ShimmerPlaceholder(
width: double.infinity,
height: 16,
borderRadius: BorderRadius.circular(4),
),
const SizedBox(height: 8),
ShimmerPlaceholder(
width: 150,
height: 12,
borderRadius: BorderRadius.circular(4),
),
],
),
),
],
),
);
}
}
class ShimmerLoadingDemo extends StatefulWidget {
const ShimmerLoadingDemo({super.key});
@override
State<ShimmerLoadingDemo> createState() => _ShimmerLoadingDemoState();
}
class _ShimmerLoadingDemoState extends State<ShimmerLoadingDemo> {
bool _isLoading = true;
List<Map<String, String>> _data = [];
@override
void initState() {
super.initState();
_loadData();
}
Future<void> _loadData() async {
setState(() => _isLoading = true);
await Future.delayed(const Duration(seconds: 3));
setState(() {
_isLoading = false;
_data = List.generate(10, (index) => {
'title': 'Item ${index + 1}',
'subtitle': 'Description for item ${index + 1}',
});
});
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(
title: Text(l10n.shimmerDemoTitle),
actions: [
IconButton(
onPressed: _loadData,
icon: const Icon(Icons.refresh),
tooltip: l10n.refreshTooltip,
),
],
),
body: LocalizedShimmerLoading(
isLoading: _isLoading,
child: _isLoading
? ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: 10,
itemBuilder: (context, index) => const ShimmerListItem(),
)
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _data.length,
itemBuilder: (context, index) {
final item = _data[index];
return ListTile(
leading: CircleAvatar(child: Text('${index + 1}')),
title: Text(item['title']!),
subtitle: Text(item['subtitle']!),
);
},
),
),
);
}
}
Text Reveal Animation
Create a gradient text reveal effect:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedTextReveal extends StatefulWidget {
final String text;
final TextStyle? style;
const LocalizedTextReveal({
super.key,
required this.text,
this.style,
});
@override
State<LocalizedTextReveal> createState() => _LocalizedTextRevealState();
}
class _LocalizedTextRevealState extends State<LocalizedTextReveal>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
)..forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final isRtl = Directionality.of(context) == TextDirection.rtl;
return Semantics(
label: l10n.textRevealAccessibility(widget.text),
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return ShaderMask(
shaderCallback: (bounds) {
final progress = _controller.value;
return LinearGradient(
begin: isRtl ? Alignment.centerRight : Alignment.centerLeft,
end: isRtl ? Alignment.centerLeft : Alignment.centerRight,
colors: [
Theme.of(context).colorScheme.onSurface,
Theme.of(context).colorScheme.onSurface,
Colors.transparent,
],
stops: [
0.0,
progress,
progress + 0.01,
],
).createShader(bounds);
},
blendMode: BlendMode.srcIn,
child: child,
);
},
child: Text(
widget.text,
style: widget.style ?? Theme.of(context).textTheme.headlineMedium,
),
),
);
}
}
class TextRevealDemo extends StatefulWidget {
const TextRevealDemo({super.key});
@override
State<TextRevealDemo> createState() => _TextRevealDemoState();
}
class _TextRevealDemoState extends State<TextRevealDemo> {
int _key = 0;
void _replay() {
setState(() => _key++);
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.textRevealDemoTitle)),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.textRevealInstructions,
style: Theme.of(context).textTheme.bodyLarge,
),
const SizedBox(height: 48),
LocalizedTextReveal(
key: ValueKey(_key),
text: l10n.revealHeadline,
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 24),
LocalizedTextReveal(
key: ValueKey(_key + 100),
text: l10n.revealSubheadline,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const Spacer(),
Center(
child: ElevatedButton.icon(
onPressed: _replay,
icon: const Icon(Icons.replay),
label: Text(l10n.replayButton),
),
),
const SizedBox(height: 32),
],
),
),
);
}
}
Radial Gradient Mask
Create a spotlight or vignette effect:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedRadialMask extends StatefulWidget {
const LocalizedRadialMask({super.key});
@override
State<LocalizedRadialMask> createState() => _LocalizedRadialMaskState();
}
class _LocalizedRadialMaskState extends State<LocalizedRadialMask> {
Offset _focusPoint = const Offset(0.5, 0.5);
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.radialMaskTitle)),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Text(
l10n.radialMaskInstructions,
style: Theme.of(context).textTheme.bodyLarge,
),
),
Expanded(
child: GestureDetector(
onPanUpdate: (details) {
final box = context.findRenderObject() as RenderBox;
final localPosition = details.localPosition;
setState(() {
_focusPoint = Offset(
(localPosition.dx / box.size.width).clamp(0.0, 1.0),
(localPosition.dy / box.size.height).clamp(0.0, 1.0),
);
});
},
onTapDown: (details) {
final box = context.findRenderObject() as RenderBox;
setState(() {
_focusPoint = Offset(
(details.localPosition.dx / box.size.width).clamp(0.0, 1.0),
(details.localPosition.dy / box.size.height).clamp(0.0, 1.0),
);
});
},
child: Semantics(
label: l10n.radialMaskAccessibility,
child: Stack(
fit: StackFit.expand,
children: [
// Background image
Image.network(
'https://picsum.photos/800/1200',
fit: BoxFit.cover,
),
// Vignette overlay
ShaderMask(
shaderCallback: (bounds) {
return RadialGradient(
center: Alignment(
_focusPoint.dx * 2 - 1,
_focusPoint.dy * 2 - 1,
),
radius: 0.8,
colors: const [
Colors.transparent,
Colors.black,
],
stops: const [0.4, 1.0],
).createShader(bounds);
},
blendMode: BlendMode.srcOver,
child: Container(
color: Colors.black.withOpacity(0.7),
),
),
// Info overlay
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Colors.black.withOpacity(0.8),
Colors.transparent,
],
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.spotlightImageTitle,
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
l10n.spotlightImageDescription,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.white.withOpacity(0.8),
),
),
],
),
),
),
],
),
),
),
),
],
),
);
}
}
Multi-Color Gradient Text
Create animated multi-color gradient text:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedAnimatedGradientText extends StatefulWidget {
final String text;
final TextStyle? style;
const LocalizedAnimatedGradientText({
super.key,
required this.text,
this.style,
});
@override
State<LocalizedAnimatedGradientText> createState() =>
_LocalizedAnimatedGradientTextState();
}
class _LocalizedAnimatedGradientTextState extends State<LocalizedAnimatedGradientText>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 3),
)..repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final isRtl = Directionality.of(context) == TextDirection.rtl;
return Semantics(
label: l10n.animatedGradientAccessibility(widget.text),
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return ShaderMask(
shaderCallback: (bounds) {
final offset = _controller.value * 2;
return LinearGradient(
begin: isRtl ? Alignment.centerRight : Alignment.centerLeft,
end: isRtl ? Alignment.centerLeft : Alignment.centerRight,
colors: const [
Color(0xFF6366F1),
Color(0xFFEC4899),
Color(0xFF8B5CF6),
Color(0xFF06B6D4),
Color(0xFF6366F1),
],
stops: [
(offset - 0.5).clamp(0.0, 1.0),
(offset - 0.25).clamp(0.0, 1.0),
offset.clamp(0.0, 1.0),
(offset + 0.25).clamp(0.0, 1.0),
(offset + 0.5).clamp(0.0, 1.0),
],
).createShader(bounds);
},
child: child,
);
},
child: Text(
widget.text,
style: (widget.style ?? Theme.of(context).textTheme.headlineLarge)?.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
);
}
}
class AnimatedGradientTextDemo extends StatelessWidget {
const AnimatedGradientTextDemo({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: Text(l10n.animatedGradientTitle),
backgroundColor: Colors.transparent,
foregroundColor: Colors.white,
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
LocalizedAnimatedGradientText(
text: l10n.brandName,
style: Theme.of(context).textTheme.displayMedium,
),
const SizedBox(height: 16),
Text(
l10n.brandTagline,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Colors.white.withOpacity(0.7),
),
textAlign: TextAlign.center,
),
],
),
),
),
);
}
}
Complete ARB File for ShaderMask
{
"@@locale": "en",
"shaderMaskDemoTitle": "Gradient Effects",
"heroHeadline": "Transform Your Ideas",
"heroDescription": "Beautiful gradient effects that adapt to any language direction",
"getStartedButton": "Get Started",
"gradientTextAccessibility": "Headline: {text}",
"@gradientTextAccessibility": {
"placeholders": {
"text": {"type": "String"}
}
},
"fadeListTitle": "Fade Edge List",
"fadeListDescription": "Scroll to see the fade effect at the edges",
"listItemSubtitle": "Additional details for item {number}",
"@listItemSubtitle": {
"placeholders": {
"number": {"type": "int"}
}
},
"horizontalFadeListTitle": "Category Browser",
"categoriesLabel": "Categories",
"categoryName": "Category {number}",
"@categoryName": {
"placeholders": {
"number": {"type": "int"}
}
},
"gradientCardAccessibility": "{title}: {subtitle}",
"@gradientCardAccessibility": {
"placeholders": {
"title": {"type": "String"},
"subtitle": {"type": "String"}
}
},
"gradientCardDemoTitle": "Feature Cards",
"analyticsCardTitle": "Analytics",
"analyticsCardSubtitle": "Real-time insights",
"analyticsCardContent": "Track your performance metrics with detailed analytics and customizable dashboards.",
"securityCardTitle": "Security",
"securityCardSubtitle": "Enterprise-grade protection",
"securityCardContent": "Keep your data safe with advanced encryption and multi-factor authentication.",
"performanceCardTitle": "Performance",
"performanceCardSubtitle": "Lightning fast",
"performanceCardContent": "Optimized for speed with intelligent caching and lazy loading.",
"learnMoreButton": "Learn More",
"actionButton": "Get Started",
"shimmerLoadingAccessibility": "Content is loading, please wait",
"shimmerDemoTitle": "Loading Skeleton",
"refreshTooltip": "Refresh data",
"textRevealAccessibility": "Revealing text: {text}",
"@textRevealAccessibility": {
"placeholders": {
"text": {"type": "String"}
}
},
"textRevealDemoTitle": "Text Reveal Effect",
"textRevealInstructions": "Watch the text reveal from the reading direction start",
"revealHeadline": "Welcome to the Future",
"revealSubheadline": "Where innovation meets elegance in every detail",
"replayButton": "Replay Animation",
"radialMaskTitle": "Spotlight Effect",
"radialMaskInstructions": "Tap or drag to move the spotlight focus",
"radialMaskAccessibility": "Interactive spotlight effect on image",
"spotlightImageTitle": "Featured Destination",
"spotlightImageDescription": "Explore beautiful places with our spotlight view mode",
"animatedGradientAccessibility": "Animated gradient text: {text}",
"@animatedGradientAccessibility": {
"placeholders": {
"text": {"type": "String"}
}
},
"animatedGradientTitle": "Brand Showcase",
"brandName": "FLUTTER",
"brandTagline": "Build beautiful, natively compiled applications"
}
Best Practices Summary
- Respect text direction: Use isRtl to flip gradients for RTL languages
- Use appropriate blend modes: BlendMode.srcIn for text, BlendMode.dstIn for fades
- Provide semantic labels: Describe gradient effects for accessibility
- Animate smoothly: Use AnimationController for dynamic gradient effects
- Consider performance: Complex shaders can be expensive on older devices
- Test across languages: Verify gradients look correct with different text lengths
- Combine with clipping: Use ClipRect to constrain shader effects
- Handle edge cases: Ensure shaders work at all container sizes
- Use meaningful colors: Choose gradient colors that convey meaning
- Maintain readability: Ensure text remains legible with gradient effects
Conclusion
ShaderMask enables powerful visual effects like gradient text, fade edges, shimmer loading, and spotlight effects that can adapt to any language direction. By creating direction-aware shaders and providing proper accessibility labels, you can build visually stunning applications that work seamlessly for users worldwide. The key is balancing visual appeal with performance and accessibility, ensuring that shader effects enhance rather than distract from the content.
Remember to test your shader effects across different devices and screen sizes, and always consider the cultural implications of color choices in your gradient designs for international audiences.