اضافه تاثيرات حركيه (Animations) على النصوص في Flutter
تضيف الرسوم المتحركة إحساسًا ديناميكيًا وجذابًا إلى واجهة التطبيق. في هذه المقالة، سوف نتعلم كيفية تطبيق تأثيرات النص المتحرك باستخدام كود Flutter المخصص استنادًا إلى AnimationController وTextAnimationStrategy. دعونا نلقي نظرة على التعليمات البرمجية المشتركة لتطوير واجهات ديناميكية
بالنسبة للمستخدمين، يساعد ذلك على زيادة جاذبية التطبيق وتحسين تجربة المستخدم.
مفهوم الرسوم المتحركة في فلاتر
يعد Flutter إطارًا ممتازًا لإنشاء تطبيقات تفاعلية وسلسة ويوفر مكتبات شاملة لدعم الرسوم المتحركة. باستخدام AnimationController وCurvedAnimation، يمكنك تصميم تأثيرات فريدة للنص والشعارات والأزرار وعناصر الواجهة الأخرى. مقدمة إلى وحدة التحكم في
الرسوم المتحركة واستراتيجية الرسوم المتحركة النصية
AnimationController
وهي مسؤولة عن التحكم في عملية الرسوم المتحركة، من البداية إلى النهاية، والتحكم في توقيت وسرعة الرسوم المتحركة. AnimationController مرن للغاية ويسمح بالتكامل مع استراتيجيات الرسوم المتحركة المختلفة.
TextAnimationStrategy
تتيح لك هذه الإستراتيجية تحديد كيفية ظهور النصوص على الشاشة تدريجيًا. تختلف استراتيجيات التيسير في كيفية تقديم النص، حرفيًا ولفظيًا.
شرح كود AnimationDemoScreen: كيفية ضبط شاشة الرسوم المتحركة
يتم تنفيذ الكود الموجود في AnimationDemoScreen بواسطة:
المتغيرات الأساسية مثل _demoText و_demoText2 التي تمثل النصوص المعروضة.
القطعة EnhancedTextRevealEffect
وهي مسؤولة عن عرض النص بتأثير متحرك، حيث يتحرك النص والاستراتيجية والحالة (السبب).
الإعدادات المخصصة
يحتوي EnhancedTextRevealEffect على عدد من الخصائص مثل التأخير والنوع والاتجاه، مما يجعل الكود مرنًا لمجموعة متنوعة من التجارب.
استراتيجيات الرسوم المتحركة النصية: لماذا نستخدم استراتيجية الرسوم المتحركة النصية؟
تتيح لك إستراتيجية الرسوم المتحركة مثل FadeBlurStrategy تطبيق تأثيرات مختلفة على النصوص. يعتمد اختيار الإستراتيجية على تجربة المستخدم المطلوبة؛ يمكن أن يكون المظهر تدريجيًا، مع طمس أو تأثيرات أخرى تضيف لمسة جمالية. أنواع الاستراتيجيات الرسومية:
FadeBlurStrategy: أضف التمويه إلى النص من خلال الانتقال التدريجي.
BaseAnimationStrategy: يعرض الحروف المتتالية بنمط بسيط.
Widget لتصميم تحريك النصوص في فلاتر
import 'package:flutter/material.dart';
import 'FadeBlurStrategy.dart';
abstract class TextAnimationStrategy {
final bool synchronizeAnimation;
const TextAnimationStrategy({
this.synchronizeAnimation = false,
});
Widget buildAnimatedCharacter({
required String character,
required Animation<double> animation,
TextStyle? style,
});
Animation<double> createAnimation({
required AnimationController controller,
required double startTime,
required double endTime,
required Curve curve,
});
}
// Base Animation Strategy
class BaseAnimationStrategy extends TextAnimationStrategy {
const BaseAnimationStrategy({
super.synchronizeAnimation,
});
@override
Widget buildAnimatedCharacter({
required String character,
required Animation<double> animation,
TextStyle? style,
}) {
return ValueListenableBuilder<double>(
valueListenable: animation,
builder: (context, value, _) => Text(character, style: style),
);
}
@override
Animation<double> createAnimation({
required AnimationController controller,
required double startTime,
required double endTime,
required Curve curve,
}) {
return Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: controller,
curve: Interval(startTime, endTime, curve: curve),
),
);
}
}
// Animation Direction
enum AnimationDirection {
forward,
reverse,
}
// Animation Unit
enum AnimationUnit {
character,
word,
}
// Enhanced Animation Manager
class EnhancedTextAnimationManager {
final AnimationController controller;
final String text;
final Curve curve;
final AnimationUnit unit;
final TextAnimationStrategy strategy;
late final List<Animation<double>> _animations;
late final List<String> _units;
EnhancedTextAnimationManager({
required this.controller,
required this.text,
required this.curve,
required this.strategy,
this.unit = AnimationUnit.character,
}) {
_units = _splitTextIntoUnits();
_initializeAnimations();
}
List<String> _splitTextIntoUnits() {
switch (unit) {
case AnimationUnit.character:
return text.split('');
case AnimationUnit.word:
// Early return for empty text
if (text.isEmpty) return [];
// Split text keeping spaces with words
final List<String> words = [];
final RegExp pattern = RegExp(r'\S+\s*');
for (final match in pattern.allMatches(text)) {
words.add(match.group(0)!);
}
return words;
}
}
void _initializeAnimations() {
if (_units.isEmpty) {
_animations = [];
return;
}
const double animationDuration = 0.8;
const double totalAnimationTime = 1.0 + animationDuration;
if (strategy.synchronizeAnimation) {
// When synchronized, all characters use the same timing
_animations = List.generate(_units.length, (index) {
return strategy.createAnimation(
controller: controller,
startTime: 0.0, // All start together
endTime: animationDuration, // All end together
curve: curve,
);
});
} else {
final double staggerOffset = _units.length > 1
? (totalAnimationTime - animationDuration) / (_units.length - 1)
: 0.0;
_animations = List.generate(_units.length, (index) {
final double start =
(index * staggerOffset / totalAnimationTime).clamp(0.0, 1.0);
final double end =
((index * staggerOffset + animationDuration) / totalAnimationTime)
.clamp(0.0, 1.0);
return strategy.createAnimation(
controller: controller,
startTime: start,
endTime: end,
curve: curve,
);
});
}
}
Animation<double> getAnimationForIndex(int index) => _animations[index];
String getUnitAtIndex(int index) => _units[index];
int get unitCount => _units.length;
void dispose() {
_animations.clear();
}
}
// Enhanced Text Reveal Widget
class EnhancedTextRevealEffect extends StatefulWidget {
final String text;
final TextStyle? style;
final Duration duration;
final Curve curve;
final bool trigger;
final AnimationUnit unit;
final TextAnimationStrategy strategy;
final AnimationDirection direction;
final Function()? onAnimationComplete;
const EnhancedTextRevealEffect({
required this.text,
this.style,
this.duration = const Duration(milliseconds: 1000),
this.curve = Curves.easeInOut,
required this.trigger,
this.unit = AnimationUnit.character,
// Now this is const-correct!
this.strategy = const FadeBlurStrategy(),
this.direction = AnimationDirection.forward,
this.onAnimationComplete,
Key? key,
}) : super(key: key);
@override
State<EnhancedTextRevealEffect> createState() =>
_EnhancedTextRevealEffectState();
}
class _EnhancedTextRevealEffectState extends State<EnhancedTextRevealEffect>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late EnhancedTextAnimationManager _animationManager;
bool _isVisible = false;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: widget.duration);
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
setState(() {
widget.onAnimationComplete?.call();
});
_controller.forward();
}
});
_initializeAnimationManager();
if (widget.trigger) {
_isVisible = true;
_controller.forward();
}
}
void _initializeAnimationManager() {
_animationManager = EnhancedTextAnimationManager(
controller: _controller,
text: widget.text,
curve: widget.curve,
unit: widget.unit,
strategy: widget.strategy,
);
}
@override
void didUpdateWidget(EnhancedTextRevealEffect oldWidget) {
super.didUpdateWidget(oldWidget);
// التعامل مع تغيير حالة trigger
if (widget.trigger != oldWidget.trigger) {
if (widget.trigger) {
setState(() {
_isVisible = true;
});
_controller.forward(from: 0);
} else {
_controller.reverse();
}
}
// التعامل مع تغييرات النص أو الاستراتيجية
if (widget.text != oldWidget.text ||
widget.unit != oldWidget.unit ||
widget.strategy != oldWidget.strategy) {
_animationManager.dispose();
_initializeAnimationManager();
}
}
@override
void dispose() {
_controller.dispose();
_animationManager.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (!_isVisible) return const SizedBox.shrink(); // إخفاء عندما تكون الرسوم غير مرئية
return Wrap(
children: List.generate(
_animationManager.unitCount,
(index) => widget.strategy.buildAnimatedCharacter(
character: _animationManager.getUnitAtIndex(index),
animation: _animationManager.getAnimationForIndex(index),
style: widget.style,
),
),
);
}
}شرح كيفيه استخدام تحريك اكثر من نص في التصميم Flutter
import 'package:flutter/material.dart';
import 'package:untitled/txt_animation/FadeBlurStrategy.dart';
import 'txt_animation/text_reveal_widget.dart';
class AnimationDemoScreen extends StatefulWidget {
const AnimationDemoScreen({Key? key}) : super(key: key);
@override
State<AnimationDemoScreen> createState() => _AnimationDemoScreenState();
}
class _AnimationDemoScreenState extends State<AnimationDemoScreen> {
final bool _isAnimating = true;
final String _demoText = "Hi , I'm Ahmed ✨";
final String _demoText2 = "Welcome To My Word ✨";
final AnimationUnit _selectedUnit = AnimationUnit.character;
final AnimationDirection _direction = AnimationDirection.forward;
String? _currentText;
@override
void initState() {
super.initState();
_currentText = _demoText;
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 100,),
Center(
child: Card(
color: Colors.black,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: EnhancedTextRevealEffect(
text: _currentText!,
trigger: _isAnimating,
strategy: const FadeBlurStrategy(),
unit: _selectedUnit,
direction: _direction,
style: const TextStyle(
fontSize: 24,
color: Colors.white,
fontWeight: FontWeight.w500,
),
onAnimationComplete: () {
setState(() {
_currentText = _currentText == _demoText ? _demoText2 : _demoText;
});
},
),
),
),
),
],
)
);
}
}
class FadeBlurStrategy extends BaseAnimationStrategy {
final double maxBlur;
const FadeBlurStrategy({this.maxBlur = 8.0}) : super(); // Make constructor const
@override
Widget buildAnimatedCharacter({
required String character,
required Animation<double> animation,
TextStyle? style,
}) {
return ValueListenableBuilder<double>(
valueListenable: animation,
builder: (context, value, _) => Opacity(
opacity: value,
child: ImageFiltered(
imageFilter: ImageFilter.blur(
sigmaX: (1 - value) * maxBlur,
sigmaY: (1 - value) * maxBlur,
),
child: Text(character, style: style),
),
),
);
}
}
