EduFlow

الوحدة 2 · التخطيط والتنسيق

Flexbox بإعدادات الجوّال الافتراضية

20 دقراءة 4 داملأ الفراغات

الدرس في لمحة

رتّب صفوف القوائم والشاشات في Trailhead باستخدام flexbox، مع معرفة القيم الافتراضية على الجوّال (الاتجاه العمودي، وعدم الانكماش، والتمدّد) التي تفسّر معظم التخطيطات المنضغطة أو الفائضة.

  1. 1الـ flexbox نفسه، بقيم افتراضية مختلفة
  2. 2flex: 1، الخاصية التي ستكتبها أكثر من غيرها
  3. 3بناء صفّ الرحلة
  4. 4التموضع المطلق والالتفاف

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

هذا خطأ تخطيط يواجهه كل مطوّر ويب في أسبوعه الأول مع React Native: صفّ فيه أيقونة وعنوان وسهم يبدو مثاليًا مع “Ridge Loop”، وينكسر مع “Old Mill Creek Trail to Eagle Point Lookout”. يختفي السهم خلف الحافة اليمنى. لا عيب في معرفتك بالـ flexbox؛ كل ما في الأمر أن flexbox في React Native يبدأ من قيم افتراضية مختلفة.

الـ flexbox نفسه، بقيم افتراضية مختلفة

يرتّب React Native كل View باستخدام flexbox (عبر Yoga، محرّك تخطيط يطبّق المواصفة). لا يوجد display: block ولا inline؛ فكل View حاوية flex. أربع قيم افتراضية تختلف عن الويب:

الخاصية الافتراضي على الويب الافتراضي في React Native
flexDirection row column
flexShrink 1 0
alignContent normal flex-start
flex اختصار لعدّة خصائص رقم واحد

أول اثنتين تفسّران معظم المفاجآت. الأبناء يتكدّسون عموديًا ما لم تقل flexDirection: 'row'، ولا ينكمشون حين تنفد المساحة، ولهذا دفع ذلك العنوان الطويل السهمَ بعيدًا.

في العمود، يمتد المحور الرئيسي من الأعلى إلى الأسفل، فيعمل justifyContent عموديًا ويعمل alignItems أفقيًا. وفي الصف، يمتد المحور الرئيسي من اليسار إلى اليمين وتتبادل الخاصيتان دوريهما. flexDirection: 'column' (الافتراضي) الرئيسي: justifyContent العرضي: alignItems flexDirection: 'row' flex: 1 الرئيسي: justifyContent
المحور الرئيسي والمحور العرضي في عمود وفي صف

القواعد التي تعرفها ما زالت سارية: justifyContent يعمل على المحور الرئيسي، وalignItems على المحور العرضي. في العمود (الافتراضي)، يوسّط justifyContent: 'center' عموديًا، ويوسّط alignItems: 'center' أفقيًا. انتقل إلى صف فتتبادل الخاصيتان دوريهما.

القيمة الافتراضية لـ alignItems هي stretch، فالابن في عمود يملأ العرض كاملًا ما لم تعطه عرضًا أو تغيّر alignItems. ولهذا يمتد Text له لون خلفية في عمود عادي من الحافة إلى الحافة.

flex: 1، الخاصية التي ستكتبها أكثر من غيرها

في React Native، يأخذ flex رقمًا واحدًا. وflex: 1 تعني «اكبر لتملأ المساحة المتبقية على المحور الرئيسي، ويُسمح لك بالانكماش». مكانان لا يكادان يستغنيان عنها أبدًا:

  1. الـ View الجذري في كل شاشة، ليملأ الشاشة بدلًا من أن يلتصق بمحتواه.
  2. كل أب لـ ScrollView أو FlatList، لأن حاوية التمرير تتمرّر داخل الارتفاع الذي يُعطى لها. فإن لم يحدّد أي سلف هذا الارتفاع، تكبر بحجم محتواها كله، ويُقطع الجزء السفلي دون أن تتمرّر أبدًا.

حين تظهر الشاشة كشريط رفيع في الأعلى، أو حين لا تتمرّر قائمة، اصعد في الشجرة وابحث عن الـ flex: 1 المفقود.

بناء صفّ الرحلة

هذا هو الصف الذي انكسر، بعد إصلاحه:

src/components/HikeRow.tsx
import { Image, StyleSheet, Text, View } from 'react-native';

export function HikeRow({ hike }: { hike: { name: string; distanceKm: number; date: string; thumbUrl: string } }) {
  return (
    <View style={styles.row}>
      <Image source={{ uri: hike.thumbUrl }} style={styles.thumb} />
      <View style={styles.body}>
        <Text style={styles.title} numberOfLines={1}>{hike.name}</Text>
        <Text style={styles.meta}>{hike.distanceKm} km · {hike.date}</Text>
      </View>
      <Text style={styles.chevron}>›</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  row: { flexDirection: 'row', alignItems: 'center', gap: 12, padding: 12 },
  thumb: { width: 56, height: 56, borderRadius: 8 },
  body: { flex: 1 },
  title: { fontSize: 17, fontWeight: '600' },
  meta: { fontSize: 14, color: '#5c6b62', marginTop: 2 },
  chevron: { fontSize: 24, color: '#9aa79f' },
});

