الصور في Flutter: Image.asset و Image.network و BoxFit
تطبيق بلا صور يبدو فارغاً: شعار التطبيق، وصور المنتجات، وصورة المستخدم الشخصية. في هذا الدرس ستتعلم مصدرين للصور: صور داخل التطبيق تُحزم معه، وصور من الإنترنت تُحمَّل عند الحاجة. وستتعلم كيف تتحكم في حجمها وشكلها، وماذا تعرض إذا فشل التحميل.
صور داخل التطبيق: assets
Section titled “صور داخل التطبيق: assets”الملفات التي تُحزم مع التطبيق (صور وخطوط وغيرها) تسمى الأصول (Assets). تخيّلها مثل الصور المطبوعة داخل كتاب: موجودة دائماً، ولا تحتاج إنترنت.
استخدامها يمر بثلاث خطوات:
1. أنشئ مجلداً للصور في جذر المشروع، بجانب lib:
my_app/├─ assets/│ └─ images/│ ├─ logo.png│ └─ cover.png├─ lib/└─ pubspec.yaml2. عرّف المجلد في pubspec.yaml تحت قسم flutter:
flutter: uses-material-design: true assets: - assets/images/كتابة اسم المجلد وبعده / تضيف كل الملفات الموجودة فيه مباشرة. ثم نفّذ flutter pub get.
3. اعرض الصورة:
Image.asset( 'assets/images/logo.png', width: 120, height: 120,)صور من الإنترنت: Image.network
Section titled “صور من الإنترنت: Image.network”Image.network( 'https://dummyjson.com/image/400x200', height: 200,)سهلة جداً، لكن الإنترنت ليس مضموناً: قد يكون بطيئاً، وقد ينقطع، وقد يكون الرابط خاطئاً. تطبيق محترم يتعامل مع الحالتين.
أثناء التحميل: loadingBuilder
Section titled “أثناء التحميل: loadingBuilder”Image.network( 'https://dummyjson.com/image/400x200', height: 200, loadingBuilder: (context, child, progress) { if (progress == null) return child; return const SizedBox( height: 200, child: Center(child: CircularProgressIndicator()), ); },)Flutter يستدعي هذه الدالة عدة مرات أثناء التحميل. عندما تكون progress تساوي null فالتحميل انتهى، فنُرجع الصورة نفسها (child). وقبل ذلك نعرض دائرة التحميل.
عند الفشل: errorBuilder
Section titled “عند الفشل: errorBuilder”Image.network( 'https://example.com/missing.png', height: 200, errorBuilder: (context, error, stackTrace) { return const SizedBox( height: 200, child: Center( child: Icon(Icons.broken_image, size: 48, color: Colors.grey), ), ); },)بدون errorBuilder يظهر للمستخدم مربع خطأ قبيح. معها يرى أيقونة هادئة تخبره أن الصورة غير متاحة.
ملء المساحة: BoxFit
Section titled “ملء المساحة: BoxFit”الصورة لها أبعادها، والمكان المخصص لها له أبعاد أخرى. الخاصية fit تحدد كيف نوفّق بينهما:
Image.asset( 'assets/images/cover.png', width: double.infinity, height: 180, fit: BoxFit.cover,)| القيمة | ماذا تفعل |
|---|---|
BoxFit.cover |
تملأ المساحة كلها وتقص الزائد. الأكثر استخداماً للأغلفة وصور المنتجات |
BoxFit.contain |
تُظهر الصورة كاملة داخل المساحة، وقد تترك فراغات |
BoxFit.fill |
تمط الصورة لتملأ المساحة، فتتشوه النِّسَب |
BoxFit.fitWidth |
تملأ العرض، وقد يُقص الارتفاع |
BoxFit.none |
الحجم الأصلي بدون أي تغيير |
تخيّل أنك تضع صورة في برواز: cover تكبّر الصورة حتى تغطي البرواز وتقص ما يخرج عنه، و contain تصغّرها حتى تدخل كلها ولو بقيت حواف فارغة، و fill تمطها بالقوة.
و width: double.infinity تعني «خذ كل العرض المتاح».
الصورة الدائرية: CircleAvatar
Section titled “الصورة الدائرية: CircleAvatar”للصور الشخصية يوجد Widget جاهز:
const CircleAvatar( radius: 32, backgroundImage: AssetImage('assets/images/logo.png'),),const CircleAvatar( radius: 32, backgroundColor: Colors.indigo, child: Text('س', style: TextStyle(color: Colors.white, fontSize: 24)),),radius: نصف القطر، فالقطر الكامل هنا 64.backgroundImage: الصورة. لاحظ أنها تأخذAssetImageأوNetworkImage، وليسImage.asset. الفرق أنAssetImageمصدر صورة، أماImage.assetفهو Widget يعرضها.- إذا لم توجد صورة، ضع أول حرف من الاسم في
childعلى خلفية ملونة، كما تفعل تطبيقات المحادثة.
صورة خلفية لصندوق
Section titled “صورة خلفية لصندوق”لوضع نص فوق صورة، اجعل الصورة خلفية لـ Container عبر DecorationImage:
Container( height: 160, alignment: Alignment.bottomRight, padding: const EdgeInsets.all(16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), image: const DecorationImage( image: AssetImage('assets/images/cover.png'), fit: BoxFit.cover, ), ), child: const Text( 'رحلة الصيف', style: TextStyle(color: Colors.white, fontSize: 22), ),)هنا الزوايا الدائرية تقص الصورة فعلاً، لأن الصورة جزء من الزخرفة نفسها وليست ابناً داخل الصندوق.
المشروع: بطاقة ملف شخصي
Section titled “المشروع: بطاقة ملف شخصي”class ProfileHeader extends StatelessWidget { const ProfileHeader({super.key});
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('الملف الشخصي')), body: Column( children: [ Image.asset( 'assets/images/cover.png', width: double.infinity, height: 160, fit: BoxFit.cover, ), const SizedBox(height: 16), const CircleAvatar( radius: 44, backgroundImage: AssetImage('assets/images/logo.png'), ), const SizedBox(height: 12), const Text( 'سارة أحمد', style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold), ), const Text('مطورة تطبيقات'), const SizedBox(height: 16), Image.network( 'https://dummyjson.com/image/400x200', height: 120, errorBuilder: (context, error, stackTrace) { return const SizedBox( height: 120, child: Center(child: Icon(Icons.broken_image, size: 40)), ); }, ), ], ), ); }}الشاشة تعرض صورة غلاف بعرض الشاشة، وتحتها الصورة الشخصية داخل دائرة، ثم الاسم والوظيفة، ثم صورة من الإنترنت. جرّب أن تطفئ الإنترنت وتعيد تشغيل التطبيق: ستظهر أيقونة الصورة المكسورة بدلاً من رسالة خطأ.
جرّب بنفسك
Section titled “جرّب بنفسك”- أضف صورتين إلى مشروعك وعرّفهما في
pubspec.yamlواعرضهما. - اعرض نفس الصورة ثلاث مرات في مربعات 150×150، بـ
coverثمcontainثمfill، وقارن بينها. - أضف
loadingBuilderإلى صورة الإنترنت في المشروع. - تحدٍّ: اجعل الصورة الشخصية تتداخل مع الغلاف: نصفها فوقه ونصفها تحته. (تلميح:
Stack، وستتعلمها بالتفصيل قريباً.)
أخطاء شائعة
Section titled “أخطاء شائعة”- كتابة مسار الصورة بدون تعريفه في pubspec.yaml: يظهر الخطأ «Unable to load asset». كل صورة يجب أن تكون داخل مجلد معرَّف.
- استخدام BoxFit.fill لصور المنتجات: تتشوه الصورة. استخدم
coverأوcontain. - حزم صور ضخمة مع التطبيق: صورة بحجم 5 ميجابايت تكبّر حجم التطبيق وتبطئه. صغّر الصور قبل إضافتها.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
ما قيمة fit التي تملأ المساحة كلها بدون تشويه النِّسَب، ولو قُص جزء من الصورة؟
cover تكبّر الصورة مع الحفاظ على نِسَبها حتى تغطي المساحة، وتقص ما يزيد. أما fill فتمطها وتشوهها.
سؤال
أضفت صورة إلى مجلد assets لكن التطبيق يقول Unable to load asset. ما أول شيء تفحصه؟
صور الأصول لا تُحزم مع التطبيق إلا إذا عُرِّفت في pubspec.yaml، وبعد التعديل يجب إعادة التشغيل الكامل.
الخلاصة
Section titled “الخلاصة”- صور الأصول: مجلد في المشروع، ثم تعريف في
pubspec.yaml، ثمImage.asset. Image.networkلصور الإنترنت، معloadingBuilderللتحميل وerrorBuilderللفشل.BoxFit.coverللملء مع القص، وcontainلإظهار الصورة كاملة.CircleAvatarللصور الشخصية، وتأخذAssetImageأوNetworkImage.DecorationImageلجعل الصورة خلفية لصندوق فوقها محتوى.