Flutter AnimatedSlide Localization: Directional Transitions, Panel Animations, and RTL-Aware Sliding
AnimatedSlide provides smooth sliding animations relative to a widget's size. Proper localization ensures that directional slides, panel transitions, and reveal animations work seamlessly across languages with correct RTL handling. This guide covers comprehensive strategies for localizing AnimatedSlide widgets in Flutter.
Understanding AnimatedSlide Localization
AnimatedSlide widgets require localization for:
- RTL-aware sliding: Ensuring slide direction respects text direction
- Panel transitions: Side panels and drawers that slide from correct edge
- Reveal animations: Content that slides in from appropriate directions
- Navigation transitions: Page and element transitions with proper direction
- Notification slides: Alerts that appear from locale-appropriate edges
- Accessibility announcements: Screen reader feedback for slide transitions
Basic AnimatedSlide with RTL Support
Start with a simple sliding panel that respects text direction:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedSlidingPanel extends StatefulWidget {
const LocalizedSlidingPanel({super.key});
@override
State<LocalizedSlidingPanel> createState() => _LocalizedSlidingPanelState();
}
class _LocalizedSlidingPanelState extends State<LocalizedSlidingPanel> {
bool _isPanelVisible = false;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final isRtl = Directionality.of(context) == TextDirection.rtl;
return Scaffold(
appBar: AppBar(
title: Text(l10n.slidingPanelTitle),
actions: [
IconButton(
icon: Icon(_isPanelVisible ? Icons.close : Icons.menu),
tooltip: _isPanelVisible ? l10n.hidePanel : l10n.showPanel,
onPressed: () => setState(() => _isPanelVisible = !_isPanelVisible),
),
],
),
body: Stack(
children: [
// Main content
Center(
child: Text(l10n.mainContent),
),
// Sliding panel
Positioned(
top: 0,
bottom: 0,
right: isRtl ? null : 0,
left: isRtl ? 0 : null,
child: AnimatedSlide(
offset: _isPanelVisible
? Offset.zero
: Offset(isRtl ? -1 : 1, 0),
duration: const Duration(milliseconds: 300),
curve: Curves.easeOutCubic,
child: Semantics(
hidden: !_isPanelVisible,
label: l10n.sidePanelAccessibility,
child: Container(
width: 280,
color: Theme.of(context).colorScheme.surface,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Text(
l10n.panelTitle,
style: Theme.of(context).textTheme.titleLarge,
),
),
const Divider(),
ListTile(
leading: const Icon(Icons.home),
title: Text(l10n.menuHome),
onTap: () {},
),
ListTile(
leading: const Icon(Icons.settings),
title: Text(l10n.menuSettings),
onTap: () {},
),
ListTile(
leading: const Icon(Icons.help),
title: Text(l10n.menuHelp),
onTap: () {},
),
],
),
),
),
),
),
],
),
);
}
}
ARB File Structure for AnimatedSlide
{
"slidingPanelTitle": "Sliding Panel",
"@slidingPanelTitle": {
"description": "Title for sliding panel demo"
},
"showPanel": "Show panel",
"@showPanel": {
"description": "Tooltip for showing panel"
},
"hidePanel": "Hide panel",
"@hidePanel": {
"description": "Tooltip for hiding panel"
},
"mainContent": "Main content area",
"@mainContent": {
"description": "Placeholder for main content"
},
"sidePanelAccessibility": "Side navigation panel",
"@sidePanelAccessibility": {
"description": "Accessibility label for side panel"
},
"panelTitle": "Navigation",
"@panelTitle": {
"description": "Title in side panel"
},
"menuHome": "Home",
"menuSettings": "Settings",
"menuHelp": "Help"
}
Toast Notifications with Sliding Animation
Create toast notifications that slide from the appropriate edge:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
enum ToastType { success, error, warning, info }
class LocalizedSlidingToast extends StatefulWidget {
const LocalizedSlidingToast({super.key});
@override
State<LocalizedSlidingToast> createState() => _LocalizedSlidingToastState();
}
class _LocalizedSlidingToastState extends State<LocalizedSlidingToast> {
final List<_ToastItem> _toasts = [];
int _toastId = 0;
void _showToast(ToastType type, String message) {
final id = _toastId++;
setState(() {
_toasts.add(_ToastItem(id: id, type: type, message: message));
});
Future.delayed(const Duration(seconds: 3), () {
if (mounted) {
setState(() {
_toasts.removeWhere((t) => t.id == id);
});
}
});
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.toastDemoTitle)),
body: Stack(
children: [
Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
onPressed: () => _showToast(ToastType.success, l10n.successMessage),
icon: const Icon(Icons.check_circle),
label: Text(l10n.showSuccessToast),
style: ElevatedButton.styleFrom(backgroundColor: Colors.green),
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: () => _showToast(ToastType.error, l10n.errorMessage),
icon: const Icon(Icons.error),
label: Text(l10n.showErrorToast),
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: () => _showToast(ToastType.warning, l10n.warningMessage),
icon: const Icon(Icons.warning),
label: Text(l10n.showWarningToast),
style: ElevatedButton.styleFrom(backgroundColor: Colors.orange),
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: () => _showToast(ToastType.info, l10n.infoMessage),
icon: const Icon(Icons.info),
label: Text(l10n.showInfoToast),
style: ElevatedButton.styleFrom(backgroundColor: Colors.blue),
),
],
),
),
// Toast container
Positioned(
top: 16,
left: 16,
right: 16,
child: Column(
children: _toasts.map((toast) {
return _SlidingToastWidget(
key: ValueKey(toast.id),
toast: toast,
onDismiss: () {
setState(() {
_toasts.removeWhere((t) => t.id == toast.id);
});
},
);
}).toList(),
),
),
],
),
);
}
}
class _ToastItem {
final int id;
final ToastType type;
final String message;
_ToastItem({required this.id, required this.type, required this.message});
}
class _SlidingToastWidget extends StatefulWidget {
final _ToastItem toast;
final VoidCallback onDismiss;
const _SlidingToastWidget({
super.key,
required this.toast,
required this.onDismiss,
});
@override
State<_SlidingToastWidget> createState() => _SlidingToastWidgetState();
}
class _SlidingToastWidgetState extends State<_SlidingToastWidget> {
bool _isVisible = false;
@override
void initState() {
super.initState();
Future.microtask(() {
if (mounted) setState(() => _isVisible = true);
});
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return AnimatedSlide(
offset: _isVisible ? Offset.zero : const Offset(0, -1),
duration: const Duration(milliseconds: 300),
curve: Curves.easeOutCubic,
child: AnimatedOpacity(
opacity: _isVisible ? 1.0 : 0.0,
duration: const Duration(milliseconds: 200),
child: Semantics(
liveRegion: true,
label: widget.toast.message,
child: Card(
margin: const EdgeInsets.only(bottom: 8),
color: _getToastColor(widget.toast.type),
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
Icon(
_getToastIcon(widget.toast.type),
color: Colors.white,
),
const SizedBox(width: 12),
Expanded(
child: Text(
widget.toast.message,
style: const TextStyle(color: Colors.white),
),
),
IconButton(
icon: const Icon(Icons.close, color: Colors.white),
onPressed: widget.onDismiss,
tooltip: l10n.dismissToast,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
],
),
),
),
),
),
);
}
Color _getToastColor(ToastType type) {
return switch (type) {
ToastType.success => Colors.green,
ToastType.error => Colors.red,
ToastType.warning => Colors.orange,
ToastType.info => Colors.blue,
};
}
IconData _getToastIcon(ToastType type) {
return switch (type) {
ToastType.success => Icons.check_circle,
ToastType.error => Icons.error,
ToastType.warning => Icons.warning,
ToastType.info => Icons.info,
};
}
}
Staggered List Animation
Create a list with staggered slide-in animations:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedStaggeredList extends StatefulWidget {
const LocalizedStaggeredList({super.key});
@override
State<LocalizedStaggeredList> createState() => _LocalizedStaggeredListState();
}
class _LocalizedStaggeredListState extends State<LocalizedStaggeredList> {
bool _isLoaded = false;
@override
void initState() {
super.initState();
Future.delayed(const Duration(milliseconds: 100), () {
if (mounted) setState(() => _isLoaded = true);
});
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final isRtl = Directionality.of(context) == TextDirection.rtl;
final items = [
_ListItemData(Icons.star, l10n.featureOne, l10n.featureOneDesc),
_ListItemData(Icons.security, l10n.featureTwo, l10n.featureTwoDesc),
_ListItemData(Icons.speed, l10n.featureThree, l10n.featureThreeDesc),
_ListItemData(Icons.language, l10n.featureFour, l10n.featureFourDesc),
_ListItemData(Icons.support, l10n.featureFive, l10n.featureFiveDesc),
];
return Scaffold(
appBar: AppBar(
title: Text(l10n.featuresTitle),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
tooltip: l10n.replayAnimation,
onPressed: () {
setState(() => _isLoaded = false);
Future.delayed(const Duration(milliseconds: 100), () {
if (mounted) setState(() => _isLoaded = true);
});
},
),
],
),
body: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
final delay = index * 100;
return TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: _isLoaded ? 1.0 : 0.0),
duration: Duration(milliseconds: 400 + delay),
curve: Curves.easeOutCubic,
builder: (context, value, child) {
return AnimatedSlide(
offset: Offset(
(1 - value) * (isRtl ? -0.5 : 0.5),
0,
),
duration: Duration.zero,
child: AnimatedOpacity(
opacity: value,
duration: Duration.zero,
child: child,
),
);
},
child: Card(
margin: const EdgeInsets.only(bottom: 12),
child: ListTile(
leading: CircleAvatar(
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
child: Icon(
item.icon,
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
title: Text(item.title),
subtitle: Text(item.description),
trailing: const Icon(Icons.chevron_right),
),
),
);
},
),
);
}
}
class _ListItemData {
final IconData icon;
final String title;
final String description;
_ListItemData(this.icon, this.title, this.description);
}
Bottom Sheet with Slide Animation
Create a custom bottom sheet with slide-up animation:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedSlidingBottomSheet extends StatefulWidget {
const LocalizedSlidingBottomSheet({super.key});
@override
State<LocalizedSlidingBottomSheet> createState() => _LocalizedSlidingBottomSheetState();
}
class _LocalizedSlidingBottomSheetState extends State<LocalizedSlidingBottomSheet> {
bool _isSheetVisible = false;
String? _selectedOption;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.bottomSheetDemoTitle)),
body: Stack(
children: [
Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (_selectedOption != null) ...[
Text(
l10n.selectedOption,
style: Theme.of(context).textTheme.bodyLarge,
),
const SizedBox(height: 8),
Text(
_selectedOption!,
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 24),
],
ElevatedButton(
onPressed: () => setState(() => _isSheetVisible = true),
child: Text(l10n.showOptions),
),
],
),
),
// Backdrop
if (_isSheetVisible)
GestureDetector(
onTap: () => setState(() => _isSheetVisible = false),
child: AnimatedOpacity(
opacity: _isSheetVisible ? 1.0 : 0.0,
duration: const Duration(milliseconds: 200),
child: Container(
color: Colors.black54,
),
),
),
// Bottom sheet
Positioned(
bottom: 0,
left: 0,
right: 0,
child: AnimatedSlide(
offset: _isSheetVisible ? Offset.zero : const Offset(0, 1),
duration: const Duration(milliseconds: 300),
curve: Curves.easeOutCubic,
child: Semantics(
hidden: !_isSheetVisible,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.vertical(
top: Radius.circular(16),
),
),
child: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Handle bar
Padding(
padding: const EdgeInsets.all(12),
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.outline,
borderRadius: BorderRadius.circular(2),
),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
l10n.selectAnOption,
style: Theme.of(context).textTheme.titleLarge,
),
),
const SizedBox(height: 16),
_OptionTile(
icon: Icons.share,
title: l10n.optionShare,
onTap: () => _selectOption(l10n.optionShare),
),
_OptionTile(
icon: Icons.edit,
title: l10n.optionEdit,
onTap: () => _selectOption(l10n.optionEdit),
),
_OptionTile(
icon: Icons.copy,
title: l10n.optionCopy,
onTap: () => _selectOption(l10n.optionCopy),
),
_OptionTile(
icon: Icons.delete,
title: l10n.optionDelete,
isDestructive: true,
onTap: () => _selectOption(l10n.optionDelete),
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.all(16),
child: SizedBox(
width: double.infinity,
child: OutlinedButton(
onPressed: () => setState(() => _isSheetVisible = false),
child: Text(l10n.cancelButton),
),
),
),
],
),
),
),
),
),
),
],
),
);
}
void _selectOption(String option) {
setState(() {
_selectedOption = option;
_isSheetVisible = false;
});
}
}
class _OptionTile extends StatelessWidget {
final IconData icon;
final String title;
final bool isDestructive;
final VoidCallback onTap;
const _OptionTile({
required this.icon,
required this.title,
this.isDestructive = false,
required this.onTap,
});
@override
Widget build(BuildContext context) {
final color = isDestructive
? Theme.of(context).colorScheme.error
: Theme.of(context).colorScheme.onSurface;
return ListTile(
leading: Icon(icon, color: color),
title: Text(title, style: TextStyle(color: color)),
onTap: onTap,
);
}
}
Carousel with Sliding Cards
Create a card carousel with sliding transitions:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedSlidingCarousel extends StatefulWidget {
const LocalizedSlidingCarousel({super.key});
@override
State<LocalizedSlidingCarousel> createState() => _LocalizedSlidingCarouselState();
}
class _LocalizedSlidingCarouselState extends State<LocalizedSlidingCarousel> {
int _currentIndex = 0;
int _previousIndex = 0;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final isRtl = Directionality.of(context) == TextDirection.rtl;
final cards = [
_CarouselCard(
title: l10n.cardOneTitle,
description: l10n.cardOneDescription,
color: Colors.blue,
icon: Icons.rocket_launch,
),
_CarouselCard(
title: l10n.cardTwoTitle,
description: l10n.cardTwoDescription,
color: Colors.green,
icon: Icons.eco,
),
_CarouselCard(
title: l10n.cardThreeTitle,
description: l10n.cardThreeDescription,
color: Colors.orange,
icon: Icons.lightbulb,
),
];
final isMovingForward = _currentIndex > _previousIndex;
return Scaffold(
appBar: AppBar(title: Text(l10n.carouselTitle)),
body: Column(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(24),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, animation) {
// Determine slide direction based on navigation direction and RTL
double direction = isMovingForward ? 1 : -1;
if (isRtl) direction *= -1;
final slideAnimation = Tween<Offset>(
begin: Offset(direction, 0),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeOutCubic,
));
return SlideTransition(
position: slideAnimation,
child: FadeTransition(
opacity: animation,
child: child,
),
);
},
child: _buildCard(cards[_currentIndex]),
),
),
),
// Navigation dots
Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(cards.length, (index) {
return GestureDetector(
onTap: () {
setState(() {
_previousIndex = _currentIndex;
_currentIndex = index;
});
},
child: Semantics(
button: true,
label: l10n.goToCard(index + 1),
selected: index == _currentIndex,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
margin: const EdgeInsets.symmetric(horizontal: 4),
width: index == _currentIndex ? 24 : 8,
height: 8,
decoration: BoxDecoration(
color: index == _currentIndex
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.outline,
borderRadius: BorderRadius.circular(4),
),
),
),
);
}),
),
),
// Navigation buttons
Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton.icon(
onPressed: _currentIndex > 0
? () {
setState(() {
_previousIndex = _currentIndex;
_currentIndex--;
});
}
: null,
icon: Icon(isRtl ? Icons.arrow_forward : Icons.arrow_back),
label: Text(l10n.previousCard),
),
TextButton.icon(
onPressed: _currentIndex < cards.length - 1
? () {
setState(() {
_previousIndex = _currentIndex;
_currentIndex++;
});
}
: null,
icon: Icon(isRtl ? Icons.arrow_back : Icons.arrow_forward),
label: Text(l10n.nextCard),
),
],
),
),
],
),
);
}
Widget _buildCard(_CarouselCard card) {
return Card(
key: ValueKey(card.title),
elevation: 8,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
card.color,
card.color.withOpacity(0.7),
],
),
borderRadius: BorderRadius.circular(16),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(card.icon, size: 64, color: Colors.white),
const SizedBox(height: 24),
Text(
card.title,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
Text(
card.description,
style: const TextStyle(
fontSize: 16,
color: Colors.white70,
),
textAlign: TextAlign.center,
),
],
),
),
);
}
}
class _CarouselCard {
final String title;
final String description;
final Color color;
final IconData icon;
_CarouselCard({
required this.title,
required this.description,
required this.color,
required this.icon,
});
}
Swipeable Item Actions
Create swipeable list items with sliding action buttons:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedSwipeableList extends StatefulWidget {
const LocalizedSwipeableList({super.key});
@override
State<LocalizedSwipeableList> createState() => _LocalizedSwipeableListState();
}
class _LocalizedSwipeableListState extends State<LocalizedSwipeableList> {
final List<String> _items = List.generate(10, (i) => 'Item ${i + 1}');
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.swipeableListTitle)),
body: _items.isEmpty
? Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.inbox,
size: 64,
color: Theme.of(context).colorScheme.outline,
),
const SizedBox(height: 16),
Text(l10n.emptyListMessage),
],
),
)
: ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) {
return _SwipeableItem(
item: _items[index],
itemLabel: l10n.itemNumber(index + 1),
onDelete: () {
final item = _items[index];
setState(() => _items.removeAt(index));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(l10n.itemDeleted(item)),
action: SnackBarAction(
label: l10n.undoButton,
onPressed: () {
setState(() => _items.insert(index, item));
},
),
),
);
},
onArchive: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.itemArchived(_items[index]))),
);
},
);
},
),
);
}
}
class _SwipeableItem extends StatefulWidget {
final String item;
final String itemLabel;
final VoidCallback onDelete;
final VoidCallback onArchive;
const _SwipeableItem({
required this.item,
required this.itemLabel,
required this.onDelete,
required this.onArchive,
});
@override
State<_SwipeableItem> createState() => _SwipeableItemState();
}
class _SwipeableItemState extends State<_SwipeableItem> {
double _slideOffset = 0;
static const double _actionWidth = 80;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final isRtl = Directionality.of(context) == TextDirection.rtl;
return GestureDetector(
onHorizontalDragUpdate: (details) {
setState(() {
_slideOffset += details.delta.dx * (isRtl ? -1 : 1);
_slideOffset = _slideOffset.clamp(-_actionWidth * 2, _actionWidth);
});
},
onHorizontalDragEnd: (details) {
setState(() {
if (_slideOffset > _actionWidth / 2) {
_slideOffset = _actionWidth;
} else if (_slideOffset < -_actionWidth) {
_slideOffset = -_actionWidth * 2;
} else {
_slideOffset = 0;
}
});
},
child: Stack(
children: [
// Background actions
Positioned.fill(
child: Row(
children: [
// Archive action (swipe right in LTR)
Container(
width: _actionWidth,
color: Colors.blue,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.archive, color: Colors.white),
Text(
l10n.archiveAction,
style: const TextStyle(color: Colors.white, fontSize: 12),
),
],
),
),
),
const Spacer(),
// Delete actions (swipe left in LTR)
Container(
width: _actionWidth * 2,
color: Colors.red,
child: Row(
children: [
Expanded(
child: GestureDetector(
onTap: () {
setState(() => _slideOffset = 0);
},
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.close, color: Colors.white),
Text(
l10n.cancelAction,
style: const TextStyle(color: Colors.white, fontSize: 12),
),
],
),
),
),
),
Expanded(
child: GestureDetector(
onTap: widget.onDelete,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.delete, color: Colors.white),
Text(
l10n.deleteAction,
style: const TextStyle(color: Colors.white, fontSize: 12),
),
],
),
),
),
),
],
),
),
],
),
),
// Main content
AnimatedSlide(
offset: Offset(_slideOffset / MediaQuery.of(context).size.width * (isRtl ? -1 : 1), 0),
duration: const Duration(milliseconds: 100),
child: Container(
color: Theme.of(context).colorScheme.surface,
child: ListTile(
leading: CircleAvatar(
child: Text(widget.item.substring(widget.item.length - 1)),
),
title: Text(widget.itemLabel),
subtitle: Text(l10n.swipeForActions),
trailing: const Icon(Icons.chevron_right),
),
),
),
],
),
);
}
}
Complete ARB File for AnimatedSlide
{
"@@locale": "en",
"slidingPanelTitle": "Sliding Panel",
"showPanel": "Show panel",
"hidePanel": "Hide panel",
"mainContent": "Main content area",
"sidePanelAccessibility": "Side navigation panel",
"panelTitle": "Navigation",
"menuHome": "Home",
"menuSettings": "Settings",
"menuHelp": "Help",
"toastDemoTitle": "Toast Notifications",
"showSuccessToast": "Show Success",
"showErrorToast": "Show Error",
"showWarningToast": "Show Warning",
"showInfoToast": "Show Info",
"successMessage": "Operation completed successfully!",
"errorMessage": "Something went wrong. Please try again.",
"warningMessage": "Please review your input carefully.",
"infoMessage": "New updates are available.",
"dismissToast": "Dismiss",
"featuresTitle": "Features",
"replayAnimation": "Replay animation",
"featureOne": "Premium Quality",
"featureOneDesc": "Experience the best in class quality",
"featureTwo": "Secure",
"featureTwoDesc": "Your data is always protected",
"featureThree": "Fast",
"featureThreeDesc": "Lightning-fast performance",
"featureFour": "Multilingual",
"featureFourDesc": "Available in multiple languages",
"featureFive": "24/7 Support",
"featureFiveDesc": "We're always here to help",
"bottomSheetDemoTitle": "Bottom Sheet",
"selectedOption": "You selected:",
"showOptions": "Show Options",
"selectAnOption": "Select an option",
"optionShare": "Share",
"optionEdit": "Edit",
"optionCopy": "Copy",
"optionDelete": "Delete",
"cancelButton": "Cancel",
"carouselTitle": "Carousel",
"cardOneTitle": "Innovation",
"cardOneDescription": "Pushing boundaries with cutting-edge technology",
"cardTwoTitle": "Sustainability",
"cardTwoDescription": "Building a greener future together",
"cardThreeTitle": "Creativity",
"cardThreeDescription": "Bringing ideas to life",
"goToCard": "Go to card {number}",
"@goToCard": {
"placeholders": {
"number": {"type": "int"}
}
},
"previousCard": "Previous",
"nextCard": "Next",
"swipeableListTitle": "Swipeable List",
"emptyListMessage": "No items remaining",
"itemNumber": "Item {number}",
"@itemNumber": {
"placeholders": {
"number": {"type": "int"}
}
},
"swipeForActions": "Swipe for actions",
"archiveAction": "Archive",
"deleteAction": "Delete",
"cancelAction": "Cancel",
"itemDeleted": "{item} deleted",
"@itemDeleted": {
"placeholders": {
"item": {"type": "String"}
}
},
"itemArchived": "{item} archived",
"@itemArchived": {
"placeholders": {
"item": {"type": "String"}
}
},
"undoButton": "Undo"
}
Best Practices Summary
- Handle RTL direction: Flip horizontal slide direction for RTL layouts
- Use relative offsets: AnimatedSlide uses fractions of widget size, not pixels
- Provide accessibility labels: Announce slide transitions for screen readers
- Combine with opacity: Fade during slides for smoother visual effect
- Choose appropriate directions: Slide from edges that make semantic sense
- Test bidirectional layouts: Verify slides work correctly in both LTR and RTL
- Consider gesture direction: Match swipe direction to expected behavior per locale
- Use easeOutCubic curve: Provides natural deceleration for slides
- Handle interrupted animations: Ensure partial slides resolve cleanly
- Respect reduced motion: Provide alternatives for users who prefer less animation
Conclusion
AnimatedSlide is a versatile widget for creating smooth sliding animations in multilingual Flutter apps. By properly handling RTL layouts, providing meaningful accessibility announcements, and choosing semantically appropriate slide directions, you create intuitive experiences for users worldwide. The patterns shown here—side panels, toast notifications, staggered lists, and swipeable items—can be adapted for any application requiring animated sliding transitions.
Remember to test your slide animations with various locales to ensure that directional movements feel natural regardless of the user's language preference.