اقرأه محورًا محورًا. المحور الرئيسي للصف أفقي؛ وalignItems: 'center' يوسّط الأبناء الثلاثة عموديًا بالنسبة إلى أطولهم (الصورة المصغّرة). للصورة المصغّرة والسهم أحجام ثابتة. أما الجسم فعليه flex: 1، فيأخذ أي عرض يتبقّى، وnumberOfLines={1} يقتطع الاسم الطويل بعلامة حذف بدلًا من دفع السهم خارج الشاشة.

أداتان أخريان تُكملان التخطيط اليومي. يسمح alignSelf لابن واحد بالخروج عن alignItems الخاصة بأبيه: زرّ “Log a hike” في عمود متمدّد يمكنه استخدام alignSelf: 'flex-end' ليستقرّ على اليمين بعرضه الطبيعي. أما aspectRatio فيحدّد حجم الصندوق انطلاقًا من بُعد واحد، وهذا مثالي للصور: أعطِ صورة الدرب width: '100%' وaspectRatio: 4 / 3، فيتبع ارتفاعها عرض الشاشة على كل هاتف دون أن تحسب أي شيء.

وحين يسيء تخطيطٌ التصرّف، تبقى أسرع حيلة لتصحيحه هي الأقدم: أعطِ الـ Views المشتبه بها لون خلفية فاقعًا مؤقتًا. سترى فورًا أي صندوق أصغر من اللازم، وأيها تمدّد، وأيها لم يحصل قط على flex: 1.

التموضع المطلق والالتفاف

كل View يكون position: 'relative' افتراضيًا، لذا يضع position: 'absolute' الابن بالنسبة إلى أبيه. هكذا تضع شارة “New” على زاوية صورة مصغّرة:

TSX
<View>
  <Image source={{ uri: hike.thumbUrl }} style={styles.thumb} />
  <View style={{ position: 'absolute', top: -4, right: -4, backgroundColor: '#e8590c', borderRadius: 8, paddingHorizontal: 6 }}>
    <Text style={{ color: 'white', fontSize: 11, fontWeight: '700' }}>NEW</Text>
  </View>
</View>

ولصفّ من الوسوم (“steep”، “lake”، “dog-friendly”) يجب أن ينتقل إلى سطر ثانٍ حين تنفد المساحة، اجمع بين flexDirection: 'row' وflexWrap: 'wrap' وgap: 8. التموضع المطلق يُخرج الابن من تدفّق الـ flex كليًا، لذا استخدمه للعناصر العائمة فقط، ولا تستخدمه أبدًا لتزييف تخطيط يستطيع الـ flex التعبير عنه.

في الدرس التالي ستجعل هذه التخطيطات تصمد أمام أحجام الهواتف المختلفة، والنتوءات (notches)، والزوايا المستديرة، والفروق بين المنصّتين.

أهم النقاط

  • كل View حاوية flex، والمحور الرئيسي يمتد من الأعلى إلى الأسفل افتراضيًا (flexDirection: 'column').
  • justifyContent يحدّد مواضع الأبناء على المحور الرئيسي؛ وalignItems يحدّدها على المحور العرضي، وقيمته الافتراضية stretch.
  • الأبناء لا ينكمشون افتراضيًا في React Native، لذا يدفع العنوان الطويل إخوته خارج الشاشة حتى تعطيه flex: 1.
  • flex: 1 تعني «خذ المساحة المتبقية»؛ والـ View الجذري في كل شاشة وكل أب لـ ScrollView يحتاجان إليها.
  • استخدم position: 'absolute' للعناصر العائمة مثل الشارات، وflexWrap: 'wrap' مع gap لصفوف الوسوم.

قراءات إضافية

تحقّق من فهمك

3 أسئلة

السؤال 1 من 3

صفّ فيه صورة مصغّرة بعرض 56 نقطة، وعنوان، وسهم (chevron). أسماء الرحلات الطويلة تدفع السهم خارج الحافة اليمنى. ما الإصلاح؟

السؤال 2 من 3

في View بالتنسيقات الافتراضية، ضبطت justifyContent: 'center'. أين يذهب الأبناء؟

السؤال 3 من 3

شاشة التفاصيل في Trailhead عبارة عن ScrollView داخل View، وهي لا تتمرّر؛ المحتوى يُقطَع ببساطة. ما السبب المرجّح؟

تدريب

املأ الفراغات

نسّق صفّ الرحلة

أكمل تنسيقات القائمة في Trailhead بحيث:

  1. تملأ الشاشةُ مساحة العرض كلها،
  2. يرتّب كل صف الصورة المصغّرة والنص والسهم جنبًا إلى جنب، موسّطة عموديًا،
  3. يأخذ عمود النص العرض المتبقي فلا تدفع الأسماء الطويلة السهم بعيدًا،
  4. تنتقل وسوم الصعوبة إلى سطر ثانٍ حين تنفد المساحة،
  5. تستقرّ شارة “NEW” على زاوية الصورة المصغّرة دون أن تؤثّر في التخطيط.
const styles = StyleSheet.create({ screen: { : 1, backgroundColor: '#f6f8f6' }, row: { flexDirection: '', alignItems: '', gap: 12, padding: 12, }, thumb: { width: 56, height: 56, borderRadius: 8 }, body: { flex: }, chips: { flexDirection: 'row', flexWrap: '', gap: 8 }, badge: { position: '', top: -4, right: -4 }, });