اضافه تاثيرات حركيه (Animations) على النصوص في Flutter

اضافه تاثيرات حركيه (Animations) على النصوص في Flutter

اضافه تاثيرات حركيه (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,
        ),
      ),
    );
  }
}
TextAnimationStrategy
تضع هذه الواجهة المجردة الأساس لاستراتيجية الرسوم المتحركة النصية. يتضمن:

خاصية SynchronizeAnimation: تحدد ما إذا كان سيتم تحريك كافة العناصر (مثل الحروف) بشكل متزامن.
وظيفة buildAnimatedCharacter: إنشاء شخصية أو جزء من النص وتحريكه باستخدام رسم متحرك محدد.
وظيفة createAnimation: إنشاء الرسوم المتحركة بناءً على AnimationController وضبط وقت البداية والنهاية والمنحنى.

BaseAnimationStrategy
تطبق هذه الإستراتيجية طريقة الحركة الأساسية:

buildAnimatedCharacter: ValueListenableBuilder يستخدم لرسم وتحريك شخصية معينة. CreativeAnimation: قم بإنشاء رسوم متحركة انتقالية بين 0.0 و1.0 باستخدام Tween وضبط المنحنى بناءً على أوقات البداية والنهاية المحددة.

EnhancedTextAnimationManager
يقوم هذا المدير بتنسيق حركة النص بناءً على الوحدات المحددة (الحروف أو الكلمات):

جهاز التحكم: يتحكم في جميع الحركات.
النص: نص لتحريك. المنحنى: يمثل نمط الحركة، مثل المنحنيات. easyInOut.
الوحدة: تحدد ما إذا كانت الرسوم المتحركة على مستوى الحرف أو الكلمة.

_splitTextIntoUnits: وظيفة لتقسيم النص إلى وحدات (أحرف أو كلمات).
_initializeAnimations: قم بإنشاء رسوم متحركة لكل وحدة بناءً على الإستراتيجية المختارة. EnhancedTextRevealEffect (أداة النص المتحرك)
EnhancedTextRevealEffect عبارة عن أداة تُستخدم لعرض النص في الوضع المتحرك. يسمح لك بما يلي:

وظيفة الزناد: إحداث الحركة.
خاصية الوحدة: تحدد مستوى الحركة (حرف أو كلمة).
خاصية الاتجاه: اتجاه الحركة (للأمام أو للخلف).
خاصية الإستراتيجية: إستراتيجية الرسوم المتحركة المستخدمة لتحريك النص.

شرح كيفيه استخدام تحريك اكثر من نص في التصميم 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;
                    });
                  },
                ),
              ),
            ),
          ),
        ],
      )
    );
  }
}
_isAnimating: التحقق مما إذا كانت الرسوم المتحركة نشطة.
_demoText و_demoText2: هذه هي النصوص التي سيتم عرضها بالتناوب.
_selectedUnit: يحدد نوع الرسوم المتحركة على مستوى الحرف (عبر AnimationUnit.character).
_direction: يحدد اتجاه الحركة، في هذا المثال يتم ضبطه للأمام (AnimationDirection. في البداية).
_currentText: النص الحالي المعروض على الشاشة، والذي يتغير عند انتهاء الرسم المتحرك.

الخلفية والهيكل العام:

تم تعيين خلفية الشاشة باللون الأسود.
يستخدم العمود لترتيب العناصر عموديا مع إضافة مسافة (SizedBox) لرفع النص 100 بكسل من الأعلى.
الخريطة في وسط النص:

تم وضع البطاقة بحيث تحتوي على النص بلون خلفية أسود ليتناسب مع الخلفية. عنصر واجهة المستخدم المحسن لتأثير النص:

تعرض هذه الأداة النص مرحبًا، اسمي أحمد مع تمكين الرسوم المتحركة (السبب: _isAnimating) واستخدام إستراتيجية FadeBlur لعرض النص بتأثيرات التمويه.
تم ضبط نمط الخط على حجم 24، اللون أبيض ووزن متوسط.
خاصية onAnimationComplete: عند اكتمال الرسم المتحرك، يتم استبدال النص المعروض بالنص التالي (مرحبًا بك في كلمتي) باستخدام setState لتحديث الشاشة.
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),
        ),
      ),
    );
  }
}

إدارة اتجاه الرسوم المتحركة واتجاه الرسوم المتحركة

يوفر AnimationDirection خيارات لعرض النص من البداية إلى النهاية (للأمام) أو للخلف. يمكن تعديل هذا الاتجاه لتحقيق تأثيرات أكثر ديناميكية وابتكارًا.

توفر إضافة تأثيرات الرسوم المتحركة النصية إلى تطبيقات Flutter تجربة مستخدم مرنة ومميزة. باستخدام AnimationController وTextAnimationStrategy، يمكنك تحقيق تأثيرات إبداعية وسلسة تضيف لمسة احترافية إلى تطبيقك.
تعليقات