طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 251 · برمجة الأجهزة الذكية
الدرس 22 · الوحدة 10: تطوير واجهات مستخدم متقنة

الحركات والانتقالات: Animated

حركاتٌ سلسة تجعل التطبيق حيًّا ومفهومًا. ⏱ نحو 40 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تنشئ قيمةً متحرّكة وتحرّكها بـ Animated.timing.
  • تربط الحركة بالظهور وباللمس.
  • تستخدم useNativeDriver لأداءٍ أفضل.

١لماذا الحركة؟

الحركة الهادفة تجعل التطبيق يبدو حيًّا ومفهومًا: ظهورٌ متدرّج، انزلاقٌ، تكبيرٌ عند اللمس. توفّر React Native نظام Animated: قيمةٌ متحرّكة نربطها بنمطٍ فتنتقل بسلاسة.

المفهومالدور
Animated.Valueقيمةٌ تتغيّر بسلاسة (مثلًا شفافيّة من 0 إلى 1)
Animated.timingيحرّك القيمة خلال مدّة
Animated.Viewعنصرٌ يقبل القيَم المتحرّكة في style
useNativeDriverيشغّل الحركة على الخيط الأصيل (أداء أعلى)

٢ظهورٌ متدرّج (Fade In)

ننشئ قيمةً تبدأ من 0 ونحرّكها إلى 1 عند الظهور، ونربطها بالشفافيّة:

import { useRef, useEffect } from 'react';import { Animated, Text, StyleSheet } from 'react-native'; export default function App() {  const opacity = useRef(new Animated.Value(0)).current;   useEffect(() => {    Animated.timing(opacity, {      toValue: 1,      duration: 900,      useNativeDriver: true,    }).start();  }, []);   return (    <Animated.View style={[styles.card, { opacity }]}>      <Text style={styles.t}>ظهرتُ بتدرّج ✨</Text>    </Animated.View>  );} const styles = StyleSheet.create({  card: { margin: 20, padding: 24, backgroundColor: '#FFF3E0', borderRadius: 14 },  t: { fontSize: 20, fontWeight: 'bold', color: '#E8820C', textAlign: 'center' },});

useRef يحفظ قيمة الحركة بين الرسمات. .start() يبدأ الانتقال. أعِد تحميل المعاينة لرؤية الظهور.

٣تكبيرٌ عند اللمس

نربط الحركة بتفاعل: الزرّ يكبر قليلًا عند الضغط ثم يعود — تغذيةٌ راجعة لمسيّة:

import { useRef } from 'react';import { Animated, Pressable, Text, StyleSheet } from 'react-native'; export default function App() {  const scale = useRef(new Animated.Value(1)).current;  const to = (v) => Animated.spring(scale, { toValue: v, useNativeDriver: true }).start();   return (    <Pressable onPressIn={() => to(0.9)} onPressOut={() => to(1)} style={{ padding: 30, alignItems: 'center' }}>      <Animated.View style={[styles.btn, { transform: [{ scale }] }]}>        <Text style={styles.t}>اضغطني</Text>      </Animated.View>    </Pressable>  );} const styles = StyleSheet.create({  btn: { backgroundColor: '#E8820C', paddingVertical: 14, paddingHorizontal: 28, borderRadius: 14 },  t: { color: 'white', fontSize: 18, fontWeight: 'bold' },});

Animated.spring يعطي حركةً نابضة طبيعيّة. onPressIn/Out يربطانها بحالة اللمس.

توقّع وتحقّق

ما الذي يمثّل قيمةً متحرّكة؟

لتحريك القيمة خلال مدّة:

أين نحفظ قيمة الحركة؟

useNativeDriver: true يفيد في:

الخلاصة

تحدٍّ برمجي: ظهور متدرّج

متقدم

اجعل بطاقةً تظهر بتدرّج عند التحميل.

المطلوب:
  • أنشئ Animated.Value بـ useRef يبدأ من 0.
  • في useEffect حرّكه إلى 1 بـ Animated.timing ثم .start().
  • اربطه بشفافيّة Animated.View، وبداخله <Text testID="msg"> نصّه «أهلًا».
👀 اعرض الناتج المطلوب (وجرّبه)
✓ التحقق الآلي من حلّك:
  • الكود يعمل بلا أخطاء في Console
  • النصّ «أهلًا» موجود
  • استُخدم Animated.timing
  • بدأت الحركة start

💡 تلميح 1
احفظ القيمة بـ useRef(new Animated.Value(0)).current.
💡 تلميح 2
لا تنسَ .start() بعد timing.

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

import { useRef, useEffect } from 'react';import { Animated, Text } from 'react-native'; export default function App() {  const opacity = useRef(new Animated.Value(0)).current;  useEffect(() => {    Animated.timing(opacity, { toValue: 1, duration: 800, useNativeDriver: true }).start();  }, []);  return (    <Animated.View style={{ padding: 20, opacity }}>      <Text testID="msg" style={{ fontSize: 22, fontWeight: 'bold', color: '#E8820C' }}>أهلًا</Text>    </Animated.View>  );}
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس