تخطَّ إلى المحتوى

الأزرار والإدخال في Flutter: Buttons و TextField

الدرس 6 من 33 · Flutter

التطبيق الذي لا يستطيع المستخدم التفاعل معه مجرد صورة. في هذا الدرس ستجعل تطبيقك يستمع للمستخدم: يضغط الأزرار، ويكتب في الحقول، ويختار من مربعات الاختيار. وفي النهاية ستبني نموذجاً يرحب بالمستخدم باسمه، ويعرض رسالة تنبيه إذا نسي كتابته.

Flutter يوفر أزراراً جاهزة بتصميم Material، ولكل نوع مكانه:

ElevatedButton(
onPressed: () {
debugPrint('زر مرتفع');
},
child: const Text('تسجيل'),
),
OutlinedButton(onPressed: () {}, child: const Text('إلغاء')),
TextButton(onPressed: () {}, child: const Text('نسيت كلمة المرور؟')),
IconButton(onPressed: () {}, icon: const Icon(Icons.share)),
ElevatedButton.icon(
onPressed: () {},
icon: const Icon(Icons.send),
label: const Text('إرسال'),
),
الزر متى تستخدمه
ElevatedButton الإجراء الأهم في الشاشة، مثل «تسجيل»
OutlinedButton إجراء ثانوي بجانب الأساسي، مثل «إلغاء»
TextButton إجراء خفيف، مثل الروابط داخل النماذج
IconButton أيقونة قابلة للضغط، مثل المشاركة أو الحذف
.icon أي زر مع أيقونة ونص معاً

تخيّل الأزرار مثل الأصوات في المحادثة: ElevatedButton صوت عالٍ واضح للأمر المهم، و TextButton همسة للأمر الجانبي. لا تجعل كل الأزرار تصرخ!

إذا أعطيت onPressed القيمة null، يصبح الزر معطلاً: رمادياً لا يستجيب للضغط:

const ElevatedButton(onPressed: null, child: Text('معطل'))

هذه الحيلة مفيدة جداً: عطّل زر «إرسال» حتى يملأ المستخدم البيانات المطلوبة، كما سترى في المشروع.

TextField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'الاسم',
hintText: 'مثلاً: سارة',
prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(),
),
)
  • labelText: اسم الحقل، ويصعد للأعلى بحركة جميلة عندما يبدأ المستخدم الكتابة.
  • hintText: مثال رمادي يظهر داخل الحقل الفارغ.
  • prefixIcon: أيقونة في بداية الحقل.
  • border: OutlineInputBorder(): إطار حول الحقل بدلاً من الخط السفلي الافتراضي.

وهناك خصائص مفيدة أخرى: obscureText: true لإخفاء كلمة المرور بنقاط، و keyboardType: TextInputType.emailAddress لإظهار لوحة مفاتيح فيها @، و TextInputType.number للوحة الأرقام.

قراءة ما كتبه المستخدم: TextEditingController

Section titled “قراءة ما كتبه المستخدم: TextEditingController”

كيف تعرف ما كُتب في الحقل؟ بـ متحكم (Controller) تربطه بالحقل:

final _nameController = TextEditingController();
@override
void dispose() {
_nameController.dispose();
super.dispose();
}
  • _nameController.text: النص المكتوب حالياً.
  • _nameController.clear(): يمسح الحقل.
  • dispose: دالة يستدعيها Flutter عندما تُغلق الشاشة نهائياً، وفيها نحرر المتحكم من الذاكرة. نسيانها يسبب تسرباً في الذاكرة (Memory Leak) مع الوقت.

تخيّل المتحكم مثل جهاز تحكم عن بعد للحقل: تقرأ به ما على الشاشة، وتمسحه، وتغيّره. وعندما تغادر الغرفة نهائياً، تطفئ الجهاز.

lib/main.dart
class GreetingForm extends StatefulWidget {
const GreetingForm({super.key});
@override
State<GreetingForm> createState() => _GreetingFormState();
}
class _GreetingFormState extends State<GreetingForm> {
final _nameController = TextEditingController();
String _greeting = '';
bool _agreed = false;
@override
void dispose() {
_nameController.dispose();
super.dispose();
}
void _submit() {
final name = _nameController.text.trim();
if (name.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('اكتب اسمك أولاً')),
);
return;
}
setState(() {
_greeting = 'أهلاً يا $name!';
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('نموذج ترحيب')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
TextField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'الاسم',
hintText: 'مثلاً: سارة',
prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(),
),
),
CheckboxListTile(
title: const Text('أوافق على الشروط'),
value: _agreed,
onChanged: (value) {
setState(() {
_agreed = value ?? false;
});
},
),
ElevatedButton(
onPressed: _agreed ? _submit : null,
child: const Text('رحّب بي'),
),
const SizedBox(height: 16),
Text(_greeting, style: const TextStyle(fontSize: 22)),
],
),
),
);
}
}

كيف يعمل خطوة بخطوة:

  1. في البداية زر «رحّب بي» معطل، لأن _agreed قيمته false فيصبح onPressed يساوي null.
  2. عند تفعيل مربع «أوافق على الشروط»، تتغير _agreed بـ setState، فيُعاد البناء ويصبح الزر فعالاً.
  3. إذا ضغط المستخدم الزر والحقل فارغ، تظهر في أسفل الشاشة رسالة مؤقتة: «اكتب اسمك أولاً». هذه الرسالة اسمها SnackBar.
  4. إذا كتب « عمر » (بمسافات زائدة) وضغط، تحذف trim() المسافات، ويظهر تحت الزر: «أهلاً يا عمر!».

لاحظ value ?? false في مربع الاختيار: القيمة الواصلة من نوع bool?، فنتعامل مع احتمال null كما تعلمت في درس Null Safety.

  1. شغّل نموذج الترحيب وجرّب السيناريوهات الأربعة السابقة.
  2. أضف حقلاً ثانياً للعمر بـ keyboardType: TextInputType.number، واطبع «أهلاً يا عمر، عمرك 15 سنة».
  3. أضف OutlinedButton بجانب الزر يمسح الحقلين والتحية.
  4. تحدٍّ: أضف حقل كلمة مرور بـ obscureText: true، مع IconButton في suffixIcon يُظهرها ويخفيها.
  • نسيان dispose للمتحكم: تسرب في الذاكرة يبطئ التطبيق مع الوقت.
  • قراءة النص بدون trim: اسم مكون من مسافات فقط يمر وكأنه صحيح.
  • كتابة onPressed: _submit(): بالأقواس تُنفَّذ الدالة فوراً أثناء البناء! الصحيح onPressed: _submit بدون أقواس.

سؤال

كيف تجعل زراً معطلاً لا يستجيب للضغط؟

سؤال

ما الطريقة الصحيحة لقراءة النص المكتوب في TextField؟

  • ElevatedButton للإجراء الأهم، و OutlinedButton و TextButton للثانوي، و IconButton للأيقونات.
  • onPressed: null يعطّل الزر، وهي طريقة ممتازة لمنع الإرسال قبل اكتمال البيانات.
  • TextField مع InputDecoration للشكل، و TextEditingController لقراءة النص، و dispose لتحريره.
  • CheckboxListTile للاختيار، و SnackBar للرسائل المؤقتة.
  • ابنِ التفاعل بنمط واحد: حدث، ثم setState، ثم إعادة بناء الشاشة.