SafeArea وواجهة النظام
فهم المناطق الآمنة
تحتوي الأجهزة الحديثة على نتوءات وزوايا مستديرة وأشرطة حالة وإيماءات تنقل يمكن أن تحجب محتوى تطبيقك. يضمن عنصر SafeArea عرض المحتوى ضمن الجزء المرئي غير المحجوب من الشاشة. يضيف تلقائيًا حشوة لتجنب عناصر واجهة النظام.
SafeArea عن حشوة MediaQuery للجهاز (التي تأخذ في الاعتبار أشرطة الحالة والنتوءات ومؤشرات الصفحة الرئيسية والتنقل بالنظام) ويطبق تلك الحشوة على عنصره الفرعي. بدونه، قد يكون النص والأزرار مخفية خلف شريط الحالة أو النتوء.
الاستخدام الأساسي لـ SafeArea
لف المحتوى في SafeArea هو أبسط طريقة لتجنب تداخل واجهة النظام.
مثال SafeArea الأساسي
class SafeAreaBasicPage extends StatelessWidget {
const SafeAreaBasicPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
// بدون AppBar — المحتوى يبدأ من الأعلى تمامًا
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.all(16),
child: Text(
'هذا النص آمن من النتوءات وأشرطة الحالة!',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
Expanded(
child: ListView.builder(
itemCount: 30,
itemBuilder: (context, index) => ListTile(
title: Text('عنصر \${index + 1}'),
),
),
),
],
),
),
);
}
}
التحكم في حواف SafeArea
يوفر SafeArea معلمات منطقية لتمكين أو تعطيل الحشوة على كل حافة بشكل فردي. يمنحك هذا تحكمًا دقيقًا.
حواف SafeArea الانتقائية
class SelectiveSafeArea extends StatelessWidget {
const SelectiveSafeArea({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
// صورة البطل تمتد من حافة إلى حافة، بما في ذلك خلف شريط الحالة
Container(
height: 300,
decoration: const BoxDecoration(
image: DecorationImage(
image: NetworkImage('https://picsum.photos/800/400'),
fit: BoxFit.cover,
),
),
child: SafeArea(
bottom: false, // لا تضف حشوة سفلية هنا
left: false,
right: false,
child: Align(
alignment: Alignment.topLeft,
child: Padding(
padding: const EdgeInsets.all(8),
child: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () {},
),
),
),
),
),
// منطقة المحتوى تحتاج منطقة آمنة سفلية
Expanded(
child: SafeArea(
top: false, // تم التعامل معها أعلاه
child: ListView(
padding: const EdgeInsets.all(16),
children: const [
Text(
'عنوان المقال',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 12),
Text(
'محتوى المقال يذهب هنا. أسفل هذه '
'القائمة محمي من مؤشر الصفحة الرئيسية على '
'الأجهزة التي تستخدم التنقل بالإيماءات.',
style: TextStyle(fontSize: 16),
),
],
),
),
),
],
),
);
}
}
خاصية minimum
تحدد خاصية minimum حدًا أدنى للحشوة يُطبق دائمًا، حتى لو كانت حشوة النظام أصغر. هذا مفيد لضمان تباعد متسق.
SafeArea مع حد أدنى للحشوة
SafeArea(
minimum: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'لوحة التحكم',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Expanded(
child: GridView.count(
crossAxisCount: 2,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
children: List.generate(
6,
(index) => Card(
child: Center(
child: Text('بطاقة \${index + 1}'),
),
),
),
),
),
],
),
)
Scaffold مع AppBar، يتعامل الـ scaffold بالفعل مع المنطقة الآمنة العلوية. تحتاج فقط إلى SafeArea عندما لا يكون لديك AppBar، أو عندما تحتاج إلى التحكم في حشوة الجوانب السفلية لمحتوى مثل الأزرار العائمة أو الأوراق السفلية.
SystemChrome: التحكم في واجهة النظام
تتيح لك فئة SystemChrome من dart:ui (المكشوفة عبر services.dart) التحكم في مظهر وسلوك عناصر واجهة النظام مثل شريط الحالة وشريط التنقل واتجاه الشاشة.
setSystemUIOverlayStyle
تتحكم هذه الطريقة في مظهر شريط الحالة وشريط التنقل — ألوانها وسطوع الأيقونات والمزيد.
تنسيق شريط الحالة وشريط التنقل
import 'package:flutter/services.dart';
class StyledSystemUIPage extends StatelessWidget {
const StyledSystemUIPage({super.key});
@override
Widget build(BuildContext context) {
// تعيين نمط واجهة النظام
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
// شريط الحالة
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark, // Android
statusBarBrightness: Brightness.light, // iOS
// شريط التنقل (Android)
systemNavigationBarColor: Colors.white,
systemNavigationBarIconBrightness: Brightness.dark,
systemNavigationBarDividerColor: Colors.grey,
),
);
return Scaffold(
body: SafeArea(
child: Center(
child: const Text(
'نمط واجهة نظام مخصص',
style: TextStyle(fontSize: 20),
),
),
),
);
}
}
statusBarBrightness في نمط شريط الحالة (فاتح = أيقونات داكنة، داكن = أيقونات فاتحة). على Android، يُستخدم statusBarIconBrightness بدلاً من ذلك. عيّن كليهما دائمًا للتوافق عبر المنصات. لاحظ أيضًا أن statusBarColor يعمل فقط على Android؛ يستخدم iOS دائمًا خلفية التطبيق.
استخدام AnnotatedRegion للتنسيق التصريحي
بدلاً من استدعاء SystemChrome.setSystemUIOverlayStyle إجرائيًا، يمكنك استخدام AnnotatedRegion لنهج تصريحي يربط النمط بدورة حياة العنصر.
مثال AnnotatedRegion
class AnnotatedRegionPage extends StatelessWidget {
const AnnotatedRegionPage({super.key});
@override
Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.light,
statusBarBrightness: Brightness.dark,
),
child: Scaffold(
body: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.indigo, Colors.blue],
),
),
child: const SafeArea(
child: Center(
child: Text(
'أيقونات شريط حالة فاتحة على خلفية داكنة',
style: TextStyle(
color: Colors.white,
fontSize: 18,
),
),
),
),
),
),
);
}
}
setEnabledSystemUIMode
تتحكم هذه الطريقة في أي تراكبات واجهة النظام مرئية. يمكنك إخفاء شريط الحالة أو شريط التنقل أو كليهما لتجارب غامرة.
أوضاع واجهة النظام
class SystemUIModes extends StatelessWidget {
const SystemUIModes({super.key});
void _setEdgeToEdge() {
// إظهار كل واجهة النظام مع السماح بالرسم خلفها
SystemChrome.setEnabledSystemUIMode(
SystemUiMode.edgeToEdge,
);
}
void _setImmersive() {
// إخفاء كل واجهة النظام؛ اسحب من الحافة لإظهار مؤقت
SystemChrome.setEnabledSystemUIMode(
SystemUiMode.immersive,
);
}
void _setImmersiveSticky() {
// إخفاء كل واجهة النظام؛ السحب من الحافة يظهر تراكب شفاف
SystemChrome.setEnabledSystemUIMode(
SystemUiMode.immersiveSticky,
);
}
void _setLeanBack() {
// إخفاء كل واجهة النظام؛ اضغط في أي مكان للإظهار
SystemChrome.setEnabledSystemUIMode(
SystemUiMode.leanBack,
);
}
void _restoreNormal() {
SystemChrome.setEnabledSystemUIMode(
SystemUiMode.manual,
overlays: SystemUiOverlay.values, // إظهار كل التراكبات
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('أوضاع واجهة النظام')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _setEdgeToEdge,
child: const Text('من حافة إلى حافة'),
),
const SizedBox(height: 12),
ElevatedButton(
onPressed: _setImmersive,
child: const Text('غامر'),
),
const SizedBox(height: 12),
ElevatedButton(
onPressed: _setImmersiveSticky,
child: const Text('غامر لزج'),
),
const SizedBox(height: 12),
ElevatedButton(
onPressed: _setLeanBack,
child: const Text('استرخاء'),
),
const SizedBox(height: 24),
OutlinedButton(
onPressed: _restoreNormal,
child: const Text('استعادة الوضع العادي'),
),
],
),
),
);
}
}
setPreferredOrientations
يمكنك قفل التطبيق على اتجاهات محددة أو السماح بجميع الاتجاهات برمجيًا.
التحكم في اتجاه الشاشة
class OrientationControlPage extends StatefulWidget {
const OrientationControlPage({super.key});
@override
State<OrientationControlPage> createState() =>
_OrientationControlPageState();
}
class _OrientationControlPageState extends State<OrientationControlPage> {
String _currentLock = 'جميع الاتجاهات';
Future<void> _lockPortrait() async {
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
setState(() => _currentLock = 'عمودي فقط');
}
Future<void> _lockLandscape() async {
await SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
setState(() => _currentLock = 'أفقي فقط');
}
Future<void> _unlockAll() async {
await SystemChrome.setPreferredOrientations(
DeviceOrientation.values,
);
setState(() => _currentLock = 'جميع الاتجاهات');
}
@override
void dispose() {
// استعادة الاتجاه دائمًا عند مغادرة الصفحة
SystemChrome.setPreferredOrientations(
DeviceOrientation.values,
);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('قفل الاتجاه')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'الحالي: \$_currentLock',
style: const TextStyle(fontSize: 18),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: _lockPortrait,
child: const Text('قفل عمودي'),
),
const SizedBox(height: 12),
ElevatedButton(
onPressed: _lockLandscape,
child: const Text('قفل أفقي'),
),
const SizedBox(height: 12),
OutlinedButton(
onPressed: _unlockAll,
child: const Text('فتح الكل'),
),
],
),
),
);
}
}
مثال عملي: عارض الوسائط من حافة إلى حافة
لنبني عارض وسائط بملء الشاشة يمتد من حافة إلى حافة، يستخدم الوضع الغامر، ينسق شريط الحالة، ويتعامل مع المناطق الآمنة بشكل صحيح.
عارض الوسائط بملء الشاشة
class FullscreenMediaViewer extends StatefulWidget {
const FullscreenMediaViewer({super.key});
@override
State<FullscreenMediaViewer> createState() =>
_FullscreenMediaViewerState();
}
class _FullscreenMediaViewerState extends State<FullscreenMediaViewer> {
bool _showControls = true;
@override
void initState() {
super.initState();
_enterFullscreen();
}
@override
void dispose() {
_exitFullscreen();
super.dispose();
}
void _enterFullscreen() {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
}
void _exitFullscreen() {
SystemChrome.setEnabledSystemUIMode(
SystemUiMode.manual,
overlays: SystemUiOverlay.values,
);
SystemChrome.setPreferredOrientations(DeviceOrientation.values);
}
void _toggleControls() {
setState(() => _showControls = !_showControls);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: GestureDetector(
onTap: _toggleControls,
child: Stack(
fit: StackFit.expand,
children: [
// محتوى الوسائط (صورة عنصر نائب)
Container(
color: Colors.grey.shade900,
child: const Center(
child: Icon(
Icons.play_circle_outline,
size: 80,
color: Colors.white54,
),
),
),
// عناصر التحكم المتراكبة
if (_showControls) ...[
// الشريط العلوي
Positioned(
top: 0,
left: 0,
right: 0,
child: SafeArea(
bottom: false,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black54,
Colors.transparent,
],
),
),
child: Row(
children: [
IconButton(
icon: const Icon(
Icons.arrow_back,
color: Colors.white,
),
onPressed: () {
_exitFullscreen();
Navigator.pop(context);
},
),
const Expanded(
child: Text(
'عنوان الفيديو',
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
),
),
IconButton(
icon: const Icon(
Icons.more_vert,
color: Colors.white,
),
onPressed: () {},
),
],
),
),
),
),
// عناصر التحكم السفلية
Positioned(
bottom: 0,
left: 0,
right: 0,
child: SafeArea(
top: false,
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Colors.black54,
Colors.transparent,
],
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// شريط التقدم
SliderTheme(
data: SliderTheme.of(context).copyWith(
trackHeight: 2,
thumbShape: const RoundSliderThumbShape(
enabledThumbRadius: 6,
),
),
child: Slider(
value: 0.3,
onChanged: (v) {},
activeColor: Colors.red,
inactiveColor: Colors.white30,
),
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
const Text(
'1:23 / 4:56',
style: TextStyle(
color: Colors.white70,
fontSize: 12,
),
),
IconButton(
icon: const Icon(
Icons.fullscreen_exit,
color: Colors.white,
),
onPressed: () {
_exitFullscreen();
Navigator.pop(context);
},
),
],
),
],
),
),
),
),
],
],
),
),
);
}
}
SafeArea محتواك من عناصر واجهة النظام مثل النتوءات وأشرطة الحالة ومؤشرات التنقل. استخدم خصائص top و bottom و left و right للتحكم في الحواف المحشوة. يمنحك SystemChrome التحكم في تنسيق شريط الحالة ومظهر شريط التنقل وأوضاع واجهة النظام (غامر، من حافة إلى حافة) واتجاه الشاشة. استخدم AnnotatedRegion للتنسيق التصريحي للنظام الذي ينظف تلقائيًا. استعد دائمًا الإعدادات الافتراضية للنظام في dispose() عند استخدام ملء الشاشة أو أقفال الاتجاه.