الدرس 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.Animated.timing/springتحرّكها؛ لا تنسَ .start().- الحركة الهادفة تحسّن التجربة؛ والإفراط يشتّت.
تحدٍّ برمجي: ظهور متدرّج
متقدماجعل بطاقةً تظهر بتدرّج عند التحميل.
المطلوب:
- أنشئ
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> );}