Download as pdf or txt
Download as pdf or txt
You are on page 1of 186

‫‪Android‬‬

‫تعلم برمجة تطبيقات االندرويد من الصفر الى االحتراف‬

‫‪Ahmed Ibrahim‬‬ ‫‪9/7/17‬‬ ‫‪Basrah - Iraq‬‬


‫فهرس المحتويات‬
‫الفصل االول (االساسيات ومصادر البيانات ‪5........................................................... )Resources‬‬
‫الملف الرئيسي ‪5............................................................................................... manifest‬‬
‫المف ‪7................................................................................................................. style‬‬
‫الملف النصي ‪7............................................................................................. strings.xml‬‬
‫مصادر البيانات ‪9........................................................................................... Resources‬‬
‫شروط تسمية المجلدات ‪9.................................................................................................‬‬
‫عمل طبقة افقية ‪10........................................................................................................‬‬
‫الكالس ‪12.............................................................................................................. Log‬‬
‫الدالة ‪13.................................................................................................................. d‬‬
‫الدالة ‪13................................................................................................................... i‬‬
‫الكالس ‪14........................................................................................................Handler‬‬
‫الدالة ‪14............................................................................................................. post‬‬
‫الدالة ‪14................................................................................................ postDelayed‬‬
‫ادارة الـ ‪ Widgets‬من داخل ملف ‪17............................................................................ Java‬‬
‫الفصل الثاني (دورة الحياة والمحافظة على البيانات) ‪20.................................................................‬‬
‫الدالة ‪22........................................................................................................ onRestart‬‬
‫حفظ واستعادة البيانات ‪22...................................................................................................‬‬
‫الدالة ‪23...................................................................................... onSaveInstanceState‬‬
‫الدالة ‪24..................................................................................onRestoreInstanceState‬‬
‫الفصل الثالث (أكثر من ‪25..................................................................................... )Activity‬‬
‫انشاء ‪ Activity‬جديدة ‪25..................................................................................................‬‬
‫الوصول الى ‪ Activity‬من ‪ Activity‬اخرى ‪26.....................................................................‬‬
‫تمرير البيانات بين الـ ‪28................................................................................... Activities‬‬
‫المرحلة االولى االرسال‪28.............................................................................................:‬‬
‫المرحلة الثانية االستالم‪28..............................................................................................:‬‬
‫الدالة ) (‪29.................................................................................................. getIntent‬‬

‫‪0‬‬
29.............................................................................. ‫ االبن‬Activity ‫استعادة نتائج من الـ‬
30......................................................................................... onActivityResult ‫الدالة‬
33.......................................................................)List views and Adapters( ‫الفصل الرابع‬
35.............................................................................................................list activity
36.......................................................................................... ListActivity ‫دوال الكالس‬
36................................................................................................ getListView ‫الدالة‬
36...................... onListItemClick (ListView l, View v, int position, long id) ‫الدالة‬
37.............................................................................. setListAdapter(adapter) ‫الدالة‬
37................................................................................................................ Adapters
37.............................................................................................. ArrayAdapter ‫الكالس‬
38................................................................................................... setAdapter ‫الدالة‬
39..................................................................................................... ArrayList ‫الكالس‬
40.......................................................................................... )Fragments( ‫الفصل الخامس‬
40............................................................................................... fragment ‫دورة حياة الـ‬
41........................................................................................................Fragment ‫انشاء‬
41................................................................................................. Fragment ‫مكونات الـ‬
42........................................................................... fragment ‫ الذي يعرض الـ‬activity ‫الـ‬
44...................................................... fragment ‫ من داخل الـ‬Widgets ‫الوصول الى العناصر‬
46...................................................................... ‫ على شكل قائمة عرض‬Fragment ‫تنظيم الـ‬
47........................................................................................ ListFragment ‫دوال الكالس‬
47................................................................................................ getListView ‫الدالة‬
47...................... onListItemClick (ListView l, View v, int position, long id) ‫الدالة‬
48.............................................................................. setListAdapter(adapter) ‫الدالة‬
49............................................................................ ‫ اخر‬Fragment ‫ بـ‬Fragment ‫استبدال‬
51..............................................................................‫ المعروضة حاليا‬Fragment ‫معرفة الـ‬
52......................................................................... Fragment ‫التعامل مع زر الرجوع في الـ‬
52..................................................................................................... ‫ متداخلة‬Fragment

1
‫مالحظات عامة عن الـ ‪53.................................................................................. Fragment‬‬
‫الفصل السادس (‪54.......................................................................................... )action bars‬‬
‫الملف ‪55............................................................................................................. menu‬‬
‫اضافة زر في الـ ‪56......................................................................................... action bar‬‬
‫الخطوة االولى ‪56.........................................................................................................‬‬
‫الخطوة الثانية ‪57..........................................................................................................‬‬
‫عمل زر مشاركة في الـ ‪58................................................................................ action bar‬‬
‫اضافة زر رجوع في الـ ‪60................................................................................ action bar‬‬
‫تغييير عناصر الـ ‪ action bar‬اثناء تشغيل البرنامج ‪62..............................................................‬‬
‫مالحظات عامة عن الـ ‪62................................................................................. action bar‬‬
‫الفصل السابع (القائمة الجانبية ‪63............................................................ )Navigation drawer‬‬
‫الفصل الثامن (قواعد البيانات ‪77............................................................................... )SQLite‬‬
‫الجزء االول ‪80...............................................................................................................‬‬
‫الكالس ‪81....................................................................................... SQLiteDatabase‬‬
‫التعديل على جدول ‪84........................................................................................SQLite‬‬
‫عمل تحديث للبيانات ‪85..................................................................................................‬‬
‫الجزء الثاني ‪86...............................................................................................................‬‬
‫الدالة ‪87........................................................................................................... query‬‬
‫استخدام دوال ‪ SQL‬مع الدالة ‪89............................................................................ query‬‬
‫الدالة ‪ getReadableDatabase‬والدالة ‪91...................................... getWritableDatabase‬‬
‫دوال التنقل ‪92..............................................................................................................‬‬
‫اخذ البيانات من االوبجكت ‪93............................................................................... Cursor‬‬
‫اغالق االوبجكت ‪ Cursor‬وقاعدة البيانات ‪94.......................................................................‬‬
‫‪95................................................................................................... CursorAdapter‬‬
‫‪95........................................................................................ SimpleCursorAdapter‬‬
‫اضافة قيم في قاعدة البيانات ‪97.........................................................................................‬‬
‫الدالة ‪98.............................................................................................. changeCursor‬‬

‫‪2‬‬
‫زيادة سرعة قواعد البيانات ‪98.............................................................................................‬‬
‫الكالس ‪99............................................................................................... AsyncTask‬‬
‫الفصل التاسع (‪102............................................................... )Notifications and messages‬‬
‫االشعارات ‪102......................................................................................... Notifications‬‬
‫النوافذ المنبثفة (‪109........................................................................................ )Messages‬‬
‫النافذة المنبثقة ‪109...............................................................................................Toast‬‬
‫مربع حوار ‪110............................................................................................... Dialog‬‬
‫رسالة التنبيه ‪113................................................................................................. Alert‬‬
‫الفصل العاشر (‪117............................................................................................. )Services‬‬
‫الجزء االول (‪118.................................................................................. )started service‬‬
‫الجزء الثاني (‪121................................................................................... )bound service‬‬
‫الموقع ‪124.................................................................................................. Location‬‬
‫الفصل الحادي عشر (‪126...................................................................................... )Sensors‬‬
‫الفصل الثاني عشر (‪135........................................................................ )Bluetooth & Wifi‬‬
‫‪135..................................................................................................... The Bluetooth‬‬
‫‪152............................................................................................................ The Wi-Fi‬‬
‫الفصل الثالث عشر (تحويل النص الى كالم) ‪169.........................................................................‬‬
‫الفصل الرابع عشر (‪175.......................................................................... )Material Design‬‬
‫انشاء ‪175............................................................................................... Recyclerview‬‬
‫الفصل االخير (مالحظات عامة) ‪184.......................................................................................‬‬

‫‪3‬‬
‫*****‬

‫هذا الكتاب من اعداد احمد ابراهيم‪ ،‬وهناك كتب اخرى كتبها الكاتب لتعلم لغات برمجة مختلفة منها‬
‫(‪ )HTML CSS, JavaScript, jQuery, PHP, Java‬يمكنكم تحميلها وقراءتها من هذا الرابط‪:‬‬
‫‪https://drive.google.com/open?id=0B2aI_a6mphOUQi1jdzlYSFhITWs‬‬
‫كل هذه الكتب مجانية اي يمكن قراءتها واعادة نشرها‪.‬‬

‫الي سؤال او استفسار يمكنكم التواصل مع الكاتب عبر الحساب الشخصي في فيسبوك‪:‬‬
‫‪https://www.facebook.com/ah.ib.93‬‬

‫‪4‬‬
‫الفصل االول (االساسيات ومصادر البيانات ‪)Resources‬‬

‫كل شاشة ‪ activity‬تعرض للمستخدم هي متكونة من ملفين االول يمثل الجزء الظاهر على الشاشة وهو‬
‫مكتوب بلغة ‪ XML‬ويسمى ‪ layout‬اما الثاني فهو مكتوب بلغة ‪ Java‬وهو يدير الجزء االول‪.‬‬

‫‪ Widgets‬هي عبارة عن كالسات وكل كالس من هذه الكالسات هو عبارة عن عنصر يعرض للمستخدم‬
‫مثال الزر ‪ button‬او حقل اظهار النص ‪ TextView‬كلها عبارة عن ‪.Widgets‬‬

‫كل ‪ Widget‬هو عبارة عن كالس مشتق من الكالس ‪ View‬او مشتق من كالس مشتق من الكالس ‪.View‬‬

‫كل عنصر من العناصر ‪ Widgets‬له عدة خصائص مثل الطول والعرض والنص الذي يظهر والـ ‪ id‬الخ‬
‫من الخصائص كلها تكون في ملف الـ ‪ XML‬الذي يعرض الطبقة ‪ ،layout‬وكل هذه الخصائص يمكن‬
‫التعامل معها بشكل سهل من خالل قسم الـ ‪ Design‬في برنامج االندرويد ستوديو او كتابتها بشكل يدوي في‬
‫قسم الـ ‪.Text‬‬

‫* الدالة ‪ setContentView‬وظيفتها هي ان تعرض ملف الـ ‪ layout‬على الشاشة ويمرر لها باراميتر يمثل‬
‫اسم ومسار الـ ‪ layout‬المراد عرضها‪ ،‬مثال‪:‬‬

‫;)‪setContentView(R.layout.activity_main‬‬

‫حيث ان ‪ R‬يمثل اسم المجلد االفتراضي الذي توضع فيه الـ ‪ layout‬والذي اسمه ‪ res‬و‪ layout‬يمثل اسم‬
‫المجلد الفرعي الذي بداخله و‪ activity_main‬هو اسم ملف الـ ‪ layout‬الذي سيعرض للمستخدم‪.‬‬

‫الملف الرئيسي ‪manifest‬‬


‫هو ملف مكتوب بلغة ‪ XML‬ويعتبر الملف الرئيسي في اي برنامج النه من خالله يستطيع االندرويد ‪ OS‬ان‬
‫يتعرف على البرنامج فهو يحتوي على المعلومات االساسية‪ ،‬فهو يحتوي على اسم البرنامج واسم ملف الجافا‬
‫الرئيسي الذي سيبدأ منه تنفيذ البرنامج باالضافة الى اسم اي ‪ activity‬يتم انشائه باالضافة الى تفاصيل‬
‫اخرى‪ ،‬يوجد هذا الملف في الـ ‪ Project‬في المجلد ‪ manifests <- app‬واسم الملف هو‬
‫‪ ، AndroidManifest.xml‬ويكون الكود الموجود بداخله بهذا الشكل‪:‬‬

‫>?"‪<?xml version="1.0" encoding="utf-8‬‬


‫"‪<manifest xmlns:android="http://schemas.android.com/apk/res/android‬‬

‫‪5‬‬
‫>"‪package="com.bignerdranch.android.myapplication‬‬
‫‪<application‬‬
‫"‪android:allowBackup="true‬‬
‫"‪android:icon="@mipmap/ic_launcher‬‬
‫"‪android:label="@string/app_name‬‬
‫"‪android:roundIcon="@mipmap/ic_launcher_round‬‬
‫"‪android:supportsRtl="true‬‬
‫>"‪android:theme="@style/AppTheme‬‬
‫‪<activity‬‬
‫"‪android:name=".MainActivity‬‬
‫> ‪android:label="@string/activity_name‬‬
‫…‬
‫>‪</activity‬‬
‫>‪</application‬‬
‫>‪</manifest‬‬

‫حيث ان الوسم >‪ <application‬يمثل كامل البرنامج والوسم >‪ <activity‬يمثل الـ ‪ activity‬وكل‬
‫‪ activity‬جديدة ننشأها يجب ان يتم ذكرها هنا (البرنامج يقوم بذلك اوتوماتيكيا)‪ ،‬وهناك مجموعه من‬
‫الخصائص مثال‪:‬‬

‫الخاصية ‪ android:icon‬تستخدم لتحديد ايقونة للبرنامج‪ ،‬واذا استخدمنا ثيم يعرض ايقونة في الـ ‪action‬‬
‫‪ bar‬فانه سيتم عرض هذه االيقونة‪ ،‬االيقونة يمكن ان تكون في المجلد ‪ mipmap‬او في المجلد ‪drawable‬‬
‫والفرق هو ان في المجلد ‪ mipmap‬ستكون ايقونة واحدة تعرض لجميع االجهزة اما في المجلد ‪drawable‬‬
‫يمكن ان نضع مجموعة من االيقونات كل ايقونة تعرض في جهاز على حسب دقة الشاشة‪ ،‬وهنا اشرنا الى‬
‫المجلد ‪ mipmap‬من خالل اضافة @ قبل اسمه ‪.‬‬

‫الخاصية ‪ android:label‬تمثل اسم البرنامج اذا كانت في الوسم >‪ <application‬او تمثل اسم الـ‬
‫‪ activity‬اذا كانت في الوسم >‪ <activity‬واالسم موجود في الملف النصي ‪. string‬‬

‫الخاصية ‪ android:theme‬تحدد الثيم‪ ،‬استخدامها في الوسم >‪ <application‬يجعل الثيم مطبق على كامل‬
‫البرنامج اما اذا كانت في الوسم >‪ <activity‬فتطبق على الـ ‪ activity‬فقط ‪ ،‬والثيم موجود في الملف ‪style‬‬
‫‪.‬‬

‫‪6‬‬
‫المف ‪style‬‬
‫هذا الملف يحمل التفاصيل الي ثيم نريد ان نستخدمه‪ ،‬عندما ننشأ مشروع جديد في برنامج االندرويد‬
‫ستوديو فان الـ ‪ IDE‬ينشأ ملف باسم ‪ style.xml‬ويضعه في المجلد‪:‬‬

‫‪app-> res->values‬‬

‫والكود في هذا الملف بشكل افتراضي ينشأ بهذا الشكل‪:‬‬

‫>‪<resources‬‬
‫>‪<!-- Base application theme. --‬‬
‫>"‪<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar‬‬
‫>‪<!-- Customize your theme here. --‬‬
‫>‪<item name="colorPrimary">@color/colorPrimary</item‬‬
‫>‪<item name="colorPrimaryDark">@color/colorPrimaryDark</item‬‬
‫>‪<item name="colorAccent">@color/colorAccent</item‬‬
‫>‪</style‬‬
‫>‪</resources‬‬

‫يمكن ان يحتوي الملف على ثيم واحد او أكثر وكل ثيم يمثل بالوسم >‪ ،<style‬ويجب ان يحتوي الثيم‬
‫(الوسم >‪ )<style‬على الخاصية ‪ name‬ونضع بداخلها اسم لكي يتمكن الملف ‪ manifest‬من تحديد الثيم‬
‫الذي سيعرض بالوصول له من خالل اسمه‪ ،‬ومن خالل الخاصية ‪ parent‬نحدد من اين سيرث هذا الثيم‬
‫خصائصه‪ ،‬يمكن ان يكون للوسم >‪ <style‬ابناء وذلك من خالل الوسم >‪ <item‬فمن خالل هذا الوسم‬
‫يمكن ان نجري التعديالت التي نريد على الثيم مثال لجعل الخلفية حمراء نكتب التالي‪:‬‬

‫>‪<item name="android:background">#FF0000</item‬‬

‫يمكن اخذ القيمة اللونية من ملف االلوان ‪ colors‬او كتابتها بشكل مباشر‪.‬‬

‫الملف النصي ‪strings.xml‬‬


‫كل النصوص التي نريدها ان تعرض في البرنامج يمكن ان تكتب في الملف النصي الذي اسمة‬
‫‪ strings.xml‬وهو موجود في المجلد ‪ app -> res -> values‬يتكون الملف من الوسم الرئيسي‬
‫<‪ >resources‬والذي بين وسمي الفتح والغلق له يكتب الوسم >‪ <string‬وله الخاصية ‪ name‬والتي من‬
‫خاللها يمكن الوصول للنص المكتوب بين وسمي الفتح والغلق فيه‪ ،‬مثال‪:‬‬

‫‪7‬‬
‫>‪<resources‬‬

‫>‪<string name="app_name">My Application</string‬‬

‫>‪<string name="My_TextView">The text here</string‬‬

‫>‪</resources‬‬

‫حيث ان ‪ app_name‬و‪ My_TextView‬تمثل اسم تعريفي للنص المكتوب بين وسمي الفتح والغلق‬
‫وتسمى هذه االسماء بـ ‪ ،resId‬ومن خالله يمكن الوصول للنص المراد عرضه ولكن الحظ ان الـ ‪resId‬‬
‫نفسه يعتبر من النوع ‪ int‬وليس من النوع ‪ ،String‬اما النص الموجود بين وسمي الفتح والغلق يعتبر من‬
‫النوع ‪ String‬وهو يمثل النص المراد التعامل معه ويمكن ان يكون هذا النص اسم البرنامج او نص يعرض‬
‫على الشاشة في مربع عرض او يمكن استعماله في اي مكان نريد‪.‬‬

‫مثال الـ ‪ resId‬للوصول الى كلمة ‪ My Application‬في المثال السابق هو (‪)string.app_name.R‬‬


‫حيث ان ‪ R‬يشير الى المجلد ‪ res‬و‪ strings‬يمثل اسم الملف الموجود فيه النص و‪ app_name‬يمثل االسم‬
‫الموجود في الخاصية ‪ ،name‬ويمكن تخزينه في متغير بهذا الشكل‪:‬‬

‫;‪int X = R.string.app_name‬‬

‫* يمكن وضع متعاقبات الهروب في داخل النص لتعرض‪ ،‬مثال لطباعة سطر جديد‪:‬‬

‫>‪<string name="My_Name">The text \n here</string‬‬

‫*يمكن عمل مصفوفة من االسماء (مصفوفة نصية) في داخل الملف ‪ string‬ولعمل ذلك نستخدم الوسم‬
‫<‪ >string-array‬ونعطيه الخاصية ‪ name‬لتمثل اسم المصفوفة وبين وسمي الفتح والغلق له نضع عناصر‬
‫المصفوفة عبر الوسم >‪ ،<item‬مثال‪:‬‬

‫>"‪<string-array name="pizzas‬‬
‫>‪<item>Diavolo</item‬‬
‫>‪<item>funghi</item‬‬
‫>‪</string-array‬‬

‫وللوصول الى هذه المصفوفة في ملف الجافا نستخدم الدالة ‪ getResources‬والدالة ‪،getStringArray‬‬
‫بهذا الشكل‪:‬‬

‫;))‪String[ ] X = getResources().getStringArray(R.array.pizzas‬‬

‫‪8‬‬
‫* يمكن عمل مجلد ‪ XML‬نصي اخر غير هذا المجلد واخذ النصوص منه لكن بشرط ان نضعه في داخل‬
‫المجلدات ‪ app -> res -> values‬ايضا‪.‬‬

‫مصادر البيانات ‪Resources‬‬


‫مصادر البيانات هي المكان الذي ياخذ البرنامج منه مصدر معلوماته وهي توجد في المجلد ‪ ،res‬مصادر‬
‫البيانات الرئيسية توجد في المجلدات (‪ )drawable, layout, values, mipmap‬في داخل المجلد ‪،res‬‬
‫حيث ان كل مجلد من هذه المجلدات مسؤول عن نوع معين من البيانات‪.‬‬

‫المجلد ‪ drawable‬نضع فيه مجلدات اليقونات البرنامج‪ ،‬وملفات الصور والصوت‬ ‫‪-1‬‬
‫المجلد ‪ layout‬نضع فيه ملفات الـ ‪ XML‬المسؤلة عن الطبقة وعرض العناصر ‪Widgets‬‬ ‫‪-2‬‬
‫المجلد ‪ values‬فيه ملف النصوص ‪ strings‬وملف االلوان ‪ colors‬وملف الثيم ‪styles‬‬ ‫‪-3‬‬
‫المجلد ‪ mipmap‬نضع فيه ايقونات التطبيق (لالجهزة القديمة)‬ ‫‪-4‬‬

‫يمكننا اضافة مصادر (مجلدات) اخرى في داخل المجلد ‪ res‬لكي يأخذ منها البرنامج معلوماته في حالة معينة‬
‫(كأن يكون الهاتف في وضع افقي او يعرض البرنامج من تابلت او هاتف إلخ)‪ ،‬لعمل ذلك نحتاج ان ننشأ‬
‫مجلدات ونلتزم بتسميات خاصة‪.‬‬

‫شروط تسمية المجلدات‬


‫يتكون اسم المجلد من نوع مصدر البيانات ثم فاصلة ( ‪ ) -‬ثم التصنيف‪ ،‬اذا كان هناك اكثر من تصنيف نفصل‬
‫بين التصنيفات بفاصلة ( ‪ ) -‬مع مالحظة عدم ترك فاصلة ( مسافة فارغة ) في اسم المجلد‪ ،‬الجدول التالي‬
‫يوضح مصادر البيانات وانواع التصنيفات ‪:‬‬

‫‪Resource type‬‬
‫التصنيفات‬
‫انواع مصادر البيانات‬

‫‪Screen Size Screen density Orientation Aspect ratio API level‬‬


‫حجم الشاشة‬ ‫دقة الشاشة‬ ‫اتجاه الهاتف‬ ‫اصدار النظام طول الشاشة‬

‫‪drawable‬‬ ‫‪-small‬‬ ‫‪-ldpi‬‬ ‫‪-land‬‬ ‫‪-long‬‬ ‫…‪-v‬‬

‫‪9‬‬
‫‪layout‬‬ ‫‪-normal‬‬ ‫‪-mdpi‬‬ ‫‪-port‬‬ ‫‪-notlong‬‬ ‫‪-v17‬‬

‫‪menu‬‬ ‫‪-large‬‬ ‫‪-hdpi‬‬ ‫‪-v18‬‬

‫‪mipmap‬‬ ‫‪-xlarge‬‬ ‫‪-xhdpi‬‬ ‫‪-v19‬‬

‫‪values‬‬ ‫‪-xxhdpi‬‬ ‫‪-v20‬‬

‫‪-xxxhdpi‬‬ ‫‪-v21‬‬

‫‪-nodpi‬‬ ‫…‪-v‬‬

‫* مثال إذا أردنا ان تظهر الطبقة (ملف الـ ‪ XML‬الخاص بالعناصر ‪ )Widgets‬في الهواتف ذوات حجم‬
‫الشاشة الكبيرة بشكل مختلف عن بقية الهواتف نعمل مجلد في داخل المجلد ‪ res‬ونسميه ‪layout-xlarge‬‬
‫نظام االندرويد بشكل افتراضي سيظهر الطبقة الموجودة في المجلد ‪ layout‬لجميع الهواتف ما عدا الهواتف‬
‫ذوات حجم الشاشة الكبيرة سيظهر فيها الطبقة الموجودة في المجلد ‪ layout-xlarge‬لكن بشرط ان تكون كال‬
‫الطبقتين بنفس االسم‪.‬‬

‫* الحظ انه يمكن ان نضع أكثر من صنف للمجلد لكي يتم عرضه مثال يمكن عمل طبقة التنفذ اال عندما‬
‫يكون الهاتف بوضع افقي وايضا الهاتف ذو حجم شاشة كبيرة ولتنفيذ ذلك يجب ان تكون هذه الطبقة في مجلد‬
‫اسمه ‪( layout-xlarge-land‬وال تنسى ان تكون كال الطبقتين بنفس االسم)‪.‬‬

‫* مثال إذا أردنا ان يظهر ثيم مختلف في االجهزة ذوات االصدار ‪ API level 21‬نعمل في داخل المجلد ‪res‬‬
‫مجلد ونسميه ‪ values-v21‬ونضع فيه الملف ‪ style‬لينفذ في االجهزة ذوات هذا النظام اما في بقية االجهزة‬
‫سينفذ الملف ‪ style‬الموجود في المجلد ‪( values‬ال تنسى ان يكون كال ملفي الـ ‪ style‬بنفس االسم)‪.‬‬

‫* برنامج االندرويد ستوديو يسهل هذه العملية‪ ،‬يمكننا ان نجعل البرنامج يسمي لنا مجلدات المصادر وذلك‬
‫من خالل الذهاب الى النافذة ‪ Project‬في البرنامج ثم نضغط بالزر اليمين على المجلد ‪ res‬ومن ثم من‬
‫الخيار ‪ New‬نختار ‪ Android resource directory‬ستظهر لنا نافذه منها نحدد مصدر البيانات‬
‫والتصنيفات التي نريدها‪ ،‬لتوضيح االمر سنأخذ مثال لعمل مجلد نضع فيه ملف ينفذ إذا كان الهاتف بشكل‬
‫افقي‪:‬‬

‫عمل طبقة افقية‬


‫في برنامج االندرويد ستوديو نذهب الى النافذة ‪ Project‬ثم نضغط بالزر اليمين على المجلد ‪ res‬ومن ثم من‬
‫الخيار ‪ New‬نختار ‪ Android resource directory‬ستظهر لنا نافذه بها عدة خيارات من الخيار‬

‫‪10‬‬
‫‪ Resource type‬نحدد ‪ layout‬وفي الخانة ‪ Available qualifiers‬نحدد الخيار ‪ Orientation‬لتكون‬
‫النافذة الظاهرة بهذا الشكل‪:‬‬

‫ثم نضغط على الزر >> لتظهر لنا صفحة اخرى‪( ،‬يمكن من الخانة ‪ Available qualifiers‬ان نحدد‬
‫صنف اخر اضافة التجاه الهاتف ونضيفه من خالل الزر >>)‪ ،‬في النافذة الجديدة من الخيار ‪Screen‬‬
‫‪ Orientation‬نحدد الـ ‪ Landscape‬ومن ثم نضغط على ‪ ،OK‬كما في هذه الصورة‪:‬‬

‫‪11‬‬
‫االن سيتكون عندنا في داخل المجلد ‪ res‬مجلد فارغ اسمة ‪ layout-land‬لذا يجب عمل طبقة في داخل هذا‬
‫المجلد (يمكن ببساطة نسخ ولصق الطبقة الرئيسية ‪ activity_main‬او ايا كان اسمها التي كانت تعرض في‬
‫الوضع العمودي ومن ثم اجراء التعديالت عليها مع مالحظة ان الطبقتين يجب ان تبقيا بنفس االسم ليتعرف‬
‫عليهما البرنامج) ‪ ،‬ان الالحقة ‪ -land‬تجعل البرنامج يحدد اي طبقة سيعرض حيث اذا كان الهاتف بوضع‬
‫عمودي سيعرض الطبقة الموجودة في ‪ res‬في داخل المجلد ‪ layout‬اما اذا كان الهاتف بوضع افقي‬
‫سيعرض الطبقة الموجودة في ‪ res‬في داخل المجلد ‪. layout-land‬‬

‫الكالس ‪Log‬‬
‫يتم استخدام هذا الكالس لعرض رسائل للمبرمج ليتأكد من ان برنامجه يعمل بالشكل المطلوب‪ ،‬لرؤية‬
‫الرسائل التي تأتي من الكالس ‪ Log‬في برنامج االندرويد ستوديو نذههب الى نافذة الـ ‪Android Monitor‬‬
‫ثم نحدد خانة ‪ logcat‬ومنها نحدد الخيار ‪ Debug‬او ‪ info‬إلخ بحسب الدالة المستخدمة لفلترة النتائج‪،‬‬
‫ولنرى الرسائل يجب ان يكون البرنامج قيد العمل‪ ،‬هذا الكالس موجود في الحزمة ‪ android.util‬لذا يجب‬
‫استدعائه قبل استخدام هذا الكالس بهذا الشكل‪:‬‬

‫;‪import android.util.Log‬‬

‫‪12‬‬
‫ويحتوي هذا الكالس على العديد من الدوال منها‪:‬‬

‫‪Log level‬‬ ‫‪Method‬‬ ‫‪Notes‬‬


‫) … (‪ERROR Log.e‬‬ ‫االخطاء‬
‫) … (‪WARNING Log.w‬‬ ‫لرسائل التحذيرات‬
‫) … (‪INFO Log.i‬‬ ‫لرسائل المعلومات‬
‫تنقيح المحتويات‪ ،‬ربما فلترت المخرجات ) … (‪DEBUG Log.d‬‬
‫) … (‪VERBUSE Log.v‬‬

‫الصيغة العامة لكل هذه الدوال هي‪:‬‬

‫)‪public static int d(String tag, String msg‬‬

‫حيث ان الباراميتر االول يمثل مصدر الرسالة (وفي العادة يكون اسم الكالس) والباراميتر الثاني يمثل نص‬
‫الرسالة التي ستعرض‪ ،‬لتوضيح االمر أكثر سنأخذ مثال عن كم دالة‪.‬‬

‫الدالة ‪d‬‬
‫هذه الدالة هي اختصار لكلمة ‪ debug‬حيث ان وظيفة هذه الدالة هي ان تعرض رسالة للمبرمج عن البرنامج‪،‬‬
‫مثال‪:‬‬

‫;)"‪Log.d("MainActivity", "The message‬‬

‫الدالة ‪i‬‬
‫هذه الدالة هي اختصار لكلمة ‪ information‬وظيفتها هي تزويد المبرمج بالمعلومات المطلوبة عن البرنامج‪،‬‬
‫مثال‪:‬‬

‫;)"‪Log.i("info", "The message‬‬

‫‪13‬‬
‫الكالس ‪Handler‬‬
‫يستخدم هذا الكالس الدارة كود معين ليعمل فيما بعد عند مرحلة معينة‪ ،‬وايضا يمكن من خالله متابعه كود‬
‫يحتاج ان يعمل على ثريد مختلفة‪ ،‬الستخدام هذا الكالس نحتاج ان نغلف الكود المراد ادارته باالوبجكت‬
‫‪ Runnable‬ومن ثم نستخدم دوال الكالس ‪ Handler‬وهي ‪ post‬و‪ postDelayed‬لتحديد متى نريد الكود‬
‫ان ينفذ‪.‬‬

‫الدالة ‪post‬‬
‫تنشر هذه الدالة الكود بمجرد ان يتاح لها ذلك‪ ،‬هذه الدالة تاخذ بين قوسيها باراميتر واحد من نوع االوبجكت‬
‫‪ ،Runnable‬االوبجكت ‪ Runnable‬وظيفته هي التشغيل حيث يتم وضع الكود المراد تشغيله داخل دالته‬
‫‪ run‬وهي المسؤلة عن ذلك و‪ Handler‬يتأكد من ان الكود ينفذ في الوقت المناسب‪ ،‬تكتب الدالة بهذه‬
‫الصيغة‪:‬‬

‫;)(‪final Handler h = new Handler‬‬


‫{ )(‪h.post(new Runnable‬‬
‫‪@Override‬‬
‫{ ) (‪public void run‬‬
‫‪// the code‬‬
‫}‬
‫;)}‬

‫الدالة ‪postDelayed‬‬
‫وظيفة هذه الدالة هي اعادة تنفيذ كود معين بعد فترة زمنية معينة‪ ،‬وتأخذ هذه الدالة بارامترين االول هو من‬
‫نوع االوبجكت ‪ Runnable‬والثاني هو ‪ ،long‬البارميتر االول يمثل كود نريد ان ننفذه في داخل الدالة ‪run‬‬
‫اما البارميرتر الثاني يمثل رقم بالملي ثانية نريد ان يتم اعادة تفيذ الكود بعد هذه المدة‪ ،‬صيغتها العمة بهذا‬
‫الشكل‪:‬‬

‫;)(‪final Handler h = new Handler‬‬


‫;)‪h.postDelayed(Runnable, long‬‬

‫مثال‪:‬‬

‫;‪int s = 0‬‬

‫‪14‬‬
public void runTimer(){
final TextView timeView = (TextView) findViewById(R.id.textView);
final Handler h = new Handler();
h.post(new Runnable() {
@Override
public void run() {
s++;
timeView.setText("Timer"+s);
h.postDelayed(this, 1000);
}
});
}

.‫ ستبقى تزداد قيمتها كل ثانية الى ماالنهاية‬s ‫ فان قيمة المتغير‬runTimer ‫هنا عند تنفيذ الدالة‬

‫ وذلك من خالل عمل عداد ساعة (المثال يظهر ملف الجافا فقط) صورة‬Handler ‫مثال يوضح الكالس‬
:‫التطبيق بهذا الشكل‬

package com.bignerdranch.android.test;

15
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.TextView;
public class MainActivity extends Activity {
public int s = 0 ;
public boolean running;
@Override
protected void onCreate(Bundle saved) {
super.onCreate(saved);
setContentView(R.layout.activity_main);
runTimer( );
}
public void onClickStart(View view){ running = true; }
public void onClickStop(View view){ running = false; }
public void onClickReset(View view){
running = false;
s = 0; }
public void runTimer(){
final TextView timeView = (TextView) findViewById(R.id.textView);
final Handler handler = new Handler();
handler.post(new Runnable() {
@Override
public void run() {
int hours = s /3600;
int minutes = (s % 3600 )/60;
int secs = s % 60;
String time = String.format("%d:%02d:%02d", hours, minutes, secs);
timeView.setText(time);

16
‫} ;‪if (running){ s++‬‬
‫;)‪handler.postDelayed(this, 1000‬‬
‫}‬
‫;)}‬
‫}}‬

‫ادارة الـ ‪ Widgets‬من داخل ملف ‪Java‬‬


‫هناك طريقتين للوصول الى اي عنصر من عناصر الـ ‪ Widgets‬والتعامل معه من داخل ملف الـ ‪.Java‬‬

‫الطريقة االولى‬
‫هذه الطريقة يمكن تنفيذها فقط مع العناصر التي تحمل الخاصية ‪( onClick‬مثل الكالس ‪ Button‬او‬
‫الكالسات المشتقة من الكالس ‪ Button‬مثل ‪ checkboxes‬و ‪ radio buttons‬و ‪ switches‬و ‪toggle‬‬
‫‪ )buttons‬حيث في ملف ‪ XML‬الذي يعرض الطبقة ‪ layout‬نضع في خاصية العنصر ‪ onClick‬اسم دالة‬
‫وننشأ دالة بنفس هذا االسم في ملف الـ ‪( Java‬الذي يرث الكالس ‪ )Activity‬ونكتب بها ما نريد لكن يجب‬
‫ان تكون الدالة من النوع عام ‪ public‬لكي يمكن الوصول لها من ملف الـ ‪ ،XML‬وكذلك يجب ان نمرر لها‬
‫باراميتر من النوع ‪( View‬حتى اذا لم نستخدمه)‪ ،‬حيث عند الضغط على هذا العنصر ستنفذ هذه الدالة‪.‬‬

‫الطريقة الثانية‬
‫هذه الطريقة تشمل جميع العناصر ‪ Widgets‬باالضافة الى انه يمكننا التعامل مع كل خواص العنصر‪.‬‬

‫اوال في داخل الملف ‪ Java‬يجب عمل استدعاء (‪ )import‬للكالس الخاص بالعنصر بهذا الشكل‪:‬‬

‫; ‪import android.widget.#‬‬

‫حيث بدال من العالمة ‪ #‬نضع اسم العنصر الذي نريد التعامل معه كأن يكون ‪ TextView‬او ‪ Button‬او‬
‫‪ CheckBox‬إلخ من العناصر‪ ،‬مثال إذا أردنا التعامل مع زر ‪ button‬سنكتب عبارة االستدعاء هذه‪:‬‬

‫; ‪import android.widget.Button‬‬

‫‪17‬‬
‫ثانيا نحدد العنصر الذي نريد الوصول اليه من خالل الـ ‪ id‬الخاص به وذلك من خالل الدالة‬
‫‪ findViewById‬والصيغة العامة لها هي‪:‬‬

‫)‪public View findViewById(int id‬‬

‫حيث انها تعيد اوبجكت من النوع ‪ View‬وتأخذ بين قوسيها الـ ‪ id‬الخاص بالعنصر ‪ ،Widget‬والحظ ان الـ‬
‫‪ id‬يعتبر من النوع ‪ int‬وليس ‪ ،String‬بهذا الشكل‪:‬‬

‫;)‪# X = (#) findViewById(ID‬‬

‫حيث بدال من العالمة ‪ #‬نضع اسم العنصر الذي نريد التعامل معه كأن يكون ‪ TextView‬او ‪ Button‬او‬
‫‪ CheckBox‬إلخ من العناصرو ‪ ID‬تمثل الـ ‪ id‬الخاص بالعنصر و‪ X‬تمثل اسم متغير (اوبجكت) من نوع‬
‫هذا العنصر‪ ،‬مثال لتحديد مربع نصي ‪ TextView‬اسم الـ ‪ id‬الخاص به هو ‪ Y‬نكتب‪:‬‬

‫;)‪TextView X = (TextView) findViewById(R.id.Y‬‬

‫هنا أصبح المتغير (االوبجكت) ‪ X‬يمثل المربع النصي الذي يحمل االيدي ‪ ،Y‬واالن سنتمكن من الوصول‬
‫الى اي خاصية من خصائص العنصر من خالل هذا االوبجكت بهذا الشكل‪:‬‬

‫;) (‪X.#‬‬

‫حيث هنا العالمة ‪ #‬تمثل اسم الدالة وغالبا اسم الدالة نفس او مشابه السم الخاصية (الموجودة في ملف الطبقة‬
‫‪ )XML‬التي نريد التعامل معها مثال الدالة ‪( setTextSize‬تحدد حجم النص) والدالة ‪( setPadding‬تحدد‬
‫االزاحة الداخلية للعنصر) والدالة ‪( getText‬تأخذ النص الموجود في العنصر) والدالة ‪( setText‬تضع نص‬
‫في العنصر) إلخ من الدوال‪ ،‬مثال النشاء دالة عند تنفيذها يتم وضع قيمة نصية في داخل حقل نصي‬
‫‪ TextView‬الـ ‪ id‬الخاص به هو ‪:Y‬‬

‫{ )‪public void test_meth(View v‬‬


‫;)‪TextView X = (TextView) findViewById(R.id.Y‬‬
‫;)"‪X.setText("The Text‬‬
‫}‬

‫بين قوسي الدالة يمكن ان نضع نص بشكل مباشر او مسار ‪ id‬لنص مكتوب في الملف ‪ string.xml‬والحظ‬
‫ايضا يجب ان نعمل استدعاء للكالس ‪ View‬وكذلك للكالس ‪ TextView‬بهذا الشكل‪:‬‬

‫;‪import android.view.View‬‬
‫;‪import android.widget.TextView‬‬

‫* اندرويد ‪ SDK‬تاتي مع مجموعة من االنترفيسز خاصة للتعامل مع مجموعة متنوعة من االحداث مثال‬
‫ضغط المستخدم على زر معين ينفذ االنترفيس ‪View.OnClickListener‬‬

‫‪18‬‬
:‫ وعند الضغط عليه تظهر رسالة‬MyId ‫ بتاعه هو‬id ‫ الـ‬button ‫مثال للوصول الى زر‬
package com.example.ahmed.myapplication;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;
Import android.widget.Toast;
import android.view.View;
public class MainActivity extends AppCompatActivity {
private Button X;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
X = (Button) findViewById(R.id.MyId);
X.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, R.string.MyName ,
Toast.LENGTH_SHORT).show();}
});
}}

‫) وهو ملون باللون‬anonymous inner class( ‫الحظ في هذا المثال اصبح عندنا كالس داخلي مجهول‬
‫ النها في داخل‬this ‫ وليس فقط‬MainActivity.this ‫ اصبح باراميترها االول‬makeText ‫ والدالة‬،‫االحمر‬
‫ يمثل اسم الكالس الرئيسي في ملف الجافا) والحظ ايضا باراميترها الثاني‬MainActivity( ‫كالس مجهول‬
‫ و‬res ‫ يشير الى المجلد‬R ‫ حيث ان‬strings.xml ‫ لنص الرسالة الموجود في الملف‬id ‫هو عبارة عن مسار‬
‫ الذي‬name ‫ يمثل االسم الموجود في الخاصية‬MyName ‫ يمثل اسم الملف الموجود فيه النص و‬strings
View ‫ لها باراميتر من النوع‬onClick ‫ والحظ ايضا الدالة‬،strings.xml ‫كتبناه للرسالة في داخل الملف‬
.‫ وكما الحظت عملنا استدعاء لهذا الكالس‬View ‫النها تمثل عناصر من الكالس‬

19
‫الفصل الثاني (دورة الحياة والمحافظة على البيانات)‬

‫كل برنامج له دورة حياة يبدأ وينتهي بها‪ ،‬ونظام التشغيل يحتوي على دوال دورة الحياة بشكل افتراضي وهي‬
‫موجودة في الكالس ‪ ،Activity‬لكن يمكننا ان نتعامل مع مراحل دورة الحياة من خالل دوال دورة الحياة‪،‬‬
‫وهذه الدوال بالترتيب هي‪:‬‬

‫‪ onCreate‬هذه الدالة تنفذ عند لحظة فتح البرنامج لتهيئة كل شيء لعمل البرنامج‪.‬‬ ‫‪.1‬‬
‫‪ onStart‬تنفذ هذه الدالة عندما يكون البرنامج في مرحلة البداية‪ ،‬هذه الدالة تنفذ بعد الدالة‬ ‫‪.2‬‬
‫‪ onCreate‬ولكن في بعض االحيان يمكن الوصول اليها مباشرتا وتنفيذها مثال اذا جاء اتصال هاتفي‬
‫واجاب عليه المستخدم سيتوقف برنامجنا بشكل مؤقت وعند الرجوع للبرنامج سيعود البرنامج للعمل‬
‫والدخول الى هذه الدالة فهنا يمكن ان نعيد تحميل البيانات التي كانت ظاهرة للمستخدم ليجدها كما‬
‫تركها قبل الخروج‪.‬‬
‫‪ onResume‬هذه الدالة تنفذ بعد الدالة ‪ onStart‬لتستأنف عمل البرنامج ولكن في بعض االحيان‬ ‫‪.3‬‬
‫يمكن الوصول اليها مباشرتا‪.‬‬
‫‪ onPause‬تنفذ هذه الدالة عند الخروج من البرنامج بشكل مؤقت هنا يمكن حفظ البيانات الغير‬ ‫‪.4‬‬
‫محفوظة والتي يمكن ان نعيدها في الدالة ‪ ، onResume‬النشاط دائما ينقل الى حالة التوقف الموقت‬
‫عند عرض عنصر فوق النشاط الحالي‪.‬‬
‫‪ onStop‬تنفذ هذه الدالة عند الخروج نهائيا من البرنامج‪ ،‬وهنا سيتم اعادة كل ما عملناه من تهيئة في‬ ‫‪.5‬‬
‫الدالة ‪ onCreate‬مثل تحرير ذاكرة النظام او كتابة بيانات الى قاعدة البيانات‪ ،‬اذا وصلنا الى هذه‬
‫المرحلة فانه سنصل الى مرحلة تحطيم البرنامج قريبا‪.‬‬
‫‪ onDestroy‬هنا اخيرا يحطم البرنامج وليس هناك مجال للتراجع فهي فرصتنا االخيرة لترتيب‬ ‫‪.6‬‬
‫الخروج النهائي‪.‬‬

‫* قبل البدأ بكتابة اي دالة من دوال دورة حياة البرنامج يجب ان نكتب ‪ @Override‬قبل الدالة الننا سوف‬
‫نعيد كتابة اسم دالة مكتوبة مسبقا (نعيد تكرارها) والبرنامج سوف يقوم بتنفيذها في الوقت المناسب‪.‬‬

‫مثال‪:‬‬

‫‪@Override‬‬
‫{ )‪protected void onCreate(Bundle savedInstanceState‬‬
‫‪// First call the "official" version of this method‬‬
‫;)‪super.onCreate(savedInstanceState‬‬
‫;)‪setContentView(R.layout.activity_main‬‬

‫‪20‬‬
Toast.makeText(this, "In onCreate", Toast.LENGTH_SHORT).show();
Log.i("info", "In onCreate");
}
@Override
public void onStart() {
// First call the "official" version of this method
super.onStart();
Toast.makeText(this, "In onStart", Toast.LENGTH_SHORT).show();
Log.i("info", "In onStart");
}
@Override
public void onResume() {
// First call the "official" version of this method
super.onResume();
Toast.makeText(this, "In onResume", Toast.LENGTH_SHORT).show();
Log.i("info", "In onResume");
}
@Override
public void onPause() {
// First call the "official" version of this method
super.onPause();
Toast.makeText(this, "In onPause", Toast.LENGTH_SHORT).show();
Log.i("info", "In onPause");
}
@Override
public void onStop() {
// First call the "official" version of this method
super.onStop();
Toast.makeText(this, "In onStop", Toast.LENGTH_SHORT).show();
Log.i("info", "In onStop");
}
@Override
public void onDestroy() {
// First call the "official" version of this method
super.onDestroy();

21
‫;)(‪Toast.makeText(this, "In onDestroy", Toast.LENGTH_SHORT).show‬‬
‫;)"‪Log.i("info", "In onDestroy‬‬
‫}‬

‫كما تالحظ في المثال في كل الدوال يجب في البداية ان نعمل استدعاء للكالس االب للدالة بهذه العبارة‬
‫‪ super.‬وهذه الجملة يجب ان تكون في بداية تنفيذ كل دالة اي قبل ان نكتب اي كود في داخل هذه الدالة‪.‬‬

‫الدالة ‪onRestart‬‬
‫هذه الدالة ايضا من ضمن دوال دورة الحياة‪ ،‬إذا انتقل المستخدم من ‪ activity‬الى ‪ activity‬اخرى ثم ضغط‬
‫على زر الرجوع في الهاتف ورجع الى الـ ‪ activity‬السابقة فانه سيتم تنفيذ الدالة ‪ ،onRestart‬وهذه الدالة‬
‫اختيارية يمكن ان نكتبها إذا احتجنا ان ننفذ كود معين في حال رجوع المستخدم او يمكن عدم كتابتها إذا لم‬
‫نكن نحتاج ذلك‪ ،‬تكتب بهذا الشكل‪:‬‬

‫‪@Override‬‬
‫{)(‪public void onRestart‬‬
‫;)(‪super.onRestart‬‬
‫‪// the code here‬‬
‫}‬

‫حفظ واستعادة البيانات‬


‫نظام االندرويد يصدر كل معلومات العناصر ‪( Widgets‬مثل االزرار ومربعات النصوص إلخ) الظاهرة‬
‫على شاشة التطبيق او التي ادخلها المستخدم والتي من الممكن ان تتغير بشكل افتراضي الى االوبجكت‬
‫‪ ،Bundle‬هذا االوبجك يمرر بشكل افتراضي كباراميتر للدالة ‪ onCreate‬ليرسل بعد ذلك البيانات التي‬
‫استلمها للكالس االب للدالة ‪ onCreate‬الحظ‪:‬‬

‫‪@Override‬‬
‫{ )‪public void onCreate(Bundle savedInstanceState‬‬
‫;)‪super.onCreate(savedInstanceState‬‬
‫‪...‬‬
‫}‬

‫‪22‬‬
‫وكما نعرف فان البرنامج يمكن ان يتوقف لسبب ما (كأن يأتي اتصال هاتفي اثناء عمل البرنامج او يضغط‬
‫المستخدم على الزر ‪ Home‬للخروج او يدير الهاتف بشكل افقي) ويدخل البرنامج في وضع دورة الحياة‬
‫‪ onPause‬او ‪ ،onStop‬ولكن المشكلة تحدث عند الرجوع للبرنامج اي يدخل البرنامج في وضع‬
‫‪ onResume‬او ‪ onStart‬او ‪ onCreate‬هنا سيعيد البرنامج تشغيل نفسه وستفقد البيانات التي ادخلها‬
‫المستخدم او التي كانت معروضة‪ ،‬ولكن كما قلنا فان النظام بشكل افتراضي قد صدر معلومات العناصر الى‬
‫االوبجكت ‪.Bundle‬‬

‫* الحظ ان المعلومات ستحذف ايضا من هذا االوبجكت إذا دخل البرنامج في وضع دورة الحياة‬
‫‪( onDestroy‬كان يضغط المستخدم على زر الرجوع ليخرج من البرنامج)‪.‬‬

‫االوبجكت ‪ Bundle‬له عدة دوال لكن اهم دالتين للتعامل معه هما الدالة *‪ get‬والدالة *‪ put‬حيث ان عالمة‬
‫النجمة نضع بدلها اسم نوع البيانات (كان تكون ‪ Int‬او ‪ String‬او ‪ Double‬إلخ من االنواع) التي نريد‬
‫حفضها او استرجاعها‪.‬‬

‫الدالة *‪ put‬تستخدم لحفظ قيمة معينة في داخل االوبجكت ونضع لهذه القيمة مفتاح خاص (كلمة نصية) من‬
‫خالله نصل للقيمة المحفوظة‪ ،‬وتأخذ الدالة بين قوسيها بارامترين االول عبارة عن قيمة نصية تمثل المفتاح‬
‫والثانية تمثل القيمة التي نريد تخزينها‪ ،‬وهذه الدالة ال تعيد اي شيء‪ ،‬بهذا الشكل‪:‬‬

‫;) ‪ObBundle.putInt( “Key”, 5‬‬

‫الدالة *‪ get‬تستخدم السترجاع القيمة المحفوظة‪ ،‬تأخذ بين قوسيها باراميتر واحد عبارة عن قيمة نصية وهو‬
‫المفتاح الخاص بالقيمة المحفوظة‪ ،‬بهذا الشكل‪:‬‬

‫;) “‪int X = ObBundle.getInt( “Key‬‬

‫الدالة ‪onSaveInstanceState‬‬
‫تستخدم هذه الدالة لكي يتم حفظ البيانات في االوبجكت ‪ Bundle‬في الوقت المناسب قبل توقف البرنامج‬
‫ودخوله في وضع دورة الحياة ‪ onPause‬او ‪ onStop‬حيث يتم استدعائها في الوقت المناسب بشكل‬
‫افتراضي من النظام‪ ،‬والصيغة العامة لها هي‪:‬‬

‫)‪void onSaveInstanceState(Bundle outState‬‬

‫مثال‪:‬‬

‫;"‪private static final String Y = "Z‬‬


‫;‪int N = 0‬‬

‫‪23‬‬
‫‪@Override‬‬
‫{ )‪public void onSaveInstanceState(Bundle X‬‬
‫;)‪super.onSaveInstanceState(X‬‬
‫;)‪X.putInt(Y, N‬‬
‫}‬

‫الحظ اننا وضعنا ‪ @Override‬قبل كتابة الدالة وكذلك مررنا الباراميتر ‪ X‬الى الدالة االب لهذه الدالة‪ ،‬كذلك‬
‫عملنا متغير ووضعناه ليمثل المفتاح الخاص بحفظ البيانات وهذا الشيء اختياري‪ ،‬االن لنفترض ان قيمة‬
‫المتغير ‪ N‬ظاهرة على الشاشة في مربع نصي ثم قام المستخدم بتغيرها من ‪ 0‬الى ‪ 5‬اثناء تشغيل البرنامج ثم‬
‫ادار الهاتف بشكل افقي هنا سيدخل البرنامج دورة الحياة ‪ onPause‬ثم ‪ onStop‬بعدها يرجع الى دورة‬
‫الحياة ‪ onCreate‬ثم ‪ onStart‬ثم ‪ onResume‬هنا سترجع قمية المتغير ‪ N‬الى القيمة االولية لها ‪ 0‬الن‬
‫البرنامج سيعيد تشغيل نفسة ولكن بما اننا خزنا قيمة المتغير ‪ N‬في داخل الدالة ‪onSaveInstanceState‬‬
‫فان النظام خزن بشكل افتراضي قيمة ‪ N‬في اخر لحظة قبل توقف البرنامج والخذ القيمة المخزنة ووضعها‬
‫مرة اخرى في المتغير ‪ N‬لكي تبقى القيمة كما هي عندما يدير المستخدم الهاتف نكتب الكود التالي في داخل‬
‫الدالة ‪ onCreate‬بهذا الشكل‪:‬‬

‫‪@Override‬‬
‫{ )‪protected void onCreate(Bundle savedInstanceState‬‬
‫;)‪super.onCreate(savedInstanceState‬‬
‫{ )‪if (savedInstanceState != null‬‬
‫;)‪N = savedInstanceState.getInt(Y‬‬
‫}}‬

‫هنا من خالل ‪ if‬الشرطية نسأل هل البرنامج يشتغل الول مرة (ستكون قيمة االوبجكت ‪ Bundle‬خالية من‬
‫اي بيانات) ام انه كان يشتغل ثم توقف مؤقتا واالن يعود للعمل من جديد (ستحتوي قيمة االوبجكت ‪Bundle‬‬
‫على بيانات ولن تكون خالية)‪.‬‬

‫الدالة ‪onRestoreInstanceState‬‬
‫تستخدم هذه الدالة إذا لم نريد استرجاع البيانات في داخل الدالة ‪( onCreate‬كما رأينا في المثال السابق)‬
‫حيث ان هذه الدالة سيتم استدعائها من قبل النظام بشكل افتراضي بعد الدالة ‪ onStart‬بشرط ان تكون هناك‬
‫بيانات مخزنة وإال فانه لن يتم تنفيذ ما بداخلها لذا فاننا ال نحتاج ان نستخدم الشرط للتحقق من ان االوبجكت‬
‫‪ Bundle‬خالي من البيانات او ال (كما فعلنا في المثال السابق)‪.‬‬

‫‪24‬‬
‫من خالل هذه الدالة يمكن استرجاع قيمة المتغير ‪ N‬الموجود في المثال السابق بدون ان ننفذ االمر في داخل‬
‫الدالة ‪ onCreate‬بهذا الشكل‪:‬‬

‫{ )‪public void onRestoreInstanceState(Bundle savedInstanceState‬‬


‫;)‪super.onRestoreInstanceState(savedInstanceState‬‬
‫;)‪N = savedInstanceState.getInt(Y‬‬
‫}‬

‫الحظ اننا مررنا الباراميتر ‪ savedInstanceState‬للدالة االب لهذه الدالة‪.‬‬

‫الفصل الثالث (أكثر من ‪)Activity‬‬

‫كما ذكرنا في البداية فان كل ‪ activity‬تعرض للمستخدم هي متكونة من ملفين االول يمثل الجزء الظاهر‬
‫على الشاشة وهو مكتوب بلغة ‪ XML‬ويسمى ‪ layout‬اما الثاني فهو مكتوب بلغة ‪ Java‬وهو يدير الجزء‬
‫االول‪ .‬وكل برنامج يجب ان يتكون من ‪ activity‬واحدة على االقل‪ ،‬وكذلك يمكننا عمل أكثر من واحدة‬
‫وجعل البرنامج يتنقل فيما بينهن‪.‬‬

‫انشاء ‪ Activity‬جديدة‬
‫يمكن انشاء ‪ activity‬جديدة في البرنامج ببساطة في برنامج االندرويد ستوديو نذهب الى نافذة الـ ‪Project‬‬
‫في داخل المجلد ‪ app‬وفي داخل المجلد ‪ Java‬نجد مجلد باسم الحزمة التي انشأناها نضغط علية بالزر االيمن‬
‫ثم نختار ‪ New → Activity → Empty Activity‬كما في هذه الصورة‪:‬‬

‫‪25‬‬
‫بعدها ستظهر لنا نافذة ندخل فيها اسم ملف الجافا واسم ملف الطبقة في الـ ‪ activity‬وكذلك نختار لها عنوان‬
‫ونضغط على ‪.Finish‬‬

‫الوصول الى ‪ Activity‬من ‪ Activity‬اخرى‬


‫لكي نعرض ‪ activity‬من ‪ activity‬اخرى نحتاج الى استخدام الدالة ‪ startActivity‬والتي هي موجودة في‬
‫الكالس ‪ Activity‬والصيغة العامة لها هي‪:‬‬

‫)‪public void startActivity(Intent intent‬‬

‫هذه الدالة ال تتصل مباشرتا بالـ ‪ activity‬الجديدة وانما ترسل اوبجكت من النوع ‪ Intent‬ليتصل بالـ ‪OS‬‬
‫وبالتحديد بالجزء ‪ ActivityManager‬وهو بدوره يقوم باحضار الـ ‪ activity‬المطلوبة ويعرف من هي‬
‫المطلوبة من خالل الباراميتر ‪.Intent‬‬

‫الـ ‪ Intent‬هو عبارة عن اوبجكت من خالله يمكن التواصل مع ‪ ،OS‬ويستخدم هذا االوبجكت للعديد من‬
‫االغراض وليس فقط الجل ‪ activity‬جديدة‪ ،‬الكالس ‪ Intent‬له عدة ‪ constructors‬وكل واحدة منها تخدم‬

‫‪26‬‬
‫غرض معين‪ ،‬ولكن ما نحتاجه هنا الجل اخبار ‪ ActivityManager‬اي ‪ activity‬نريدها لتبدأ العمل‬
‫(لعرضها) نحتاج هذه الصيغة‪:‬‬

‫)‪public Intent(Context packageContext, Class<?> cls‬‬

‫الباراميتر االول يمثل الحزمة الموجودة فيها الـ ‪ activity‬والباراميتر الثاني يحدد الكالس الرئيسي والذي‬
‫يحمل نفس اسم ملف الجافا الرئيسي في الـ ‪ ،activity‬وال ننسى ان نعمل استدعاء للكالس ‪ Intent‬لنتمكن‬
‫من التعامل معه‪ ،‬بهذا الشكل‪:‬‬

‫;‪import android.content.Intent‬‬

‫مثال‪:‬‬

‫;)‪Button B = (Button)findViewById(R.id.button‬‬
‫{ )(‪B.setOnClickListener(new View.OnClickListener‬‬
‫‪@Override‬‬
‫{ )‪public void onClick(View v‬‬
‫;)‪Intent i = new Intent(MainActivity.this, Main2Activity.class‬‬
‫;)‪startActivity(i‬‬
‫}‬
‫;)}‬

‫في هذا المثال عند الضغط على الزر سوف يعرض ‪ activity‬جديدة اسمها ‪ ،Main2Activity‬الحظ‬
‫الباراميتر االول لالوبجكت ‪ Intent‬كتبنا فيه ‪ MainActivity.this‬وليس فقط ‪ this‬الننا في كالس داخلي‬
‫مجهول حيث ان ‪ MainActivity‬تمثل اسم الكالس الرئيسي في ملف الجافا في الـ ‪ activity‬الحالية‪.‬‬

‫* إذا كانت عندنا اثنين ‪ activity‬االولى اسمها ‪ MainActivity‬والثانية اسمها ‪ Main2Activity‬وعند‬


‫الضغط على زر معين في االولى سيتم عرض الثانية حيث ان االولى ستمر بمرحلة دورة الحياة ‪onPause‬‬
‫ثم ‪ ،onStop‬إذا ضغط المستخدم على زر الرجوع في الهاتف سيعود الى االولى لتمر بمرحلة دورة الحياة‬
‫‪ onStart‬ثم ‪ onResume‬والثانية ستمر بمرحلة دورة الحياة ‪.onDestroy‬‬

‫‪27‬‬
‫تمرير البيانات بين الـ ‪Activities‬‬
‫يتم تبادل البيانات بين اثنين ‪ activity‬من خالل تحميل االوبجكت ‪ Intent‬قيم اضافية‪ ،‬وتكون العملية‬
‫بمرحلتين‪ ،‬المرحلة االولى‪ :‬اعطائه قيمة في الـ ‪ activity‬االول ليرسلها‪ ،‬المرحلة الثانية‪ :‬استالم واخذ القيمة‬
‫منه في الـ ‪ activity‬الثاني‪.‬‬

‫المرحلة االولى االرسال‪:‬‬


‫تتم عملية االرسال من خالل تحميل االوبجكت ‪ Intent‬بقمة عن طريق الدالة ‪ ،putExtra‬الصيغة العامة لها‬
‫هي‪:‬‬

‫)‪public Intent putExtra(String name, value‬‬

‫تأخذ الدالة بارامترين االول قيمة نصية عبارة عن اسم ليمثل القيمة المرسلة ويجب ان ال يتشابه مع اسم لقيمة‬
‫مرسلة اخرى‪ ،‬اما الباراميتر الثاني فهو يمثل القيمة التي نريد ان نرسلها مهما كان نوعها (‪ int‬او ‪ String‬او‬
‫اي نوع اخر وكذلك يمكن ان تكون القيمة مفردة او مصفوفة)‪.‬‬

‫مثال‪( :‬يكتب هذا الكود في الـ ‪ activity‬الذي نريد ارسال المعلومات منه)‬

‫;)‪Intent i = new Intent(MainActivity.this, Main2Activity.class‬‬


‫;)‪i.putExtra("KEY", 5‬‬
‫;)‪startActivity(i‬‬

‫* يمكن تحميل االوبجكت ‪ Intent‬باكثر من قيمة من خالل كتابة الدالة ‪ putExtra‬أكثر من مرة فكل واحدة‬
‫منها ستحمل قمية واسم خاص بهذه القيمة‪.‬‬

‫المرحلة الثانية االستالم‪:‬‬


‫تتم عملية االستالم من خالل استخراج القيم التي يحملها االوبجكت ‪ Intent‬وذلك بستخدام هذه الدالة‪:‬‬

‫)‪public # get#Extra(String name, # defaultValue‬‬

‫الباراميتر االول يمثل االسم الذي كتبناه مع القمية عند ارسالها والبارميتر الثاني يمثل القيمة االفتراضية التي‬
‫سيتم اعادتها في حال لم يجد االسم‪ ،‬العالمة ‪ #‬تمثل نوع البيانات التي نريد استخراجها كأن تكون ‪ int‬او‬
‫‪ String‬إلخ من االنوع‪ ،‬مثال إذا كانت القيمة التي نريد استخراجها من النوع ‪ boolean‬تكون صيغة الدالة‬
‫بهذا الشكل‪:‬‬

‫)‪public boolean getBooleanExtra(String name, boolean defaultValue‬‬

‫‪28‬‬
‫وإذا كانت القمية عبارة عن مصفوفة نضيف للنوع كلمة ‪ ،Array‬مثال إذا كانت القمية مصفوفة ومن النوع‬
‫‪ int‬تكون الدالة بهذا الشكل ‪.getIntArrayExtra‬‬

‫الدالة ) (‪getIntent‬‬
‫تستخدم هذه الدالة دائما عندما نريد استرجاع االوبجكت الذي بدأ الـ ‪ ،activity‬وهذا ما نرسلة عند استخدام‬
‫الدالة )‪.startActivity (Intent‬‬

‫* تستخدم هذه الدالة مع الدالة السابقة الستالم القيم المرسلة‪.‬‬

‫مثال‪( :‬يكتب هذا الكود في الـ ‪ activity‬الذي نريد استالم المعلومات فيه)‬

‫;)‪int x = getIntent().getIntExtra("KEY", 0‬‬

‫هذا المثال يستلم القيمة التي ارسلها المثال السابق باالسم ‪.KEY‬‬

‫استعادة نتائج من الـ ‪ Activity‬االبن‬


‫كل برنامج له ‪ activity‬رئيسي يعرض في بداية تشغيل البرنامج ويسمى االب واذا احتوى البرنامج على‬
‫‪ activities‬اخرى فانها ستسمى ابناء‪ ،‬تعلمنا قبل قليل كيف نرسل البيانات من الـ ‪ activity‬االب واستالمها‬
‫في االبن‪ ،‬االن نريد ان نعرف كيف نرسل المعلومات من ‪ activity‬االبن الى االب الن المستخدم عندما‬
‫يضغط على زر الرجوع في الهاتف فانه سيعود من الـ ‪ activity‬االبن الى االب والـ ‪ activity‬االبن سوف‬
‫يمر بمرحلة دورة الحياة ‪ onDestroy‬وتفقد البيانات‪.‬‬

‫إذا أردنا ان نسمع رد من الـ ‪ activity‬االبن فبدال من ان نستخدم الدالة ‪ startActivity‬لعرض الـ ‪activity‬‬
‫االبن نستخدم الدالة ‪ ،startActivityForResult‬الصيغة العامة لها هي‪:‬‬

‫)‪public void startActivityForResult(Intent intent, int requestCode‬‬

‫الباراميتر االول هو نفس باراميتر الدالة ‪ ،startActivity‬اما الباراميتر الثاني فهو عبارة عن رقم من خالله‬
‫يمكننا ان نميز الرد من اي ‪ activity‬ابن قد جاء (النه يمكن ان يكون في البرنامج عدة ‪ activities‬ابناء)‪،‬‬
‫تكتب هذه الدالة في الـ ‪ activity‬االب‪.‬‬

‫‪29‬‬
‫في الـ ‪ activity‬االبن نضع القيم التي نريد ارسالها لالب في الدالة ‪ ،setResult‬لهذه الدالة صيغتين يمكن ان‬
‫نستخدم اي واحدة نريد حسب الحاجة‪:‬‬

‫)‪public final void setResult(int resultCode‬‬


‫)‪public final void setResult(int resultCode, Intent data‬‬

‫الصيغة االولى فيها باراميتر واحد وهو عبارة عن قيمة ثابتة وله شكلين اما ان يكون‬
‫‪ Activity.RESULT_OK‬او ‪ ،Activity.RESULT_CANCELED‬وضع النتيجة في هذا‬
‫البارميتر يمكن ان يكون مفيد إذا أردنا ان نعرض ‪ activity‬ابن ومعرفة هل ضغط المستخدم على زر موافق‬
‫ام الغاء (كيف انتهى الـ ‪.)activity‬‬

‫الصيغة الثانية هي نفس االولى لكن تأخذ باراميتر ثاني وهو عبارة عن اوبجكت ‪ Intent‬يمكن ان نحمله ما‬
‫نريد من البيانات لكي يتم ارسالها بستخدام الدالة ‪ putExtra‬بهذا الشكل‪:‬‬

‫;)(‪Intent data = new Intent‬‬


‫;)‪data.putExtra(“KEY”, true‬‬
‫;)‪setResult(RESULT_OK, data‬‬

‫يمكن وضع الكود السابق في داخل دالة ونستدعي الدالة عند الضغط على زر معين لمعرفة فيما إذا تم الضغط‬
‫على الزر ام ال‪.‬‬

‫* ليس بالضرورة ان نكتب الدالة ‪ setResult‬في الـ ‪ activity‬االبن فيمكن عدم كتابتها إذا لم نكن مهتمين‬
‫بالتمييز بين القيم التي تعيدها‪ ،‬لذا يمكن ان ندع الـ ‪ OS‬يرسل قيمة الـ ‪ resultCode‬االفتراضية (دائما يتم‬
‫اعادة قمية الـ ‪ resultCode‬للـ ‪ activity‬االب إذا بدأ عرض الـ ‪ activity‬االبن بستخدام الدالة‬
‫‪ )startActivityForResult‬لذا إذا لم نكتب الدالة ‪ setResult‬فانه بمجرد ان يضغط المستخدم على زر‬
‫الرجوع من الهاتف سترسل القيمة ‪ Activity.RESULT_CANCELED‬للـ ‪ activity‬االب‪.‬‬

‫الدالة ‪onActivityResult‬‬
‫عندما يضغط المستخدم زر الرجوع من الهاتف سيتم تنفيذ هذه الدالة بشكل اوتوماتيكي‪ ،‬ولكن الحظ ان هذه‬
‫الدالة تكتب في الـ ‪ activity‬االب النها تستخدم الستقبال البيانات المرسلة من الـ ‪ activity‬االبن‪ ،‬وقبل‬
‫كتابتها يجب ان نكتب ‪ @Override‬النها موجودة افتراضيا ونحن سنعمل تكرار لها‪ ،‬الصيغة العامة لها‬
‫هي‪:‬‬

‫)‪protected void onActivityResult(int requestCode, int resultCode, Intent data‬‬

‫‪30‬‬
‫ والذي قلنا انه‬startActivityForResult ‫لها ثالثة بارامترات االول هو الرقم الذي ارسلناه من خالل الدالة‬
‫ الباراميترين الثاني والثالث هما نفس البيانات التي‬،‫ ابن نستلم منه البيانات‬activity ‫يستخدم لمعرفة اي‬
.‫ من خالل بارامتراتها‬setResult ‫ارسلتهما الدالة‬

‫ مثال كاستخراج المعلومات من االوبجكت‬،‫في داخل هذه الدالة نكتب ما نريد عمله مع البيانات المستلمة‬
.‫ والتي شرحنا عنها سابقا في هذا الفصل‬get#Extra ‫بواسطة الدالة‬

:‫مثال‬

)‫ االب‬activity ‫ (يكتب هذا الكود في الـ‬-1

import android.app.Activity;
import android.content.Intent;
import android.view.View;
import android.widget.Button;
...
public class MainActivity extends AppCompatActivity {
private static final int R = 0;
private boolean X;
...
@Override
protected void onCreate(Bundle ...) {
...
Button B = (Button)findViewById(R.id.button);
B.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent(MainActivity.this, Main2Activity.class);
i.putExtra("NAME", 5);
startActivityForResult(i, R);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) {

31
// do some thing
}
if (requestCode == R) {
if (data == null) {
return;
}
X = data.getBooleanExtra("KEY", false);
}}}

)‫ االبن‬activity ‫ (يكتب هذا الكود في الـ‬-2

import android.view.View;
import android.widget.Button;
import android.content.Intent;
...
public class Main2Activity extends AppCompatActivity {
private int Q;
@Override
protected void onCreate(Bundle ...) {
...
Q = getIntent().getBooleanExtra("NAME", 0);
Button Bu = (Button)findViewById(R.id.button_u);
Bu.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
clicked(true);
}
});
}
private void clicked(boolean is) {
Intent data = new Intent();
data.putExtra("KEY", is);
setResult(RESULT_OK, data);
}}

32
‫* من المهم ان تالحظ في المثال الدالة ‪ onActivityResult‬انشأناها في الـ ‪ activity‬االب ولكن لم‬
‫نستدعيها في اي مكان لتنفيذها وذلك النها ستنفذ وتمرر البيانات كبارامترات لها اوتوماتيكيا عندما يضغط‬
‫المستخدم على زر الرجوع في الهاتف ليعود من الـ ‪ activity‬االبن الى الـ ‪ activity‬االب‪.‬‬

‫الفصل الرابع (‪)List views and Adapters‬‬

‫‪ list view‬هي عبارة عن قائمة لعرض مجموعة من الخيارات (روابط) لتترتب بشكل عمودي والمسؤول‬
‫عن ذلك هو الوسم <‪ >ListView‬الذي يكتب في ملف الـ ‪ XML‬وياخذ الخاصية ‪ android:entries‬التي‬
‫نسند لها قيمة عبارة عن مصفوفة نصية من االسماء المكتوبة في الملف النصي ‪ ، string‬مثال اذا كانت عندنا‬
‫في الملف النصي ‪ string‬المصفوفة التالية ‪:‬‬

‫>"‪<string-array name="options‬‬
‫>‪<item>Drinks</item‬‬
‫>‪<item>Food</item‬‬
‫>‪</string-array‬‬

‫يمكن ان نكتب في ملف الـ ‪ XML‬الكود التالي للوصول الى هذه المصفوفة وعرضها بشكل قائمة متسلسلة‬
‫عموديا‪:‬‬

‫‪<ListView‬‬
‫"‪android:id="@+id/list_options‬‬
‫"‪android:layout_width="match_parent‬‬
‫"‪android:layout_height="match_parent‬‬
‫>‪android:entries="@array/options"/‬‬

‫ستكون القائمة بهذا الشكل‪:‬‬

‫‪33‬‬
‫عند الضغط على هذه االسماء (الروابط) لن يحدث اي شيء‪ ،‬ولكي نستجيب لضغط المستخدم على احد هذه‬
‫الروابط نحتاج في االول ان ننشأ االوبجكت ‪ OnItemClickListener‬والذي سيراقب اي عنصر يتم‬
‫الضغط علية ومن خالل دالته ‪ onItemClick‬يمكن ان نخبر البرنامج ماذا سيفعل عند الضغط على العنصر‬
‫‪ ،‬الدالة ‪ onItemClick‬تاخذ اربع بارامترات االول يمثل نوع العنصر (في حالتنا هو ‪ )ListView‬اما‬
‫الباراميتر الثالث هو رقم يمثل تسلسل العنصر في القائمة (يبدأ من الرقم ‪ ، )0‬الباراميتر الرابع يمثل تسلسل‬
‫العنصر في المصدر الذي تجلب منه القائمة بياناتها فاذا كانت القائمة تجلب البيانات من مصفوفة فهو يمثل‬
‫رقم تسلسل العنصر في المصفوفة اما اذا كانت تجلب البيانات من قاعدة البيانات فهذا الرقم سيمثل رقم‬
‫تسلسله في قاعدة البيانات ‪ ، _id‬يمكن استخدام هذه البارامترات لتحديد العنصر الذي تم الضغط عليه‪.‬‬

‫مثال‪( :‬يكتب في ملف الجافا التابع للـ ‪ activity‬التي تعرض قائمة العناصر في داخل الدالة ‪)onCreate‬‬

‫= ‪AdapterView.OnItemClickListener itemClickListener‬‬
‫{ ) (‪new AdapterView.OnItemClickListener‬‬
‫‪public void onItemClick(AdapterView<?> listView, View itemView,‬‬
‫{ )‪int position, long id‬‬
‫{)‪if (position == 0‬‬
‫‪// Do something the user clicked on the first item‬‬
‫} }‬
‫;}‬

‫‪ OnItemClickListener‬هو كالس داخلي مجهول مع الكالس ‪ AdapterView‬وهنا انشأنا اوبجكت منه‪،‬‬


‫الكالس ‪ ListView‬هو كالس مشتق من الكالس ‪ ، AdapterView‬لذا هنا كتبنا ‪ AdapterView‬لكن‬
‫يمكن ان نستبدله ونكتب ‪ ListView‬اذا كنا نتعامل فقط مع قائمة من نوع ‪.ListView‬‬

‫‪34‬‬
‫المثال السابق سينفذ كود معين عند الضغط على اول عنصر‪ ،‬لكننا لم نبين له العنصر االول من اي قائمة! لذا‬
‫يجب ان نربط االوبجكت الذي عملناه ‪ itemClickListener‬مع قائمة العرض التي عندنا ‪ ListView‬من‬
‫خالل الدالة ‪ setOnItemClickListener‬بهذا الشكل‪:‬‬

‫;)‪ListView listView = (ListView) findViewById(R.id.list_options‬‬


‫;)‪listView.setOnItemClickListener(itemClickListener‬‬

‫االن سينفذ الكود في داخل ‪ if‬عند الضغط على العنصر االول في القائمة (‪ ،)Drinks‬ويمكن تكرار عبارة‬
‫الشرط لالستجابة الى بقية عناصر القائمة‪.‬‬

‫* يمكن كتابة نفس المثال السابق لكن بطريقة مختلفة اي بدال من ان نعمل اوبجكت من الكالس‬
‫‪ AdapterView.OnItemClickListener‬سنعمل كالس اعتيادي وهو يعمل (‪ )implements‬للكالس‬
‫‪ ،AdapterView.OnItemClickListener‬بهذا الشكل‪:‬‬

‫{‪class myClass implements AdapterView.OnItemClickListener‬‬


‫‪@Override‬‬
‫‪public void onItemClick(AdapterView<?> listView, View itemView,‬‬
‫{)‪int position, long id‬‬
‫{)‪if (position == 0‬‬
‫‪// Do something the user clicked on the first item‬‬
‫}}}‬

‫يمكن ربط الكالس الذي عملناه ‪ myClass‬مع قائمة العرض التي عندنا ‪ ListView‬من خالل الدالة‬
‫‪ setOnItemClickListener‬بهذا الشكل‪:‬‬

‫;)‪ListView listView = (ListView) findViewById(R.id.list_options‬‬


‫;)) (‪listView.setOnItemClickListener(new myClass‬‬

‫‪list activity‬‬
‫يمكن ان نعمل ‪ activity‬مخصصة فقط لعرض قائمة كما فعلنا قبل قليل لكن هذه المرة سننشأ ‪list activity‬‬
‫جاهزة‪ ،‬اي اننا ال نحتاج ان ننشأ ملف طبقة ‪ XML‬ونضع فيه ‪ ListView‬النه ستنشأ لنا طبقة تحتوي على‬
‫قائمة عرض واحدة بشكل برمجي وللوصول الى هذه القائمة نستخدم الدالة ‪ getListView‬وعندها نحدد ما‬
‫هي البيانات التي ستعرض على القائمة ‪ ،‬ولالستجالة لنقر المستخدم على العناصر ال نحتاج ان ننشأ‬
‫االوبجكت ‪ OnItemClickListener‬النه سينشأ لنا بشكل جاهز كل ما نحتاجه هو الدالة‬
‫‪ onListItemClick‬لتحديد ماذا سنفعل عندما يتم الضغط على عنصر ما من القائمة ‪.‬‬

‫‪35‬‬
‫يتم انشاء ‪ list activity‬بشكل اعتيادي مثل انشاء اي ‪ ،activity‬في برنامج االندرويد ستوديو نذهب الى‬
‫‪ File -> New -> Activity -> Empty Activity‬ثم من القائمة التي تظهر نلغي االشارة من انشاء‬
‫الطبقة ‪ Layout‬الننا ال نحتاجها ثم نضغط ‪ ،OK‬ثم نجعل الكالس الرئيسي يرث (‪ )extends‬للكالس‬
‫‪ ListActivity‬بدال من الكالس ‪ ،Activity‬نعمل استدعاء (‪ )import‬لحزمة الكالس ‪ListActivity‬‬
‫ونلغي استدعاء حزمة الكالس ‪ ،Activity‬بهذا الشكل‪:‬‬

‫;‪import android.app.ListActivity‬‬

‫دوال الكالس ‪ListActivity‬‬

‫الدالة ‪getListView‬‬
‫عندما نستخدم ‪ list activity‬فاننا ال نمتلك ملف طبقة ‪ ،XML‬ونحن نعلم انها تمتلك عنصر ‪ListView‬‬
‫واحد‪ ،‬للوصول لهذا العنصر نستخدم هذه الدالة وهي تعيد اوبجكت من النوع ‪ ،ListView‬سابقا في الـ‬
‫‪ activity‬االعتيادية كنا نكتب هذا الكود لتحديد الـ ‪:ListView‬‬

‫;)‪ListView listView = (ListView) findViewById(R.id.list_options‬‬

‫لنحصل على نفس هذا االوبجكت في الـ ‪ list activity‬نكتب هذا الكود‪:‬‬

‫;) (‪ListView listView = getListView‬‬

‫وكال الكودين هما نفس الشيء‪.‬‬

‫الدالة )‪onListItemClick (ListView l, View v, int position, long id‬‬


‫تستخدم هذه الدالة لجعل الـ ‪ list activity‬تستجيب عند الضغط على العناصر في القائمة‪ ،‬حيث نضع بداخلها‬
‫اي كود نريد وسيتم استدعاء وتنفيذ الدالة بشكل اوتوماتيكي عند الضغط على عنصر من القائمة‪ ،‬تأخذ هذه‬
‫الدالة اربع بارامترات يمكن استخدامها للتعامل مع العنصر المضغوط حيث ان الباراميتر االول يمثل نوع‬
‫العنصر (في حالتنا هو ‪ )ListView‬اما الباراميتر الثالث هو رقم يمثل تسلسل العنصر في القائمة (يبدأ من‬
‫الرقم ‪ ، )0‬الباراميتر الرابع يمثل تسلسل العنصر في المصفوفة الممررة للقائمة اما اذا كانت القائمة تأخذ‬
‫بياناتها من قاعدة البيانات فهذا الباراميتر سيمثل رقم تسلسل العنصر في قاعدة البيانات ‪_id‬‬

‫مثال‪:‬‬

‫{)‪public void onListItemClick(ListView listView, View view, int position, long id‬‬

‫‪36‬‬
‫‪// Do something‬‬
‫}‬

‫الدالة )‪setListAdapter(adapter‬‬
‫يمرر لها كباراميتر اوبجكت (يحمل مصفوفة) من نوع الكالس ‪ ArrayAdapter‬لتضيف قيم المصفوفة الى‬
‫العنصر ‪ ListView‬الموجود في الطبقة ‪( XML‬والتي قلنا انها ستنشأ فيما بعد برمجيا) لتظهر على شكل‬
‫قائمة‪.‬‬

‫مثال‪:‬‬

‫(>‪ArrayAdapter<String> listAdapter = new ArrayAdapter<String‬‬


‫;)‪this, android.R.layout.simple_list_item_1, Drinks‬‬
‫;)‪setListAdapter(listAdapter‬‬

‫حيث ان ‪ Drinks‬يمثل اسم مصفوفة نصية‬

‫‪Adapters‬‬
‫رأينا في بداية هذا الفصل كيف اضفنا مصفوفة نصية موجودة في الملف النصي ‪ string‬الى القائمة‬
‫‪ ListView‬من خالل خاصيتها ‪ android:entries‬وهذا ممكن الن البيانات في الملف النصي هي ‪static‬‬
‫لكن اذا اردنا ان نصل الى مصفوفة مكتوبه في ملف جافا او في قاعدة بيانات لنعرضها في الـ ‪ListView‬‬
‫مثال هنا سنحتاج الى استخدام ‪ ، adapter‬الـ ‪ adapter‬هو عبارة عن حلقة وصل بين البيانات وعناصر‬
‫العرض ‪ ، Widgets‬وهناك عدة انواع منه كل واحد منها متخصص في شيء معين مثال ‪ArrayAdapter‬‬
‫هو احد هذه االنواع ومتخصص بالتعامل مع المصفوفات‪.‬‬

‫الكالس ‪ArrayAdapter‬‬
‫نستخدم هذا الكالس لنمرر له مصفوفة وهو بدوره يمررها لكي تعرض في قائمة عرض في واجهة المستخدم‬
‫الكالس ‪ ArrayAdapter‬من النوع ‪ Generic‬وهو موجود في الحزمة ‪ ،android.widget‬الصيغة العامة‬
‫النشاء اوبجكت منه هي‪:‬‬

‫‪37‬‬
‫‪ArrayAdapter <DataType> Ob = new ArrayAdapter<DataType> (context, display,‬‬
‫;)‪array‬‬

‫حيث ان ‪ DataType‬تمثل نوع البيانات الموجودة في المصفوفة والتي نريد ان نضيفها (‪String, Integer‬‬
‫او اي نوع اوبجكت اخر)‪ ،‬ويأخذ الكالس ثالثة بارامترات‪:‬‬

‫االول هو ‪ context‬يمثل كالس الـ ‪ Activity‬الحالي (الكالس ‪ Activity‬هو كالس مشتق من الكالس‬
‫‪ ،)context‬عندما نستخدم الكالس ‪ ArrayAdapter‬في داخل ‪ activity‬يمكن ان نكتب ‪ this‬لتشير الى الـ‬
‫‪ context‬الحالي‪.‬‬

‫الثاني هو مسؤول عن كيفية عرض عناصر المصفوفة في القائمة‪ ،‬حيث ان القيمة التالية‬
‫‪ android.R.layout.simple_list_item_1‬تخبر البرنامج ان يعرض عناصر المصفوفة كل منها بشكل‬
‫منفرد ‪ ،‬اما القيمة التالية ‪ android.R.layout.simple_list_item_activated_1‬تخبر البرنامج ان‬
‫يعرض كل عنصر بشكل منفرد لكن عند الضغط على العنصر يبقى العنصر مفعل لتمييزة عن بقية العناصر‪.‬‬

‫الثالث هو ‪ array‬عبارة عن اسم المصفوفة التي نريد ان نعرض بياناتها في القائمة‪.‬‬

‫ما يفعله الكالس ‪ ArrayAdapter‬في الحقيقة هو يأخذ كل عنصر في المصفوفة ويحوله الى ‪String‬‬
‫باستخدام الدالة ‪ toString‬ويضع كل نتيجة في مربع عرض نصي ‪ text view‬ثم يظهر كل مربع عرض‬
‫نصي في صف منفرد في قائمة العرض‪.‬‬

‫الدالة ‪setAdapter‬‬
‫تستخدم هذه الدالة لتمرر البيانات التي جلبها الـ ‪ adapter‬الى العنصر ‪ widget‬الذي سيعرض البيانات‪.‬‬

‫مثال‪ :‬إذا كانت عندنا قائمة عرض ‪ ListView‬في ملف الطبقة ‪ XML‬واردنا هذه القائمة ان تعرض‬
‫مصفوفة مكتوبة في كالس في ملف جافا‪ ،‬سنكتب الكود التالي في ملف الجافا التابع للـ ‪ activity‬التي تعرض‬
‫القائمة‪:‬‬

‫(>‪ArrayAdapter<String> listAdapter = new ArrayAdapter<String‬‬


‫;)‪this, android.R.layout.simple_list_item_1, Drinks‬‬
‫;)‪ListView listView = (ListView) findViewById(R.id.List_Name‬‬
‫;)‪listView.setAdapter(listAdapter‬‬

‫حيث ان ‪ Drinks‬يمثل اسم مصفوفة نصية و‪ List_Name‬يمثل الـ ‪ id‬الخاص بالقائمة ‪.ListView‬‬

‫‪38‬‬
‫الكالس ‪ArrayList‬‬
‫هذا الكالس من النوع ‪ Generic‬وهو موجود في الحزمة ‪ ، java. util‬وظيفة هذا الكالس هو ان يحمل قمة‬
‫من نوع ما على شكل قائمة‪ ،‬صيغته العامة بهذا الشكل‪:‬‬

‫;)(>‪ArrayList< DataType> Ob = new ArrayList< DataType‬‬

‫حيث ان ‪ DataType‬تمثل نوع البيانات التي نريد ان نضيفها للقائمة (‪ String, Integer‬او اي نوع‬
‫اوبجكت اخر) والـ ‪ Ob‬هو االسم الذي نختاره‪ ،‬مثال‪:‬‬

‫;)(>‪ArrayList< String> mList = new ArrayList< String‬‬

‫لكي نضيف قيم الى هذه القائمة نستخدم الدالة ‪ add‬بهذا الشكل‪:‬‬

‫;)"‪mList.add("Ahmed‬‬
‫;)"‪mList.add("Ali‬‬

‫ويمكن ان نستخرج البيانات من هذه القائمة من خالل الدالة ‪ get‬حيث ان هذه الدالة تأخذ باراميتر واحد هو‬
‫عبارة عن رقم يمثل تسلسل العنصر الذي نريد استخراجه في القائمة (اول عنصر في القائمة يأخذ الرقم ‪،)0‬‬
‫بهذا الشكل‪:‬‬

‫{ )‪for (int i = 0; i < mList.size(); i++‬‬


‫;)‪String names = mList.get(i‬‬
‫;)(‪Toast.makeText(this, names, Toast.LENGTH_SHORT).show‬‬
‫}‬

‫حيث ان الدالة ‪ size‬تعيد رقم عناصر القائمة (اول عنصر في القائمة يأخذ الرقم ‪)0‬‬

‫‪39‬‬
‫الفصل الخامس (‪)Fragments‬‬

‫الـ ‪ Fragment‬هو عبارة عن واجهه (إطار) يتم عرضها للمستخدم من خالل اي ‪ activity‬في البرنامج‬
‫ويمكن ان يعرض الـ ‪ activity‬الواحد عدة ‪ ،Fragments‬الـ ‪ Fragment‬تتكون من واجهة عرض‬
‫‪( layout‬عبارة عن ملف ‪ )XML‬وملف جافا‪.‬‬

‫دورة حياة الـ ‪fragment‬‬


‫للـ ‪ fragment‬دورة حياة شبيهه بدورة حياة الـ ‪ ،activity‬لكن الفرق هو ان دورة حياة الـ ‪ activity‬مرتبطة‬
‫بنظام جهاز االندرويد ‪ OS‬اما دورة حياة الـ ‪ Fragment‬فال يعلم عنها النظام اي شيء فهي تكون شأن‬
‫خاص داخل الـ ‪ activity‬لذلك فان للـ ‪ fragment‬دوال دورة حياة اكثر قليال النها يمكن ان تدور دورة‬
‫حياته اثناء دالة واحدة من دورة حياة الـ ‪ activity‬الن المستخدم يمكن ان يعرض ‪ Fragment‬ثم يلغيها‬
‫لينتقل الى ‪ Fragment‬اخر وهو في داخل نفس الـ ‪ ، activity‬لتوضيح االمر عملنا مقارنة بين دوال دورة‬
‫حياة الـ ‪ activity‬ودوال دورة حياة الـ ‪:fragment‬‬

‫‪activity‬‬ ‫‪Fragment‬‬
‫تحدث عندما يرتبط ‪ Fragment‬مع‬
‫)‪onAttach (Activity‬‬
‫‪activity‬‬
‫مشابهه للدالة ‪ onCreate‬الخاصة بالـ‬
‫)‪onCreate (Bundle‬‬ ‫‪ activity‬فهي ستعمل على تهيئة بدأ الـ‬
‫‪fragment‬‬
‫الـ ‪ fragment‬يستخدم‬
‫‪onCreateView(LayoutInflater,‬‬
‫‪ LayoutInflater‬لتهيئة العرض في‬
‫) (‪onCreate‬‬ ‫)‪ViewGroup, Bundle‬‬
‫هذه المرحلة‬
‫يتم استدعاء هذه الدالة عندما يتم االنتهاء‬
‫) ‪onActivityCreated( Bundle‬‬ ‫من الدالة ‪ onCreate‬الخاصة بالـ‬
‫‪activity‬‬
‫هذه الدالة يتم استدعائها عندما يكون الـ‬
‫) (‪onStart‬‬ ‫) (‪onStart‬‬
‫‪ fragment‬على وشك ان يكون مرئي‬
‫(‪onResume‬‬ ‫هذه الدالة تستدعى عندما يعرض الـ‬
‫) (‪onResume‬‬
‫)‬ ‫‪ fragment‬والـ ‪ activity‬في مرحلة الـ‬

‫‪40‬‬
‫‪Running‬‬
‫تستدعى هذه الدالة عندما ال يكون الـ‬
‫) (‪onPause‬‬ ‫) (‪onPause‬‬ ‫‪ fragment‬في وضع التفاعل مع‬
‫المستخدم‬
‫تستدعى عندما ال يعود الـ ‪fragment‬‬
‫) (‪onStop‬‬ ‫) (‪onStop‬‬
‫مرئي‬
‫تعطي الـ ‪ fragment‬فرصة ليزيل كل‬
‫) (‪onDestroyView‬‬
‫المصادر المتعلقة بالعرض‬
‫في هذه المرحلة يستطيع الـ ‪fragment‬‬
‫(‪onDestroy‬‬ ‫) (‪onDestroy‬‬
‫ازالة كل المصادر التي انشأت‬
‫)‬
‫اخيرا عندما تفقط الـ ‪fragment‬‬
‫) (‪onDetach‬‬
‫االتصال مع الـ ‪activity‬‬

‫انشاء ‪Fragment‬‬
‫النشاء ‪ Fragment‬في برنامج االندرويد ستوديو نذهب الى‪:‬‬

‫)‪File → New → Fragment → Fragment (Blank‬‬

‫ستظهر لنا نافذة منها نعطي اسم للـ ‪ Fragment‬ونعلم على خيار ‪ create layout XML‬ونعطي اسم لملف‬
‫الـ ‪ XML‬ويمكن ان نلغي العالمة من الخيارات االخرى إذا لم نرد ان ينشأ لنا بشكل اوتوماتيكي دوال‬
‫وانترفيس بداخل الـ ‪( Fragment‬ليكون الـ ‪ Fragment‬فارغ ونحن نضيف به ما نريد) ثم نضغط على‬
‫‪ Finish‬ليتم انشاء ملفين االول ملف جافا والثاني ملف ‪.XML‬‬

‫مكونات الـ ‪Fragment‬‬


‫عند انشاء الـ ‪ Fragment‬سيتكون عندنا ملفين‪:‬‬

‫االول ملف الـ ‪ :XML‬في ملف الـ ‪ XML‬الخاص بالـ ‪ Fragment‬يمكن ان نشكل المظهر بالشكل الذي‬
‫نريد كأي ‪ layout‬اعتيادية‪.‬‬

‫الثاني ملف الجافا‪ :‬ملف الجافا الخاص بالـ ‪ Fragment‬يتم انشائه لنا بهذا الشكل‪:‬‬

‫‪41‬‬
package com.hfad.workout;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class Work extends Fragment {
public Work ( ) { }
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_work, container, false);
}}

‫ بالحزمة التالية‬android.support.v4.app.Fragment ‫* اوال نحتاج ان نستبدل الحزمة‬


android.app.Fragment

‫ الذي سميناه ويتم انشاء الكالس بنفس االسم وهذا الكالس يجب ان‬Fragment ‫ تمثل اسم الـ‬Work ‫حيث ان‬
android.app ‫ الموجود في الحزمة‬Fragment ‫) للكالس‬extends( ‫يرث‬

‫ الخاصة بالـ‬layout ‫ يحتاج ان يستدعيها االندرويد في كل مرة يريد عرض الطبقة‬onCreateView ‫الدالة‬
‫ الكود التالي في داخلها‬،‫ النه من خاللها نحدد اي طبقة نريد عرضها‬،fragment

inflater.inflate(R.layout.fragment_work, container, false)

‫ يمثل اسم الطبقة) اي هذه العبارة اشبه بالدالة‬fragment_work( ‫هو المسؤل عن الطبقة المراد عرضها‬
.activity ‫ التي تحدد الطبقة التي تعرض في الـ‬setContentView

‫) وبدون بارامترات وإال فانه سيحدث‬public( ‫ عامة‬constructor ‫ يجب ان يحتوي على‬fragment ‫* كل‬
.‫عندنا خطأ وقت التشغيل‬

fragment ‫ الذي يعرض الـ‬activity ‫الـ‬


fragment ‫ الذي سيعرض الـ‬activity ‫ الخاص بالـ‬XML ‫ ملف ال‬layout ‫ في‬:XML ‫اوال ملف الـ‬
‫ ويمكن ان يحتل الـ‬،<LinearLayout> ‫< ويمكن وضعه في داخل إطار‬fragment> ‫نكتب الوسم‬

42
‫ جزء من الصفحة الظاهرة او كل الصفحة التي تعرض للمستخدم (على حسب حجم اإلطار الذي‬fragment
:‫نريده) بهذا الشكل‬

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
class="com.hfad.workout.Work"
android:id="@+id/detail_frag"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

‫ ومن خاللها نحدد الحزمة واسم الكالس لملف الجافا‬class ‫< وضعنا الخاصية‬fragment> ‫في داخل الوسم‬
،‫ المراد عرضة‬fragment ‫ خاص بالـ‬id ‫ نضع لها‬id ‫ اما الخاصية‬،‫ المراد عرضه‬fragment ‫الخاص بالـ‬
.activity ‫ في الـ‬fragment ‫بهذه الخطوة سيعرض الـ‬

:‫ بهذا الشكل‬،‫ وال يوجد اي فرق بينهم‬name ‫ بالخاصية‬class ‫* يمكن استبدال الخاصية‬

android:name="com.hfad.workout.Work"

<fragment> ‫ واحد سنكرر وسم الـ‬activity ‫ ونريد عرضهم في‬fragment ‫* إذا كان عندنا أكثر من‬
‫ يمكن ايضا اضافة الخاصية‬،‫الذي كتبناه قبل قليل في الكود مع وضع الحزمة والكالس الخاص بكل منهم‬
:‫ بهذا الشكل‬،‫< لتحديد مساحة الشاشة التي سيشغلها كل منهم‬fragment> ‫ في داخل الوسم‬weight

android:layout_weight="2"

fragment ‫ الذي سيعرض الـ‬activity ‫ يفضل ان نجعل ملف الجافا الرئيسي الخاص بالـ‬:‫ثانيا ملف الجافا‬
‫ للحزمة‬import ‫ ونعمل‬AppCompatActivity ‫ بدال من الكالس‬Activity ‫) الكالس‬extends( ‫يرث‬
android.support.v7.app.AppCompatActivity ‫ بدال من الحزمة‬android.app.Activity

findFragmentById ‫ والدالة‬fragments ‫ للتعامل مع الـ‬getFragmentManager ‫* نستخدم الدالة‬


:‫ بهذا الشكل‬،id ‫ المراد التعامل معه من خالل الـ‬fragment ‫لتحديد الـ‬

Work X = (Work) getFragmentManager().findFragmentById(R.id.detail_frag);

43
‫حيث ان ‪ Work‬يمثل اسم الكالس الرئيسي لملف الجافا للـ ‪ ،fragment‬والـ ‪ detail_frag‬تمثل الـ ‪ id‬الذي‬
‫اعطيناه للـ ‪ fragment‬في ملف الـ ‪ XML‬الخاص بالـ ‪ ،activity‬في هذه الجملة سيتكون عندنا اوبجكت‬
‫اسمة ‪ X‬من نوع الكالس ‪( Work‬كالس الـ ‪ )fragment‬وباالستعانة بهذا االوبجكت يمكن التعامل مع‬
‫الدوال الموجودة في كالس الـ ‪ ،fragment‬مثال للوصول الى دالة في الكالس ‪ Work‬اسمها ‪ meth‬وتأخذ‬
‫باراميتر واحد من النوع ‪ int‬نكتب التالي‪:‬‬

‫;)‪X.meth(5‬‬

‫الوصول الى العناصر ‪ Widgets‬من داخل الـ ‪fragment‬‬


‫بما ان ملف الجافا للـ ‪ fragment‬يرث الكالس ‪ Fragment‬بدال من الكالس ‪ Activity‬فانه من داخل ملف‬
‫الجافا الخاص بالـ ‪ fragment‬ال يمكن الوصول بشكل مباشر لعناصر الكالس ‪( View‬العناصر ‪Widgets‬‬
‫مثل االزرار والحقول النصية إلخ) لذا يجب ان نصل باالول الى جذر الكالس ‪ View‬من خالل الدالة‬
‫‪ getView‬والتي تعيد اوبجكت من النوع ‪ View‬وبالتعامل مع هذا االوبجكت يمكن الوصول الى العناصر‬
‫بالشكل االعتيادي كما كنا نعمل سابقا‪ ،‬مثال‪:‬‬

‫‪@Override‬‬
‫{ ) (‪public void onStart‬‬
‫;) (‪super.onStart‬‬
‫;) (‪View V = getView‬‬
‫{ ) ‪if ( V != null‬‬
‫;)‪TextView title = (TextView) V.findViewById(R.id.text_id‬‬
‫;)”‪title.setText(“Hello‬‬
‫}}‬

‫في هذا المثال كتبنا الكود في دالة دورة الحياة ‪ onStart‬والحظ كيف استدعينا الدالة االب لها ‪ super‬وقبلها‬
‫عملنا ‪ @Override‬الننا سنعيد تكرار الدالة‪ ،‬عملنا العبارة الشرطية ‪ if‬للتأكد من انه يوجد عناصر في الـ‬
‫‪ layout‬يمكن عدم كتابتها لكن يفضل كتابتها دائما لتجنب وقوع اخطاء وقت التشغيل‪.‬‬

‫* إذا وضعنا في ملف الـ ‪ XML‬التابع للـ ‪( fragment‬الموضوع في داخل ‪ activity‬او في داخل‬
‫‪ fragment‬اخر) في الخاصية ‪ onClick‬في عنصر معين اسم دالة ليتم تنفيذها في حال الضغط على هذا‬
‫العنصر‪ ،‬فان هذه الدالة إذا كانت مكتوبة في نفس ملف الجافا التابع للـ ‪ fragment‬الذي يحتوي على العنصر‬
‫لن يتم تنفيذها وعند الضغط على الزر سيحدث خطأ الن البرنامج سيبحث عن هذه الدالة في الـ ‪activity‬‬
‫االب (الحاوي على الـ ‪ )fragment‬ان وجدت نفذها وان لم توجد سيحدث خطأ‪.‬‬

‫‪44‬‬
Fragment ‫ يجب ان نجعل الكالس الرئيسي للـ‬Fragment ‫* بالنسبة للتعامل مع االزرار في داخل الـ‬
‫ فهذا االنترفيس يحتوي على دالة‬View.OnClickListener ‫) لالنترفيس‬implements( ‫يجلب‬
‫ الخاص باي‬id ‫ وبالتعامل مع هذا الباراميتر نأخذ الـ‬View ‫ هذه الدالة تاخذ باراميتر من النوع‬onClick
.‫ العنصر الذي تم الضغط عليه‬id ‫ التي تعيد‬getId ‫عنصر (مهما كان نوع العنصر) من خالل الدالة‬

:)1 ‫مثال (جزء‬

@Override
public void onClick(View v){
switch (v.getId()){
case R.id.start_button:
onClickStart(v);
break;
case R.id.stop_button:
onClickStop(v);
break;
}}

‫ اذا تم الضغط‬start_button ‫ في هذا المثال عندنا زرين االول اسمه‬،‫@ للدالة‬Override ‫كما تالحظ عملنا‬
‫ سينفذ‬stop_button id ‫ اما اذا ضغط على الزر الثاني صاحب الـ‬onClickStart ‫عليه سيتم تنفيذ الدالة‬
‫ لكن الحظ ان هذا الكود غير كافي للتعامل مع هذين الزرين يجب ان نصل للزر ثم‬،onClickStop ‫الدالة‬
.setOnClickListener ‫نستخدم الدالة‬

)onCreateView ‫ (يكتب هذا الكود في داخل الدالة‬:)2 ‫مثال (جزء‬

@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_stopwatch, container, false);
Button startButton = (Button) layout.findViewById(R.id.start_button);
startButton.setOnClickListener(this);
Button stopButton = (Button) layout.findViewById(R.id.stop_button);
stopButton.setOnClickListener(this);
return layout;
}

45
‫* اي ببساطة للتعامل مع االزرار نحتاج ان نكتب الكود في المثال جزء ‪ 1‬والمثال جزء ‪ 2‬في داخل الـ‬
‫‪ Fragment‬الذي يحتوي على االزرار المراد التعامل معها ليتم تنفذ الدوال عند الضغط على االزرار‪.‬‬

‫تنظيم الـ ‪ Fragment‬على شكل قائمة عرض‬


‫في بعض االحيان نحتاج الى ان نعمل قائمة في داخل الـ ‪( Fragment‬كأن تكون قائمة من الخيارات وعند‬
‫الضغط على اي خيار تعرض تفاصيل هذا الخيار) يمكن عمل ذلك بشكل يدوي (من خالل وضع الوسم‬
‫>‪ <ListView‬في الطبقة ‪ layout‬ملف الـ ‪ XML‬لعمل قائمة يشبه ما عملناه في الفصل السابق مع الـ‬
‫‪ )activity‬او يمكن ان نأخذ نموذج جاهز لتسهيل االمر (يشبه ما عملناه في الفصل السابق مع ‪list‬‬
‫‪ ،)activity‬النموذج الجاهز موجود في الكالس ‪ ،ListFragment‬لعمل ذلك نعمل ‪ Fragment‬اعتيادي‬
‫بالذهاب الى‪:‬‬

‫)‪File → New → Fragment → Fragment (Blank‬‬

‫ثم من النافذة التي تظهر نعطي اسم للـ ‪ Fragment‬ونلغي العالمات من انشاء طبقة ‪ XML‬وانشاء انترفيس‬
‫ودوال‪ ،‬لكي يتم انشاء فقط ملف الجافا الن ملف الـ ‪ XML‬سينشأ فيما بعد بشكل برمجي‪ ،‬في ملف الجافا‬
‫الذي تم انشائه نحذف الكود الموجود فيه ونضع بدله الكود التالي‪:‬‬

‫;‪package com.hfad.workout‬‬
‫;‪import android.os.Bundle‬‬
‫;‪import android.app.ListFragment‬‬
‫;‪import android.view.LayoutInflater‬‬
‫;‪import android.view.View‬‬
‫;‪import android.view.ViewGroup‬‬
‫{ ‪public class Work extends ListFragment‬‬
‫‪@Override‬‬
‫‪public View onCreateView(LayoutInflater inflater,‬‬
‫{ )‪ViewGroup container, Bundle savedInstanceState‬‬
‫;)‪return super.onCreateView(inflater, container, savedInstanceState‬‬
‫}}‬

‫* مع مراعات ان ‪ com.hfad.workout‬يمثل اسم الحزمة و ‪ Work‬اسم الـ ‪ Fragment‬الذي سميناه‬


‫(يجب ان يكون الكالس بنفس االسم الذي اعطيناه للـ ‪)Fragment‬‬

‫الدالة ‪ onCreateView‬هي اختيارية اي يمكن عدم وضعها إذا لم نكن نحتاج الكود ان ينفذ في لحظة انشاء‬
‫الـ ‪.Fragment‬‬

‫‪46‬‬
‫في الـ ‪ ListFragment‬ال نحتاج الى ملف الـ ‪ layout XML‬ولكي نضع المعلومات في القائمة التي ستنشأ‬
‫في الطبقة عند العرض نستخدم ملف الجافا الذي انشأناه للوصول لذلك‪.‬‬

‫* يعرض الـ ‪ ListFragment‬في الـ ‪ activity‬كأي ‪ Fragment‬اعتيادي وكما ذكرنا سابقا اي من خالل‬
‫الوسم <‪ >fragment‬الذي نكتبه في ملف الـ ‪ XML‬الخاص بالـ ‪.activity‬‬

‫دوال الكالس ‪ListFragment‬‬

‫الدالة ‪getListView‬‬
‫عندما نستخدم ‪ ListFragment‬فاننا ال نمتلك ملف طبقة ‪ ،XML‬ونحن نعلم انها تمتلك عنصر ‪ListView‬‬
‫واحد‪ ،‬للوصول لهذا العنصر نستخدم هذه الدالة وهي تعيد اوبجكت من النوع ‪ ،ListView‬بهذا الشكل‪:‬‬

‫;) (‪ListView listView = getListView‬‬

‫هذا الكود هو تقريبا نفس الكود التالي الذي كنا نكتبه للوصول الى العناصر ‪ Widgets‬في الـ ‪Fragment‬‬
‫االعتيادية‪:‬‬

‫;)‪ListView listView = (ListView) view.findViewById(R.id.list_name‬‬

‫الدالة )‪onListItemClick (ListView l, View v, int position, long id‬‬


‫تستخدم هذه الدالة لجعل الـ ‪ Fragment‬يستجيب عند الضغط على العناصر في القائمة‪ ،‬حيث اننا نكتب هذه‬
‫الدالة في ملف الجافا للـ ‪ Fragment‬ونضع بداخلها اي كود نريد وسيتم استدعاء وتنفيذ الدالة بشكل‬
‫اوتوماتيكي عند الضغط على عنصر من القائمة‪ ،‬تأخذ هذه الدالة اربع بارامترات يمكن استخدامها للتعامل مع‬
‫العنصر المضغوط حيث ان الباراميتر االول يمثل نوع العنصر (في حالتنا هو ‪ )ListView‬اما الباراميتر‬
‫الثالث هو رقم يمثل تسلسل العنصر في القائمة (يبدأ من الرقم ‪ ، )0‬الباراميتر الرابع يمثل تسلسل العنصر في‬
‫المصفوفة الممررة للقائمة اما اذا كانت القائمة تاخذ بياناتها من قاعدة البيانات فهذا الباراميتر سيمثل تسلسل‬
‫العنصر في قاعدة البيانات ‪_id‬‬

‫مثال‪:‬‬

‫{)‪public void onListItemClick(ListView listView, View view, int position, long id‬‬
‫‪// Do something‬‬
‫}‬

‫‪47‬‬
setListAdapter(adapter) ‫الدالة‬
‫ لتضيف قيم المصفوفة الى‬ArrayAdapter ‫يمرر لها كباراميتر اوبجكت (يحمل مصفوفة) من نوع الكالس‬
‫ (والتي قلنا انها ستنشأ فيما بعد برمجيا) لتظهر على شكل‬XML ‫ الموجود في الطبقة‬ListView ‫العنصر‬
.‫قائمة‬

)ListFragment ‫ (يكتب هذا الكود في ملف الجافا الذي عملناه للـ‬:‫مثال‬

public class Work extends ListFragment {


@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
String[ ] names = new String[5];
for (int i = 0; i < names.length; i++){
names[i] = "List "+i;
}
ArrayAdapter<String> adapter = new ArrayAdapter<String>(inflater.getContext(),
android.R.layout.simple_list_item_1, names);
setListAdapter(adapter);
return super.onCreateView(inflater, container, savedInstanceState);
}}

‫ يمكن ان‬activity ‫ النه في داخل الـ‬this ‫ لم نكتب فقط‬ArrayAdapter ‫الحظ الباراميتر االول للكالس‬
‫ هو كالس ابن من الكالس‬Activity ‫ الحالي وذلك الن الكالس‬context ‫ لتشير الى الـ‬this ‫نكتب‬
‫ ليس ابن‬Fragment ‫) ال يمكن عمل ذلك الن الكالس‬fragment ‫ اما في حالتنا هذه (داخل الـ‬Context
getContext ‫ نستعين بالدالة‬Fragment ‫ ال ينفع لذا في الـ‬this ‫ لذلك فان استخدام‬Context ‫للكالس‬
.‫ الحالي‬Context ‫ للوصول الى الـ‬LayoutInflator ‫التابعة لالوبجكت‬

‫ اعتيادي كما ذكرنا سابقا‬Fragment ‫ (كأي‬activity ‫ في الـ‬ListFragment ‫هنا بمجرد ان نعرض هذا الـ‬
‫) ستعرض لنا قائمة‬activity ‫ الخاص بالـ‬XML ‫> الذي نكتبه في ملف الـ‬fragment< ‫اي من خالل الوسم‬
.‫ مترتبة بشكل عمودي‬names ‫بعناصر المصفوفة‬

48
‫استبدال ‪ Fragment‬بـ ‪ Fragment‬اخر‬
‫في بعض االحيان يكون عندنا ‪ Fragment‬يظهر للمستخدم ونريد عند الضغط على زر معين ان يتم استبدال‬
‫الـ ‪ Fragment‬الظاهر بـ ‪ Fragment‬اخر لكي يعرض في مكانه‪ ،‬لعمل ذلك نحتاج ان نكتب في ملف الـ‬
‫‪ XML‬الخاص بالـ ‪ activity‬التي تعرض الـ ‪ Fragment‬الوسم <‪ >FrameLayout‬بدال من الوسم‬
‫<‪ >fragment‬الذي كنا نستخدمه‪ ،‬ال نعطي للوسم <‪ >FrameLayout‬الخاصية ‪ class‬او الخاصية‬
‫‪( name‬والتي كنا بهذه الخواص نحدد عنوان الـ ‪ Fragment‬الذي سيعرض في هذه المساحة) بدال من هذه‬
‫الخواص نحدد عنوان الـ ‪ Fragment‬الذي سيعرض من خالل الكود الذي نكتبه في ملف الجافا الخاص بالـ‬
‫‪ activity‬الحاوي على الـ <‪. >FrameLayout‬‬

‫* نستخدم الوسم <‪ >fragment‬إذا أردنا ان نعرض ‪ Fragment‬في ‪ activity‬ويبقى ظاهر في مكانه داخل‬
‫الـ ‪ ,activity‬اما إذا أردنا ان نخصص إطار (مساحة) معين في داخل الـ ‪ activity‬لكي يتم عرض‬
‫‪ Fragment‬في كل مرة يحدث شيء (كضغط المستخدم على زر مثال)‪.‬‬

‫* لكي نحدد ونستبدل الـ ‪ Fragment‬الذي سيعرض في داخل الوسم <‪ >FrameLayout‬نكتب في داخل‬
‫كود الجافا التابع للـ ‪ activity‬هذه الخطوات‪:‬‬

‫اوال‪ :‬نعمل اوبجكت اعتيادي لكالس الـ ‪ Fragment‬المراد عرضة بهذا الشكل (على فرض ان اسم الكالس‬
‫هو ‪:)Work‬‬

‫;) (‪Work F = new Work‬‬

‫ثانيا‪ :‬نبدأ بالحصول على ‪ FragmentTransaction‬من الـ ‪ ،fragment manager‬يتم ذلك بهذا الشكل‬

‫;) (‪FragmentTransaction T = getFragmentManager( ).beginTransaction‬‬

‫وال ننسى ان نعمل استدعاء (‪ )import‬للحزمة ‪android.app.FragmentTransaction‬‬

‫ثالثا‪ :‬نستخدم الدالة ‪ replace‬الستبدال (وضع) الـ ‪ ،Fragment‬بهذا الشكل‪:‬‬

‫;)‪T. replace(R.id.fragment_container, F‬‬

‫حيث ان الباراميتر االول للدالة يمثل الـ ‪ id‬الذي اعطيناه للوسم <‪ ،>FrameLayout‬والباراميتر الثاني‬
‫يمثل اوبجكت كالس الـ ‪( fragment‬والذي عملناه في الخطوة اوال وسميناه ‪.)F‬‬

‫رابعا (اختياري)‪ :‬استخدام بعض الدوال االخرى مثل الدالة ‪ add‬او الدالة ‪ remove‬الضافة او حذف‬
‫‪ ،fragment‬بهذا الشكل‪:‬‬

‫;)‪T.add(R.id.fragment_container, F‬‬

‫‪49‬‬
‫;)‪T.remove(F‬‬

‫كذلك يمكن استخدام الدالة ‪ setTransition‬لتحديد ما نوع االنتقال (التحول) الذي نريد‪ ،‬تأخذ بين قوسيها‬
‫باراميتر يحدد نوع االنيميشن (تاثير الحركة) الذي نريده وهناك أربع انواع هي‪:‬‬

‫‪ TRANSIT_FRAGMENT_CLOSE‬الـ ‪ fragment‬يحذف من الـ ‪stack‬‬

‫‪ TRANSIT_FRAGMENT_OPEN‬الـ ‪ fragment‬يبدأ باالضافة‬

‫‪ TRANSIT_FRAGMENT_FADE‬اضافة تاثير التالشي على الـ ‪fragment‬‬

‫‪ TRANSIT_NONE‬ال يوجد انيميشن‬

‫* كل هذه االنواع يجب ان نسبقها بـ ‪ ،FragmentTransaction‬مثال‪:‬‬

‫;)‪T. setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE‬‬

‫نستخدم الدالة ‪ addToBackStack‬الضافة خاصية الى زر الرجوع في الهاتف إذا ضغط علية المستخدم‬
‫ليرجعه الى الـ ‪ fragment‬الذي كان معروض قبل هذا الـ ‪ ،fragment‬هذه الدالة تاخذ باراميتر واحد من‬
‫النوع ‪ String‬يحدد ماذا نريد منها‪ ،‬إذا أردنا ان يرجع المستخدم الى الـ ‪ fragment‬السابق عند الضغط على‬
‫زر الرجوع في الهاتف نمرر لها ‪ ،null‬مثال‪:‬‬

‫;)‪T. addToBackStack(null‬‬

‫إذا لم نستخدم هذه الدالة فانه بشكل افتراضي عند الضغط على زر الرجوع في الهاتف لن نعود الى الـ‬
‫‪ fragment‬السابق وانما سنعود الى الـ ‪ activity‬السابق وان لم يوجد سنخرج من البرنامج‪.‬‬

‫خامسا‪ :‬بعد ان نعمل كل التأثيرات وما نريد على عملية النقل نستخدم الدالة ‪ commit‬لتطبيق التأثيرات على‬
‫الـ ‪ ،activity‬مثال‪:‬‬

‫;) (‪T. commit‬‬

‫مثال‪( :‬يكتب هذا الكود في داخل ملف الجافا التابع للـ ‪ activity‬التي ستحتوي على الـ ‪)Fragment‬‬

‫;) (‪Work F = new Work‬‬


‫;) (‪FragmentTransaction T = getFragmentManager( ).beginTransaction‬‬
‫;)‪T. replace(R.id.fragment_container, F‬‬
‫;)‪T. setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE‬‬
‫;)‪T. addToBackStack(null‬‬
‫;) (‪T. commit‬‬

‫‪50‬‬
‫معرفة الـ ‪ Fragment‬المعروضة حاليا‬
‫إذا كان عندنا اكثرمن ‪ Fragment‬تعرض في الـ ‪ activity‬واردنا ان نعرف الـ ‪ Fragment‬المعروضة‬
‫حاليا يمكننا ذلك من خالل اضافة تاك عن طريق الدالة ‪( replace‬والتي شرحنا عنها قبل قليل) حيث يضاف‬
‫هذا التاك كباراميتر ثالث للدالة ونعطيه اي اسم ونضعه بين عالمتي تنصيص‪ ،‬بهذا الشكل‪:‬‬

‫;)”‪T. replace(R.id.fragment_container, F, “my_tag‬‬

‫حيث ان ‪ T‬يمثل اوبجكت من النوع ‪ FragmentTransaction‬و ‪ R.id.fragment_container‬يمثل الـ‬


‫‪ id‬الذي اعطيناه للوسم <‪ >FrameLayout‬و ‪ F‬يمثل الـ ‪ fragment‬التي ستعرض وسيكون بداخلها تاك‬
‫باالسم ‪.my_tag‬‬

‫يمكننا تحديد الـ ‪ Fragment‬المعروضة حاليا من خالل هذا التاك وذلك عن طريق الدالة‬
‫‪ findFragmentByTag‬وهي ستعيد لنا اوبجكت يمثل الـ ‪ Fragment‬المعروضة لنتعامل معه‪ ،‬بهذا‬
‫الشكل‪:‬‬

‫;)(‪FragmentManager fragMan = getFragmentManager‬‬


‫;)" ‪Fragment fragment = fragMan.findFragmentByTag("my_tag‬‬
‫{)‪if (fragment instanceof TopFragment‬‬
‫‪// the current Fragment is TopFragment do something‬‬
‫}‬
‫{)‪if (fragment instanceof PizzaFragment‬‬
‫‪// the current Fragment is PizzaFragment do something‬‬
‫}‬

‫هذا المثال يفترض ان عندنا اثنين ‪ Fragments‬االولى اسمها ‪ TopFragment‬والثانية اسمها‬


‫‪ ،PizzaFragment‬ونحن سنختبر هل االوبجكت العائد من الدالة ‪ findFragmentByTag‬هو نسخة من‬
‫الـ ‪ TopFragment‬او هو نسخة من الـ ‪ PizzaFragment‬وذلك من خالل الـ ‪ ،instanceof‬ومنه نعرف‬
‫الـ ‪ Fragment‬المعروض حيث ان الذي سيتطابق هو المعروض‪.‬‬

‫‪51‬‬
‫التعامل مع زر الرجوع في الـ ‪Fragment‬‬
‫كل عرض للـ ‪ Fragment‬يعتبر تغير في زر الرجوع للـ ‪ ،Fragment‬مثال االنتقال لعرض ‪Fragment‬‬
‫جديدة عن طريق زر ما او فتح ‪ activity‬جديد ادى الى فتح ‪ Fragment‬او من خالل الضغط على زر‬
‫الرجوع والرجوع الى الـ‪ Fragment‬السابقة كلها تعتبر تغير في زر الرجوع للـ ‪.Fragment‬‬

‫في بعض االحيان نحتاج ان ننفذ كود ما عند حدوث تغير في زر الرجوع للـ ‪ ،Fragment‬ولعمل ذلك نحتاج‬
‫ان نستخدم االنترفيس ‪ FragmentManager.OnBackStackChangedListener‬للوصول الى دالته‬
‫‪ onBackStackChanged‬والتي يتم تنفيذ الكود الموجود داخلها كلما يحدث تغير في زر الرجوع للـ‬
‫‪ ،Fragment‬ويكتب الكود بهذا الشكل‪( :‬يكتب هذا الكود في داخل الدالة ‪ onCreate‬التابعة للـ ‪activity‬‬
‫الذي سيعرض الـ ‪)Fragments‬‬

‫(‪getFragmentManager().addOnBackStackChangedListener‬‬
‫{ ) (‪new FragmentManager.OnBackStackChangedListener‬‬
‫{ ) (‪public void onBackStackChanged‬‬
‫‪// Code to run when the back stack changes‬‬
‫}‬
‫;)}‬

‫‪ Fragment‬متداخلة‬
‫ليس فقط الـ ‪ activity‬يمكن ان تحتوي بداخلها ‪ ، fragment‬الـ ‪ fragment‬نفسه يمكن ان يحتوي على‬
‫‪ fragment‬اخر‪ ،‬عملية وضع ‪ fragment‬بداخل ‪ fragment‬هي تقريبا نفس عملية وضع ‪fragment‬‬
‫بداخل ‪ activity‬مع وجود اختالفات بسيطة‪ ،‬بالنسبة لملف الـ ‪( XML‬الذي سيحتوي على الـ ‪)fragment‬‬
‫سواء كان تابع للـ ‪ fragment‬او للـ ‪ activity‬فهو ال يختلف نعمله بنفس الخطوات التي ذكرناها سابقا‬
‫(الحظ انه يفضل استخدام الطريقة التي نضيف بها الوسم <‪ >FrameLayout‬ونحدد الـ ‪ fragment‬الذي‬
‫سيتواجد هنا من كود الجافا)‪ ،‬اما بالنسبة لملف الجافا التابع للـ ‪ fragment‬الذي سنضع فيه ‪ fragment‬اخر‬
‫سيختلف فقط في شيء واحد وهو بدال من ان نستخدم الدالة ‪ getFragmentManager‬نستخدم الدالة‬
‫‪getChildFragmentManager‬‬

‫مثال‪( :‬يكتب هذا الكود في داخل ملف الجافا التابع للـ ‪ Fragment‬الذي سيحتوي على الـ ‪ Fragment‬اخر‪،‬‬
‫يمكن ان نضعه في داخل الدالة ‪ onCreateView‬لينفذ لحظة عرض ‪)Fragment‬‬

‫;) (‪Work F = new Work‬‬


‫;) (‪FragmentTransaction T = getChildFragmentManager( ).beginTransaction‬‬

‫‪52‬‬
‫;)‪T. replace(R.id.fragment_container, F‬‬
‫;)‪T. setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE‬‬
‫;)‪T. addToBackStack(null‬‬
‫;) (‪T. commit‬‬

‫حيث ان ‪ Work‬يمثل اسم الـ ‪( Fragment‬اسم كالسه الرئيسي) المراد عرضه و‪fragment_container‬‬
‫يمثل قيمة الـ ‪ id‬الخاصة بالوسم <‪ >FrameLayout‬الذي سيعرض بداخله الـ ‪ ،Fragment‬من االفضل‬
‫ولكيال نفقد بيانات الـ ‪ Fragment‬الذي سيعرض هنا ان نجعل هذا الكود ينفذ فقط إذا كان يتم تشغيل البرنامج‬
‫(او الـ ‪ )activity‬الول مرة وليس مجرد حدوث اعادة تشغيل لدورة الحياة (كأن يدير المستخدم الهاتف)‬
‫ونعرف ذلك من خالل االوبجكت ‪ Bundle‬يكون فارغ‪ ،‬اي يكتب الكود السابق بهذا الشكل‪:‬‬

‫‪@Override‬‬
‫‪public View onCreateView(LayoutInflater inflater,‬‬
‫{ )‪ViewGroup container, Bundle savedInstanceState‬‬
‫{)‪if(savedInstanceState != null‬‬
‫الكود الخاص لحفظ البيانات ‪//‬‬
‫{ ‪}else‬‬
‫الكود السابق الستبدال الفراكمنت يكتب هنا ‪//‬‬
‫}‬
‫;)‪return inflater.inflate(R.layout.fragment_workout_detail, container, false‬‬
‫}‬

‫االن بهذا الشكل لن يتم اعادة استبدال الـ ‪ fragment‬إذا ادار المستخدم الهاتف وانما فقط عندما ينشأ الـ‬
‫‪ activity‬الول مرة‪ ،‬وبذلك يمكن حفظ بيانات الـ ‪ fragment‬الداخلية‪.‬‬

‫مالحظات عامة عن الـ ‪Fragment‬‬


‫* لحفظ البيانات (المتغيرات المحلية) عندما تمر الـ ‪ fragment‬بدورة الحياة ‪ stop‬كأن يخرج المستخدم من‬
‫البرنامج ا و يدير الهاتف بشكل افقي‪ ،‬نستخدم نفس الطريقة التي ذكرناها سابقا في حفظ بيانات الـ ‪activity‬‬
‫(عن طريق باراميتر االوبجكت ‪ Bundle‬والدالة ‪ )onSaveInstanceState‬ويمكن ان نكتب في الدالة‬
‫‪ onCreate‬الستعادة القيمة المحفوظة او نكتب الكود في داخل الدالة ‪ onCreateView‬الخذ القيمة من‬
‫االوبجكت ‪.Bundle‬‬

‫‪53‬‬
‫الفصل السادس (‪)action bars‬‬

‫‪ action bar‬هو الشريط الموجود في اعلى التطبيق والذي يظهر فيه اسم البرنامج وفي الغالب نضع فيه‬
‫خيار البحث او زر لالعدادات او الظهار االختصارات للتنقل بين البرنامج او ماشابه‪ ،‬ويمكن ان يبقى ثابت‬
‫في التطبيق عند التنقل بين الـ ‪.activitys‬‬

‫لعمل الـ ‪ action bar‬نحتاج ان نطبق ثيم كامل على الـ ‪ activity‬او على كامل التطبيق‪ ،‬هناك مجموعة‬
‫متنوعة من الثيمات مثال يوجد الثيم ‪ AppCompat‬والذي ياتي مع حزم جوجل ‪ support‬وهذه الحزم تدعم‬
‫االنظمة القديمة من انظمة الهواتف من ‪ API level 7‬فما فوق او الثيم ‪ Holo‬الذي يدعم االصدارات من‬
‫‪ API level 17‬فما فوق او الثيم ‪ Material‬الذي يدعم االصدارات ‪ API level 21‬فما فوق‪.‬‬

‫الثيم ‪ AppCompat‬موجود في الحزمة ‪ android.support.v7.app.AppCompatActivity‬اما الثيم‬


‫‪ Holo‬والثيم ‪ Material‬موجودة في الحزمة ‪ ،android.app.Activity‬عندما نقول ان هذا الثيم موجود‬
‫في هذه الحزمة فهذا يعني ان ملف الجافا في الـ ‪ activity‬يجب ان يجلب (‪ )import‬للحزمة الموجود فيها‬
‫الثيم لكي يكون من الممكن تطبيقه اضافة الى الكالس الرئيسي في ملف الجافا يجب ان يرث الكالس‬
‫‪ Activity‬بالنسبة للثيم ‪ Holo‬والثيم ‪.Material‬‬

‫يكتب الثيم في ملف ‪ ،style‬مثال إذا أردنا تطبيق الثيم ‪ Holo‬يكون شكل ملف ‪ style‬بهذ الشكل‪:‬‬

‫>‪<resources‬‬
‫>"‪<style name="AppTheme" parent="android:Theme.Holo.Light‬‬
‫>‪<!-- Customize your theme here. --‬‬
‫>‪</style‬‬
‫>‪</resources‬‬

‫اما اذا اردنا تطبيق الثيم ‪ Material‬نكتب نفس الكود السابق فقط نغير قيمة الخاصية ‪ parent‬الى‪:‬‬

‫"‪parent="android:Theme. Material.Light‬‬

‫‪54‬‬
‫الملف ‪menu‬‬
‫هذا الملف مسؤول عن الـ ‪ action bar‬والعناصر التي ستظهر في هذا الجزء‪ ،‬يوجد هذا الملف في المجلد‬
‫‪ res->menu‬واسم الملف هو ‪ menu_main.xml‬إذا لم يكن برنامج االندرويد ستوديو قد انشأه يمكن ان‬
‫ننشأه بشكل يدوي ونكتب فيه هذا الكود‪:‬‬

‫"‪<menu xmlns:android="http://schemas.android.com/apk/res/android‬‬
‫"‪xmlns:tools="http://schemas.android.com/tools‬‬
‫"‪xmlns:app="http://schemas.android.com/apk/res-auto‬‬
‫>"‪tools:context=".MainActivity‬‬
‫‪<item‬‬
‫"‪android:id="@+id/action_settings‬‬
‫"‪android:orderInCategory="100‬‬
‫"‪android:title="action_settings‬‬
‫>‪app:showAsAction="never"/‬‬
‫>‪</menu‬‬

‫كل ملف ‪ menu‬يجب ان يتكون من الوسم االب >‪ <menu‬وبين وسمي الفتح والغلق لهذا الوسم نضع‬
‫العناصر التي نريد ان نضيفها حيث ان كل عنصر نعبر عنه بالوسم >‪ <item‬ولهذا الوسم عدة خصائص من‬
‫اهمها‪:‬‬

‫من هذه الخاصية نعطي اسم فريد من نوعه للوصول الى هذا العنصر من‬
‫‪android:id‬‬
‫ملف الجافا‬
‫ايقونة العنصر‪ ،‬وهي عبارة عن مصدر ‪ drawable‬او ‪mipmap‬‬ ‫‪android:icon‬‬
‫هذا يمثل نص العنصر‪ ،‬ربما ال يظهر هذا النص إذا كان العنصر له ايقونة‬
‫والمساحة التكفي لكالهما‪ ،‬إذا ظهر العنصر ‪ overflow‬في الشريط‬
‫‪android:title‬‬
‫العلوي فانه فقط النص سيظهر‪.‬‬
‫قمة رقمية تساعد االندرويد على ان يقرر ترتيب العناصر في الشريط‬ ‫‪android:orderInCategory‬‬
‫هذه الخاصية تحدد كيف سيظهر شكل ترتيب العناصر في الشريط وهي‬
‫تأخذ أحد هذه القيم االربعة‪:‬‬
‫‪ ifRoom‬مساحة العنصر في الشريط وان لم توجد مساحة يوضع‬
‫العنصر في ‪overflow‬‬
‫تضمين نص العنصر‬ ‫‪withText‬‬ ‫‪app:showAsAction‬‬
‫ضع العنصر في ‪ overflow‬وليس في الشريط‬ ‫‪never‬‬

‫‪55‬‬
‫الرئيسي‬
‫دائما ضع العنصر في الشريط الريسي (ال يفضل‬ ‫‪always‬‬
‫استخدام هذه الخاصية مع العديد من العناصر النه يمكن‬
‫ان تتضارب العناصر فيما بينها)‬

‫اضافة زر في الـ ‪action bar‬‬


‫لكي نضيف زر الى الـ ‪ action bar‬نحتاج في االول ان نضيف وسم >‪ <item‬ليمثل العنصر في الملف‬
‫‪ ،menu‬مثال يمكن ان نضيف هذا الكود الى الملف ‪:menu‬‬

‫"‪<item android:id="@+id/action_create_order‬‬
‫"‪android:title="@string/action_create_order‬‬
‫"‪android:icon="@drawable/ic_action_new_event‬‬
‫"‪android:orderInCategory="1‬‬
‫>‪app:showAsAction="ifRoom"/‬‬

‫الحظ هنا استخدمنا نص موجود في الملف ‪ string‬باالسم ‪ ،action_create_order‬وال تنسى ان تعمل‬


‫صورة بصيغة ‪ png‬وباالسم ‪ ic_action_new_event‬ويجب ان تكون مربعة وبعدة احجام‪:‬‬

‫حجم ‪ 48‬بكسل وتوضع في المجلد ‪drawable-mdpi‬‬ ‫‪-1‬‬


‫حجم ‪ 72‬بكسل وتوضع في المجلد ‪drawable-hdpi‬‬ ‫‪-2‬‬
‫حجم ‪ 96‬بكسل وتوضع في المجلد ‪drawable-xhdpi‬‬ ‫‪-3‬‬
‫حجم ‪ 144‬بكسل وتوضع في المجلد ‪drawable-xxhdpi‬‬ ‫‪-4‬‬
‫حجم ‪ 192‬بكسل وتوضع في المجلد ‪drawable-xxxhdpi‬‬ ‫‪-5‬‬

‫ومن ثم نضع كل هذه المجلدات في داخل المجلد ‪ ،res‬نعمل أكثر من صورة لكي يختار النظام الصورة‬
‫االمثل لكل هاتف‪.‬‬

‫بعد اضافة العنصر في الملف ‪ menu‬نحتاج ان نكتب الكود البرمجي في ملف الجافا التابع للـ ‪ activity‬التي‬
‫ستعرض الـ ‪ ،action bar‬سنتبع الخطوتين التاليتين الضافة الكود في ملف الجافا‪:‬‬

‫الخطوة االولى‬
‫الدالة ‪onCreateOptionsMenu‬‬

‫‪56‬‬
‫ تاخذ هذه الدالة باراميتر واحد عبارة عن اوبجكت‬action bar ‫يتم تنفيذ هذه الدالة عندما يتم انشاء قائمة الـ‬
‫) للحزمة‬import( ‫ وال ننسى عند استخدام هذه الدالة يجب ان نستدعي‬،Menu ‫من النوع‬
‫ الضافة‬inflate ‫ والدالة‬getMenuInflater ‫ بداخل هذه الدالة نستخدم الدالة‬،android.view.Menu
:‫ بهذا الشكل‬action bar ‫العناصر الى الـ‬

@Override
public boolean onCreateOptionsMenu(Menu menu){
getMenuInflater().inflate(R.menu.menu_main, menu);
return super.onCreateOptionsMenu(menu);
}

‫ بهذا الكود نحن نأخذ العناصر من الملف المصدر‬،‫@ للدالة‬Override ‫الحظ اننا عملنا‬
.action bar ‫ الخاص بالـ‬Menu ‫ ونضيفهم الى االوبجكت‬menu_main.xml

‫الخطوة الثانية‬
onOptionsItemSelected ‫الدالة‬

‫ حيث يتم‬،‫ عند الضغط عليها‬action bar ‫ يتفاعل مع العناصر في الـ‬activity ‫تستخدم هذه الدالة لجعل الـ‬
‫ تاخذ هذه الدالة باراميتر واحد هو عبارة عن‬،action bar ‫استدعائها كلما يتم الضغط على عنصر في الـ‬
‫ العنصر الذي تم‬id ‫ هذه الدالة تعيد‬getItemId ‫ لهذا االوبجكت دالة اسمها‬،MenuItem ‫اوبجكت من النوع‬
‫ سيكون شكل‬،android.view.MenuItem ‫) الحزمة‬import( ‫ وال تنسى يجب ان تستدعي‬،‫الضغط عليه‬
:‫كود الدالة بهذا الشكل‬

@Override
public boolean onOptionsItemSelected(MenuItem item){
switch (item.getItemId()){
case R.id.action_create_order:
//code to run when the Create Order item is clicked
return true;
case R.id.action_settings:
//code to run when the settings item is clicked
return true;
default:
return super.onOptionsItemSelected(item);
}

57
‫}‬

‫الحظ اننا عملنا ‪ @Override‬للدالة‪ ،‬والحظ اننا نكتب اي كود نريد بدال من التعليقات الموجودة في الكود‬
‫كان نفتح ‪ activity‬جديدة مثال‪ ،‬نعيد ‪ true‬لكي نخبر البرنامج انه تم تنفيذ الكود‪ .‬ليكون شكل الزر عند‬
‫الضغط على الـ ‪( overflow‬النقاط الثالثة في الـ ‪ )action bar‬هكذا‪:‬‬

‫عمل زر مشاركة في الـ ‪action bar‬‬


‫يمكن عمل زر لمشاركة البيانات ووضعه في الـ ‪ action bar‬لنتمكن من نقل بيانات من تطبيقنا الى تطبيقات‬
‫اخرى في الهاتف‪ ،‬يوجد نموذج جاهز يمكن استخدامه للزر اي ال نحتاج ان نضع ايقونة للزر او نحدد ما هي‬
‫التطبيقات التي يمكن ان نتعامل معها الن النظام سيعمل كل ذلك‪ ،‬كل ما نحتاجه هو ان نمرر البيانات عبر‬
‫االوبجكت ‪ Intent‬ونحدد ماهي نوع البيانات‪ ،‬مثال اذا مررنا نصوص عبر ‪ ACTION_SEND‬فان‬
‫النظام سيظهر البرامج التي يمكن مشاركة البيانات النصية عبرها‪ ،‬لعمل زر المشاركه يجب ان نضيف‬
‫عنصر الى الملف ‪ menu_main.xml‬كأي عنصر عادي عبر الوسم >‪ <item‬لكن نحدد ان هذا زر‬
‫مشاركة عبر اضافة الخاصية ‪ android:actionProviderClass‬الى الوسم >‪ <item‬ونسند لها القيمة‬
‫‪ android.widget.ShareActionProvider‬ليكون شكل وسم العنصر هكذا‪:‬‬

‫"‪<item android:id="@+id/action_share‬‬
‫"‪android:title="@string/action_share‬‬
‫"‪android:orderInCategory="2‬‬
‫"‪app:showAsAction="ifRoom‬‬
‫>‪android:actionProviderClass="android.widget.ShareActionProvider" /‬‬

‫الحظ هنا استخدما نص موجود في الملف ‪ string‬باالسم ‪ ،action_share‬واالن نذهب الى ملف الجافا‬
‫التابع للـ ‪ activity‬التي ستعرض الـ ‪ action bar‬ونضيف لها الدالة ‪( onCreateOptionsMenu‬والتي‬

‫‪58‬‬
‫شرحنا عنها قبل قليل في هذا الفصل في الموضوع ‪ -‬اضافة زر الى القائمة ‪ -‬الخطوة االولى)‪ ،‬بداخل هذه‬
‫الدالة نعمل التالي‪:‬‬

‫اوال نعمل اوبجكت ‪ Intent‬ونمرر له ‪ Intent.ACTION_SEND‬بهذا الشكل‪:‬‬

‫;)‪Intent intent = new Intent(Intent.ACTION_SEND‬‬

‫ثانيا نحدد نوع االوبجكت ‪ Intent‬من خالل الدالة ‪ ،setType‬تكون بهذا الشكل (إذا كانت القيمة المرسلة‬
‫نصية)‪:‬‬

‫;)"‪intent.setType("text/plain‬‬

‫ثالثا نحدد العنصر المراد التعامل معه للنشر من خالل ‪ id‬العنصر الذي اعطيناه له في الوسم >‪ <item‬بهذا‬
‫الشكل‪:‬‬

‫;)‪MenuItem menuItem = menu.findItem(R.id.action_share‬‬

‫رابعا نوضح للنظام ان هذا العنصر هو عنصر نشر من خالل الوصول للـ ‪ action provider‬لكي يظهر‬
‫النظام البرامج التي من الممكن النشر بها وذلك من خالل الدالة ‪ getActionProvider‬والتي ستعيد قيمة من‬
‫النوع ‪ ShareActionProvider‬لتكون مرجع لالوبجكت ‪ ،Intent‬وال تنسى ان تعمل استدعاء (‪)import‬‬
‫للحزمة ‪ ،android.widget.ShareActionProvider‬ليكون شكل الكود بهذا الشكل‪:‬‬

‫= ‪ShareActionProvider shareActionProvider‬‬

‫;)(‪(ShareActionProvider) menuItem.getActionProvider‬‬

‫خامسا نمرر البيانات المراد ارسالها لالوبجكت ‪ Intent‬بهذا الشكل‪:‬‬

‫;)"‪intent.putExtra(Intent.EXTRA_TEXT, "This is example text‬‬

‫سادسا نمرر االوبجكت ‪ Intent‬للدالة ‪ setShareIntent‬لكي يتم نقل البيانات الى البرنامج الهدف‪ ،‬بهذا‬
‫الشكل‪:‬‬

‫;)‪shareActionProvider.setShareIntent(intent‬‬

‫* سيكون شكل كامل الكود المذكور في النقاط الستة في داخل الدالة ‪ onCreateOptionsMenu‬بهذا‬
‫الشكل‪:‬‬

‫‪@Override‬‬
‫{)‪public boolean onCreateOptionsMenu(Menu menu‬‬
‫;)‪getMenuInflater().inflate(R.menu.menu_main, menu‬‬
‫;)‪Intent intent = new Intent(Intent.ACTION_SEND‬‬

‫‪59‬‬
intent.setType("text/plain");
MenuItem menuItem = menu.findItem(R.id.action_share);
ShareActionProvider shareActionProvider =
(ShareActionProvider) menuItem.getActionProvider();
intent.putExtra(Intent.EXTRA_TEXT, "This is example text");
shareActionProvider.setShareIntent(intent);
return super.onCreateOptionsMenu(menu);
}

‫ لتحديد الملف الذي سنأخذ منه مصدر المعلومات (الموجود فيه وسم‬getMenuInflater ‫نستحدم الدالة‬
.)<item> ‫العنصر‬

:‫سيكون شكل زر المشاركة هكذا‬

action bar ‫اضافة زر رجوع في الـ‬


‫ زر رجوع الهاتف يعيد‬،‫ يختلف عن زر الرجوع الموجود في الهاتف‬action bar ‫زر الرجوع في الـ‬
‫ سيعيد المستخدم‬action bar ‫ السابقة التي كان قد زارها المستخدم اما زر رجوع الـ‬activity ‫المستخدم للـ‬
.‫ االب اي حسب تسلسل هرمية التطبيق‬activity ‫للـ‬

60
‫لعمل هذا الزر يجب في االول ان نحدد في ملف الـ ‪ )AndroidManifest.xml( manifest‬الـ ‪activity‬‬
‫االب لكل ‪ ،activity‬يتم عمل ذلك باضافة الخاصية ‪ android:parentActivityName‬لوسم الـ‬
‫‪ activity‬ونسند لها اسم الـ ‪ activity‬االب وهذا يعمل مع االجهزة ذات النظام ‪ API level 16‬فما فوق اما‬
‫بالنسبة لالصدارات االقدم نستخدم الوسم <‪ >meta-data‬الذي يكتب بين وسمي الفتح والغلق للوسم‬
‫>‪ <activity‬وياخذ الخاصية ‪ name‬و ‪ ،value‬ويمكن ان نكتب الطريقتين معا لتنفذ على جميع االجهزة‬
‫بهذا الشكل‪:‬‬

‫"‪<activity android:name=".OrderActivity‬‬
‫>"‪android:parentActivityName=".MainActivity‬‬
‫"‪<meta-data android:name="android.support.PARENT_ACTIVITY‬‬
‫>‪android:value=".MainActivity" /‬‬
‫>‪</activity‬‬

‫هنا جعلنا الـ ‪ activity‬االبن هو ‪ OrderActivity‬واالب هو ‪ ،MainActivity‬االن سنعمل زر رجوع للـ‬


‫‪ action bar‬لنضعه في الـ ‪ OrderActivity‬وعند الضغط عليه نعود الى الـ ‪.MainActivity‬‬

‫* الضافة زر الرجوع في الـ ‪ action bar‬ال نحتاج ان نضيف اي عنصر الى الملف ‪main_menu.xml‬‬
‫وانما فقط من خالل ملف الجافا التابع للـ ‪ activity‬الذي سيظهر فيه هذا الزر‪.‬‬

‫في ملف الجافا التابع للـ ‪ activity‬الذي سيظهر فيه هذا الزر‪ ،‬نكتب في داخل الدالة ‪ ،onCreate‬في االول‬
‫نأخذ مرجع للـ ‪ action bar‬باستخدام الدالة ‪ getActionBar‬لنستخدمه مع الدالة‬
‫‪ setDisplayHomeAsUpEnabled‬والتي سنمرر لها القيمة ‪ ،true‬بهذا الشكل‪:‬‬

‫;)(‪ActionBar actionBar = getActionBar‬‬


‫;)‪actionBar.setDisplayHomeAsUpEnabled(true‬‬

‫بتنفيذ هذا الكود البسيط سيظهر عندنا الزر في الـ ‪.activity‬‬

‫‪61‬‬
‫تغييير عناصر الـ ‪ action bar‬اثناء تشغيل البرنامج‬
‫يمكن تغيير عناصر الـ ‪ action bar‬اثناء عمل البرنامج مثال كأن نجعل زر ‪ Button‬عند الضغط عليه‬
‫يختفي عنصر من عناصر الـ ‪ ،action bar‬يمكننا عمل اي تغيير نريد في اي وقت نريد من خالل استدعاء‬
‫الدالة ‪ invalidateOptionsMenu‬هذه الدالة تخبر االندرويد ان عناصر الـ ‪ action bar‬بحاجة الى‬
‫التغيير‪ ،‬مثال إذا كان عندنا زر عند الضغط عليه سيتم تنفيذ دالة اسمها ‪ change‬فاننا سنكتب الدالة‬
‫‪ invalidateOptionsMenu‬بداخلها‪ ،‬بهذا الشكل‪:‬‬

‫{ )‪public void change(View v‬‬


‫;) (‪invalidateOptionsMenu‬‬
‫}‬

‫حيث هنا الدالة ‪ invalidateOptionsMenu‬ستقوم هي اوتوماتيكيا باستدعاء وتنفيذ دالة اخرى اسمها‬
‫‪ onPrepareOptionsMenu‬لذا يمكننا عمل ‪ @Override‬للدالة وكتابة اي تغييرات نريدها على عناصر‬
‫الـ ‪ action bar‬في داخلها‪ ،‬تاخذ هذه الدالة باراميتر واحد هو عبارة عن اوبجكت من النوع ‪.Menu‬‬

‫مثال‪( :‬يمكن ان يكتب هذا الكود في اي مكان حتى لو خارج الدالة ‪)change‬‬

‫‪@Override‬‬
‫{)‪public boolean onPrepareOptionsMenu(Menu menu‬‬
‫;)‪menu.findItem(R.id.action_share).setVisible(false‬‬
‫;)‪return super.onPrepareOptionsMenu(menu‬‬
‫}‬

‫الدالة ‪ findItem‬مسؤولة عن تحديد العنصر المراد التعامل معه من خالل الـ ‪ id‬حيث ان ‪action_share‬‬
‫يمثل ‪ id‬لعنصر في الـ ‪( action bar‬اعطيناه له في الملف ‪ ،)main_menu.xml‬والدالة ‪setVisible‬‬
‫مسؤولة عن جعل عنصر ما مرئي او غير مرئي بتمرير قيمة ‪ true‬لجعل العنصر مرئي او ‪ false‬لجعله‬
‫غير مرئي‪.‬‬

‫مالحظات عامة عن الـ ‪action bar‬‬


‫* من خالل الدالة ‪ getActionBar‬يمكننا ان نأخذ مرجع للـ ‪ action bar‬ومنه ننفذ عدة دوال الجراء‬
‫التعديالت على الـ ‪ ،action bar‬منها‪:‬‬

‫‪62‬‬
‫الدالة ‪setDisplayHomeAsUpEnabled‬‬

‫الظهار زر الرجوع في الـ ‪( action bar‬والتي شرحنا عنها سابقا في هذا الفصل)‪.‬‬

‫الدالة ‪setTitle‬‬

‫الظهار عنوان في الـ ‪ action bar‬وإذا كان عنوان الـ ‪ activity‬هو الظاهر فهذه الدالة ستستبدله بالعنوان‬
‫الممرر لها‪ ،‬مثال‪:‬‬

‫;)"‪getActionBar().setTitle("Title‬‬

‫او كتابة نفس هذا المثال لكن بطريقة مختلفة‪:‬‬

‫;”‪String X = “Title‬‬
‫;)(‪ActionBar actionBar = getActionBar‬‬
‫;)‪actionBar.setTitle(X‬‬

‫الفصل السابع (القائمة الجانبية ‪)Navigation drawer‬‬

‫ا لقائمة الجانبية هي القائمة التي تظهر من جانب الشاشة عند سحب الشاشة من الجانب او عند الضغط على‬
‫ايقونتها‪ ،‬وهي تحتوي على روابط الهم عناصر البرنامج لينتقل لها المستخدم‪.‬‬

‫‪63‬‬
‫يتم تنفيذ القائمة الجانبية باستخدام طبقة خاصة تدعى ‪ DrawerLayout‬وهي موجودة في الكالس‬
‫‪ ،android.support.v4.widget.DrawerLayout‬يجب ان يكون المشروع الذي نعمل به يدعم المكتبة‬
‫‪ support.v7.app.AppCompatActivity‬وللتأكد من ان مشروعنا يدعم هذه المكتبة في برنامج‬
‫االندرويد ستوديو نذهب الى‪:‬‬

‫‪File → Project Structure → App → Dependencies‬‬

‫لتكون النافذه بهذا الشكل‪:‬‬

‫‪64‬‬
‫إذا لم تكن المكتبة ‪ support.v7.app.AppCompatActivity‬مضافة نضيفها من عالمة الزائد الموجودة‬
‫في اعلى النافذة على جهة اليمين (كما موضح في الصورة السابقة)‪.‬‬

‫الطبقة ‪ DrawerLayout‬تتكون من جزئين اساسيين االول هو ‪ FrameLayout‬وهو المسؤول عن عرض‬


‫الـ ‪ Fragments‬والتي تكون ظاهرة بشكل افتراضي والثاني هو ‪ ListView‬وهو المسؤول عن قائمة‬
‫الروابط والتي تكون مخفية وعند سحبها تظهر‪ ،‬وتمثل الطبقة ‪ DrawerLayout‬في ملف الـ ‪ XML‬التابع‬
‫للـ ‪ activity‬التي ستعرض القائمة الجانبية بالوسم <‪>android.support.v4.widget.DrawerLayout‬‬
‫وبين وسمي الفتح والغلق له نضع الوسم <‪ >FrameLayout‬والوسم <‪>ListView‬‬

‫‪65‬‬
‫ والثاني‬TopFragment ‫ االول اسمه‬Fragments ‫لتوضيح فكرة القائمة الجانبية سنفترض ان عندنا أربع‬
‫ وعندنا الـ‬StoresFragment ‫ والرابع اسمه‬PastaFragment ‫ والثالث اسمه‬PizzaFragment ‫اسمه‬
‫ سنعمل القائمة‬،MainActivity ‫ اسمه‬Fragments ‫ الذي سيعرض القائمة الجانبية وهذه الـ‬activity
:‫الجانبية من خالل عمل عدة خطوات‬

‫الخطوة االولى‬
:‫ نحذف الكود المكون افتراضيا ونكتب هذا الكود‬،MainActivity ‫ التابع للـ‬XML ‫في ملف الـ‬

<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawe_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">

<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />

66
‫‪<ListView‬‬
‫"‪android:id="@+id/drawer‬‬
‫"‪android:layout_width="240dp‬‬
‫"‪android:layout_height="match_parent‬‬
‫"‪android:layout_gravity="start‬‬
‫"‪android:choiceMode="singleChoice‬‬
‫"‪android:divider="@android:color/transparent‬‬
‫"‪android:dividerHeight="0dp‬‬
‫>‪android:background="#ffffff"/‬‬

‫>‪</android.support.v4.widget.DrawerLayout‬‬

‫الوسم >‪ <FrameLayout‬يمثل الجزء الظاهر عندما تكون القائمة الجانبية غير ظاهرة والوسم‬
‫>‪ <ListView‬يمثل جزء القائمة الجانبية الذي يظهر ويختفي عند السحب‪ ،‬وضعنا في الوسم‬
‫>‪ <ListView‬قيمة خاصية العرض ‪ 240dp‬وهذه القيمة تمثل عرض القائمة الجانبية عندما تظهر‪ ،‬اما‬
‫الخاصية ‪ layout_gravity‬اسندنا لها القيمة ‪ start‬وهذا يعني انه اذا كان نظام الهاتف من اليسار لليمين‬
‫(مثال لغة الهاتف انجليزية) ستظهر النافذة من اليسار لليمين اما اذا كان النظام من اليمين الى اليسار (لغة‬
‫الهاتف عربية مثال) ستظهر النافذة من اليمين لليسار‪ ،‬اما الخاصية ‪ choiceMode‬وضعنا لها القيمة‬
‫‪ singleChoice‬فهذا يعني انه يمكن تحديد عنصر واحد في كل مرة‪ ،‬اما الخاصيتان ‪ dividerHeight‬و‬
‫‪ divider‬فهما مسؤولتان عن الخط الذي يفصل بين الخيارات (الروابط) في القائمة الجانبية‪.‬‬

‫الخطوة الثانية‬
‫في الملف النصي ‪ string‬نكتب اسماء الخيارات (التي ستعرض في القائمة عند سحبها وكل خيار سيمثل‬
‫‪ Fragment‬عند الضغط عليه سيفتح هذا الـ ‪ )Fragment‬على شكل مصفوفة بهذا الشكل‪:‬‬

‫>"‪<string-array name="titles‬‬
‫>‪<item>Home</item‬‬
‫>‪<item>Pizzas</item‬‬
‫>‪<item>Pasta</item‬‬
‫>‪<item>Stores</item‬‬
‫>‪</string-array‬‬

‫‪67‬‬
‫ التي‬activity ‫ (الـ‬MainActivity ‫بعد كتابة االسماء في الملف النصي نذهب الى ملف الجافا التابع للـ‬
‫ ونرتبها بشكل قائمة متسلسلة‬string ‫ستعرض القائمة الجانبية) ونجلب مصفوفة االسماء من الملف النصي‬
:‫ (شرحنا عن هذا الكالس في فصل الرابع) ليكون الكود بهذا الشكل‬ArrayAdapter ‫من خالل الكالس‬

public class MainActivity extends Activity {


private String[] titles;
private ListView drawerList;
@Override
protected void onCreate(Bundle savedInstanceState) {
….
titles = getResources().getStringArray(R.array.titles);
drawerList = (ListView)findViewById(R.id.drawer);
drawerList.setAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_activated_1, titles));
}

‫ وكذلك للحزمة‬android.widget.ListView ‫) للحزمة‬import( ‫وال تنسى ان تعمل استدعاء‬


android.widget.ArrayAdapter

‫الخطوة الثالثة‬
‫في هذه الخطوة نكتب كود لالستجابة عند الضغط على عناصر القائمة ونعمل ذلك من خالل الكالس‬
‫ نكتب هذا الكود في ملف الجافا التابع للـ‬،)‫ (والذي شرحنا عنه في الفصل الرابع‬onItemClickListener
:‫ بهذا الشكل‬،MainActivity

)onCreate ‫(هذا الكود يكتب خارج الدالة‬

private class DrawerItemClickListener implements


ListView.OnItemClickListener{
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long
id){
selectItem(position);
}}

)onCreate ‫(هذا الكود يكتب في داخل الدالة‬

68
drawerList.setOnItemClickListener(new DrawerItemClickListener());

selectItem ‫ وسيتم تنفيذ الدالة‬، ‫ هو اوبجكت القائمة النصية وقد عرفناه في الخطوة الثانية‬drawerList
:‫عند الضغط على اي عنصر في القائمة ويمكن ان يكون شكل هذه الدالة هكذا‬

)onCreate ‫(هذا الكود يكتب خارج الدالة‬

private void selectItem(int position){


Fragment fragment;
switch (position){
case 1:
fragment = new PizzaFragment();
break;
case 2:
fragment = new PastaFragment();
break;
case 3:
fragment = new StoresFragment();
break;
default:
fragment = new TopFragment();
}
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, fragment);
ft.addToBackStack(null);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}

‫ التي ستعرض بشكل افتراضي عند فتح‬Fragment ‫ نكتب هذا هذا الكود لتحديد الـ‬onCreate ‫في الدالة‬
:‫البرنامج الول مرة‬

if (savedInstanceState == null){
selectItem(0);
}

69
‫االن أصبح عندنا قائمة عرض متكاملة تقريبا‪ ،‬عند بداية فتح البرنامج سيفتح لنا الـ ‪ TopFragment‬والقائمة‬
‫الجانبية مخفية وعند سحب الشاشة ستظهر قائمة بها عدة خيارات وعند الضغط عليها ستستبدل الـ‬
‫‪ fragment‬المعروض بالـ ‪ fragment‬الجديد‪ .‬ففي الخطوات القادمة سنعدل على هذه القائمة الضافة بعض‬
‫المميزات لها‪.‬‬

‫الخطوة الرابعة‬
‫في هذه الخطوة سنجعل القائمة الجانبية تختفي اوتوماتيكيا عند الضغط على عنصر فيها وليس هناك داعي‬
‫لغلقها يدويا‪ ،‬لعمل ذلك ناخذ مرجع للـ ‪ DrawerLayout‬ونستخدمه مع الدالة ‪ closeDrawer‬والتي تاخذ‬
‫باراميتر واحد هو عبارة عن العنصر ‪ View‬المراد غلقه (في حالتنا هذه العنصر هو ‪ ،)ListView‬بهذا‬
‫الشكل‪:‬‬

‫;‪DrawerLayout drawerLayout‬‬
‫;)‪drawerLayout = (DrawerLayout)findViewById(R.id.drawe_layout‬‬
‫;)‪drawerLayout.closeDrawer(drawerList‬‬

‫يوضع السطر االول من هذا الكود خارج الدالة ‪ onCreate‬اما السطر الثاني يوضع في داخل الدالة‬
‫‪ onCreate‬والسطر الثالث يوضع في داخل الدالة ‪ selectItem‬ليتم تنفيذه عند الضغط على عنصر من‬
‫عناصر القائمة‪ drawerList ،‬هو اوبجكت القائمة النصية ‪ ListView‬وقد عرفناه في الخطوة الثانية‪.‬‬

‫الخطوة الخامسة‬
‫في هذه الخطوة سنغير عنوان الـ ‪ action bar‬باالعتماد على العنصر المضغوط في القائمة لنبين للمستخدم‬
‫اي خيار تعرض الصفحة االن‪ ،‬نعمل ذلك من خالل الدالة ‪ setTitle‬بالتعاون مع ‪( getActionBar‬وقد‬
‫شرحنا عنهم في الفصل السادس)‪ ،‬بهذا الشكل‪:‬‬

‫{)‪private void setActionBarTitle(int p‬‬


‫;‪String title‬‬
‫{)‪if (p == 0‬‬
‫;)‪title = getResources().getString(R.string.app_name‬‬
‫{ ‪}else‬‬
‫;]‪title = titles[p‬‬
‫}‬
‫;)‪getActionBar().setTitle(title‬‬

‫‪70‬‬
‫}‬

‫حيث ان ‪ titles‬هي مصفوفة نصية عرفناها في الخطوة الثانية ووضعنا فيها عناصر القائمة الجانبية‪ ،‬واالن‬
‫نستدعي هذه الدالة من داخل الدالة ‪ selectItem‬لتنفذ عند الضغط على اي عنصر من القائمة الجانبية بهذا‬
‫الشكل‪:‬‬

‫;)‪setActionBarTitle(position‬‬

‫حيث ان ‪ position‬يمثل باراميتر الدالة ‪ selectItem‬والذي يحمل رقم تسلسل العنصر في القائمة‪.‬‬

‫الخطوة السادسة‬
‫في هذه الخطوة نريد ان نجري تعديالت بناءا على ما اذا كانت القائمة الجانبية مفتوحة (مسحوبة) او مغلقة‬
‫وهذا االمر مهم لعمل العديد من االشاء‪ ،‬اوال نحتاج الى استخدام ‪ ActionBarDrawerToggle‬وهو‬
‫مخصص للتعامل مع القائمة الجانبية واالصغاء الحداثها كالغضط على عنصر او اي حدث ممكن يحدث لها‪،‬‬
‫نعمل ‪ ActionBarDrawerToggle‬جديد وتمرير اربع بارامترات له االول يمثل الـ ‪ Context‬الحالي‬
‫(يمكن كتابة ‪ this‬اذا كنا في داخل ‪ )activity‬اما الباراميتر الثاني هو ‪ DrawerLayout‬اما الثالث والرابع‬
‫هما مصدر لقيم نصية تكتب في المف النصي ‪ ،string‬يمكننا ان نستخدم الدالتين التابعتين له لتحدديد ما اذا‬
‫كانت القائمة الجانبية مفتوحة او مغلقة‪ ،‬الدالة ‪ onDrawerClosed‬يتم تنفيذ ما بداخلها اذا كانت القائمة‬
‫الجانبية مغلقة‪ onDrawerOpened ،‬يتم تنفيذ ما بداخلها اذا كانت القائمة الجانبية مفتوحة‪ ،‬يكتب الكود بهذا‬
‫الشكل‪:‬‬

‫اوال نكتب هذا الكود في الملف النصي ‪string‬‬

‫>‪<string name="open_drawer">Open drawer</string‬‬


‫>‪<string name="close_drawer">Close drawer</string‬‬

‫ثم نكتب هذا الكود في ملف‪:‬‬

‫;‪private ActionBarDrawerToggle drawerToggle‬‬


‫‪...‬‬
‫{ )‪protected void onCreate(Bundle savedInstanceState‬‬
‫‪...‬‬
‫‪drawerToggle = new ActionBarDrawerToggle(this, drawerLayout,‬‬
‫)‪R.string.open_drawer, R.string.close_drawer‬‬
‫{‬
‫{ )‪public void onDrawerClosed(View view‬‬

‫‪71‬‬
super.onDrawerClosed(view);
// Code to run when the drawer is closed
}
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
// Code to run when the drawer is open
}
};
}

‫ بعد هذا الكود‬،‫ عرفناه في الخطوة الرابعة‬DrawerLayout ‫ هو اوبجكت من الـ‬drawerLayout ‫حيث ان‬
‫ التي عندنا من خالل الدالة‬DrawerLayout ‫ بالـ‬ActionBarDrawerToggle ‫مباشرتا علينا ان نربط الـ‬
‫ لكن في‬ActionBarDrawerToggle ‫ (يكتب هذا الكود خارج الـ‬:‫ بهذا الشكل‬setDrawerListener
)onCreate ‫داخل الدالة‬

drawerLayout.setDrawerListener(drawerToggle);

.‫ يختفي عند سحب القائمة الجانبية ويظهر عند غلقها‬action bar ‫االن يمكن مثال ان نجعل عنصر في الـ‬

‫ (شرحنا عنها في الفصل السادس) بداخل الدالتين‬invalidateOptionsMenu ‫لذا يمكن ان نستدعي الدالة‬
‫الدالة‬ ‫بدورها‬ ‫هي‬ ‫لتستدعي‬ onDrawerClosed‫و‬ onDrawerOpened
‫ (شرحنا عنها ايضا في الفصل السادسل) ليكون شكل كود الدالة‬onPrepareOptionsMenu
)onCreate ‫ (يكتب هذا الكود خارج الدالة‬:‫ بهذا الشكل‬onPrepareOptionsMenu

72
‫{)‪public boolean onPrepareOptionsMenu(Menu menu‬‬
‫;)‪boolean drawerOpen = drawerLayout.isDrawerOpen(drawerList‬‬
‫;)‪menu.findItem(R.id.action_share).setVisible(!drawerOpen‬‬
‫;)‪return super.onPrepareOptionsMenu(menu‬‬
‫}‬

‫الدالة ‪ isDrawerOpen‬تاخذ باراميتر عبارة عن اوبجكت من النوع ‪( ListView‬عملنا هذا االوبجكت‬


‫باالسم ‪ drawerList‬في الخطوة الثانية) وتعيد القيمة ‪ true‬إذا كانت مفتوحة القائمة الجانبية او ‪ false‬إذا‬
‫كانت مغلقة‪.‬‬

‫الخطوة السابعة‬
‫في هذه الخطوة نريد ان نجعل المستخدم يفتح ويغلق القائمة الجانبية من خالل ايقونة تظهر في الـ ‪action‬‬
‫‪( bar‬اضافة الى امكانية السحب)‬

‫يمكن ذلك باستخدام ‪ ActionBarDrawerToggle‬والذي شرحنا عنه قبل قليل في الخطوة السادسة‪ ،‬اوال‬
‫نضيف زر الرجوع في الـ ‪( action bar‬شرحنا عنه في الفصل السادس) بهذا الكود‪( :‬يكتب هذا الكود في‬
‫الدالة ‪)onCreate‬‬

‫;)‪getActionBar().setDisplayHomeAsUpEnabled(true‬‬
‫;)‪getActionBar().setHomeButtonEnabled(true‬‬

‫‪73‬‬
‫بعد ان مكنا ظهور زر الرجوع في الـ ‪ action bar‬علينا االن ان نجعله يستجيب للنقر عليه (الحظ اننا ال‬
‫نريده ان يرجع للـ ‪ activity‬االب بحسب تسلسل هرمي) وذلك من خالل الدالة‬
‫‪( onOptionsItemSelected‬شرحنا عنها في الفصل السادس)‪ ،‬بهذا الشكل‪:‬‬

‫‪@Override‬‬
‫{)‪public boolean onOptionsItemSelected(MenuItem item‬‬
‫{))‪if (drawerToggle.onOptionsItemSelected(item‬‬
‫;‪return true‬‬
‫}‬
‫;)‪return super.onOptionsItemSelected(item‬‬
‫}‬

‫حيث ان ‪ drawerToggle‬يمثل الـ ‪ ActionBarDrawerToggle‬عرفناه في الخطوة السادسة‪ ،‬إذا اعاد‬


‫هذا السطر )‪ true drawerToggle.onOptionsItemSelected(item‬فهذا يعني انه تم الضغط على زر‬
‫الرجوع لذا ننفذ ما بداخل ‪ if‬اما إذا اعاد ‪ false‬فهذا يعني انه تم الضغط على عنصر اخر في الـ ‪action‬‬
‫‪.bar‬‬

‫االن أصبح عندنا زر رجوع عند الضغط عليه تظهر القائمة الجانبية وعند الضغط علية مرة اخرى تغلق‬
‫القائمة الجانبية‪ ،‬ما نريده االن هو ان نغير شكل ايقونة زر الرجوع لتتناسب مع حالة القائمة الجانبية إذا كانت‬
‫مغلقة تظهر ايقونة بشكل ثالثة خطوط افقية اما إذا كانت مفتوحة تكون االيقونة بشكل سهم‪ ،‬لعمل ذلك نحتاج‬
‫ان نستخدم الدالة ‪ onPostCreate‬ومن داخل هذه الدالة نستدعي الدالة ‪ ،syncState‬نكتب الكود بهذا‬
‫الشكل‪( :‬يكتب هذا الكود خارج الدالة ‪)onCreate‬‬

‫‪@Override‬‬
‫{)‪protected void onPostCreate(Bundle savedInstanceState‬‬
‫;)‪super.onPostCreate(savedInstanceState‬‬
‫;)(‪drawerToggle.syncState‬‬
‫}‬

‫حيث ان ‪ drawerToggle‬يمثل الـ ‪ ActionBarDrawerToggle‬عرفناه في الخطوة السادسة‪ ،‬واخيرا‬


‫نحتاج الى ان نمرر كل التغييرات التي تحدث الى الـ ‪ ActionBarDrawerToggle‬من خالل استخدام‬
‫دالته ‪ ،onConfigurationChanged‬بهذا الشكل‪( :‬يكتب هذا الكود خارج الدالة ‪)onCreate‬‬

‫‪@Override‬‬
‫{)‪public void onConfigurationChanged(Configuration newConfig‬‬
‫;)‪super.onConfigurationChanged(newConfig‬‬
‫;)‪drawerToggle.onConfigurationChanged(newConfig‬‬

‫‪74‬‬
}

‫الخطوة الثامنة‬
‫ عند ما ندير‬action bar ‫ اوال مشكلة تغير العنوان في الـ‬،‫في هذه الخطوة سنحل ثالث مشاكل اخيرة‬
‫ ثالثا‬،‫ السابقة‬Fragment ‫ ثابت عند الرجوع بزر الهاتف للـ‬action bar ‫ ثانيا مشكلة بقاء عنوان الـ‬،‫الهاتف‬
.‫ السابقة‬Fragment ‫مشكلة بقاء التاشير على العنصر الحالي في القائمة الجانبية عند الرجوع بزر الهاتف للـ‬

‫ فكما نعلم فان تدوير الهاتف‬،‫ عند تدوير الهاتف‬action bar ‫اوال نحل المشكلة االولى وهي تغير عنوان الـ‬
‫ سنعمل ذلك من خالل الدالة‬،‫ لها من جديد‬create ‫ ستتوقف ويتم عمل‬activity ‫يتسبب في ان الـ‬
‫ المعروضة ليتم‬Fragment ‫ (شرحنا عنها في فصول سابقة) لذا سنحفظ الـ‬onSaveInstanceState
:‫ نعمل ذلك بهذا الشكل‬،‫عرضها من جديد بعد تدوير الهاتف‬

public class MainActivity extends Activity {


private int currentPosition = 0;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
if (savedInstanceState != null){
currentPosition = savedInstanceState.getInt("position");
setActionBarTitle(currentPosition);
}else {
selectItem(0);
}
...
}
private void selectItem(int position){
currentPosition = position;
...
}
@Override
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
outState.putInt("position", currentPosition);

75
}

}

.‫ تم كتابتها في الخطوة الخامسة‬setActionBarTitle ‫الدالة‬

‫ السابقة وذلك عن‬Fragment ‫ ثابت عند الرجوع بزر الهاتف للـ‬action bar ‫ثانيا نحل مشكلة بقاء عنوان الـ‬
‫ في‬Fragment ‫ المعروضة حاليا واختبار ما معروض وما عندنا من‬Fragment ‫طريق اضافة تاك للـ‬
‫ (شرحنا عن هذه االشياء‬FragmentManager.OnBackStackChangedListener ‫داخل االنترفيس‬
‫ (هذه الدالة قد كتبناها في الخطوة الثالثة‬replace ‫ في البداية نضيف التاك داخل الدالة‬،)‫في الفصل الخامس‬
:‫وهنا فقط نضيف لها اسم التاك كباراميتر ثالث) بهذا الشكل‬

ft.replace(R.id.content_frame, fragment, "visible_fragment");


:onCreate ‫االن بعد ان اضفنا التاك سنكتب الكود التالي في داخل الدالة‬
getFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener(){
public void onBackStackChanged(){
FragmentManager fragMan = getFragmentManager();
Fragment fragment = fragMan.findFragmentByTag("visible_fragment");
if (fragment instanceof TopFragment){
currentPosition = 0;
}
if (fragment instanceof PizzaFragment){
currentPosition = 1;
}
if (fragment instanceof PastaFragment){
currentPosition = 2;
}
if (fragment instanceof StoresFragment){
currentPosition = 3;
}
setActionBarTitle(currentPosition);
}
});

.‫ تم كتابتها في الخطوة الخامسة‬setActionBarTitle ‫الدالة‬

76
‫ثالثا حل مشكلة بقاء التاشير على العنصر الحالي في القائمة الجانبية عند الرجوع بزر الهاتف للـ ‪Fragment‬‬
‫السابقة‪ ،‬حل هذه المشكلة بسيط وذلك من خالل الدالة ‪ ، setItemChecked‬هذه الدالة تستخدم لجعل عنصر‬
‫في القائمة ‪ ListView‬يكون مفعل ( كانه تم الضغط عليه ) او الغاء التفعيل من العنصر‪ ،‬وهي تاخذ‬
‫بارامترين االول هو رقم موقع العنصر ( الذي نريد ان نجعله مفعل ) في القائمة ‪ ListView‬والثاني عبارة‬
‫عن قيمة ‪ boolean‬اذا كان ‪ true‬فانه سيتم تفعيله اما اذا كانت ‪ false‬فسيتم الغاء التفعيل‪ ،‬بهذا الشكل ‪( :‬‬
‫نكتب هذا الكود في داخل الدالة ‪ onBackStackChanged‬التي عملناها قبل قليل لنضمن انه يتم تفعيل‬
‫العنصر المطلوب في كل مرة يحدث فيها تغيير في زر رجوع الـ ‪) Fragment‬‬

‫;)‪drawerList.setItemChecked(currentPosition, true‬‬

‫حيث ان ‪ drawerList‬هو اوبجكت القائمة ‪ ListView‬عملناه في الخطوة الثانية‪.‬‬

‫الفصل الثامن (قواعد البيانات ‪)SQLite‬‬

‫ن ظام االندرويد يخزن قواعد البيانات على شكل ملفات في مجلد داخل النظام‪ ،‬كل قاعدة بيانات تتكون من‬
‫ملفين االول هو ‪ database file‬وله نفس اسم البرنامج وهو الملف الرئيسي لقاعدة البيانات حيث ان كل‬
‫البيانات يتم تخزينها هنا‪ ،‬الثاني هو ‪ journal file‬يحمل نفس اسم البرنامج ويضاف له الالحقة ‪-journal‬‬
‫هذا الملف يحمل كل التغييرات التي تحدث في قاعدة البيانات على سبيل المثال إذا حدثت مشكلة فان نظام‬
‫االندرويد سيستخدم هذا الملف ليرجع الى اخر تغيير‪.‬‬

‫االندرويد ياتي معد عدة كالسات لتسمح لنا بالتعامل مع قواعد البيانات ‪ ،SQLite‬هناك ثالثة انواع من‬
‫الكالسات الريسية تقوم بكل العمل هي‪:‬‬

‫‪The SQLite Helper‬‬


‫نعمل اوبجكت من خالل الكالس ‪ ،SQLiteOpenHelper‬فهذا الكالس يمكننا من انشاء وادارة قواعد‬
‫البيانات‪.‬‬

‫‪The SQLite Database‬‬

‫‪77‬‬
‫الكالس ‪ SQLiteDatabase‬يعطينا صالحية النفوذ (االتصال) بقواعد البيانات‪.‬‬

‫‪Cursors‬‬
‫الكالس ‪ Cursor‬يسمح لنا بالقراءة والكتابة على قواعد البيانات‪.‬‬

‫انشاء كالس قاعدة البيانات‬


‫ننشأ قاعدة البيانات من خالل عمل كالس اعتيادي يرث (‪ )extends‬الكالس ‪SQLiteOpenHelper‬‬
‫ويجب ان نكتب داخله الدالة ‪ onCreate‬والدالة ‪ onUpgrade‬ونعمل لهم ‪ @override‬وكتابة هذه الدوال‬
‫امر الزامي (حتى لو بقيت الدالة ‪ onUpgrade‬فارغة)‪ ،‬وكل واحدة من هذه الدوال تأخذ باراميتر واحد من‬
‫النوع ‪ ،SQLiteDatabase‬اضافة الى ذلك فان هذا الكالس يجب ان يحتوي على ‪ constructor‬والتي‬
‫تأخذ باراميتر واحد من النوع ‪.Context‬‬

‫الدالة ‪ onCreate‬يتم استدعائها عندما يتم انشاء قواعد البيانات الول مرة على جهاز االندرويد‪ ،‬لذا فهذه‬
‫الدالة يجب ان تحتوي على كل البيانات الالزمة النشاء قواعد البيانات لبرنامجنا‪.‬‬

‫الدالة ‪ onUpgrade‬يتم استدعائها عندما تحتاج قواعد البيانات الى تحديث‪ ،‬مثال إذا اردنا تغيير جدول في‬
‫قاعدة البيانات بعد ان نشرنا التطبيق فهذه الدالة هي التي تعمل ذلك‪.‬‬

‫الـ ‪ constructor‬مسؤولة عن وضع اسم لقاعدة البيانات ورقم يمثل اصدار قاعدة البيانات‪ ،‬اذا لم تكن تحتوي‬
‫قاعدة البيانات على اسم فانه لن يتم حفظها في النظام اي ستنشأ في الذاكرة وبمجرد ان يخرج المستخدم من‬
‫البرنامج ستدمر قاعدة البيانات‪( ،‬هذا االمر قد يكون مفيد عندما يريد المبرمج ان يختبر البرنامج)‪ ،‬اما بالنسبة‬
‫لرقم االصدار فمن خالله يستطيع النظام ان يحدد فيما اذا كانت قاعدة البيانات تحتاج الى تغيير ام ال (بالنسبة‬
‫لالجهزة المثبت عليها البرنامج وقاعدة البيانات مسبقا) فاذا كان الرقم في هذا االصدار اكبر من الرقم المثبت‬
‫مسبقا في الهاتف سيستدعي الدالة ‪ onUpgrade‬اما اذا كان الرقم اقل فانها ستستدعي الدالة‬
‫‪ ، onDowngrade‬بالنسبة لالجهزة الغير مثبت عليها التطبيق فانه غير مهم رقم االصدار الن قاعدة‬
‫البيانات سيتم انشائها من جديد من خالل الدالة ‪.onCreate‬‬

‫* يمكن ان يكون الكود بهذا الشكل‪:‬‬

‫;‪package com.bignerdranch.android.chapter6‬‬
‫;‪import android.content.Context‬‬
‫;‪import android.database.sqlite.SQLiteDatabase‬‬
‫;‪import android.database.sqlite.SQLiteOpenHelper‬‬
‫{ ‪public class MyDb extends SQLiteOpenHelper‬‬

‫‪78‬‬
‫;"‪private static final String DB_NAME = "starbuzz‬‬
‫;‪private static final int DB_VERSION = 1‬‬
‫{)‪MyDb(Context context‬‬
‫;)‪super(context, DB_NAME, null, DB_VERSION‬‬
‫}‬
‫‪@Override‬‬
‫{)‪public void onCreate(SQLiteDatabase db‬‬
‫‪// do something‬‬
‫}‬
‫‪@Override‬‬
‫{)‪public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion‬‬
‫‪// do something‬‬
‫}‬
‫‪@Override‬‬
‫{)‪public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion‬‬
‫‪// do something‬‬
‫}‬
‫}‬

‫هذا الكالس يسمى كالس قاعدة البيانات‪ ،‬حيث ان ‪ MyDb‬يمثل اسم الكالس الذي عملناه‪ ،‬والـ‬
‫‪ constructor‬تستدعى من خالل الكالس االب لها (‪ )super‬وهو يمرر له اربع بارامترات‪ ،‬االول يمثل‬
‫المحتوى ‪ context‬اما الثاني فهو يمثل اسم قاعدة البيانات‪ ،‬اما الثالث فهو مرتبط باالكالس ‪Cursors‬‬
‫(سنشرح عنه في الجزء الثاني) اما الرابع فهو يمثل رقم اصدار قاعدة البيانات (كتبناه ‪ 1‬النه سيكون اول‬
‫نسخة لنا) ‪ ،‬بالنسبة للدالة ‪ onUpgrade‬فهي تاخذ ثالث بارامترات االول يمثل قاعدة البيانات اما الثاني يمثل‬
‫رقم نسخة االصدار االحدث اما الثالث يمثل رقم نسخة االصدار االقدم ‪ ،‬بالنسبة للدالة ‪onDowngrade‬‬
‫فهي اختيارية نستخدمها اذا نشرنا تطبيق وحدثناه ثم اردنا ان نتراجع عن التحديث الى اصدار سابق‪.‬‬

‫* سنقسم هذا الفصل الى جزئين‪ ،‬الجزء االول سنتعلم فيه كيف ننشأ قاعدة البيانات وكيف نعدل عليها ونحدثها‬
‫وكل ذلك نكتبه في داخل كالس قاعدة البيانات (الكالس الذي انشاناه قبل قليل‪ ،‬الذي يرث الكالس‬
‫‪ ،)SQLiteOpenHelper‬اما اجزء الثاني سنتعلم فيه كيف سنربط برنامجنا مع قاعدة البيانات‪.‬‬

‫‪79‬‬
‫الجزء االول‬
‫كل الكود الذي سنتعلمه في هذا الجزء يكتب داخل كالس قاعدة البيانات (الكالس الذي انشاناه قبل قليل‪ ،‬الذي‬
‫يرث الكالس ‪.)SQLiteOpenHelper‬‬

‫داخل قاعدة البيانات ‪SQLite‬‬


‫البيانات في داخل قاعدة البيانات ‪ SQLite‬تخزن على شكل جداول‪ ،‬كل جدول يحتوي على صفوف واعمدة‪،‬‬
‫نحن نحتاج ان ننشأ جدول لكل جزء مختلف من البيانات التي نريد ان نسجلها‪ ،‬يمكن ان يكون شكل قاعدة‬
‫بيانات لحفظ اسماء الطالب ودرجاتهم واسم مدرستهم بهذا الشكل‪:‬‬

‫‪_id NAME SCHOOL_NAME SCORE‬‬


‫‪1 Ahmed‬‬ ‫‪AL Edrise‬‬ ‫‪80‬‬
‫‪2‬‬ ‫‪Ali‬‬ ‫‪Motfoqin‬‬ ‫‪95‬‬
‫‪3 Qusai‬‬ ‫‪Motafoqin‬‬ ‫‪77‬‬

‫بعض االعمدة يمكن ان تعين على انها ‪ ،primary key‬وهي ال يمكن ان تأخذ قيم متكررة‪ ،‬نظام االندرويد‬
‫يتوقع ان يكون هناك عمود ‪ primary key‬اسمه ‪ _id‬فيه ارقام متسلسلة‪ ،‬لذا من االفضل عمل هذا العمود‬
‫لتجنب بعض االخطاء (هذا العمود نعمله لكن ال نضيف له القيم وانما هي تضاف اوتوماتيكيا)‪.‬‬

‫كل عمود في الجدول مصمم ليحمل نوع معين من البيانات‪ ،‬مثال ارقام او نصوص‪ ،‬انواع البيانات هي‪:‬‬

‫‪INTEGER‬‬ ‫اي قمية من النوع ‪integer‬‬


‫‪TEXT‬‬ ‫اي نوع من انواع الحروف‬
‫‪REAL‬‬ ‫اي ارقام كسرية‬
‫قيم من نوع ‪ ،booleen‬تواريخ‪ ،‬اوقات ‪NUMERIC‬‬
‫‪BLOB‬‬ ‫اوبجكت ثنائي كبير‬

‫* نحن نعمل قواعد البيانات باستخدام لغة )‪ ،Structured Query Language (SQL‬نحن نستخدم اوامر‬
‫هذه اللغة لعمل الجداول‪ ،‬مثال لعمل الجدول السابق (جدول طالب المدرسة) نكتب‪:‬‬

‫‪CREATE TABLE DRINK (_id INTEGER PRIMARY KEY AUTOINCREMENT,‬‬


‫‪NAME TEXT,‬‬
‫‪SCHOOL_NAME TEXT,‬‬

‫‪80‬‬
‫;)‪SCORE INTEGER‬‬

‫من خالل االمر ‪ CREATE TABLE‬نقول ما هي االعمدة التي نريد انشائها في الجدول وما هي نوع‬
‫البيانات لكل عمود‪ ،‬الـ ‪ _id‬هو العمود ‪ primary key‬للجدول و‪ AUTOINCREMENT‬تعني انه عندما‬
‫نخزن صف جديد في الجدول ‪ SQLite‬سوف تضع رقم فريد لكل صف (ارقام متسلسلة تبدأ من ‪ 1‬فصاعدا)‪.‬‬

‫الكالس ‪SQLiteDatabase‬‬
‫هو كالس مشتق من الكالس ‪ ،Object‬وكما ذكرنا سابقا (في بداية هذا الفصل) فان هذا الكالس يمرر‬
‫كباراميتر للدالة ‪ onCreate‬والدالة ‪( onUpgrade‬ولدوال اخرى)‪ ،‬ويمكن ان نستفاد منه من خالل استخدام‬
‫دواله التالية‪:‬‬

‫الدالة ‪execSQL‬‬
‫هي احدى دوال الكالس ‪ ،SQLiteDatabase‬هذه الدالة تاخذ بين قوسيها باراميتر واحد هو عبارة عن‬
‫اوامر ‪ ،SQL‬يمكن ان نكتب الكود التالي النشاء قاعدة بيانات تحمل الجدول السابق (اسماء طالب المدرسة)‪:‬‬

‫‪@Override‬‬
‫{)‪public void onCreate(SQLiteDatabase db‬‬
‫"(‪db.execSQL("CREATE TABLE STUDENT‬‬
‫" ‪+ "_id INTEGER PRIMARY KEY AUTOINCREMENT,‬‬
‫" ‪+ "NAME TEXT,‬‬
‫" ‪+ " SCHOOL_NAME TEXT,‬‬
‫;)";)‪+ " SCORE INTEGER‬‬
‫}‬

‫االن تم انشاء جدول فارغ من البيانات‪ ،‬والدخال البيانات فيه نستخدم الدالة ‪.insert‬‬

‫الدالة ‪insert‬‬
‫هي احدى دوال الكالس ‪ ،SQLiteDatabase‬وتستخدم لملئ جدول قواعد البيانات وتعيد قيمة ‪ id‬الخاص‬
‫بالصف المضاف وإذا لم تتمكن من اضافة الصف تعيد القيمة ‪ ،-1‬والستخدام هذه الدالة نحتاج ان نحدد‬
‫الجدول الذي نريد ان نضيف له والقيمة التي نريد اضافتها‪.‬‬

‫يتم اضافة القيمة من خالل عمل االوبجكت ‪ ContentValues‬وهذا االوبجكت يحمل زوج من البيانات‬
‫اسم‪/‬قيمة‪ ،‬حيث ان االسم يمثل اسم العمود‪ ،‬وباستخدام دالته ‪ put‬تضاف القيم بهذا الشكل‪:‬‬

‫‪81‬‬
‫;)(‪ContentValues StudentValues = new ContentValues‬‬
‫;)"‪StudentValues.put("NAME", "Ahmed‬‬
‫;)" ‪StudentValues.put("SCHOOL_NAME", " AL Edrise‬‬
‫;)"‪StudentValues.put("SCORE", "80‬‬

‫االن سنستخدم الدالة ‪ insert‬الضافة صف البيانات هذا الى الجدول المسمى ‪ STUDENT‬بهذا الشكل‪:‬‬

‫;)‪db.insert("STUDENT", null, StudentValues‬‬

‫حيث ان ‪ db‬يمثل االوبجكت ‪ ،SQLiteDatabase‬الباراميتر الثاني في هذه الدالة في اغلب االحيان يكون‬
‫‪ ،null‬بما اننا نحتاج ان نظيف العديد من الصفوف وكل صف يحتاج ان نعمل ‪ insert‬لذا من االفضل ان‬
‫نكتب الكود في داخل دالة ونستدعي الدالة وقت ما نشاء‪ ،‬بهذا الشكل‪:‬‬

‫‪private static void insertV(SQLiteDatabase db, String name, String SCHOOL, int‬‬
‫{ )‪score‬‬
‫;)(‪ContentValues StudentValues = new ContentValues‬‬
‫;)‪StudentValues.put("NAME", name‬‬
‫;)‪StudentValues.put("SCHOOL_NAME", SCHOOL‬‬
‫;)‪StudentValues.put("SCORE", score‬‬
‫;)‪db.insert("STUDENT", null, StudentValues‬‬
‫}‬

‫االن اصبحت عندنا دالة جاهزة يمكن ان نضيف صف للجدول بمجرد ان نستدعيها‪.‬‬

‫الدالة ‪update‬‬
‫هي احدى دوال الكالس ‪ ،SQLiteDatabase‬وهي تستخدم لتغيير قيمة بيانات موجودة في جدول قاعدة‬
‫البيانات وهي تعيد رقم يمثل رقم تسجيل تحديث‪ ،‬صيغتها العامة بهذا الشكل‪:‬‬

‫‪public int update (String table,‬‬


‫‪ContentValues values,‬‬
‫‪String whereClause,‬‬
‫)‪String[ ] whereArgs‬‬

‫حيث ان الباراميتر االول يمثل اسم الجدول المراد تحيدث بياناته‪ ،‬الباراميتر الثاني يمثل االوبجكت‬
‫‪( ContentValues‬شرحنا عنه قبل قليل في الدالة ‪ )insert‬وهو سيحمل القيمة الجديدة مع اسم العمود‪،‬‬

‫‪82‬‬
‫الباراميتر الثالث يمثل الشرط في التحديث‪ ،‬الباراميتر الرابع يمثل قيمة الشرط‪ ،‬لتوضيح الفكرة لنأخذ مثال‬
‫لتغيير قيمة الجدول الذي عملناه قبل قليل (في الدالة ‪:)insert‬‬

‫;)(‪ContentValues StudentValues = new ContentValues‬‬


‫;)"‪StudentValues.put("SCHOOL_NAME", " Motfoqin‬‬
‫‪db.update (“STUDENT”,‬‬
‫‪StudentValues,‬‬
‫‪“NAME = ?”,‬‬
‫;)}”‪new String[ ] {“Ahmed‬‬

‫حيث ان ‪ db‬يمثل االوبجكت ‪ ،SQLiteDatabase‬هنا الشرط هو ان تكون قيمة العمود ‪ NAME‬هي‬


‫‪ Ahmed‬عندها ستتغير قمية العمود ‪ SCHOOL_NAME‬الى ‪ ،Motfoqin‬العالمة ? تعني (قيمة ما)‬
‫وهذه القيمة نحددها بالباراميتر الرابع‪.‬‬

‫* إذا وضعنا قيمة البارامترين الثالث والرابع ‪ null‬فهذا يعني اننا سنغير قيمة الجدول بالكامل‪.‬‬

‫* يمكن ان نضع في الدالة أكثر من شرط لتحديد التحديث‪ ،‬مثال إذا أردنا ان نضع الشرط هو ان تكون قيمة‬
‫العمود ‪ NAME‬هي ‪ Ahmed‬او ان تكون قيمة العمود ‪ SCHOOL_NAME‬هي ‪ ،AL Edrise‬عندها‬
‫سيكون شكل الدالة ‪ update‬هكذا‪:‬‬

‫‪db.update (“STUDENT”,‬‬
‫‪StudentValues,‬‬
‫‪“NAME = ? OR SCHOOL_NAME = ?”,‬‬
‫;)}”‪new String[ ] {“Ahmed”, “AL Edrise‬‬

‫* الحظ ان قيمة الشرط (الباراميتر الرابع) يجب ان تكون قيمة نصية فاذا أردنا ان نتعامل مع عمود قيمته‬
‫عددية نضعه في داخل هذا الباراميتر ونحوله الى قيمة نصية‪ ،‬بهذا الشكل‪:‬‬

‫‪db.update (“STUDENT”,‬‬
‫‪StudentValues,‬‬
‫‪“_id = ?”,‬‬
‫;)} )‪new String[ ] { Integer.toString(1‬‬

‫الدالة ‪delete‬‬
‫هي احدى دوال الكالس ‪ ،SQLiteDatabase‬وهي تستخدم لحذف قيمة صف معين من الجدول وهي تعمل‬
‫بطريقة مشابهه لطريقة عمل الدالة ‪ update‬من حيث الشروط‪ ،‬صيغتها العامة بهذا الشكل‪:‬‬

‫‪83‬‬
‫‪public int delete (String table,‬‬
‫‪String whereClause,‬‬
‫)‪String[ ] whereArgs‬‬

‫الباراميتر االول هو اسم الجدول‪ ،‬الباراميتر الثاني هو الشرط في الحذف‪ ،‬الباراميتر الثالث هو قيمة الشرط)‬
‫شرحنا عن الشروط قبل قليل في الدالة ‪ ،)upgrade‬مثال إذا أردنا ان نحذف الصف إذا كانت قيمة العمود‬
‫‪ NAME‬هي ‪ Ahmed‬سنكتب التالي‪:‬‬

‫‪db.delete (“STUDENT”,‬‬
‫‪“NAME = ?”,‬‬
‫;)}”‪new String[ ] {“Ahmed‬‬

‫حيث ان ‪ db‬يمثل االوبجكت ‪.SQLiteDatabase‬‬

‫* إذا وضعنا قيمة البارامترين الثاني والثالث ‪ null‬فهذا يعني اننا سنحذف كل البيانات في الجدول‪.‬‬

‫التعديل على جدول ‪SQLite‬‬


‫شرحنا قبل قليل كيف نجري التعديالت على بيانات الجدول من حذف واضافة قيم من خالل الدوال ‪delete‬‬
‫و‪ ،update‬لكن االن سنتعلم كيف نعدل على الجدول نفسه من اضافة عمود او تغيير اسم الجدول‪ ،‬لعمل مثل‬
‫هكذا تعديالت نحتاج ان نستخدم لغة ‪ ،SQL‬وكما ذكرنا سابقا نكتب لغة ‪ SQL‬بين قوسي الدالة ‪.execSQL‬‬

‫اضافة عمود الى الجدول‬


‫لكي نضيف عمود الى جدول قاعدة بيانات تم انشاءه مسبقا نستخدم امر لغة ‪ ،SQL‬بهذا الشكل‪:‬‬

‫‪ALTER TABLE STUDENT‬‬


‫‪ADD COLUMN CLASS NUMERIC‬‬

‫في هذا الكود اضفنا الى الجدول الذي اسمه ‪ STUDENT‬عمود اسمه ‪ CLASS‬ويحمل قيم رقمية‪ ،‬ولكي‬
‫نضيف هذا الكود نحتاج الى استخدام الدالة ‪( execSQL‬شرحنا عنها مسبقا في هذا الفصل) بهذا الشكل‪:‬‬

‫‪db.execSQL("ALTER‬‬ ‫‪TABLE‬‬ ‫‪STUDENT‬‬ ‫‪ADD‬‬ ‫‪COLUMN‬‬ ‫‪CLASS‬‬


‫;)";‪NUMERIC‬‬

‫حيث ان ‪ db‬يمثل االوبجكت ‪.SQLiteDatabase‬‬

‫‪84‬‬
‫اعادة تسمية الجدول‬
‫لكي نعيد تسمية جدول قاعدة بيانات موجود مسبقا نستخدم امر لغة ‪ ،SQL‬بهذا الشكل‪:‬‬

‫‪ALTER TABLE STUDENT‬‬


‫‪RENAME TO SCHOOL‬‬

‫في هذا الكود غيرنا اسم الجدول ‪ STUDENT‬الى االسم ‪ ،SCHOOL‬ولكي نضيف هذا الكود نحتاج الى‬
‫استخدام الدالة ‪( execSQL‬شرحنا عنها مسبقا في هذا الفصل) بهذا الشكل‪:‬‬

‫;)";‪db.execSQL("ALTER TABLE STUDENT RENAME TO SCHOOL‬‬

‫حيث ان ‪ db‬يمثل االوبجكت ‪.SQLiteDatabase‬‬

‫حذف الجدول‬
‫لكي نحذف جدول قاعدة بيانات بالكامل نحتاج الى استخدام امر لغة ‪ ،SQL‬بهذا الشكل‪:‬‬

‫‪DROP TABLE STUDENT‬‬

‫هذا االمر سيحذف الجدول المسمى ‪ STUDENT‬وهذا االمر يمكن ان يكون مفيد اذا لم نعد بحاجة الى‬
‫الجدول واردنا تقليل مساحة التخزين‪ ،‬ولكي نضيف هذا الكود نحتاج الى استخدام الدالة ‪( execSQL‬شرحنا‬
‫عنها مسبقا في هذا الفصل) بهذا الشكل‪:‬‬

‫;)";‪db.execSQL("DROP TABLE STUDENT‬‬

‫حيث ان ‪ db‬يمثل االوبجكت ‪.SQLiteDatabase‬‬

‫عمل تحديث للبيانات‬


‫كما ذكرنا في بداية هذا الفصل فان الدلة ‪ onUpgrade‬لن يتم استدعائها وتنفيذها اال اذا كان رقم اصدار‬
‫قاعدة البيانات (الباراميتر الثالث الممرر للـ ‪ )constructor‬اعلى من النسخة المخزنة في الهاتف‪ ،‬فمثال اذا‬
‫نشرنا تطبيقنا ذو رقم االصدار ‪ 1‬وبعد مدة اردنا عمل تحديث لقاعدة البيانات‪ ،‬فلكي نسمح لجميع المستخدمين‬
‫(الذين يملكون البرنامج القديم مسبقا او المستخدمين الجدد) ان يحصلو على قاعدة البيانات الجديدة سنحتاج ان‬
‫نكتب التحديث في الدالة ‪ onCreate‬والدالة ‪ onUpgrade‬وهنا سنكرر الكود اضافة الى انه اذا اردنا ان‬
‫ننزل اصدار ثالث ستكون فوضى‪ ،‬لذا يفضل ان نكتب الكود بهذه الطريقة للسهولة‪:‬‬

‫;‪private static final int DB_VERSION = 2‬‬

‫‪85‬‬
‫‪@Override‬‬
‫{)‪public void onCreate(SQLiteDatabase db‬‬
‫;)‪updateMyDatabase(db, 0, DB_VERSION‬‬
‫}‬
‫‪@Override‬‬
‫{)‪public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion‬‬
‫;)‪updateMyDatabase(db, oldVersion, newVersion‬‬
‫}‬
‫{)‪private void updateMyDatabase(SQLiteDatabase db, int oldV, int newV‬‬
‫{)‪if (oldV < 1‬‬
‫هذا الكود يتم تنفيذه فقط للمستخدمين الجدد الذي ال يملكون نسخة سابقة من البرنامج ( الكود القديم لقاعدة ‪//‬‬
‫البيانات )‬
‫}‬
‫{)‪if (oldV < 2‬‬
‫هذا الكود ينفذ للمستخدمين الجدد والمستخدمين القدماء الذين لديهم نسخة سابقة من البرنامج ( التحديث ‪//‬‬
‫يضاف هنا )‬
‫}‬
‫}‬

‫المتغير ‪ DB_VERSION‬يمثل رقم االصدار فهو يمرر كباراميتر ثالث للـ ‪ ،constructor‬في الكود‬
‫السابق إذا أردنا ان نحدث الى اصدار ثالث كل ما نحتاجه هو ان نضيف عبارة ‪ if‬اخرى بداخل الدالة‬
‫‪ ،updateMyDatabase‬بهذا الشكل‪:‬‬

‫{)‪if (oldV < 3‬‬


‫هذا الكود ينفذ لجميع المستخدمين (التحديث الثالث يضاف هنا) ‪//‬‬
‫}‬

‫الجزء الثاني‬
‫في هذا الجزء سنتعلم كيف نربط قاعدة البيانات مع البرنامج الخاص بنا الستخراج البيانات وعرضها وكذلك‬
‫السماح للمستخدم باضافة بيانات لقاعدة البيانات‪ ،‬نعمل كل ذلك من خالل الكالس ‪.Cursors‬‬

‫‪86‬‬
‫الدالة ‪query‬‬
‫هذه الدالة موجودة في الكالس ‪ ،SQLiteDatabase‬من خاللها نحدد بالضبط البيانات التي نريد ان نجلبها‬
‫من قاعدة البيانات‪ ،‬كان تكون جدول بالكامل او عمود في هذا الجدول او االسماء التي تبدأ بالحرف ‪ B‬مثال او‬
‫اي شرط نضعه لناخذ البيانات المطلوبة بالضبط‪ ،‬وهي تعيد اوبجكت من النوع ‪ Cursor‬والـ ‪ activities‬في‬
‫برنامجنا يمكنها ان تستخدمه للوصول للبيانات‪ ،‬الصيغة العامة لهذه الدالة هي‪:‬‬

‫‪public Cursor query(String table,‬‬


‫‪String[ ] columns,‬‬
‫‪String selection,‬‬
‫‪String[ ] selectionArgs,‬‬
‫‪String groupBy,‬‬
‫‪String having,‬‬
‫;)‪String orderBy‬‬

‫الباراميتر االول للدالة من خالله نحدد اسم الجدول‪ ،‬الباراميتر الثاني يمثل اسم العمود (او االعمدة)‪،‬‬
‫الباراميتر الثالث نضع فيه الشرط‪ ،‬الباراميتر الرابع نضع فيه قيمة الشرط‪ ،‬البارامترين الخامس والسادس‬
‫نستخدمهم إذا أردنا ان نجمع البيانات‪ ،‬الباراميتر السابع نستخدمه إذا أردنا البيانات المعادة ان تترتب بشكل‬
‫معين (مثال بشكل ابجدي)‪ ،‬هناك امكانيات اخرى لهذه الدالة تمكننا من اضافة شروط اخرى لتحديد البيانات‬
‫العائدة‪ ،‬لمزيد من المعلومات يمكنك زيارة الموقع التالي‪:‬‬

‫‪https://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.ht‬‬
‫‪ml‬‬

‫* لتوضيح الية عمل هذه الدالة سنأخذ بعض االمثلة بشروط مختلفة العادة بيانات من الجدول ‪STUDENT‬‬
‫التالي‪:‬‬

‫‪_id NAME SCHOOL_NAME SCORE‬‬


‫‪1 Ahmed‬‬ ‫‪AL Edrise‬‬ ‫‪80‬‬
‫‪2‬‬ ‫‪Ali‬‬ ‫‪Motfoqin‬‬ ‫‪95‬‬
‫‪3 Qusai‬‬ ‫‪Motafoqin‬‬ ‫‪77‬‬

‫مثال العادة العمود ‪ NAME‬والعمود ‪ SCHOOL_NAME‬من الجدول ‪:STUDENT‬‬

‫‪Cursor cursor = db.query(“STUDENT”,‬‬


‫‪new String[ ] {“NAME”, “SCHOOL_NAME”},‬‬
‫;)‪null, null, null, null, null‬‬

‫‪87‬‬
‫حيث ان ‪ db‬يمثل االوبجكت ‪.SQLiteDatabase‬‬

‫مثال العادة قيمة من عمود عندما تكون قيمة عمود اخر تساوي قيمة نحن نحددها‪ ،‬في هذا الكود سنعيد اسم‬
‫مدرسة الطالب ‪:Ali‬‬

‫‪Cursor cursor = db.query(“STUDENT”,‬‬


‫‪new String[ ] {“NAME”, “SCHOOL_NAME”},‬‬
‫‪“NAME = ?”,‬‬
‫‪new String[ ] {“Ali”},‬‬
‫;)‪null, null, null‬‬

‫هذا يعني ان يعيد قيمة العمود ‪ NAME‬والعمود ‪ SCHOOL_NAME‬عندما تكون قيمة العمود ‪NAME‬‬
‫هي ‪ ،Ali‬اي انه سيعيد القيمة ‪ Ali‬والقيمة ‪ ،Motfoqin‬العالمة ? تعني (قيمة ما) وهذه القيمة نحددها في‬
‫الباراميتر الرابع‪ ،‬والتي حددناها هنا ‪.Ali‬‬

‫مثال يمكن وضع القيمة داخل الشرط‪:‬‬

‫‪Cursor cursor = db.query(“STUDENT”,‬‬


‫‪new String[ ] {“SCHOOL_NAME”, “SCORE”},‬‬
‫‪“NAME = Ali”,‬‬
‫;)‪null, null, null, null‬‬

‫هنا سيعيد قيمة العمود ‪ SCHOOL_NAME‬وقيمة العمود ‪ SCORE‬عندما تكون قيمة العمود ‪NAME‬‬
‫هي ‪ ،Ali‬اي انه سعيد القيمة ‪ Motfoqin‬والقيمة ‪.95‬‬

‫مثال لعمل شرط متعدد‪:‬‬

‫‪Cursor cursor = db.query(“STUDENT”,‬‬


‫‪new String[ ] {“NAME”, “SCHOOL_NAME”},‬‬
‫‪“NAME = ? OR SCHOOL_NAME = ?”,‬‬
‫‪new String[ ] {“Ali”, “AL Edrise”},‬‬
‫;)‪null, null, null‬‬

‫هنا ستعيد القيمة إذا كانت قيمة العمود ‪ SCHOOL_NAME‬هي ‪ AL Edrise‬اوقيمة العمود ‪NAME‬‬
‫هي ‪ ،Ali‬لذا فانه سيعيد القيمتان ‪ Ahmed‬و‪ AL Edrise‬والقيمتان ‪ Ali‬و‪.Motfoqin‬‬

‫* يجب ان تكون الشروط عبارة عن قيم نصية فاذا كان الشرط الذي نريد ان نضعه قيمة عددية يجب ان‬
‫نحولة الى نص‪ ،‬بهذا الشكل‪:‬‬

‫‪Cursor cursor = db.query(“STUDENT”,‬‬

‫‪88‬‬
‫‪new String[ ] {“NAME”, “SCHOOL_NAME”},‬‬
‫‪“_id = ?”,‬‬
‫‪new String[ ] { Integer.toString(1) },‬‬
‫;)‪null, null, null‬‬

‫هذا المثال سيعيد القيمتان ‪ Ahmed‬و‪.AL Edrise‬‬

‫مثال إذا أردنا ان نرجع البيانات لتترتب ابجديا (بشكل افتراضي البيانات تترتب على حسب قيمة ‪_id‬‬
‫تصاعديا)‪ ،‬يمكن ان نكتب التالي‪:‬‬

‫‪Cursor cursor = db.query(“STUDENT”,‬‬


‫‪new String[ ] {“_id”, “NAME”, “SCORE”},‬‬
‫‪null, null, null, null,‬‬
‫;)”‪“NAME ASC‬‬

‫هذا المثال يعني ان يجلب لنا كل البيانات في العمود ‪ NAME‬والعمود ‪ SCORE‬وترتب البيانات ابجديا‬
‫بشكل تصاعدي اي من ‪ A‬الى ‪( Z‬وإذا كانت ارقام فمن أصغر رقم الى اكبرها) وهذا بحسب قيم العمود‬
‫‪ ،NAME‬وإذا أردنا ان ترتب القيم تنازليا نستبدل الكلمة ‪ ASC‬بالكلمة ‪.DESC‬‬

‫مثال لترتيب البيانات القادمة لكن بحسب قيم أكثر من عمود‪ ،‬اي في االول يرتب حسب قيم العمود االول وإذا‬
‫تكررت القيم سترتب القيم المكررة على حسب العمود الثاني‪ ،‬بهذا الشكل‪:‬‬

‫‪Cursor cursor = db.query(“STUDENT”,‬‬


‫‪new String[ ] {“_id”, “NAME”, “SCORE”},‬‬
‫‪null, null, null, null,‬‬
‫;)”‪“SCORE DESC, NAME‬‬

‫في هذا المثال سترتب البيانات تنازليا على حسب العمود ‪ SCORE‬وإذا كان هناك ارقام متساوية في هذا‬
‫العمود فانها سترتب تصاعديا على حسب العمود ‪.NAME‬‬

‫استخدام دوال ‪ SQL‬مع الدالة ‪query‬‬


‫يمكن استخدام دوال ‪ SQL‬مع الدالة ‪ ،query‬الجدول التالي يوضح بعض هذه الدوال‪:‬‬

‫‪AVG‬‬ ‫متوسط القيمة‬


‫عدد الصفوف في الجدول ‪COUNT‬‬
‫‪SUM‬‬ ‫المجموع‬

‫‪89‬‬
‫‪MAX‬‬ ‫أكبر قيمة‬
‫‪MIN‬‬ ‫أصغر قيمة‬

‫مثال ليعيد عدد الطالب في الجدول (عدد الصفوف في الجدول ‪ )STUDENT‬باالعتماد على العمود ‪_id‬‬
‫من خالل الدالة ‪ ،COUNT‬بهذا الشكل‪:‬‬

‫‪Cursor cursor = db.query(“STUDENT”,‬‬


‫‪new String[ ] {“COUNT(_id) AS my_count”},‬‬
‫;) ‪null, null, null, null, null‬‬

‫هذه الدالة ستعيد الرقم ‪( 3‬حيث انها ستعمل عمود جديد اسمه ‪ my_ count‬وتضع فيه القيمة)‬

‫مثال العادة متوسط القيمة لدرجات الطالب من خالل الدالة ‪ ،AVG‬بهذا الشكل‪:‬‬

‫‪Cursor cursor = db.query(“STUDENT”,‬‬


‫‪new String[ ] {“AVG(SCORE) AS score”},‬‬
‫;) ‪null, null, null, null, null‬‬

‫مثال سنستخدم فيه الباراميتر السادس للدالة (‪ )GROUP BY‬من خالله يمكن ان نقسم النتائج العائدة على‬
‫حسب قيم عمود ما‪ ،‬مثال إذا أردنا ان نعرف كم طالب في كل مدرسة (كل مدرسة كم مرة متكرر اسمها)‪،‬‬
‫يمكن ان نكتب الكود التالي‪:‬‬

‫‪Cursor cursor = db.query(“STUDENT”,‬‬


‫‪ew String[ ] {“SCHOOL_NAME”, “COUNT(_id) AS my_count”},‬‬
‫‪null, null,‬‬
‫‪“SCHOOL_NAME”,‬‬
‫;) ‪null, null‬‬

‫هذا المثال سيعيد لنا البيانات على الشكل التالي‪:‬‬

‫‪SCHOOL_NAME my_count‬‬
‫‪AL Edrise‬‬ ‫‪1‬‬
‫‪Motfoqin‬‬ ‫‪2‬‬

‫‪90‬‬
‫الدالة ‪ getReadableDatabase‬والدالة ‪getWritableDatabase‬‬
‫كما ذكرنا قبل قليل لجلب البيانات نحتاج الى استخدام الدالة ‪ query‬وهذه الدالة موجودة في الكالس‬
‫‪ SQLiteDatabase‬فللوصول لها نحتاج الى مرجع لقاعدة البيانات‪ ،‬الكالس ‪SQLiteOpenHelper‬‬
‫يحتوي على بعض الدوال يمكنها ان تساعدنا وهذه الدوال هي ‪getReadableDatabase‬‬
‫و‪ getWritableDatabase‬والتي تعطينا نفوذ لقاعدة البيانات‪ ،‬صيغتها العامة هي‪:‬‬

‫;)‪SQLiteOpenHelper myDb = new MyDb(this‬‬


‫;)(‪SQLiteDatabase db = myDb.getReadableDatabase‬‬

‫وصيغة الدالة االخرى هي‪:‬‬

‫;)‪SQLiteOpenHelper myDb = new MyDb(this‬‬


‫;)(‪SQLiteDatabase db = myDb.getWritableDatabase‬‬

‫حيث ان ‪ MyDb‬يمثل كالس قاعدة البيانات (الكالس الذي جعلناه يرث ‪ – extends -‬الكالس‬
‫‪ SQLiteOpenHelper‬وكتبينا فيه جداول قاعدة البيانات)‪ ،‬واالن سنتمكن من استخدام االوبجكت ‪ db‬مع‬
‫الدالة ‪.query‬‬

‫كال الدالتين تعيدان لنا اوبجكت يمكن منه ان نقرا ونكتب منه على قاعدة البيانات‪ ،‬لكن الفرق بينهما هو ان‬
‫الدالة ‪ getWritableDatabase‬متخصصة في الكتابة على قاعدة البيانات بمعنى انها اذا لم تتمكن من‬
‫الكتابة على قاعدة البيانات (مثال القرص ممتلئ) فهي سترمي استثناء ‪ SQLiteException‬اما الدالة‬
‫‪ getReadableDatabase‬لن ترمي استثناء اذا لم تتمكن من الكتابة لكنها سترمي استثناء اذا لم تتمكن من‬
‫القراءة‪ ،‬لذا من االفضل استخدام ‪ getReadableDatabase‬للقراءة من قاعدة البيانات والدالة‬
‫‪ getWritableDatabase‬للكتابة على قاعدة البيانات‪.‬‬

‫نكتب هذه الدوال بين قوسي ‪ try‬النها قد ترمي استثناء‪ ،‬بهذا الشكل‪:‬‬

‫{‪try‬‬
‫;)‪SQLiteOpenHelper myDb = new MyDb(this‬‬
‫;)(‪SQLiteDatabase db = myDb.getReadableDatabase‬‬
‫‪Cursor cursor = db.query(“STUDENT”,‬‬
‫‪new String[ ] {“NAME”, “SCHOOL_NAME”},‬‬
‫;) ‪null, null,null, null, null‬‬
‫‪// Code to do something with the cursor‬‬
‫{)‪} catch(SQLiteException e‬‬
‫‪Toast toast =Toast.makeText(this, “Database unavailable”,‬‬
‫;)‪Toast.LENGTH_SHORT‬‬

‫‪91‬‬
‫;)(‪toast.show‬‬
‫}‬

‫دوال التنقل‬
‫الدالة ‪ query‬تعيد كل او جزء من قاعدة البيانات على شكل اوبجكت ‪ ،Cursor‬احيانا قد نحتاج الى التنقل‬
‫بين للبيانات المعادة‪ ،‬يمكننا ذلك من خالل دوال التنقل وهي تابعة للكالس ‪ ،Cursor‬هناك أربع دوال رئيسية‬
‫هي‪:‬‬

‫الدالة ‪moveToFirst‬‬
‫من خاللها يمكن االنتقال الى اول قيمة معادة في االوبجكت ‪ ،Cursor‬نقصد باول قيمة معادة الصف االول‬
‫في الجدول (قد يشمل أكثر من عمود)‪ ،‬وهي ستعيد ‪ true‬إذا وجدت قيمة وتعيد القيمة ‪ false‬إذا لم يكن‬
‫االوبجكت ‪ Cursor‬يعيد اي قيمة‪.‬‬

‫{ )) (‪if (cursor.moveToFirst‬‬
‫‪//Do something‬‬
‫;}‬

‫الدالة ‪moveToLast‬‬
‫من خاللها يمكن االنتقال الى اخر قيمة معادة في االوبجكت ‪ ،Cursor‬نقصد باخر قيمة معادة الصف االخير‬
‫في الجدول (قد يشمل أكثر من عمود)‪ ،‬وهي ستعيد ‪ true‬إذا وجدت قيمة وتعيد القيمة ‪ false‬إذا لم يكن‬
‫االوبجكت ‪ Cursor‬يعيد اي قيمة‪.‬‬

‫{ )) (‪if (cursor. moveToLast‬‬


‫‪//Do something‬‬
‫;}‬

‫الدالة ‪moveToPrevious‬‬
‫هذه الدالة تنقلنا الى الصف السابق من البيانات (الجدول) وهي تعيد ‪ true‬إذا وجدت قيمة وانتقلت بشكل‬
‫صحيح او تعيد ‪ false‬إذا فشلت في االنتقال او إذا لم تكن هناك بيانات‪.‬‬

‫{ )) (‪if (cursor.moveToPrevious‬‬
‫‪//Do something‬‬
‫;}‬

‫‪92‬‬
‫الدالة ‪moveToNext‬‬
‫هذه الدالة تنقلنا الى الصف القادم من البيانات (الجدول) وهي تعيد ‪ true‬إذا وجدت قيمة وانتقلت بشكل‬
‫صحيح او تعيد ‪ false‬إذا فشلت في االنتقال او إذا لم تكن هناك بيانات‪.‬‬

‫{ )) (‪if (cursor. moveToNext‬‬


‫‪//Do something‬‬
‫;}‬

‫* يجب االنتقال الى الصف المناسب من جدول البيانات الذي يعيده االوبجكت ‪ Cursor‬حتى لو كانت القيمة‬
‫المعادة هي عبارة عن صف واحد يجب تحديد هذا الصف بهذه الدوال (دوال التنقل) ثم بداخل ‪ if‬الشرط‬
‫الخاص بالدالة نكتب كود اخذ البيانات من االوبجكت ‪.Cursor‬‬

‫اخذ البيانات من االوبجكت ‪Cursor‬‬


‫ناخذ البيانات من هذا االوبجكت من خالل الدالة *‪ get‬حيث ان العالمة * نضع بدلها نوع البيانات الموجودة‬
‫في جدول قاعدة البيانات كأن تكون ‪ int‬او ‪ String‬او غيرها‪ ،‬هذه الدالة تأخذ باراميتر واحد بين قوسيها هو‬
‫رقم تسلسل العمود المراد اخذ بياناته‪.‬‬

‫مثال إذا كان عندنا في قاعدة البيانات الجدول التالي‪:‬‬

‫‪_id NAME SCHOOL_NAME SCORE‬‬


‫‪1 Ahmed‬‬ ‫‪AL Edrise‬‬ ‫‪80‬‬
‫‪2‬‬ ‫‪Ali‬‬ ‫‪Motfoqin‬‬ ‫‪95‬‬
‫‪3 Qusai‬‬ ‫‪Motafoqin‬‬ ‫‪77‬‬
‫فإذا كتبنا الكود التالي‪:‬‬

‫‪Cursor cursor = db.query(“STUDENT”,‬‬


‫‪new String[ ] {“NAME”, “SCHOOL_NAME”},‬‬
‫‪“_id = ?”,‬‬
‫‪new String[ ] { Integer.toString(1) },‬‬
‫;)‪null, null, null‬‬

‫هنا االوبجكت ‪ cursor‬سيحمل القيمة التالية‬

‫‪NAME SCHOOL_NAME‬‬

‫‪93‬‬
Ahmed AL Edrise
:‫فاذا كتبنا الكود التالي‬

String name = cursor.getString(0);

Ahemd ‫ هي‬name ‫هنا ستكون قيمة المتغير‬

‫ وقاعدة البيانات‬Cursor ‫اغالق االوبجكت‬


،close ‫ نغلقه هو وقاعدة البيانات من خالل الدالة‬cursor ‫اخيرا بعد ان ننتهي من اخذ البيانات من االوبجكت‬
:‫بهذا الشكل‬

cursor.close( );
db.close( );

)‫ في ملف الجافا الذي سنجلب فيه البيانات من قاعدة البيانات‬onCreate ‫ (هذا الكود يكتب في الدالة‬:‫مثال‬

try{
SQLiteOpenHelper myDb = new MyDb (this);
SQLiteDatabase db = myDb.getReadableDatabase();
Cursor cursor = db.query("STUDENT",
new String[ ] {"NAME", "SCHOOL_NAME"},
"_id = ?",
new String[ ] {Integer.toString(1)},
null, null, null);
if (cursor.moveToFirst()){
String nameText = cursor.getString(0);
String school = cursor.getString(1);
TextView name = (TextView)findViewById(R.id.name);
name.setText(nameText);
TextView school = (TextView)findViewById(R.id.school);
description.setText(school);
}
cursor.close();
db.close();
} catch (SQLiteException e){

94
‫‪Toast toast = Toast.makeText(this, "Database unavailable",‬‬
‫;)‪Toast.LENGTH_SHORT‬‬
‫;)(‪toast.show‬‬
‫}‬

‫‪CursorAdapter‬‬
‫يستخدم الـ ‪ CursorAdapter‬في الغالب الجل عرض البيانات في داخل ‪ ListView‬لتظهر للمستخدم على‬
‫شكل قائمة متسلسلة عموديا‪ ،‬وهو نفس الـ ‪( ArrayAdapter‬شرحنا عنه سابقا) لكن الفرق هو ان الـ‬
‫‪ ArrayAdapter‬ياخذ البيانات من المصفوفة (وهو ابطئ ويستهلك ذاكرة أكثر) اما الـ ‪CursorAdapter‬‬
‫فياخذ البيانات من االوبجكت ‪( Cursor‬وهو أسرع ويستهلك ذاكرة اقل)‪.‬‬

‫‪SimpleCursorAdapter‬‬
‫هذا الكالس مشتق من الكالس ‪ CursorAdapter‬ويؤدي نفس عمله تقريبا‪ ،‬هناك مالحظة مهمة يجب‬
‫االنتباه لها وهي عندما نجلب اعمدة من جدول من قاعدة البيانات ونحتاج ان نستخدم ‪( CursorAdapter‬او‬
‫الكالس المشتق منه ‪ )SimpleCursorAdapter‬فيجب ان نجلب العمود المسمى ‪ _id‬والذي اعطيناه‬
‫خاصية ‪( primary key‬شرحنا عنه سابقا) وذلك الن القائمة ‪ ListView‬تحتاج هذا العمود النه من خالله‬
‫تحدد العنصر الذي تم الضغط عليه‪.‬‬

‫عندما نستخدم الكالس ‪ SimpleCursorAdapter‬نحتاج ان نخبره كيف سيتم عرض البيانات واي‬
‫‪ Cursor‬عليه ان يستخدم الخذ البيانات منه وماهي االعمدة التي نريد عرضها وفي اي عنصر عرض‪ ،‬بهذه‬
‫الصيغة‪:‬‬

‫‪SimpleCursorAdapter adapter = new SimpleCursorAdapter(Context context,‬‬


‫‪int layout,‬‬
‫‪Cursor cursor,‬‬
‫‪String[ ] fromColumns,‬‬
‫‪int[ ] toViews,‬‬
‫;)‪int[ ] flags‬‬

‫حيث ان الباراميتر االول يشير الى المحتوى ‪ context‬الحالية‪ ،‬الباراميتر الثاني يشير الى كيفية عرض‬
‫العناصر في القائمة يمكن مثال استخدام ‪ android.R.layout.simple_list_item_1‬لعرض كل عنصر‬
‫في القائمة بسطر منفصل‪ ،‬الباراميتر الثالث يمثل االوبجكت ‪ Cursor‬الذي انشأناه لجلب البيانات من قاعدة‬
‫البيانات وكما قلنا فهو يجب ان يحتوي على العمود ‪ _id‬اضافة الى البيانات التي نريد استخراجها‪ ،‬الباراميتر‬

‫‪95‬‬
‫الرابع يمثل االعمدة التي نريد استخراج البيانات منها ووضعها في عنصر العرض‪ ،‬الباراميتر الخامس يمثل‬
‫عنصر العرض‪ ،‬اما الباراميتر السادس يستخدم لتحديد سلوك االوبجكت ‪ cursor‬وفي الغالب نضع له القيمة‬
‫‪.0‬‬

‫* كما ذكرنا سابقا فانه عندما نفتح منفذ لقاعدة البيانات وكذلك عند عمل االوبجكت ‪ cursor‬يجب علينا‬
‫غلقهما وذلك لتحرير الذاكرة‪ ،‬وهنا ايضا يجب ان نغلقهما عند استخدام ‪ CursorAdapter‬لكن ما يجب علينا‬
‫االنتباه له هو انه يجب ان نغلقهما عندما ننتهي تماما من عرض البينانات لذلك يفضل ان تتم عملية االغالق‬
‫في داخل الدالة ‪ onDestroy‬التابعة للـ ‪ activity‬لكي يتم الغلق بعد ان تحطم الـ ‪ activity‬وذلك الن قائمة‬
‫العرض ‪ ListView‬لن تقوم باخذ كل البيانات دفعة واحدة من االوبجكت ‪ cursor‬وانما تاخذ فقط ما سيظهر‬
‫على الشاشة للمستخدم وعندما يحرك المستخدم شريط التمرير في الشاشة ليستعرض بقية القائمة هنا في هذه‬
‫اللحظة ستعود الـ ‪ ListView‬الى االوبجكت ‪ cursor‬الخذ ما سيظهر فقط‪.‬‬

‫مثال‪( :‬يكتب في ملف الجالفا الخاص بالـ ‪ activity‬الذي سيعرض القائمة ‪)ListView‬‬

‫;‪private SQLiteDatabase db‬‬


‫;‪private Cursor cursor‬‬
‫‪@Override‬‬
‫{ )‪protected void onCreate(Bundle savedInstanceState‬‬
‫;)‪super.onCreate(savedInstanceState‬‬
‫;)(‪ListView listDrinks = getListView‬‬
‫{‪try‬‬
‫;)‪SQLiteOpenHelper myDb = new MyDb(this‬‬
‫;)(‪db = myDb.getReadableDatabase‬‬
‫‪cursor = db.query("STUDENT",‬‬
‫‪new String[]{"_id", "NAME"},‬‬
‫;)‪null, null, null, null, null‬‬
‫‪CursorAdapter listAdapter = new SimpleCursorAdapter(this,‬‬
‫‪android.R.layout.simple_list_item_1,‬‬
‫‪cursor,‬‬
‫‪new String[ ]{"NAME"},‬‬
‫‪new int[]{android.R.id.text1},‬‬
‫;)‪0‬‬
‫;)‪listDrinks.setAdapter(listAdapter‬‬
‫{)‪} catch (SQLiteException e‬‬
‫‪Toast toast = Toast.makeText(this, "Database unavailable",‬‬
‫;)‪Toast.LENGTH_SHORT‬‬

‫‪96‬‬
‫;)(‪toast.show‬‬
‫}}‬
‫‪@Override‬‬
‫{)(‪public void onDestroy‬‬
‫;)(‪super.onDestroy‬‬
‫;)(‪cursor.close‬‬
‫;)(‪db.close‬‬
‫}‬

‫* كما تالحظ لقد استخدمنا الدالة ‪ setAdapter‬لوضع البيانات في القائمة ‪ ListView‬وذلك النه يمكننا‬
‫استخدام كل دوالها (والتي شرحنا عنها سابقا) مع ‪ CursorAdapter‬كما استخدمناها مع ‪.ArrayAdapter‬‬

‫اضافة قيم في قاعدة البيانات‬


‫شرحنا في الجزء االول من هذا الفصل كيف نضيف البيانات او نحدثها او نحذفها من خالل الدوال ‪insert‬‬
‫و‪ update‬و‪ delete‬وذلك في داخل كالس قاعدة البيانات (الكالس الذي يرث الكالس‬
‫‪ ،)SQLiteOpenHelper‬وبنفس الطريقة ونفس هذه الدوال يمكننا ان نضيف قيم الى قاعدة البيانات (ذلك‬
‫مهم لجعل المستخدم يضيف البيانات الى قاعدة البيانات اثناء تشغيل البرنامج من خالل عناصر العرض‬
‫الموجودة في الشاشة)‪.‬‬

‫لنأخذ مثال ونفترض ان عندنا العنصر ‪ CheckBox‬ظاهر للمستخدم وعند الضغط علية تنفذ الدالة‬
‫‪ onFavoriteClicked‬ونريد ان نخزن القيم العائدة من هذا العنصر (تعود القيم من هذا العنصر باستخدام‬
‫الدالة ‪ isChecked‬وهي تعيد اما ‪ 0‬وتعني العنصر غير مفعل او ‪ 1‬وتعني العنصر مفعل) في عمود في‬
‫جدول في قاعدة البيانات اسمة ‪ ،FAVORITE‬يمكن ان يكون شكل الدالة ‪ onFavoriteClicked‬هكذا‪:‬‬

‫{)‪public void onFavoriteClicked(View view‬‬


‫;)‪CheckBox favorite = (CheckBox)findViewById(R.id.favorite‬‬
‫;)(‪ContentValues drinkValues = new ContentValues‬‬
‫;))(‪drinkValues.put("FAVORITE", favorite.isChecked‬‬
‫;)‪SQLiteOpenHelper myDb = new MyDb(DrinkActivity.this‬‬
‫{‪try‬‬
‫;)(‪SQLiteDatabase db = myDb.getWritableDatabase‬‬
‫[‪db.update("DRINK", drinkValues, "_id = ?", new String‬‬
‫;)})‪]{Integer.toString(1‬‬
‫;)(‪db.close‬‬

‫‪97‬‬
‫{)‪} catch (SQLiteException e‬‬
‫‪Toast.makeText(this, "Database unavailable",‬‬
‫;)(‪Toast.LENGTH_SHORT).show‬‬
‫}}‬

‫حيث ان ‪ DrinkActivity‬يمثل اسم ملف الجافا التابع للـ ‪ activity‬التي كتبنا هذه الدالة داخلها‪.‬‬

‫الدالة ‪changeCursor‬‬
‫هذه الدالة تابعة للكالس ‪ ، CursorAdapter‬وظيفة هذه الدالة هي ان تحدث االوبجكت ‪ cursor‬الموجود‪،‬‬
‫حيث ان االوبجكت ‪ cursor‬يجلب البيانات من قاعدة البيانات ولكن اذا حدث تغيير على قاعدة البيانات فهو‬
‫لن يعلم ان هناك تغيير حدث إال في حال اعادة انشائه من جديد‪ ،‬مثال اذا كانت عندنا ‪ activity‬وهي تعرض‬
‫للمستخدم قائمة عرض ‪ ListView‬تجلب بياناتها من قاعدة البيانات‪ ،‬اذا انتقل المستخدم من هذه الـ‬
‫‪ activity‬الى ‪ activity‬اخرى وغير بعض البيانات في قاعدة البيانات ثم عاد الى الـ ‪ activity‬السابقة التي‬
‫تعرض ‪ ListView‬من قاعدة البيانات فانه سيجد ان البيانات هي نفسها ولم تتغير (يحتاج ان يخرج من‬
‫البرنامج ويعود لكي يجد التغيير) وسبب ذلك هو ان عند الرجوع الى ‪ activity‬السابقة لن يتم اعادة انشاء‬
‫االوبجكت ‪ cursor‬وسيتم استدعاء الدالة ‪ ، onRestart‬لذا يجب ان نعيد كتابة كود انشاء االوبجكت ‪cursor‬‬
‫مع استخدام الدالة ‪ changeCursor‬لتستبدل بيانات االوبجكت ‪ cursor‬السابق ببيانات هذا االوبجكت‬
‫‪ cursor‬الجديد‪ ،‬ويكتب هذا الكود في داخل الدالة ‪ ،onRestart‬الصيغة العامة لهذه الدالة هي‪:‬‬

‫;)‪public void changeCursor(Cursor newCursor‬‬

‫تاخذ باراميتر واحد هو عبارة عن اوبجكت ‪ cursor‬الجديد (الذي يحمل البيانات المحدثة) الذي نريد استبداله‬
‫باالوبجكت ‪ cursor‬السابق‪.‬‬

‫زيادة سرعة قواعد البيانات‬


‫إذا كانت قاعدة البيانات ضخمة فهي ستجعل الهاتف بطيء في التشغيل والتعامل مع برنامجنا‪ ،‬ولحل هذه‬
‫المشكلة علينا ان نتعامل مع الثريد‪ ،‬هناك ثالثة انواع من الثريد وهي‪:‬‬

‫‪ -1‬الثريد الرئيسي (‪)The main event thread‬‬


‫هذه الثريد هي العمود الفقري في االندرويد‪ ،‬فهي مسؤلة عما يعرض من احداث على الشالشة‪ ،‬وهي‬
‫تستدعي كل الدوال في الـ ‪ activities‬التابعة للبرنامج‪.‬‬
‫‪ -2‬ثريد العرض (‪)The render thread‬‬

‫‪98‬‬
‫بشكل اعتيادي فاننا ال نتفاعل مع هذه الثريد لكنها تقرأ قائمة لتحديثات الشاشة وتستدعي المستوى‬
‫المنخفض من الهاردوير العادة رسم الشاشة ليظهر المنظر جميل على الشاشة‪.‬‬
‫‪ -3‬كل بقية الثريد التي نخلقها (‪)All of the other thread that you create‬‬

‫في الشكل االعتيادي فان البرنامج كله سيتم تنفيذه على الثريد الرئيسي‪ ،‬وبما ان قاعدة البيانات ستحتاج الى‬
‫الكثير من العمل لينفذها الهاتف لذا من االفضل نقل االكواد الخاصة بالتعامل مع قاعدة اللبيانات الى ثريد‬
‫الخلفية‪.‬‬

‫الكالس ‪AsyncTask‬‬
‫هذا الكالس يسمح لنا باجراء العمليات في الخلفية‪ ،‬وعندما تنتهي العملية يسمح لنا بتحديث العناصر في الثريد‬
‫الرئيسي‪ ،‬وكذلك يمكن نشر نتيجة التقدم اثناء عمل العملية‪.‬‬

‫نحن ننشأ الكالس ‪ AsyncTask‬من خالل عمل كالس اعتيادي داخلي في داخل ملف الجافا التابع للـ‬
‫‪ activity‬التي ستتعامل مع قاعدة البيانات وجعله يرث (‪ )extends‬الكالس ‪( AsyncTask‬وال تنسى ان‬
‫تعمل ‪ import‬لهذا الكالس)‪ ،‬هذا الكالس يحتوي على عدة دوال منها الدالة ‪ doInBackground‬حيث ان‬
‫الكود في داخل هذه الدالة ينفذ في ثريد الخلفية‪ ،‬الدالة ‪ onPreExecute‬هذه الدالة تنفذ قبل الدالة‬
‫‪ ،doInBackground‬الدالة ‪ onPostExecute‬تنفذ بعد الدالة ‪ ،doInBackground‬الدالة‬
‫‪ onProgressUpdate‬منها يمكن نشر مستوى تقدم تنفيذ العملية‪.‬‬

‫يكون شكل الكالس هكذا‪:‬‬

‫;‪import android.os.AsyncTask‬‬
‫‪...‬‬
‫{ ‪public class MainActivity extends Activity‬‬
‫‪...‬‬
‫>‪private class MyAsyncTask extends AsyncTask<Params, Progress, Result‬‬
‫{ )(‪protected void onPreExecute‬‬
‫‪//Code to run before executing the task‬‬
‫}‬
‫{ )‪protected Result doInBackground(Params... params‬‬
‫‪//Code that you want to run in a background thread‬‬
‫}‬
‫{ )‪protected void onProgressUpdate(Progress... values‬‬
‫‪//Code that you want to run to publish the progress of your task‬‬
‫}‬

‫‪99‬‬
‫{ )‪protected void onPostExecute(Result result‬‬
‫‪//Code that you want to run when the task is complete‬‬
‫}‬
‫}}‬

‫كما تالحظ فان الكالس ياخذ ثالثة بارامترات من النوع ‪ ،generic‬الباراميتر االول (‪ )Params‬هو نوع‬
‫اوبجكت يتم تمريره للدالة ‪( doInBackground‬مثال اذا مررنا للدالة ‪ doInBackground‬اوبجكت من‬
‫النوع ‪ Integer‬سنكتب هذا الباراميتر ‪ Integer‬اما اذا لم نمرر لها اي اوبجكت سنكتب هذا الباراميتر‬
‫‪ ،)void‬الباراميتر الثاني (‪ )Progress‬نوع اوبجكت يشير الى تقدم العملية ويمرر للدالة‬
‫‪( onProgressUpdate‬مثال اذا مررنا للدالة ‪ onProgressUpdate‬اوبجكت من النوع ‪ Integer‬سنكتب‬
‫هذا الباراميتر ‪ Integer‬اما اذا لم نمرر لها اي اوبجكت سنكتب هذا الباراميتر ‪ ،)void‬الباراميتر الثالث‬
‫(‪ )Result‬يشير الى نتيجة العملية وهو يمرر للدالة ‪( onPostExecute‬مثال اذا مررنا للدالة‬
‫‪ onProgressUpdate‬اوبجكت من النوع ‪ Integer‬سنكتب هذا الباراميتر ‪ Integer‬اما اذا لم نمرر لها اي‬
‫اوبجكت سنكتب هذا الباراميتر ‪.)void‬‬

‫* واالن سنأخذ هذه الدوال بالتفصيل‪.‬‬

‫الدالة ‪onPreExecute‬‬
‫يتم استدعاء هذه الدالة قبل ان ي تم بدأ المهمة في الخلفية‪ ،‬وهي تستخدم لتهيئة المهمة‪ ،‬وتستدعى من قبل الثريد‬
‫الرئيسي لذا لديها نفوذ الى العناصر ‪ views‬في واجهة المستخدم‪ ،‬وهذه الدالة ال تأخذ اي بارامترات وال تعيد‬
‫اي قيمة‪.‬‬

‫الدالة ‪doInBackground‬‬
‫هذه الدالة تعمل في الثريد في الخلفية وهي تعمل مباشرتا بعد الدالة ‪ ،onPreExecute‬ونحن نحدد ما هو‬
‫نوع الباراميتر الذي ستستقبل وما هي نوع القيمة التي ستعيدها (يمكن وضع اي قيمة نريد)‪ ،‬وبداخلها نضع‬
‫كل الكود المتعلق بجلب البيانات من قاعدة البيانات (من بداية فتح قاعدة البيانات الى ان يتم اغالقها)‪ ،‬هذه‬
‫الدالة ليس لها نفوذ للعناصر ‪ views‬في واجهة المستخدم (اي ال يمكن بداخلها ان نكتب كود للتعامل مع أحد‬
‫عناصر ‪.)view‬‬

‫الدالة ‪onProgressUpdate‬‬
‫هذه الدالة اختيارية اي يمكن ان ال نكتبها ان لم نحتاجها وهي يتم استدعائها في الثريد الرئيسي لذا فهي لديها‬
‫نفوذ الى العناصر ‪ views‬في واجهة المستخدم‪ ،‬يمكن باستخدام هذه الدالة ان نظهر للمستخدم نتيجة تقدم‬
‫المهمة من خالل عمل تحديث مستمر لعنصر ‪ view‬على الشاشة ونحن نحدد ما نوع الباراميتر الممرر لها‪،‬‬
‫ولكي تعمل هذه الدالة يجب ان يتم استدعاء للدالة ‪ publishProgress‬من داخل الدالة ‪doInBackground‬‬
‫بهذا الشكل‪:‬‬

‫‪100‬‬
‫{)‪protected Boolean doInBackground(Integer... count‬‬
‫{) ‪for( int i = 0; i < count; i++‬‬
‫;)‪publishProgress(i‬‬
‫}‬
‫}‬
‫{)‪protected void onProgressUpdate(Integer... progress‬‬
‫;)]‪setProgress(progress[0‬‬
‫}‬

‫الدالة ‪onPostExecute‬‬
‫تنفذ هذه الدالة بعد ان تنت هي المهمة في ثريد الخلفية ويتم استدعاءها من قبل الثريد الرئيسي لذا فهي لديها نفوذ‬
‫للعناصر ‪ views‬في واجهة المستخدم‪ ،‬ونحن نستخدم هذه الدالة لتقديم نتائج المهمة الى المستخدم حيث انه‬
‫تمرر لها نتائج الدالة ‪ doInBackground‬لذا فهي يجب ان تأخذ باراميتر يطابق ما تعيدة الدالة‬
‫‪( doInBackground‬نحن فقط نمرر لها اوبجكت من نفس النوع الذي تعيده الدالة ‪doInBackground‬‬
‫وهو سيكون حامال للنتيجة التي تعيدها هذه الدالة بشكل اوتوماتيكي)‪ ،‬مثال‪:‬‬

‫{)‪protected void onPostExecute(Boolean success‬‬


‫{) ‪if( !success‬‬
‫‪Toast toast = Toast.makeText(mainActivity.this, “Database unavailable”,‬‬
‫;)(‪Toast.LENGTH_SHORT).show‬‬
‫}}‬

‫تنفيذ الكالس ‪AsyncTask‬‬


‫بعد ان نعمل كالس ‪( AsyncTask‬كالس داخلي يرث الكالس ‪ )AsyncTask‬ونضع فيه الدوال ونكتب فيه‬
‫الكود المراد تنفيذه في ثريد الخلفية‪ ،‬نحتاج االن ان نعمل استدعاء لهذا الكالس ليتم تنفيذ الكود المكتوب داخله‬
‫وذلك من خالل دالته ‪ execute‬وهي تاخذ باراميتر واحد ويجب ان يكون نفس نوع الباراميتر الممرر للدالة‬
‫‪ ،doInBackground‬يتم االستدعاء بهذا الشكل‪:‬‬

‫;)‪new MyAsyncTask().execute(X‬‬

‫حيث ان ‪ MyAsyncTask‬يمثل اسم الكالس الداخلي الذي جعلناه يرث الكالس ‪.AsyncTask‬‬

‫* في الحقيقة دائما قواعد البيانات (ال نقصد كالس قاعدة البيانات وانما الكود المكتوب في الـ ‪ activity‬التي‬
‫تتعامل مع قاعدة البيانات) تكتب في داخل الكالس ‪ AsyncTask‬لكي تنفذ في الخلفية‪.‬‬

‫‪101‬‬
‫الفصل التاسع (‪)Notifications and messages‬‬

‫االشعارات ‪Notifications‬‬
‫االشعارات هي عبارة عن رسائل تظهر للمستخدم على شكل قائمة في الشريط العلوي للهاتف‪ ،‬ويمكن سحب‬
‫االشعار لالسف الظهار كامل المحتوى‪ ،‬نحن نستخدم الـ ‪ notification service‬الرسال االشعارات‪.‬‬

‫في اإلصدارات الجديدة لالندرويد (‪ API 26‬فما فوق) أصبح للمستخدم إمكانية التحكم في االشعارات‬
‫للبرنامج كأن يلغيها او يلغي العالمة التي تظهر على ايقونة البرنامج ‪ budge‬او يلغي البعض منها‪ ،‬ما نقصده‬
‫بالبعض منها انه أصبح المبرمج مجبور على ان يقسم االشعارات الى مجموعة من القنوات ‪( Channels‬إذا‬
‫لم نستخدم ال قنوات فان االشعارات ستظهر على األجهزة القديمة فقط اما باستخدام القنوات فان االشعارات‬
‫ستظهر على األجهزة الحديثة فقط)‪ ،‬نعمل شرط لمحدد من خالله اإلصدار أي هل نستخدم القنوات او ال‪ ،‬بهذا‬
‫الشكل‪:‬‬

‫‪102‬‬
‫{ )‪if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O‬‬
‫الكود الذي يعمل على االصدارات الحديثة (القنوات) ‪//‬‬
‫{ ‪} else‬‬
‫الكود الذي يعمل على االصدارات القديمة ‪//‬‬
‫}‬

‫انشاء االشعار‬
‫يتم انشاء االشعارات من خالل انشاء اوبجكت ‪ notification builder‬جديد‪ ،‬بهذا الشكل‪:‬‬

‫;‪NotificationCompat.Builder builder‬‬

‫;)‪builder = new NotificationCompat.Builder(this, id‬‬

‫هذا الشكل بالنسبة لألجهزة التي نظامها ‪ API 26‬فما فوق‪ ،‬اما األجهزة التي نظامها اقل نكتب لها هذا الكود‪:‬‬

‫;‪NotificationCompat.Builder builder‬‬

‫;)‪builder = new NotificationCompat.Builder(this‬‬

‫االن أصبح عندنا اوبجكت اسمه ‪ builder‬نضيف له الدوال التي من خاللها نحدد شكل ومحتوى االشعار‪،‬‬
‫هذه الدوال هي‪:‬‬

‫الدالة ‪setSmallIcon‬‬
‫هذه الدالة يجب ان نظيفها اجباري (وإال سيحدث خطأ)‪ ،‬من خاللها نحدد االيقونه التي تظهر بجوار االشعار‪،‬‬
‫وتأخذ بين قوسيها عنوان االيقونة‪.‬‬

‫الدالة ‪setContentTitle‬‬
‫هذه الدالة يجب ان نظيفها اجباري (وإال سيحدث خطأ)‪ ،‬نضع بين قوسيها قيمة نصية تمثل عنوان االشعار‪.‬‬

‫الدالة ‪setContentText‬‬
‫هذه الدالة يجب ان نظيفها اجباري (وإال سيحدث خطأ)‪ ،‬نضع بين قوسيها نص يمثل نص محتوى االشعار‪.‬‬

‫الدالة ‪setAutoCancel‬‬
‫هذه الدالة اختيارية (أي يمكن اضافتها او ال)‪ ،‬تأخذ قيمة ‪ true‬او ‪ false‬إذا أسندنا لها القيمة ‪ true‬فهذا يعني‬
‫ان يختفي االشعار عند الضغط عليه‪.‬‬

‫‪103‬‬
‫الدالة ‪setPriority‬‬
‫هذه الدالة اختيارية (أي يمكن اضافتها او ال)‪ ،‬تعطي األولوية لالشعارات وهذه الدالة تستخدم فقط مع‬
‫األجهزة التي نظامها التشغيلي ‪ API 25‬او اقل‪ ،‬يمكن ان نسد لها احدى هذه القيم‪:‬‬

‫‪Notification.PRIORITY_MAX‬‬

‫‪Notification.PRIORITY_HIGH‬‬

‫‪Notification.PRIORITY_DEFAULT‬‬

‫‪Notification.PRIORITY_LOW‬‬

‫‪Notification.PRIORITY_MIN‬‬

‫هذه القيم مرتبة من األولوية القصوى الى اقل أولوية (مرتبة بالتسلسل)‪.‬‬

‫الدالة ‪setDefaults‬‬
‫هذه الدالة اختيارية (أي يمكن اضافتها او ال)‪ ،‬هذه الدالة تحدد القيم التي نريدها ان تكون بشكل افتراضي‪،‬‬
‫وهي تأخذ أحد هذه القيم‪:‬‬

‫‪Notification.DEFAULT_ALL‬‬

‫‪Notification.DEFAULT_LIGHTS‬‬

‫‪Notification.DEFAULT_VIBRATE‬‬

‫‪Notification.DEFAULT_SOUND‬‬

‫الدالة ‪setVibrate‬‬
‫هذه الدالة اختيارية (أي يمكن اضافتها او ال)‪ ،‬نحدد من خاللها االهتزاز عند ظهور االشعار‪ ،‬وهي تأخذ‬
‫مصفوفة من االعداد تمثل االهتزاز‪ ،‬مثال‪:‬‬

‫)}‪.setVibrate(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400‬‬

‫‪104‬‬
setContentIntent ‫الدالة‬
‫ معين‬activity ‫ هذه الدالة تمكننا من ان نجعل االشعار يعرض‬،)‫هذه الدالة اختيارية (أي يمكن اضافتها او ال‬
.‫ وسنشرح عنه بعد قليل‬pending intent ‫عند الضغط عليه باستخدام‬

:‫هناك العديد من الخصائص االخرى يمكن ان تجدها على هذا الرابط‬

https://developer.android.com/reference/android/app/Notification.Builder.html

:‫مثال‬

NotificationCompat.Builder builder;
builder = new NotificationCompat.Builder(this, id);
builder.setContentTitle(“The title”)
.setSmallIcon(android.R.drawable.ic_popup_reminder)
.setContentText(“The text of notification”)
.setDefaults(Notification.DEFAULT_ALL)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.setVibrate(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});

‫ التي نريد فتحها عند الضغط‬activity ‫ يمثل اوبجكت يحمل معلومات عن الـ‬pendingIntent ‫حيث ان‬
.‫على االشعار وسنشرح عنه بعد قليل‬

activity ‫االشعار يبدأ‬


‫ يتم ذلك باستخدام‬،‫ معين عند الضغط عليه‬activity ‫في اغلب االحيان نريد ان نجعل االشعار يعرض‬
:‫ وبعدة خطوات‬pending intent

‫ نفترض‬،‫ التي نريدها ان تعرض عند الضغط على االشعار‬activity ‫ داخلي لنحدد الـ‬intent ‫ انشاء‬-1
:‫ هكذا‬،MainActivity ‫اننا نريد ان نعرض‬

Intent intent = new Intent(this, MainActivity.class);

105
:‫ بهذا الشكل‬setFlags ‫ نستخدم الدالة‬-2

intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_SINGLE_TOP);

:‫ بهذا الشكل‬PendingIntent ‫ ننشأ االوبجكت‬-3

PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);

‫ لكي تبدأ الـ‬setContentIntent ‫ الى االشعار باستخدام الدالة‬pending intent ‫ اخيرا نضيف الـ‬-4
:‫ بهذا الشكل‬،‫ عند الضغط على االشعار‬activity

.setContentIntent(pendingIntent);

‫ارسال االشعارات‬
‫ وذلك‬built-in services ‫ اوال علينا النفوذ الى الـ‬،notification service ‫نرسل االشعارات بستخدام الـ‬
‫ الذي نريد‬service ‫ وهي تأخذ باراميتر واحد هو عبارة عن اسم الـ‬getSystemService ‫باستخدام الدالة‬
:‫ ليكون شكل الكود بهذا الشكل‬،)notification service ‫استخدامه (في حالتنا هذه هو‬

public static final int NOTIFY_ID = 5453;


...
NotificationManager notifManager;
notifManager =
(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = builder.build();
notifManager.notify(NOTIFY_ID, notification);

‫ لتحديد االشعار فاذا ارسلنا اشعار اخر بنفس هذا الرقم سيستبدل االشعار السابق‬NOTIFY_ID ‫نستخدم‬
‫ هو‬builder‫ و‬،)‫باالشعار الجديد (قد يكون هذا مفيد إذا أردنا تحديث اشعار موجود بمعلومات جديدة‬
‫ نرسل االشعار‬notify ‫االوبجكت الذي انشأناه قبل قليل وهنا سنبنيه ومن ثم بالدالة‬

‫مثال متكامل‬

106
‫االن سنأخذ مثال متكامل لبناء اشعار وسيعمل على جميع األجهزة ومن خالله أيضا سنتعلم كيفية بناء قنياة‬
‫ حيث انه بمجرد‬createNotification ‫ سنضع كل محتويات االشعار في داخل دالة اسمها‬،Channel
:‫ مثال‬،‫استدعائها سيظهر االشعار للمستخدم‬

private NotificationManager notifManager;


public void createNotification(String aMessage) {
final int NOTIFY_ID = 1002;
String name = "The name of Channel"; // The user-visible name of the channel.
String id = "special id for channel"; //each channel has special id.
String description = "The description of channel"; // The user-visible description of
the channel.
NotificationCompat.Builder builder;
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |

Intent.FLAG_ACTIVITY_SIN
GLE_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
if (notifManager == null) {
notifManager =
(NotificationManager)getSystemService(Context.NOTIFICATION_SERVI
CE);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // for API 26 or
up
int importance = NotificationManager.IMPORTANCE_HIGH;
// Create channel
NotificationChannel mChannel = notifManager.getNotificationChannel(id);
if (mChannel == null) {
mChannel = new NotificationChannel(id, name, importance);
mChannel.setDescription(description);
mChannel.enableVibration(true);
mChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300,
200, 400});
notifManager.createNotificationChannel(mChannel);

107
}
builder = new NotificationCompat.Builder(this, id);
builder.setContentTitle(this.getString(R.string.app_name)) // required
.setSmallIcon(android.R.drawable.ic_popup_reminder) // required
.setContentText(aMessage) // required
.setDefaults(Notification.DEFAULT_ALL)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.setTicker(aMessage)
.setVibrate(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
} else { // for API 25 or less
builder = new NotificationCompat.Builder(this);
builder.setContentTitle(this.getString(R.string.app_name)) // required
.setSmallIcon(android.R.drawable.ic_popup_reminder) // required
.setContentText(aMessage) // required
.setDefaults(Notification.DEFAULT_ALL)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.setTicker(aMessage)
.setVibrate(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400})
.setPriority(Notification.PRIORITY_MAX);
}
Notification notification = builder.build();
notifManager.notify(NOTIFY_ID, notification);
}

‫ لجعل االشعار يظهر للمستخدم عند حدوث امر ما حتى‬Services ‫• يمكن كتابة كود هذه الدالة في الـ‬
.‫لو كان التطبق مغلق‬

108
‫النوافذ المنبثفة (‪)Messages‬‬
‫هناك أنواع عديدة من النوافذ المنبثفة يمكن انشائها‪ ،‬في هذا الجزء سنأخذ البعض منها‪.‬‬

‫النافذة المنبثقة ‪Toast‬‬

‫تستخدم هذه النافذة المنبثقة الظهار تلميح ما للمستخدم‪ ،‬يمكن ان نعمل ذلك من خالل الدالة ‪makeText‬‬
‫الموجودة في الكالس ‪ Toast‬بهذه الصيغة العامة‪:‬‬

‫)‪public static Toast makeText(Context context, int resId, int duration‬‬

‫حيث ان الباراميتر االول هو مثال من الكالس ‪ Activity‬والذي هو بدوره كالس فرعي من الكالس‬
‫‪ Context‬فيمكن ان يحمل القيمة ‪ this‬او اسم الـ ‪ activity‬او نجلبة باستخدام الدالة‬
‫‪ ، getApplicationContext‬الباراميتر الثاني هو عبارة عن مسار الـ ‪ id‬لنص الرسالة الذي سيعرض اذا‬
‫كان مكتوب في ملف الـ ‪ string‬والحظ ان المسار ‪ resId‬يعتبر من النوع ‪ int‬او نكتب الرسالة بشكل مباشر‬
‫هنا في هذه الدالة بوضعها بين عالمتي تنصيص‪ ،‬البارميتر الثالث يمثل المدة الزمنية التي تعرض بها الرسالة‬
‫ثم تختفي وهو اما يأخذ القيمة ‪ LENGTH_SHORT‬او القيمة ‪. LENGTH_LONG‬‬

‫ويجب ان نستخدم الدالة ‪ show‬معها لكي تعرض الرسالة‪ ،‬مثال‪:‬‬

‫;) (‪Toast.makeText(this, "The Message", Toast.LENGTH_SHORT).show‬‬

‫‪109‬‬
‫او يمكن كتابة السطر السابق بهذا الشكل‪:‬‬

‫;)‪Toast toast = makeText(getApplicationContext(), "The message", duration‬‬


‫;)(‪toast.show‬‬

‫التنسا انه يجب عمل استدعاء للكالس ‪ Toast‬بهذا الشكل‪:‬‬

‫;‪Import android.widget.Toast‬‬

‫نافذة هذه الرسالة تظهر بشكل افتراضي في أسفل الشاشة (كما في الصورة في األعلى) لكن لكي نجعلها‬
‫تظهر في مكان اخر من الشاشة علينا ان نستخدم الدالة ‪ setGravity‬حيث ان هذه الدالة تأخذ ثالثة‬
‫بارامترات األول يمثل محتوى الـ ‪ Gravity‬والثاني يمثل محور االحداثيات ‪ X‬والباراميتر الثالث يمثل محو‬
‫االحداثيات ‪ ،y‬مثال لعرض النافذة في منتصف اعلى الشاشة نكتب الكود التالي‪:‬‬

‫;)‪Toast toast = makeText(getApplicationContext(), "The message", duration‬‬


‫;)‪toast.setGravity(Gravity.TOP | Gravity.CENTER, 0, 0‬‬
‫;)(‪toast.show‬‬

‫مربع حوار ‪Dialog‬‬


‫هنا سنتعلم كيف سنظهر للمستخدم رسالة ونظع فيها ما نريد من ازرار وغيرها‪ ،‬بهذا الشكل‪:‬‬

‫‪110‬‬
‫لعمل مثل هذا الـ ‪ Dialog‬سنفترض ان عندنا زر اعتيادي عند الضغط علية سيتم تنفيذ الدالة‬
‫‪ ،show_dialog‬سنعمل طبقة ‪ layout‬إضافية (غير تلك المرتبطة بالـ ‪ activity‬لنظع بداخلها ازرار‬
‫والحقول التي ستظهر في الرسالة) لنفترض ان اسم الطبقة الجديدة هو ‪ my_layout‬ويمكنك ان تضع فيها ما‬
‫تشاء كأي طبقة اعتيادية‪.‬‬

‫ننشأ المربع الحواري باستخدام الكالس ‪ ،Dialog‬بهذا الشكل‪:‬‬

‫;)‪final Dialog mydialog = new Dialog(this‬‬

‫االن أصبح عندنا اوبجكت اسمه ‪ mydialog‬يمكن ان نضيف له الدوال للتعامل مع هذه الرسالة‪.‬‬

‫الدالة ‪setCancelable‬‬
‫هذه الدالة تحدد فيما إذا كنا نريد المربع الحواري يختفي إذا ضغط المستخدم على مساحة خارج هذا المربع‬
‫(وهنا سنسد لها القيمة ‪ )true‬اما إذا لم نرد ذلك لنسند لها القيمة ‪.false‬‬

‫;)‪mydialog.setCancelable(false‬‬

‫الدالة ‪setContentView‬‬
‫من خالل هذه الدالة سنحدد الطبقة ‪ layout‬التي نريدها ان تكون هي المربع الحواري (والتي قلنا سنسميها‬
‫‪ )my_layout‬بهذا الشكل‪:‬‬

‫‪111‬‬
mydialog.setContentView(R.layout. my_layout);

dismiss ‫الدالة‬
‫ مثال نضعها في زر الغاء في داخل طبقة الرسالة عند‬،‫من خالل هذه الدالة يمكن ان نخفي المربع الحواري‬
:‫ مثال‬،‫الضغط علية تختفي الرسالة‬

mydialog.dismiss();

show ‫الدالة‬
:‫ مثال‬،‫نكتب هذه الدالة في األخير عندما ننتهي من اعداد الرسالة لنعطي امر باظهار المربع الحواري‬

mydialog.show();

‫ وفيها زرين ومربعين نصيين كما في الصورة‬my_layout ‫• سنفترض انه عملنا طبقة اضافية اسمها‬
:‫ مثال‬،‫ ليتم تنفيذها عند الضغط على زر معين‬show_dialog ‫ واالن نريد ان ننفذ الدالة‬،‫في األعلى‬

public void show_dialog ( ){


final Dialog mydialog = new Dialog(this);
mydialog.setCancelable(false);
mydialog.setContentView(R.layout. my_layout);
final EditText editText_username =
(EditText)
mydialog.findViewById(R.id.edtxt_dialog_username);
final EditText editText_password =
(EditText)
mydialog.findViewById(R.id.edtxt_dialog_password);
Button btn_cancel = (Button) mydialog.findViewById(R.id.btn_dialog_cancel);
Button btn_sign = (Button) mydialog.findViewById(R.id.btn_dialog_sgn);
btn_sign.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// User clicked on sign button
}
});
btn_cancel.setOnClickListener(new View.OnClickListener() {
@Override

112
‫{ )‪public void onClick(View view‬‬
‫‪// User clicked on cancel button‬‬
‫;)(‪mydialog.dismiss‬‬
‫}‬
‫;)}‬
‫;) (‪mydialog.show‬‬
‫}‬

‫الحظ هنا عندما أردنا الوصول الى الحقول النصية واالزرار لم نستخدم فقط الدالة ‪ findViewById‬بل‬
‫سبقناها باسم االوبجكت ‪.mydialog‬‬

‫رسالة التنبيه ‪Alert‬‬


‫نقصد بها الرسالة التي تظهر للمستخدم لتخبره بامر ما وعليه ان يختار ‪ yes‬او ‪ ،no‬وهذا هو شكلها‪:‬‬

‫النشاء هكذا نوع من رسائل التنبيه علينا أوال ان ننشأ اوبجكت من ‪ ،AlertDialog‬بهذا الشكل‪:‬‬

‫;)‪AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this‬‬

‫‪113‬‬
‫االن أصبح عندنا اوبجكت اسمه ‪ alertDialogBuilder‬يمكن ان نضيف له الدوال لوضع الخصائص على‬
‫الرسالة‪ ،‬من هذه الدوال‪:‬‬

‫الدالة ‪setTitle‬‬

‫من خالل هذه الدالة يمكننا ان نضع عنوان لرسالة التنبيه‪ ،‬بهذا الشكل‪:‬‬

‫;)"‪alertDialogBuilder.setTitle("Your Title‬‬

‫الدالة ‪setIcon‬‬

‫من خالل هذه الدالة نضع ايقونة لتظهر مع الرسالة‪ ،‬بهذا الشكل‪:‬‬

‫;)‪alertDialogBuilder.setIcon(R.drawable.ahmed‬‬

‫الدالة ‪setMessage‬‬

‫نكتب بين قوسيها نص الرسالة‪ ،‬بهذا الشكل‪:‬‬

‫;)"!‪alertDialogBuilder .setMessage("Click yes to exit‬‬

‫الدالة ‪setCancelable‬‬

‫هذه الدالة اما تأخذ القيمة ‪( true‬هذا يعني انه عند الضغط على المساحات الفارغة خارج الرسالة سوف‬
‫تذهب رسالة التنبيه) او القيمة ‪( false‬هذا يعني انه عند الضغط على المساحات الفارغة خارج الرسالة سوف‬
‫لن تذهب رسالة التنبيه)‪ ،‬مثال‪:‬‬

‫;)‪alertDialogBuilder.setCancelable(false‬‬

‫الدالة ‪setPositiveButton‬‬

‫تستخدم هذه الدالة للتعامل مع الزر موافق وماذا نريد من البرنامج ان يفعل عند الضغط على هذا الزر‪ ،‬مثال‪:‬‬

‫‪alertDialogBuilder.setPositiveButton("Yes",‬‬ ‫‪new‬‬
‫{ )(‪DialogInterface.OnClickListener‬‬

‫{ )‪public void onClick(DialogInterface dialog, int id‬‬

‫‪// The user clicked on Yes button do something‬‬

‫}‬

‫‪114‬‬
‫;)}‬

‫الدالة ‪setNegativeButton‬‬
‫تسخدم هذه الدالة للتعامل مع زر اإللغاء وماذا نريد من البرنامج ان يفعل إذا ضغط المستخدم على هذا الزر‪،‬‬
‫مثال‪:‬‬

‫‪alertDialogBuilder.setNegativeButton("No",‬‬ ‫‪new‬‬
‫{ )(‪DialogInterface.OnClickListener‬‬

‫{ )‪public void onClick(DialogInterface dialog, int id‬‬

‫‪// The user clicked on No button do something‬‬

‫;)(‪dialog.cancel‬‬

‫}‬

‫;)}‬

‫تستخدم الدالة ‪ cancel‬لكي نخفي ظهور الرسالة ووضعناها في داخل الزر ‪ No‬اللغاء الرسالة عند الضغط‬
‫على هذا الزر‪.‬‬

‫الدالة ‪create‬‬
‫بعد ان جمعنا البيانات التي نريد عرضها في االوبجكت ‪ alertDialogBuilder‬فانه حان الوقت لبناء هذا‬
‫االوبجكت بستخدام هذه الدالة‪ ،‬بهذا الشكل‪:‬‬

‫;)(‪AlertDialog alertDialog = alertDialogBuilder.create‬‬

‫الدالة ‪show‬‬
‫بعد ان نبني االوبجكت بالدالة ‪ create‬حان االن وقت عرض الرسالة‪ ،‬تستخدم هذه الدالة لهذا الغرض‪ ،‬بهذا‬
‫الشكل‪:‬‬

‫;)(‪alertDialog.show‬‬

‫• لنأخذ االن مثال متكامل لعرض رسالة تنبيه‪ ،‬مثال‪:‬‬

‫{) (‪public void alert_show‬‬

‫‪115‬‬
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
alertDialogBuilder.setTitle("Your Title")
.setIcon(R.drawable.ahmed)
.setMessage("Click yes to exit!")
.setCancelable(false);
alertDialogBuilder.setPositiveButton("Yes", new
DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// if this button is clicked, close current activity
}
});
alertDialogBuilder.setNegativeButton("No", new
DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// if this button is clicked, just close the dialog box and do not do any thing
dialog.cancel();
}
});
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
}

.‫ ستظهر رسالة التنبيه‬alert_show ‫بمجرد ان ننفذ الدالة‬

116
‫الفصل العاشر (‪)Services‬‬

‫‪ service‬هو مكون من مكونات التطبيق مثل الـ ‪ activity‬لكن بدون واجهة مستخدم ولديه دورة حياة ابسط‬
‫من الـ ‪ activity‬وهي تأتي بحزمة من المزاية لكتابة الكود ليعمل بينما المستخدم يفعل اشياء اخرى في‬
‫الهاتف‪ ،‬احيانا نريد بعض العمليات ان تعمل بغض النظر عن اي برنامج علية التركيز في الهاتف‪ ،‬مثال كنا‬
‫نريد تحميل ملف ضخم او تشغيل مقطع موسيقي في الخلفية او االنتظار لوصول رسالة من االنترنت (حتى‬
‫لو كان البرنامج مغلق) هنا نحتاج ان نتعامل مع الـ ‪.services‬‬

‫هناك نوعين من الـ ‪service‬‬

‫‪Started services -1‬‬


‫هذا النوع يمكن ان يعمل في الخلفية الجل غير مسمى حتى لو كانت الـ ‪ activity‬التي بدأته اصبحت‬
‫‪ ،destroyed‬مثال إذا أردنا تحميل ملف ضخم من النت من االرجح اننا سنعمل ‪Started service‬‬
‫وعندما تنتهي العملية سيتوقف الـ ‪.service‬‬
‫‪Bound services -2‬‬
‫هذا النوع مرتبط بمكونات اخرى في البرنامج مثل الـ ‪ activity‬والتي يمكنها ان تتفاعل معه كان‬
‫ترسل له الطلبات وتحصل منه على النتائج‪ ،‬وهذا النوع سيبقى يعمل طالما المكون المرتبط معه ال‬
‫يزال موجود اما إذا أصبح ‪ destroyed‬فهذا الـ ‪ service‬سيتوقف ايضا‪.‬‬

‫انشاء الـ ‪services‬‬


‫يتم انشاء الـ ‪ services‬في برنامج اندرويد ستوديو من خالل الذهاب الى ‪ File→New→Service‬ونختار‬
‫اما ‪ Service‬او ‪ ،IntentService‬الكالس ‪ Service‬هو الكالس الرئيسي لعمل الـ ‪ services‬وهو يحتوي‬
‫على الدوال الرئيسية وفي العادة نرث هذا الكالس إذا كنا نريد ان نعمل النوع الثاني ‪ ،bound service‬اما‬
‫الكالس ‪ IntentService‬هو كالس مشتق من الكالس ‪ Service‬وهو مصمم للتعامل مع الـ ‪ intents‬وفي‬
‫العادة سنرث هذا الكالس إذا كنا نريد ان نعمل النوع االول ‪.started service‬‬

‫* عندما نعمل ‪ service‬بهذه الطريقة فان برنامج االندرويد ستوديو بشكل اوتوماتيكي يصرح في ملف الـ‬
‫‪ AndroidManifest.xml‬عن الـ ‪ service‬من خالل الوسم <‪ >service‬بهذا الشكل‪:‬‬

‫‪117‬‬
‫‪<service‬‬
‫"‪android:name=".MyService‬‬
‫>"‪android:exported="false‬‬
‫>‪</service‬‬

‫كما تالحظ فان لهذا الوسم خاصيتين االولى هي ‪ name‬وهي تحمل اسم الـ ‪ service‬ويجب ان يكون االسم‬
‫مسبوق بنقطية لكي يتمكن من دمجه مع الحزمة‪ ،‬اما الخاصية الثانية هي تحدد فيما إذا كان الـ ‪service‬‬
‫سيستخدم في برامج اخرى إذا كانت القيمة ‪ false‬فهذا يعني انه فقط هذا البرنامج يستطيع استخدامه‪.‬‬

‫* سنقسم هذا الفصل الى جزئين الجزء االول نتكلم فيه عن عمل النوع االول ‪ started service‬اما الجزء‬
‫الثاني سنتكلم فيه عن عمل النوع الثاني ‪.bound service‬‬

‫الجزء االول (‪)started service‬‬


‫ننشا هذا النوع من خالل الذهاب الى ‪ ،File→New→Service→IntentService‬ثم من النافذة التي‬
‫تظهر نعطية اسم ويمكن ان نلغي العالمة من الخيار ‪ include helper start method‬بعدها يمكن ان‬
‫نستبدل الكود الذي يعطيه لنا البرنامج بالكود التالي‪:‬‬

‫;‪package com.hfad.joke‬‬
‫;‪import android.app.IntentService‬‬
‫;‪import android.content.Intent‬‬
‫{ ‪public class MyService extends IntentService‬‬
‫{ )(‪public MyService‬‬
‫;)"‪super("MyService‬‬
‫}‬
‫‪@Override‬‬
‫{ )‪protected void onHandleIntent(Intent intent‬‬
‫‪// Code to do something‬‬
‫}‬
‫}‬

‫حيث ان ‪ MyService‬يمثل االسم الذي اعطيناه للـ ‪ ،service‬وكل ‪ service‬يجب ان يحتوي على‬
‫‪ ،constructor‬وبداخل الدالة ‪ onHandleIntent‬نكتب الكود الذي نريده ان ينفذ عندما يتم استدعاء هذا الـ‬
‫‪ service‬والكود هنا يسعمل في ثريد منفصل في الخلفية‪.‬‬

‫‪118‬‬
‫استدعاء الـ ‪ service‬من داخل الـ ‪activity‬‬
‫لكي يبدأ عمل الـ ‪ service‬نحتاج ان نستدعيه من داخل الـ ‪ activity‬ويتم ذلك بنفس الطريقة التي نبدأ بها‬
‫‪ activity‬جديدة‪ ،‬الفرق الوحيد هو انه لبدأ الـ ‪ service‬نستخدم الدالة ‪ startService‬بداال من الدالة‬
‫‪ ،startActivity‬ولكن ارسال البيانات الى الـ ‪ service‬تكون بنفس الطريقة وذلك من خالل االوبجكت‬
‫‪.intent‬‬

‫لتوضيح فكرة عمل الـ ‪ service‬سنأخذ هذا المثال البسيط‪ ،‬سنعمل زر اعتيادي يظهر للمستخدم (موجود في‬
‫الـ ‪ )activity‬عند الضغط عليه تنفذ دالة اسمها ‪ onClick‬هذه الدالة سترسل رسالة نصية للـ ‪service‬‬
‫ليستلمها ويعرضها في الـ ‪( Log‬نرى الرسالة في برنامج االندرويد ستوديو في الـ ‪ ،)logcat‬اوال نكتب كود‬
‫الدالة ‪ onClick‬في داخل الـ ‪:activity‬‬

‫{)‪public void onClick(View view‬‬


‫;)‪Intent intent = new Intent(this, DelayedMessageService.class‬‬
‫;)"‪intent.putExtra("mess", "My Message‬‬
‫;)‪startService(intent‬‬
‫}‬

‫حيث ان ‪ DelayedMessageService‬يمثل اسم كالس السيرفر الرئيسي الذي انشأناه‪.‬‬

‫ثانيا سنكتب الكود التالي في الـ ‪ service‬في داخل الدالة ‪:onHandleIntent‬‬

‫‪@Override‬‬
‫{ )‪protected void onHandleIntent(Intent intent‬‬
‫;)"‪String text = intent.getStringExtra("mess‬‬
‫;)‪Log.v("DelayedMessageService", text‬‬
‫}‬

‫حيث ان ‪ DelayedMessageService‬يمثل اسم كالس السيرفر الرئيسي الذي انشأناه‪.‬‬

‫عرض معلومات للمستخدم من الـ ‪service‬‬

‫كما ذكرنا سابقا فان ما في داخل الدالة ‪ onHandleIntent‬سيعمل في ثريد في الخلفية‪ ،‬ولكي نعرض اي‬
‫كود للمستخدم يحتاج هذا الكود ان يعمل في الثريد الرئسي‪ ،‬لذا نستخدم الكالس ‪( handler‬شرحنا عنه سابقا)‬
‫في داخل الدالة ‪ onStartCommand‬وهي موجودة في داخل الـ ‪ service‬وتنفذ في الثريد الرئيسي وهي‬

‫‪119‬‬
‫ ليكون كود‬،intent service ‫ حيث يتم استدعائها في كل مرة يبدأ فيها الـ‬onHandleIntent ‫تنفذ قبل الدالة‬
:‫ بهذا الشكل‬service ‫الـ‬

public class MyService extends IntentService {


private Handler handler;
...
@Override
public int onStartCommand(Intent intent, int flags, int startId){
handler = new Handler();
return super.onStartCommand(intent, flags, startId);
}
@Override
protected void onHandleIntent(Intent intent) {
final String text = intent.getStringExtra("mess");
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), text,
Toast.LENGTH_SHORT).show();
}
});
}}

‫ وفي داخلها‬super.onStartCommand ‫ يجب ان تعيد القيمة‬onStartCommand ‫كما تالحظ الدالة‬


‫ ويمكن‬onHandleIntent ‫ لنستخدم هذا االوبجكت في داخل الدالة‬Handler ‫نعمل اوبجكت من الكالس‬
‫ هناك شيء مهم يجب االنتباه‬،)‫ (شرحنا عنها سابقا‬post ‫كتابة اي كود نريد عرضه للمستخدم في داخل الدالة‬
‫ سابقا في الـ‬Context ‫ والذي هو يجب ان يكون المحتوى‬makeText ‫له وهو الباراميتر االول للدالة‬
‫ ودائما عندما نريد االشارة للمحتوى‬this ‫ ال يمكن ان نكتب‬service ‫ لكن في الـ‬this ‫ كنا نضع‬activity
.getApplicationContext ‫ نكتب‬service ‫ من داخل الـ‬Context

‫ وفي‬،‫ لكي تنفذ في الخلفية عند حدوث امر ما‬Service ‫ يمكن كتابة االشعارات في هذا الـ‬/‫مالحظة‬
.‫ لكي يتم عرض المحتوى على الشاشة‬handler ‫االشعارات ال داعي ان نستخدم الكالس‬

120
‫الجزء الثاني (‪)bound service‬‬
‫هذا النوع مرتبط بمكونات اخرى في البرنامج مثل الـ ‪ activity‬والتي يمكنها ان تتفاعل معه كان ترسل له‬
‫الطلبات وتحصل منه على النتائج‪ ،‬وهذا النوع سيبقى يعمل طالما المكون المرتبط معه ال يزال موجود اما إذا‬
‫أصبح ‪ destroyed‬فهذا الـ ‪ service‬سيتوقف ايضا‪.‬‬

‫ننشا هذا النوع من خالل الذهاب الى ‪ ،File→New→Service→Service‬ثم من النافذة التي تظهر‬
‫نعطية اسم ويمكن ان نلغي العالمة من الخيار ‪ exported‬إذا بقيت هذه العالمة فهذا يعني اننا نريد للـ‬
‫‪ service‬من خارج هذا التطبيق ان تنفذ له‪ ،‬لكن الخيار ‪ enabled‬نمكنه النه إذا لم يكن مفعل فهذا يعني ان‬
‫الـ ‪ activity‬ال يمكنها ان تشغل البرنامج‪ ،‬سيكون لنا ملف بهذا الكود‪:‬‬

‫;‪package com.hfad.odometer‬‬
‫;‪import android.app.Service‬‬
‫;‪import android.content.Intent‬‬
‫;‪import android.os.IBinder‬‬
‫{ ‪public class MyService extends Service‬‬
‫‪@Override‬‬
‫{ )‪public IBinder onBind(Intent intent‬‬
‫‪// Code to bind the Service‬‬
‫}}‬

‫حيث ان ‪ MyService‬يمثل اسم الـ ‪ Service‬الذي اعطيناه له‪ ،‬وهو يجب ان يحتوي على الدالة ‪onBind‬‬
‫فمن خاللها نربط الـ ‪ service‬بالـ ‪.activity‬‬

‫يكون ربط الـ ‪ activity‬بالـ ‪ service‬بهذه الخطوات‪:‬‬

‫الـ ‪ activity‬تستخدم االوبجكت ‪ ServiceConnection‬لالتصال بالـ ‪service‬‬ ‫‪-1‬‬


‫الـ ‪ activity‬تمرر ‪ intent‬يحمل المعلومات التي نريد ارسالها الى الـ ‪service‬‬ ‫‪-2‬‬
‫الـ ‪ service‬يعمل االوبجكت ‪ Binder‬وهذا االوبجكت سيحمل مرجع للـ ‪ ،service‬ثم الـ ‪service‬‬ ‫‪-3‬‬
‫سيبعث االوبجكت ‪ Binder‬عبر االتصال الى الـ ‪activity‬‬
‫عندما تستلم الـ ‪ activity‬االوبجكت ‪ Binder‬تستخرج الـ ‪ service‬وتبدأ في استخدامه‬ ‫‪-4‬‬

‫* لكي نسمح للـ ‪ activity‬باالرتباط مع الـ ‪ service‬علينا ان ننشأ االوبجكت ‪ Binder‬ونمرره للدالة‬
‫‪ onBind‬لتعيده لكي تستلمه الـ ‪.activity‬‬

‫انشاء الـ ‪Binder‬‬

‫‪121‬‬
‫يجب ان ننشأ الـ ‪ Binder‬بانفسنا في الـ ‪ Service‬الذي انشأناه قبل قليل‪ ،‬نحن سننشأ الـ ‪ Binder‬على شكل‬
‫كالس داخلي في داخل الـ ‪ service‬وسوف نسميه ‪ ،MyBinder‬ليكون كامل شكل كود الـ ‪ service‬هكذا‪:‬‬

‫{ ‪public class MyService extends Service‬‬


‫;) (‪private final IBinder B = new MyBinder‬‬
‫{‪public class MyBinder extends Binder‬‬
‫{) (‪MyService X‬‬
‫;‪return MyService.this‬‬
‫}‬
‫}‬
‫‪@Override‬‬
‫{ )‪public IBinder onBind(Intent intent‬‬
‫;‪return B‬‬
‫}}‬

‫عندما ترتبط الـ ‪ activity‬مع الـ ‪ service‬فان االتصال سيستدعي الدالة ‪ onBind‬والتي ستعيد االوبجكت‬
‫‪ MyBinder‬فعندما تستلم الـ ‪ activity‬االوبجكت ‪ MyBinder‬من االتصال فهي ستستخدم الدالة ‪X‬‬
‫لتحصل على االوبجكت ‪.MyService‬‬

‫دوال الكالس ‪Service‬‬

‫عندما انشأنا الـ ‪ bound service‬جعلناه يرث الكالس ‪ ،Service‬وهذا الكالس لديه بعض الدوال المهمة‬
‫منها‪:‬‬

‫الي شيء تستخدم‬ ‫متى تستدعى‬ ‫الدالة‬


‫لالجراءات التي نريدها ان تنفذ اول بدأ الـ‬ ‫عندما يتم انشاء الـ ‪service‬‬
‫) (‪onCreate‬‬
‫‪service‬‬ ‫الول مرة‬
‫نحن ال نحتاج ان نستخدم هذه الدالة إذا لم يكن الـ‬ ‫عندما تبدأ الـ ‪ activity‬الـ‬
‫(‪onStartCommand‬‬
‫‪ service‬الذي نستخدمه من النوع االول‬ ‫‪ service‬من خالل الدالة‬
‫)‬
‫‪started service‬‬ ‫‪startService‬‬
‫يجب علينا دائما تنفيذ هذه الدالة وجعلها تعيد‬
‫اوبجكت ‪ IBinder‬وإذا لم نكن نريد الـ‬ ‫عندما تحتاج ‪ activity‬ان‬
‫) (‪onBind‬‬
‫‪ activities‬ان ترتبط بالـ ‪ service‬نجعلها تعيد‬ ‫ترتبط بالـ ‪service‬‬
‫القيمة ‪null‬‬
‫استخدم هذه الدالة لتنظيف كل المصادر المفتوحة‬ ‫عندما لم تعد الـ ‪service‬‬ ‫) (‪onDestroy‬‬

‫‪122‬‬
‫تستخدم وهي على وشك ان‬
‫تتحطم‬

service ‫ بالـ‬activity ‫ربط الـ‬

:‫ بالخطوتين التاليتين‬service ‫ بالـ‬activity ‫لكي نربط الـ‬

:‫ والذي هو عبارة عن انترفيس فيه دالتين هما‬ServiceConnection ‫ نستخدم االوبجكت‬:‫اوال‬

‫ واالوبجكت‬service ‫ يتم استدعاء هذه الدالة عندما ينشأ االتصال بالـ‬onServiceConnected ‫الدالة‬
.service ‫ يستلم والذي من خالله نأخذ مرجع للـ‬Binder

.service ‫ تستدعى هذا الدالة عندما يفقد االتصال بالـ‬onServiceConnected ‫الدالة‬

‫ الن‬Binder ‫ بهذا الشكل (راجع الكود السابق في انشاء الـ‬activity ‫يمكن ان يكون شكل الكود في داخل الـ‬
:)‫ واالوبجكت والدوال داخله‬service ‫هذا الكود يعتمد عليه في اسم الـ‬

public class MainActivity extends Activity {


private OdometerService odometer;
private boolean bound = false;
...
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
MyService.MyBinder bnd= (MyService.MyBinder)binder;
odometer = bnd.X( );
bound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
bound = false;
}
}; }

‫ ليوجه الى الـ‬intent ‫ وننشأ‬bindService ‫ من خالل الدالة‬service ‫ بالـ‬activity ‫ نربط الـ‬:‫ثانيا‬


‫ إذا أردنا الربط‬onStart ‫ ويمكن ان يكون في داخل الدالة‬activity ‫ يكتب هذا الكود في داخل الـ‬،service
:‫ بهذا الشكل‬،activity ‫ان يتم في بداية عرض الـ‬

123
‫‪@Override‬‬
‫{)(‪protected void onStart‬‬
‫;)(‪super.onStart‬‬
‫;)‪Intent intent = new Intent(this, MyService.class‬‬
‫;)‪bindService(intent, connection, Context.BIND_AUTO_CREATE‬‬
‫}‬

‫حيث ان ‪ Context.BIND_AUTO_CREATE‬يخبر االندرويد ان ينشأ الـ ‪ service‬إذا لم يكن موجود‪،‬‬


‫بهذه الخطوتين أصبح عندنا اتصال وترابط بين الـ ‪ activity‬والـ ‪ service‬واالن يمكننا نستخدم الدوال‬
‫المكتوبة في داخل الـ ‪ service‬من داخل الـ ‪.activity‬‬

‫* يفضل ان نلغي الربط بالـ ‪ service‬إذا كانت الـ ‪ activity‬غير مرئية (دخلت في دورة الحياة ‪ )stop‬من‬
‫خالل الدالة ‪ ،unbindService‬يمكن ان يكون الكود بهذا الشكل‪:‬‬

‫‪@Override‬‬
‫{)(‪protected void onStop‬‬
‫;)(‪super.onStop‬‬
‫{)‪if (bound‬‬
‫;)‪unbindService(connection‬‬
‫;‪bound = false‬‬
‫}‬
‫}‬

‫الحظ عملنا ‪ if‬الشرط لنتأكد من ان االتصال كان قد تم عند الربط ولم يفشل‪ bound ،‬هو اسم متغير انشأناه‬
‫قبل قليل‪ ،‬و‪ connection‬هو اسم اوبجكت انشأناه قبل قليل‪.‬‬

‫الموقع ‪Location‬‬
‫إذا أردنا تحدد موقع الجهاز نستخدم ‪ location service‬وهو سيستخدم المعلومات من ‪ GPS‬النظام‪ ،‬من‬
‫خالل الخطوتين التاليتين‪:‬‬

‫اوال‪ :‬ننشأ ‪ LocationListener‬جديد ونضع فيه الدوال التالية‪ ،‬هكذا‪:‬‬

‫{ )(‪LocationListener listener = new LocationListener‬‬


‫‪@Override‬‬

‫‪124‬‬
‫} { )‪public void onLocationChanged(Location location‬‬
‫‪@Override‬‬
‫} { )‪public void onStatusChanged(String provider, int status, Bundle extras‬‬
‫‪@Override‬‬
‫} { )‪public void onProviderEnabled(String provider‬‬
‫‪@Override‬‬
‫} { )‪public void onProviderDisabled(String provider‬‬
‫;}‬

‫الدالة االولى ‪ onLocationChanged‬يتم استدعائها في كل مرة يتم فيها اخبار الـ ‪ LocationListener‬ان‬
‫موقع الجهاز قد تغير‪ ،‬الباراميتر الممرر لها يقدم الموقع الحالي‪ ،‬الدالة الثانية ‪ onStatusChanged‬يتم‬
‫استدعائها عندما تتغير حالة الـ ‪ ،GPS‬الدالة الثالثة ‪ onProviderEnabled‬يتم استدعائها عندما يتم تفعيل‬
‫الـ ‪ ،GPS‬الدالة الرابعة ‪ onProviderDisabled‬يتم استدعائها عندما يتم الغاء تفعيل الـ ‪ ،GPS‬يمكن ان‬
‫نضع الكود الذي نريد تنفيذه حسب الحالة في الدالة المناسبة اما الدالة التي النحتاج ان نكتب فيها كود يمكن‬
‫ان نتركها فارغة لكن يجب ان تكتب كل هذه الدوال حتى لو لم نكن نحتاجها‪.‬‬

‫ثانيا‪ :‬نربط الـ ‪ LocationListener‬بالـ ‪ location service‬من خالل االوبجكت ‪LocationManager‬‬


‫وهذا االوبجكت يعطينا نفوذ للـ ‪ ،location service‬يمكن ان ننشأ اوبجكت جديد بهذا الشكل‪:‬‬

‫= ‪LocationManager lm‬‬
‫)‪(LocationManager‬‬
‫;)‪getSystemService(Context.LOCATION_SERVICE‬‬

‫وقد رأينا سابقا في االشعارت كيف استخدمنا الدالة ‪ getSystemService‬واالن نستخدمها ايضا لنحدد نوع‬
‫الـ ‪ SERVICE‬وفي حالتنا هو ‪ ،LOCATION‬بعد ان ننشأ هذا االوبجكت علينا ان نستخدمة دالته‬
‫‪ requestLocationUpdates‬لنحدد معايير تحديث الـ ؤ وهي تأخذ اربع بارامترات االول يمثل مزود الـ‬
‫‪ ،GPS‬اما الثاني فيمثل اقل وقت بين كل تحديث للموقع بالملي ثانية‪ ،‬اما الثالث فهو يمثل اقصر مسافة‬
‫لتحديث الموقع بالمتر‪ ،‬اما الرابع فيمثل الـ ‪ ،LocationListener‬يمكن ان يكون شكل الدالة هكذا‪:‬‬

‫‪lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1,‬‬


‫;)‪listener‬‬

‫اخذ االذن الستخدام الـ ‪GPS‬‬

‫‪125‬‬
‫بعد ان عملنا الخطوتين السابقتين لتحديد الموقع علينا ان نأخذ االذن من المستخدم لكي نستطيع استخدام الـ‬
‫‪ GPS‬وإال لن يسمح لنا نظام االندرويد باستخدامه‪ ،‬ولكي نخبر نظام االندرويد اننا بحاجة الى االذن نحتاج ان‬
‫نذكر ذلك في الملف ‪ AndroidManifest.xml‬باستخدام الوسم <‪ >uses-permission‬بهذا الشكل‪:‬‬

‫‪<uses-permission‬‬
‫>‪android:name="android.permission.ACCESS_FINE_LOCATION" /‬‬

‫إذا لم نكتب هذا الكود فان البرنامج لن يعمل‪.‬‬

‫الدالة ‪distanceTo‬‬

‫تستخدم هذه الدالة لتحديد المسافة ب ين موقعين باالمتار‪ ،‬وهي تأخذ باراميتر واحد عبارة عن متغير من النوع‬
‫‪ ،Location‬يمكن ان يكون شكل الدالة هكذا‪:‬‬

‫;)‪double X = location.distanceTo(lastLocation‬‬

‫حيث ان ‪ location‬هو اوبجكت من النوع ‪ Location‬يمثل الموقع الحالي و‪ lastLocation‬هو متغير من‬
‫النوع ‪ Location‬يحمل قيمة اخر موقع كان فيه الجهاز‪ ،‬هنا سيحمل المتغير ‪ X‬رقم يمثل عدد االمتار بين‬
‫اخرموقع والموقع الحالي‪.‬‬

‫الفصل الحادي عشر (‪)Sensors‬‬

‫الـ ‪ Sensors‬هي المتحسسات الموجودة في الهاتف مثل متحسس درجة االضاءة ومتحسس االتجاه واالرتفاع‬
‫إلخ‪ ،‬سنتعلم في هذا الفصل كيف نتحكم ونتعامل معها‪ ،‬انواع المستشعرات هي‪:‬‬

‫‪Temperature sensor -1‬‬


‫وجد في نظام االندرويد ‪ ،API level 14‬وهذا مستشعر لدرجة الحرارة يعطيها بالسيليزي‪ ،‬وهو‬
‫يستشعر درجة حرارة الجو المحيط به‪.‬‬

‫‪126‬‬
‫‪Accelerometer -2‬‬
‫يقيس التسارع بثالثة محاور بوحدة ‪m/s2‬‬
‫‪Gravity sensor -3‬‬
‫يعيد االتجاه الحالي باالستناد على ثالثة محاور ويعيد الجاذبية نوحدة ‪ ،m/s2‬هذا المستشعر ينفذ‬
‫بشكل افتراضي كفلتر افتراضي للمستشعر السابق (مستشعر التسارع)‬
‫‪Linear acceleration sensor -4‬‬
‫تسارع ثالثة محاور خطية‬
‫‪Gyroscope sensor -5‬‬
‫معدل دوران الجهاز في ثالث محاور لتحديد االتجاه بقيمة ‪ ،radians/second‬وفي الغالب يستخدم‬
‫مع (مستشعر التسارع) لتحديد االتجاه بدقة‪.‬‬
‫‪Rotation vector sensor -6‬‬
‫عائدات اتجاه الجهاز كمجموع الزاوية حول الجهاز‪ ،‬بشكل نموذجي يستخدم كمدخل للدالة‬
‫‪( getRotationMatrixFromVector‬هذه الدالة تابعة الى ‪ )SensorManager‬لتحول نتائجه‬
‫الى شكل مصفوفة‪ ،‬هذا المستشعر يستخدم بشكل افتراضي لتصحيح النتائج العائدة من الكثير من‬
‫المستشعرات األخرى مثل ‪ gyroscopes‬و‪accelerometers‬‬
‫‪Magnetic field sensor -7‬‬
‫مقياس للمغناطيسية الذي يجد الحقل المغناطيسي الصحيح على المحاور الثالثة بالمايكرو تسال ‪.μT‬‬
‫‪Pressure sensor -8‬‬
‫مقياس الغضط الجوي‪ ،‬يعيد قيمة الضغط الجوي بالملي بار ‪ mbars‬كقيمة مفردة‪ ،‬هذا المستشعر‬
‫يمكن ان يستخدم لقياس االرتفاع بالتعاون مع الدالة ‪ getAltitude‬التابعة لـ ‪SensorManager‬‬
‫لتقارن الضغط الجوي في موقعين‪ ،‬اويمكن من خالله التنبأ بحالة الجو‪.‬‬
‫‪Relative humidity sensor -9‬‬
‫متحسس الرطوبة بشكل نسبي ويعيد النتيجة على شكل نسبة مئوية‪ ،‬هذا المستشعر موجود وجد في‬
‫نظام االندرويد ‪.API level 14‬‬
‫‪Proximity sensor‬‬ ‫‪-10‬‬
‫متحسس المسافة‪ ،‬هذا المستشعر يقيس المسافة بين الجهاز واالوبجكت الهدف بالسانتيميتر‪ ،‬كيفية‬
‫تحديد المسافة تعتمد على نوع المستشعر فبعض االنواع يعيد فقط النتيجة ‪ near‬او ‪ far‬وليس‬
‫بالسانتيميتر‪ ،‬بشكل افتراضي يستخدم هذا المستشعر لتحديد البعد عن االذن الطفاء االضاءة عند‬
‫االتصال مثال‪.‬‬
‫‪Light sensor‬‬ ‫‪-11‬‬
‫يقيس شدة االضاءة المحيطة بوحدة اللوكس ‪ ،lux‬وفي العادة يستخدم هذا المستشعر لتحديد اضاءة‬
‫الشاشة‪.‬‬

‫‪127‬‬
‫للوصول الى اي متحسس علينا اوال ان نتعامل مع ‪ SensorManager‬ونأخذ مرجع له من خالل الدالة‬
‫‪( getSystemService‬شرحنا عنها في الفصل التاسع)‪ ،‬بهذا الشكل‪:‬‬

‫‪SensorManager‬‬ ‫‪SM‬‬ ‫=‬


‫;)‪(SensorManager)getSystemService(Context.SENSOR_SERVICE‬‬

‫للوصول الى مستشعر محدد نستخدم االوبجكت ‪ ،Sensor‬فهو يحتوي على عدة ثوابت والتي تحدد نوع‬
‫المستشعر الذي نريد التعامل معه‪ ،‬وهذه الثوابت هي‪:‬‬

‫‪Sensor.TYPE_AMBIENT_TEMPERATURE -1‬‬
‫‪Sensor.TYPE_ACCELEROMETER -2‬‬
‫‪Sensor.TYPE_GRAVITY -3‬‬
‫‪Sensor.TYPE_LINEAR_ACCELERATION -4‬‬
‫‪Sensor.TYPE_GYROSCOPE -5‬‬
‫‪Sensor.TYPE_ROTATION_VECTOR -6‬‬
‫‪Sensor.TYPE_MAGNETIC_FIELD -7‬‬
‫‪Sensor.TYPE_PRESSURE -8‬‬
‫‪Sensor.TYPE_RELATIVE_HUMIDITY -9‬‬
‫‪Sensor.TYPE_PROXIMITY‬‬ ‫‪-10‬‬
‫‪Sensor.TYPE_LIGHT‬‬ ‫‪-11‬‬

‫* الحظ انه ليس كل اجهزة االندرويد تحتوي على كل هذه المستشعرات‪ ،‬اغلب االجهزة تحتوي على بعض‬
‫من هذه المستشعرات‪ ،‬لذلك من المهم ان نعرف الجهاز الذي سيعمل عليه برنامجنا هل يحتوي على هذا‬
‫المستشعر ام ال‪ ،‬هناك طريقتين لمعرفة الجهاز ان كان يحتوي على مستشعر معين ام ال‪.‬‬

‫الطريقة االولى‬

‫هذه الطريقة تشمل التطبيقات المنشورة على متجر ‪ Google Play‬فقط‪ ،‬النه بهذه الطريقة المتجر سوف لن‬
‫يعرض البرنامج لالجهزة التي ال تحتوي على المستشعر الذي نحدده في الملف ‪AndroidManifest.xml‬‬
‫النه في هذا الملف سنحدد هذا المستشعر‪ ،‬مثال إذا أردنا ان يعرض البرنامج فقط لالجهزة التي تحتوي على‬
‫المستشعر ‪ proximity‬نكتب في الملف ‪ AndroidManifest.xml‬الكود التالي‪:‬‬

‫>‪<uses-feature android:name="android.hardware.sensor.proximity" /‬‬

‫لكن هذه الطريقة ال تحدد كل التفاصيل التي نريد ان نعرفها عن هذا المستشعر فقط تحدد ان كان موجود ام‬
‫ال‪.‬‬

‫‪128‬‬
‫الطريقة الثانية‬

‫هذه الطريقة نكتشف من خاللها ان كان الجهاز يحتوي على مستشعر معين او ال بعد ان يتم تنصيب التطبيق‬
‫على الجهاز‪ ،‬ميزة هذه الطريقة انه تمكننا من معرفة تفاصيل المستشعر‪ ،‬يكتب كود هذه الطريقة في ملف‬
‫الجافا من خالل االستعانة بالكالس ‪ List‬وبالدالة ‪ getSensorList‬هذه الدالة تعيد القيم على شكل مصفوفة‬
‫من النوع ‪ Sensor‬وهي تأخذ باراميتر واحد بين قوسيها هذا الباراميتر يحدد القيم التي ستعيدها‪ ،‬مثال اذا‬
‫اردناها ان تعيد معلومات عن مستشعر معين نكتبه لها كباراميتر مثال اذا اردنا ان تعيد معلومات عن‬
‫المستشعر ‪ Gyroscope‬نكتب ‪ Sensor.TYPE_GYROSCOPE‬ليكون باراميتر لها‪ ،‬بهذا الشكل‪:‬‬

‫= ‪SensorManager mgr‬‬
‫;)‪(SensorManager)getSystemService(Context.SENSOR_SERVICE‬‬
‫;)‪List<Sensor> Sen = mgr.getSensorList(Sensor.TYPE_GYROSCOPE‬‬

‫االن اصبحت عندنا مصفوفة باالسم ‪ Sen‬من النوع ‪ Sensor‬تحتوي على كل المعلومات عن المستشعر‬
‫‪ ،Gyroscope‬اما إذا أردنا الدالة ان تعيد معلومات عن كل المستشعرات الموجودة في الجهاز نمرر لها‬
‫الباراميتر ‪ ،Sensor.TYPE_ALL‬بهذا الشكل‪:‬‬

‫;)‪List<Sensor> Sen = mgr.getSensorList(Sensor.TYPE_ALL‬‬

‫االن أصبح عندنا مصفوفة اسمها ‪ Sen‬من النوع ‪ Sensor‬تحتوي على كل المعلومات عن كل المستشعرات‬
‫في الجهاز والستخراج هذه القيم يمكن ان نستخدم الدوال التالية كما موضح في هذا المثال‪( :‬يكتب هذا الكود‬
‫في ملف الجافا في داخل الدالة ‪)onCreate‬‬

‫;‪String info‬‬
‫‪@Override‬‬
‫{ )‪public void onCreate(Bundle savedInstanceState‬‬
‫‪...‬‬
‫;)‪TextView text = (TextView)findViewById(R.id.text‬‬
‫= ‪SensorManager mgr‬‬
‫;)‪(SensorManager)getSystemService(Context.SENSOR_SERVICE‬‬
‫;)‪List<Sensor> Sen = mgr.getSensorList(Sensor.TYPE_ALL‬‬
‫{ )‪for(Sensor s : Sen‬‬
‫;"‪info +="Name: " + s.getName() + "\n‬‬
‫;"‪info +=" Vendor: " + s.getVendor() + "\n‬‬
‫;"‪info +=" Version: " + s.getVersion() + "\n‬‬
‫;"‪info +=" Resolution: " + s.getResolution() + "\n‬‬

‫‪129‬‬
‫;"‪info +=" Max Range: " + s.getMaximumRange() + "\n‬‬
‫;"‪info +=" Power: " + s.getPower() + " mA\n‬‬
‫}‬
‫;)‪text.setText(info‬‬
‫}‬

‫هذا المثال سيعرض لنا المعلومات في حقل نصي على الشاشة حيث ان الدالة ‪ getName‬تعيد اسم المستشعر‬
‫والدالة ‪ getVendor‬تعيد اسم الشركة المصنعة لهذا المستشعر والدالة ‪ getVersion‬تعيد نسخة اصدار‬
‫المستشعر والدالة ‪ getResolution‬تعيد دقة المستشعر بوحدة تناسب نوع المستشعر والدالة‬
‫‪ getMaximumRange‬تعيد اقصى نطاق للمستشعر بوحدة تناسب نوع المستشعر اما الدالة ‪getPower‬‬
‫تعيد قياس الطاقة الكهربائية التي يستهلكها المستشعر في الوقت الحالي بالملي امبير‪.‬‬

‫االصغاء للمستشعرات‬

‫يمكننا اخذ المعلومات من المستشعرات والتعامل معها في برنامجنا من خالل االصغاء لها فاذا لم نكن نصغي‬
‫فهي لن تكون قيد العمل وذلك حفاظا على البطارية‪ ،‬لذا علينا التأكد من عدم استخدام المستشعرات ان لم نكن‬
‫نحتاج اليها‪ ،‬اوال علينا ان نجعل ملف الجافا في الـ ‪ activity‬يرث (‪ )implements‬االنترفيس‬
‫‪ ،SensorEventListener‬ثانيا علينا ان نعمل اوبجكت لمستشعر معين ليكون مرجع للتعامل معه وذلك من‬
‫خالل الدالة ‪ getDefaultSensor‬التابعة الى ‪ ،SensorManager‬مثال للوصول الى مستشعر الضوء‬
‫نكتب الكود التالي‪:‬‬

‫= ‪SensorManager mgr‬‬
‫;)‪(SensorManager)getSystemService(Context.SENSOR_SERVICE‬‬
‫;)‪Sensor Lg = mgr.getDefaultSensor(Sensor.TYPE_LIGHT‬‬

‫إذا أردنا ان نصل الى مستشعر اخر فقط نغير اسم المستشعر الممرر لها كباراميتر‪ ،‬اذا كان المستشعر غير‬
‫موجود في الجهاز فهذه الدالة ستعيد القيمة ‪.null‬‬

‫الدالة ‪registerListener‬‬

‫هذه الدالة تصغي الى اي تغير ممكن ان يحدث في مستشعر معين‪ ،‬ويمكن ان نحدد لها المدة التي نريدها ان‬
‫تخبرنا عن التغيرات التي تحدث في المستشعر‪ ،‬من خالل أحد هذه القيم‪:‬‬

‫‪( SENSOR_DELAY_NORMAL -1‬تخبرنا عن التغير كل ‪ 200,000‬ملي ثانية)‬


‫‪( SENSOR_DELAY_UI -2‬تخبرنا عن التغير كل ‪ 60,000‬ملي ثانية)‬

‫‪130‬‬
‫‪( SENSOR_DELAY_GAME -3‬تخبرنا عن التغير كل ‪ 20,000‬ملي ثانية)‬
‫‪( SENSOR_DELAY_FASTEST -4‬تخبرنا عن التغير باقصى سرعة ممكنة)‬

‫يمكن ان يكون شكل الدالة هكذا‪:‬‬

‫;)‪mgr.registerListener(this, Lg, SensorManager.SENSOR_DELAY_NORMAL‬‬

‫حيث ان ‪ mgr‬يمثل مرجع للـ ‪ SensorManager‬والباراميتر الثاني ‪ Lg‬يمثل اوبجكت المستشعر الذي‬
‫حددناه عبر الدالة ‪.getDefaultSensor‬‬

‫الدالتين ‪ onSensorChanged‬و‪onAccuracyChanged‬‬

‫يجب كتابة هاتين الدالتين الننا جعلنا الـ ‪ activity‬ترث االنترفيس ‪ ،SensorEventListener‬حيث ان‬
‫الدالة ‪ onAccuracyChanged‬تقيس قيم المستشعر وهي تاخذ باراميتر واحد هو عبارة عن اوبجكت من‬
‫النوع ‪ SensorEvent‬سنشرح عنه بعد قليل‪ ،‬الدالة ‪ onAccuracyChanged‬تؤدي رد فعل عند حدوث‬
‫تغير في قيمة المستشعر وهي تأخذ بارامتيرن االول عبارة عن اوبجكت من النوع ‪ Sensor‬اما الباراميترر‬
‫الثاني هو عبارة عن متغير من النوع ‪.int‬‬

‫* يمكن ان نصغي الى المستشعرات في داخل الـ ‪ activity‬بدون ان نجعل الـ ‪ activity‬ترث‬
‫(‪ )implements‬االنترفيس ‪ SensorEventListener‬وذلك من خالل كتابة هذا االنترفيس في داخلها على‬
‫شكل كالس داخلي‪ ،‬بهذا الشكل‪:‬‬

‫{ )(‪final SensorEventListener mySensorEventListener = new SensorEventListener‬‬


‫{ )‪public void onSensorChanged(SensorEvent sensorEvent‬‬
‫‪// TODO Monitor Sensor changes.‬‬
‫}‬
‫{ )‪public void onAccuracyChanged(Sensor sensor, int accuracy‬‬
‫‪// TODO React to a change in Sensor accuracy.‬‬
‫}‬
‫;}‬

‫الباراميتر ‪ SensorEvent‬له أربع خصائص ليصف احداث المستشعر‪ ،‬وهي‪:‬‬

‫‪ sensor -1‬اوبجكت المستشعر الذي بدأ الحدث‪.‬‬


‫‪ accuracy -2‬دقة المستشعر ندما يحدث الحدث (‪.)low, medium, high, unreliable‬‬
‫‪ timestamp -3‬الوقت بالنانو ثانية لحدوث حدث المستشعر‪.‬‬

‫‪131‬‬
‫‪ values -4‬مصفوفة عددية من النوع ‪ float‬تحتوي على القيمة او القيم الجديدة المالحظة‪ ،‬يمكن ان‬
‫تكون القيم (عناصر المصفوفة) مختلفة من مستشعر الخر‪ ،‬الجدوال تالي يوضح قيمها‪:‬‬

‫‪132‬‬
‫الدالة ‪unregisterListener‬‬

‫تستخدم هذه الدالة عندما نريد التوقف لالصغاء الى المستشعر (حفاضا على البطارية وسرعة البرنامج) وهي‬
‫تأخذ بارامترين‪ ،‬االول يمثل المحتوى الحالي اما الثاني يمثل اوبجكت المستشعر الذي عملناه له من خالل‬
‫الدالة ‪ ،getDefaultSensor‬إذا أردنا ان نوقف االصغاء الى مستشعر الضوء مثال (اسم اوبجكته ‪)Lg‬‬
‫نكتب الكود التالي‪:‬‬

‫;)‪mgr.unregisterListener(this, Lg‬‬

‫حيث ان ‪ mgr‬يمثل مرجع للـ ‪.SensorManager‬‬

‫مراقبة تحرك الجهاز ودورانه‬

‫‪133‬‬
‫المستشعرات ‪ Accelerometers‬و‪ compasses‬و‪ gyroscopes‬تعطينا االمكانية لتنفيذ وظيفة معينة‬
‫باالعتماد على ميالن الجهاز ودورانة‪ ،‬فباالعتماد على هذه المستشعرات يمكننا ان ننفذ طرق ادخال مبتكرة‬
‫باالظافة الى لمس الشاشة والكيبورد‪ ،‬يمكننا بهذه المميزات ان نؤدي وظائف ال حصر لها‪.‬‬

‫وضع ميالن الجهاز الطبيعي‬

‫قبل البدء بقياس تحرك وميالن الجهاز علينا ان نعرض الوضع االعتيادي للجهاز‪ ،‬حيث تكون قيمة المحاور‬
‫الثالثة ‪ ،0‬الوضع الطبيعي للجهاز يختلف من جهاز الخر لكن بالنسبة الغلب الهواتف الذكية يكون عندما‬
‫يكون الجهاز موضوع على ظهره (الشاشة لالعلى) على طاولة واعلى الجهاز يشير باتجاه الشمال‪،‬‬
‫المستشعرات ي مكن ان تعيد لنا قيم المحاور باالعتماد على الوضع الطبيعي للجهاز او باالعتماد على وضع‬
‫العرض للجهاز (الوضع الذي يمسكه به المستخدم ليتصفح الجهاز‪ ،‬يمكن ان يكون بشكل عمودي او افقي)‬

‫يمكننا ان نجد دوران الشاشة الحالي باستخدام الدالة ‪ getRotation‬التابعة لالوبجكت ‪ ،Display‬بهذا‬
‫الشكل‪:‬‬

‫;‪String windowSrvc = Context.WINDOW_SERVICE‬‬


‫;))‪WindowManager wm = ((WindowManager) getSystemService(windowSrvc‬‬
‫;)(‪Display display = wm.getDefaultDisplay‬‬
‫;)(‪int rotation = display.getRotation‬‬
‫{ )‪switch (rotation‬‬
‫‪case (Surface.ROTATION_0) : break; // Natural‬‬
‫‪case (Surface.ROTATION_90) : break; // On its left side‬‬
‫‪case (Surface.ROTATION_180) : break; // Upside down‬‬
‫‪case (Surface.ROTATION_270) : break; // On its right side‬‬
‫;‪default: break‬‬
‫}‬

‫‪134‬‬
‫الفصل الثاني عشر (‪)Bluetooth & Wifi‬‬

‫‪The Bluetooth‬‬
‫لكي نستخدم بلوتوث الجهاز نحتاج ان ناخذ االذن في البداية من داخل الملف ‪AndroidManifest.xml‬‬
‫بهذا الشكل‪:‬‬

‫>‪<uses-permission android:name=”android.permission.BLUETOOTH”/‬‬

‫إذا أردنا ان يكون عندنا االذن كآدمن في الجهاز لتغيير اعدادات البلوتوث نستخدم هذا االذن باالضافة الى‬
‫االذن السابق‪:‬‬

‫>‪<uses-permission android:name=”android.permission.BLUETOOTH_ADMIN”/‬‬

‫بعد ذلك علينا ان نأخذ مرجع للبلوتوث في داخل الـ ‪ activity‬لنتعامل معه وذلك من خالل الكالس‬
‫‪ BluetoothAdapter‬وبأستدعاء الدالة ‪ getDefaultAdapter‬بهذا الشكل‪:‬‬

‫;)(‪BluetoothAdapter blu = BluetoothAdapter.getDefaultAdapter‬‬

‫هذا الكود سيعيد لنا القيمة ‪ null‬إذا كان الجهاز ال يدعم البلوتوث‪ ،‬اما إذا كان يدعم البلوتوث ستعيد لنا مرجع‬
‫لبلوتوث الجهاز وسنحتاجه في كل العمليات التي سنجريها على البلوتوث‪ ،‬بعد ذلك علينا التأكد من ان‬
‫البلوتوث في الجهاز يعمل قبل استخدامه وذلك من خالل الدالة ‪ isEnabled‬التابعة للكالس‬
‫‪ BluetoothAdapter‬والتي تعيد القيمة ‪ true‬إذا كان البلوتوث يعمل او تعيد القيمة ‪ false‬إذا لم يكن يعمل‬
‫البلوتوث‪ ،‬بهذا الشكل‪:‬‬

‫{ )) (‪if (blu.isEnabled‬‬
‫;)(‪String address = blu.getAddress‬‬
‫;)(‪String name = blu.getName‬‬
‫}‬

‫حيث من خالل الدالة ‪ getName‬نحصل على اسم البلوتوث ومن خالل الدالة ‪ getAddress‬نحصل على‬
‫عنوانه (والذي يسمى ‪.)MAC address‬‬

‫* إذا كنا قد اخذنا االذن كادمن في الملف ‪ Manifest‬فانه يمكننا ان نغير اسم بلوتوث الجهاز من خالل الدالة‬
‫‪ setName‬بهذا الشكل‪:‬‬

‫‪135‬‬
‫;)”‪blu.setName(“Ahmed‬‬

‫* اليجاد حالة البلوتوث في الوقت الحالي يمكننا استخدام الدالة ‪ getState‬والتي تعيد أحد هذه القيم‪:‬‬

‫‪STATE_TURNING_ON -1‬‬

‫‪STATE_ON -2‬‬

‫‪STATE_TURNING_OFF -3‬‬

‫‪STATE_OFF -4‬‬

‫تشغيل واطفاء البلوتوث‬


‫بما ان اغلب المستخدمين يبقون البلوتوث مغلق في جهازهم ان لم يكن يحتاجون الى استخدامه لذا عندما‬
‫نحتاج ان نستخدم البلوتوث في برنامجنا علينا ان نطلب فتح البلوتوث من المستخدم وذلك من خالل عمل‬
‫‪ Intent‬جديد ونمرر له القيمة ‪ BluetoothAdapter.ACTION_REQUEST_ENABLE‬ومن ثم‬
‫نمرره الى الدالة ‪( startActivityForResult‬شرحنا عنها في الفصل الثالث) بهذا الشكل‪:‬‬

‫;)‪Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE‬‬


‫;)‪startActivityForResult(intent, 0‬‬

‫هذا الكود سيظهر رسالة للمستخدم (فقط إذا كان البلوتوث مغلق) تطلب منه ان يفتح البلوتوث فاذا ضغط على‬
‫موافق سيتم تشغيل البلوتوث اما إذا ضغط على ال لن يتم تشغيله‪ ،‬تكون الرسالة بهذا الشكل‪:‬‬

‫الدالة ‪ startActivityForResult‬شرحنا عنها في الفصل الثالث وقلنا انها تستخدم لفتح ‪ activity‬جديدة‬
‫وتستلم البيانات التي تعيدها الـ ‪ activity‬االبن من خالل الدالة ‪( onActivityResult‬الرسالة المنبثقة التي‬
‫تظهر للمستخدم تعتبر ‪ activity‬ابن) لذا من خالل هذه الدالة يمكننا ان نعرف فيما إذا ضغط المستخدم على‬
‫موافق او الغاء في النافذة المنبثقة‪ ،‬فاذا ضغط على موافق فهذه الدالة ستستلم القيمة ‪ RESULT_OK‬اما إذا‬
‫ضغط على الغاء (او لم يشتغل البلوتوث الي سبب كان) فهذه الدالة ستستلم القيمة‬
‫‪.RESULT_CANCELED‬‬

‫‪136‬‬
:‫مثال‬

@Override
public void onCreate(Bundle savedInstanceState) {
...
BluetoothAdapter blu = BluetoothAdapter.getDefaultAdapter();
if (! blu.isEnabled() ) {
// Bluetooth isn’t enabled, prompt the user to turn it on.
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, 1);
} else {
// Bluetooth is enabled, Do something
}}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1 && resultCode == RESULT_OK) {
// Bluetooth has been enabled, Do something
}}

‫* يمكننا ان نغلق ونفتح البلوتوث بدون طلب االذن من المستخدم إذا كنا قد اخذنا االذن كآدمن في الملف‬
‫ لتشغيل البلوتوث‬enable ‫ (شرحنا في بداية هذا الفصل كيف نعمل ذلك) وذلك من خالل الدالة‬Manifest
:‫ بهذا الشكل‬،BluetoothAdapter ‫ كال الدالتين تابعتين للكالس‬،‫ الطفاء البلوتوث‬disable ‫والدالة‬

BluetoothAdapter blu = BluetoothAdapter.getDefaultAdapter();


blu.enable();
blu.disable();

‫جعل الجهاز مرئي‬


:‫ تعيد أحد قم الثوابت التالية‬BluetoothAdapter ‫ التابعة للكالس‬getScanMode ‫الدالة‬

SCAN_MODE_CONNECTABLE_DISCOVERABLE -1
‫ وهذا يعني ان الجهاز مرئي ويمكن‬،‫هذه القيمة تعني ان تحقق البحث وصفحة البحث كالهما مفعل‬
.‫رؤيته من قبل االجهزة االخرى لالتصال به‬
SCAN_MODE_CONNECTABLE -2

137
‫هذه القيمة تعني ان تحقق الصفحة مفعل فقط‪ ،‬وهذا يعني انه يمكن لالجهزة التي كان عندها ارتباط‬
‫مع هذا البلوتوث ان تجده وتتواصل معه اما االجهزة االخرى فال يمكنها ان تراه عند البحث‪.‬‬
‫‪SCAN_MODE_NONE -3‬‬
‫هذه القيمة تعني ان الجهاز غير مرئي وال يمكن الي جهاز اخر ان يراه عند البحث‪.‬‬

‫بشكل افتراضي تكون بلوتوثات اجهزة االندرويد غير مرئية لجعلها مرئية علينا طلب االذن من المستخدم‬
‫عبر رسالة منبثقة (الرسالة المنبثقة تعتبر ‪ activity‬ابن)‪ ،‬نعمل ذلك من خالل عمل ‪ Intent‬جديد ونمرر له‬
‫القيمة ‪ ACTION_REQUEST_DISCOVERABLE‬وبعدها نمرر هذا الـ ‪ Intent‬الى الدالة‬
‫‪( startActivityForResult‬شرحنا عنها في الفصل الثالث)‪ ،‬ليكون الكود بهذا الشكل‪:‬‬

‫‪Intent intent = new‬‬


‫;)‪Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE‬‬
‫;)‪startActivityForResult(intent, 0‬‬

‫بكتابة هذا الكود ستظهر رسالة للمستخدم بهذا الشكل‪:‬‬

‫إذا ضغط المستخدم على الزر موفق في الرسالة سيكون البلوتوث مرئي لالجهزة االخرى لمدة ‪ 120‬ثانية‬
‫وبعدها يعود غير مرئي‪ ،‬وهذا الوقت هو بشكل افتراضي اما إذا أردنا ان نغير هذا الوقت يمكننا ان نرسل‬
‫المدة الزمنية التي نريدها عبر اضافتها الى الـ ‪ Intent‬الذي ارسلناه بستخدام الدالة ‪ putExtra‬حيث‬
‫باراميترها االول يكون ‪ BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION‬اما‬
‫باراميترها الثاني يكون رقم يمثل الوقت الذي نريده بالثانية‪ ،‬بهذا الشكل‪:‬‬

‫‪Intent intent = new‬‬


‫;)‪Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE‬‬
‫‪intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,‬‬
‫;)‪300‬‬
‫;)‪startActivityForResult(intent, 0‬‬

‫‪138‬‬
‫لكي نعرف إذا ضغط المستخدم على الزر موافق او رفض جعل الجهاز مرئي نستخدم الدالة‬
‫‪( onActivityResult‬والتي شرحنا عنها في الفصل الثالث وضيفتها استقبال النتائج التي تعيدها الـ‬
‫‪ activity‬االبن)‪ ،‬بهذا الشكل‪:‬‬

‫‪@Override‬‬
‫{ )‪protected void onActivityResult(int requestCode, int resultCode, Intent data‬‬
‫{ )‪if (requestCode == 0 && resultCode == RESULT_CANCELED‬‬
‫‪// Discovery canceled by user‬‬
‫}}‬

‫االجهزة المحفوظة مسبقا في الهاتف‬


‫لكي يتمكن جهازي اندرويد من االتصال فيما بينهما عن طريق البلوتوث عليهما ان يرتبطو في البداية ثم‬
‫يتناقلو البيانات‪ ،‬إذا ارتبط الجهاز مع جهاز اخر فانه سيتم حفظه لالتصال معه في المرة القادمة دون الحاجة‬
‫لربطه من جديد‪ ،‬ولكي نبحث في قائمة االجهزة المرتبطة (المحفوظة) مسبقا‪ ،‬نستخدم الدالة‬
‫‪ getBondedDevices‬والتي تعيد االجهزة المرتبطة والمحفوظة في الجهاز على شكل مصفوفة من نوع‬
‫االوبجكت ‪ ،BluetoothDevice‬مثال الخذ اسم وعنوان االجهزة المحفوظة (ان وجدت)‪:‬‬

‫;)(‪BluetoothAdapter blu = BluetoothAdapter.getDefaultAdapter‬‬


‫;)(‪Set<BluetoothDevice> pd = blu.getBondedDevices‬‬
‫{ )‪if (pd.size( ) > 0‬‬
‫{ )‪for (BluetoothDevice device : pd‬‬
‫;) (‪String deviceName = device.getName‬‬
‫;) (‪String deviceMacAddress = device.getAddress‬‬
‫}‬
‫}‬

‫كل ما نحتاجة عندما نريد ان نبدأ اتصال مع اي جهاز هو الـ ‪ ،MAC address‬يمكن عرض االجهزة‬
‫المرتبطة في الجهاز والتي استخرجناها على شكل مصفوفة في قائمة عرض ‪ ListView‬باستخدام‬
‫‪ ArrayAdapter‬ليراها المستخدم (شرحنا عنهما في فصول سابقه)‪.‬‬

‫البحث عن واستكشاف االجهزة‬


‫لكي نبدأ البحث عن اجهزة البلوتوث نحتاج ان نستخدم الدالة ‪ startDiscovery‬التابعة للكالس‬
‫‪ ،BluetoothAdapter‬عملية ال بحث عن اجهزة تأخذ بعض الوقت وعلينا التأكد دائما من ان نلغي عملية‬
‫البحث بعد ان نحدد الجهاز الذي نريد االتصال به وقبل بدأ عملية االتصال من خالل الدالة‬

‫‪139‬‬
‫‪ cancelDiscovery‬التابعة للكالس ‪ ،BluetoothAdapter‬اي ال يمكن ان نعمل عملية االتصال باي‬
‫جهاز والتطبيق ال يزال يبحث عن (يستكشف) االجهزة االخرى‪ ،‬ولكي نحدد فيما اذا كان التطبيق ال يزال‬
‫يجري عملية البحث عن االجهزة ام ال نستخدم الدالة ‪ isDiscovering‬التابعة للكالس‬
‫‪ BluetoothAdapter‬حيث ستعيد القيمة ‪ true‬اذا كان التطبيق يجري عملية االستكشاف حاليا وبخاللف‬
‫ذلك تعيد القيمة ‪.false‬‬

‫عملية البحث واستكتشاف االجهزة االخرى غير متزامنه‪ ،‬اي بمعنى اننا إذا أردنا ان نعرف حالة تقدم عملية‬
‫االستكشاف (بدأ العملية‪ ،‬انتهاء العملية‪ ،‬ايجاد جهاز) علينا ان ننشأ الكالس ‪ BroadcastReceiver‬ثم نسجل‬
‫الدخول له وبعد االنتهاء نسجل الخروج منه‪.‬‬

‫الخطوة االولى (انشاء ‪)BroadcastReceiver‬‬


‫ننشأ الكالس ‪ BroadcastReceiver‬جديد ونعطيه اسم ويجب ان نعمل بداخله ‪ @override‬لدالته‬
‫‪ onReceive‬هذه الدالة يتم استدعائها اوتوماتيكيا كلما يحدث تغير في حالة تقدم عملية االستكشاف (بدأ‬
‫العملية‪ ،‬انتهاء العملية‪ ،‬ايجاد جهاز) وذلك من خالل الباراميتر الثاني الممرر لها (تأخذ هذه الدالة بارامترين)‬
‫والذي هو عبارة عن ‪ ،intent‬فنوع هذا الـ ‪ intent‬يحدد حالة عملية االستكشاف‪ ،‬ولكي نعرف نوع هذا الـ‬
‫‪ intent‬نستخدم الدالة ‪ getAction‬حيث ستعيد لنا أحد هذه القيم الثالثة‪:‬‬

‫عملية‬ ‫بدأت‬ ‫اي‬ ‫‪BluetoothAdapter.ACTION_DISCOVERY_STARTED -1‬‬


‫االستكشاف‪.‬‬
‫عملية‬ ‫‪ BluetoothAdapter.ACTION_DISCOVERY_FINISHED -2‬اي انتهت‬
‫االستكشاف‪.‬‬
‫‪ BluetoothDevice.ACTION_FOUND -3‬اي اننا عثرنا على جهاز بلوتوث جديد‪.‬‬

‫سيكون شكل الـ ‪ BroadcastReceiver‬والدالة ‪ onReceive‬داخله بهذا الشكل (يكتب خارج الدالة‬
‫‪ onCreate‬التابعة للـ ‪:)activity‬‬

‫;‪String dStarted = BluetoothAdapter.ACTION_DISCOVERY_STARTED‬‬


‫;‪String dFinished = BluetoothAdapter.ACTION_DISCOVERY_FINISHED‬‬
‫;‪String dFound = BluetoothDevice.ACTION_FOUND‬‬
‫{ ) (‪BroadcastReceiver bReciever = new BroadcastReceiver‬‬
‫‪@Override‬‬
‫{ )‪public void onReceive(Context context, Intent intent‬‬
‫;)(‪String action = intent.getAction‬‬

‫‪140‬‬
‫{ ))‪if (dStarted.equals(action‬‬
‫‪// Discovery has started do something.‬‬
‫}‬
‫{ ))‪if (dFinished.equals(action‬‬
‫‪// Discovery has completed do something.‬‬
‫}‬
‫{ ))‪if (dFound.equals(action‬‬
‫‪// Discovery has found a device do something.‬‬
‫;} } }‬

‫الدالة ‪ equals‬تستخدم للمقارنة بين قيمتين نصيتين فاذا كانتا متساويتين تعيد القيمة ‪.true‬‬

‫إذا وجد التطبيق جهاز بلوتوث جديد فان الباراميتر الثاني ‪ intent‬للدالة ‪ onReceive‬سيحمل معه معلومات‬
‫عن هذا الجهاز والستخراج هذه المعلومات نستخدم الدالة ‪( get#Extra‬شرحنا عنها في الفصل الثالث)‪،‬‬
‫حيث ان هذا الـ ‪ intent‬سيحمل اسم جهاز البلوتوث والتي تكون على شكل قيمة نصية ‪ String‬ونستخرجها‬
‫بهذا الشكل (يكتب هذا الكود في داخل الدالة ‪:)onReceive‬‬

‫;)‪String dName = intent.getStringExtra(BluetoothDevice.EXTRA_NAME‬‬

‫وكذلك يحمل هذا الـ ‪ intent‬معلومات مفصلة عن الجهاز تأتي على شكل قيمة اوبجكت ‪parcelable‬‬
‫ونستخرجها بهذا الشكل (يكتب هذا الكود في داخل الدالة ‪:)onReceive‬‬

‫= ‪BluetoothDevice rDv‬‬
‫;)‪intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE‬‬
‫االن أصبح عندنا االوبجكت ‪ rDv‬يحمل معلومات عن الجهاز الذي وجدناه مثل اسم وعنوان الجهاز‪ ،‬ويمكن‬
‫ان نستخرج هذه المعلومات منه من خالل هذه الدوال بهذا الكشل‪:‬‬

‫‪String address = rDv.getAddress(); // MAC address‬‬


‫;)(‪String name = rDv.getName‬‬

‫االسم هو نفسه الذي استخرجناه عن طريق الـ ‪ EXTRA_NAME‬قبل قليل اي ان المتغير ‪dName‬‬
‫والمتغير ‪ name‬سيحملون نفس القيمة‪.‬‬

‫بعد ان نستخرج معلومات الجهاز يمكن ان نضعها في قائمة عرض ‪ ListView‬لعرض االجهزة التي نجدها‬
‫للمستخدم لكي يراها‪.‬‬

‫الخطوة الثانية (تسجيل الدخول الى الـ ‪)BroadcastReceiver‬‬

‫‪141‬‬
‫بعد ان انشأنا الـ ‪ BroadcastReceiver‬علينا ان نسجل الدخول له‪ ،‬يتم ذلك من خالل الدالة‬
‫‪ registerReceiver‬والتي تأخذ بارامترين االول هو عبارة عن اسم الـ ‪ BroadcastReceiver‬الذي‬
‫انشأناه قبل قليل اما الباراميتر الثاني فهو عبارة عن ‪ IntentFilter‬جديد نعمله ونمرر له بين قوسيه أحد هذه‬
‫القيم الثالثة بحسب السبب الذي نسجل الدخول الجله‪:‬‬

‫‪BluetoothAdapter.ACTION_DISCOVERY_STARTED -1‬‬
‫‪BluetoothAdapter.ACTION_DISCOVERY_FINISHED -2‬‬
‫‪BluetoothDevice.ACTION_FOUND -3‬‬

‫يمكن ان يكون شكل عملية تسجيل الدخول للـ ‪ BroadcastReceiver‬بهذا الشكل (يمكن ان يكتب في داخل‬
‫الدالة ‪ onCreate‬التابعة للـ ‪:)activity‬‬

‫;))‪registerReceiver(bReciever, new IntentFilter(dStarted‬‬


‫;))‪registerReceiver(bReciever, new IntentFilter(dFinished‬‬
‫;))‪registerReceiver(bReciever, new IntentFilter(dFound‬‬

‫الخطوة الثالثة (تسجيل الخروج من الـ ‪)BroadcastReceiver‬‬


‫بعد ان نسجل عملية الدخول وننتهي من اخذ المعلومات عن الجهاز واالتصال به وننتهي من العملية بالكامل‬
‫نسجل الخروج من الـ ‪ BroadcastReceiver‬عن طريق الدالة ‪ unregisterReceiver‬والتي تأخذ‬
‫باراميتر واحد بين قوسيها وهو عبارة عن اسم الـ ‪ BroadcastReceiver‬الذي انشأناه قبل قليل‪ ،‬مثال‬
‫(يمكن ان يكتب في داخل الدالة ‪ onDestroy‬التابعة للـ ‪:)activity‬‬

‫;)‪unregisterReceiver(bReciever‬‬

‫االتصال باالجهزة االخرى‬


‫لكي نتمكن من ارسال البيانات بين جهازين علينا أوال ان نجري عملية اتصال فيما بينهم‪ ،‬وتتم هذه العملية من‬
‫خالل الكالسين‪:‬‬

‫‪ BluetoothServerSocket -‬يستخدم النشاء حلقة االتصال واالصغاء الستالم بيانات‪.‬‬


‫‪ BluetoothSocket -‬يستخدم لتهيئة عملية ارسال البيانات الى الجهاز االخر‪.‬‬
‫• النشاء تطبيق يرسل ويستلم البيانات علينا استخدام كال الكالسين‪ ،‬ولفتح قناة اتصال على أحد‬
‫الجهازين ان يؤدي دور المستلم واألخر يؤدي دور المرسل‪.‬‬

‫‪142‬‬
‫الدالة‬ ‫استخدام‬ ‫يجب‬ ‫البيانات‬ ‫الستالم‬ ‫كخادم‬ ‫الجهاز‬ ‫لجعل‬
‫‪ listenUsingRfcommWithServiceRecord‬التابعة للكالس ‪ BluetoothServerSocket‬لالصغاء‬
‫الى أي طلب اتصال قادم‪ ،‬هذه الدالة ستعيد اوبجكت من نوع الكالس ‪ ،BluetoothServerSocket‬هذه‬
‫الدالة تأخذ بين قوسيها متغيرين األول هو عبارة عن اسم نحن نختاره والثاني عبارة عن رمز يكون فريد من‬
‫نوعه يكون من نوع االكالس ‪ ،UUID‬والحظ ان الكالس ‪ BluetoothSocket‬في الجهاز العميل‬
‫(المرسل) يجب ان يعرف هذا الرمز لكي تحدث عملية االتصال‪.‬‬

‫عند اجراء عملية اتصال ألول مرة بين جهازين غير مرتبطين (‪ )not pairing‬ستظهر نافذة حوار للمستخدم‬
‫تطلب منه ربط الجهازين بهذا الشكل‪:‬‬

‫إذا نجح طلب االتصال فأن الدالة ‪ accept‬التابعة للكالس ‪BluetoothServerSocket‬ستعيد اوبجكت من‬
‫نوع الكالس ‪ BluetoothSocket‬ومن خالل هذا االوبجكت نتمكن من ارسال البيانات في قناة االتصال‬
‫هذه‪.‬‬

‫• عند استخدام الدالة ‪ accept‬يفضل اجراء عملية االصغاء لالتصاالت القادمة في ثريد في الخلفية‬
‫‪ background thread‬بدال من الثريد ‪ UI‬حتى حدوث عملية االتصال‪ ،‬ويجب االنتباه أيضا الى ان‬
‫الجهاز يكون مرئي عند محاولة االتصال به‪.‬‬

‫مثال‪ :‬لالصغاء الى طلبات االتصال القادمة‬

‫;‪private BluetoothSocket transferSocket‬‬


‫{ )‪private UUID startServerSocket(BluetoothAdapter bluetooth‬‬
‫;)”‪UUID uuid = UUID.fromString(“a60f35f0-b93a-11de-8a39-08002009c666‬‬
‫;”‪String name = “bluetoothserver‬‬
‫{ ‪try‬‬
‫= ‪final BluetoothServerSocket btserver‬‬
‫;)‪bluetooth.listenUsingRfcommWithServiceRecord(name, uuid‬‬

‫‪143‬‬
Thread acceptThread = new Thread(new Runnable() {
public void run() {
try {
// Block until client connection established.
BluetoothSocket serverSocket = btserver.accept();
// Start listening for messages.
listenForMessages(serverSocket);
// Add a reference to the socket used to send messages.
transferSocket = serverSocket;
} catch (IOException e) {
Log.e(“BLUETOOTH”, “Server connection IO Exception”, e);
}
}
});
acceptThread.start();
} catch (IOException e) {
Log.e(“BLUETOOTH”, “Socket listener IO Exception”, e);
}
return uuid;
}

‫االن نأتي الى الجهاز الذي سيجري عملية االتصال‬

‫ وهي تأخذ بين قوسيها باراميتر‬createRfcommSocketToServiceRecord ‫يتم ذلك من خالل الدالة‬


‫ وهذا االوبجكت يحدد‬BluetoothDevice ‫ هذه الدالة تابعة لالوبجكت‬،UUID ‫واحد هو عبارة عن الرمز‬
‫ ثم بعد ذلك‬،‫الجهاز الهدف ويجب ان يكون الجهاز الهدف يصغي لعملية االرسال كما شرحناه قبل قليل‬
.connect ‫نستخدم الدالة‬

‫ يفضل اجراء عملية ارسال طلب االتصاالت في ثريد في الخلفية‬connect ‫• عند استخدام الدالة‬
.‫ حتى حدوث عملية االتصال‬UI ‫ بدال من الثريد‬background thread

‫ الرسال طلب اتصال‬:‫مثال‬

private void connectToServerSocket(BluetoothDevice device, UUID uuid) {


try{
BluetoothSocket clientSocket=
device.createRfcommSocketToServiceRecord(uuid);

144
// Block until server connection accepted.
clientSocket.connect();
// Start listening for messages.
listenForMessages(clientSocket);
// Add a reference to the socket used to send messages.
transferSocket = clientSocket;
} catch (IOException e) {
Log.e(“BLUETOOTH”, “Bluetooth client I/O Exception”, e);
}
}

‫نقل البيانات بين األجهزة المتصلة‬


‫ على كال الجهازين ومن خالله‬Bluetooth Socket ‫بعد حدوث عملية االتصال بين الجهازين سيكون عندنا‬
‫ البيانات تتناقل عبر الجافا من خالل االوبجكت‬،‫يمكن االرسال واالستالم بنفس الطريقة لكالهما‬
getInputStream ‫ حيث يمكن الوصول لهم عبر الدالة‬OutputStream ‫ واالوبجكت‬InputStream
.‫ على التوالي‬getOutputStream ‫والدالة‬

‫ ارسال واستقبال قيمة نصية عبر البلوتوث‬:‫مثال‬

private void listenForMessages(BluetoothSocket socket, StringBuilder incoming) {


listening = true;
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
try {
InputStream instream = socket.getInputStream();
int bytesRead = -1;
while (listening) {
bytesRead = instream.read(buffer);
if (bytesRead != -1) {
String result = “ ”;
while ((bytesRead == bufferSize) && (buffer[bufferSize-1] != 0)){
result = result + new String(buffer, 0, bytesRead - 1);
bytesRead = instream.read(buffer);
}

145
‫;)‪result = result + new String(buffer, 0, bytesRead - 1‬‬
‫;)‪incoming.append(result‬‬
‫}‬
‫;)(‪socket.close‬‬
‫}‬
‫{ )‪} catch (IOException e‬‬
‫;)‪Log.e(TAG, “Message received failed.”, e‬‬
‫}‬
‫{ ‪finally‬‬
‫}‬
‫}‬

‫مثال متكامل‬

‫هذا المثال يعرض أجهزة البلوتوث المحفوظة مسبقا في الجهاز لنختار منها الجهاز الذي نريد االتصال به‬
‫ويوجد حقل نصي الرسال ما مكتوب فيه من نص الى الجهاز الذي اخترناه وأيضا استالم نص من الجهاز‬
‫االخر‪ ،‬تم تجربة هذا المثال لالتصال باالردوينو الرسال واستالم النص‪ ،‬شكل هذا المثال‪:‬‬

‫ملف الـ ‪XML‬‬


‫>?"‪<?xml version="1.0" encoding="utf-8‬‬
‫‪<LinearLayout‬‬

‫‪146‬‬
xmlns:android="http://schemas.android.com/apk/res/andro
id"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ListView
android:id="@+id/paired_devices_list"
android:layout_width="match_parent"
android:layout_height="150dp">

</ListView>

<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="Text to send"
android:inputType="textPersonName" />

<Button
android:id="@+id/send_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Send and Receive" />

<TextView
android:id="@+id/show_text_receive"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

‫ملف الجافا‬
package com.example.ahmed.testbluetooth;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;

147
import android.bluetooth.BluetoothSocket;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.util.Set;
import java.util.UUID;

public class MainActivity extends AppCompatActivity {

public static final UUID MY_UUID =


UUID.fromString("00001101-0000-1000-8000-
00805F9B34FB");
private BluetoothAdapter blu;
private ListView list_devices;
private Set<BluetoothDevice> pd;
private BluetoothSocket btSocket = null;
private EditText editText;
private TextView textView;

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

editText =
(EditText)findViewById(R.id.editText);
textView =

148
(TextView)findViewById(R.id.show_text_receive);

make_list();
click_on_list();

Button button =
(Button)findViewById(R.id.send_button);
button.setOnClickListener(new
View.OnClickListener() {
@Override
public void onClick(View view) {

send_data(editText.getText().toString());
receive_data();
}
});

// method to put all paired devices in list view


private void make_list(){
blu = BluetoothAdapter.getDefaultAdapter();
if (blu.isEnabled()){
pd = blu.getBondedDevices();
if (pd.size( ) > 0) {
String names[] = new String[pd.size()];
int index = 0;
for (BluetoothDevice device : pd) {
names[index] = device.getName();
index++;
}
ArrayAdapter<String> listAdapter = new
ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, names);
list_devices = (ListView)
findViewById(R.id.paired_devices_list);
list_devices.setAdapter(listAdapter);
}
}

149
}

// method will execute when click on list view


private void click_on_list(){
list_devices.setOnItemClickListener(new
AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?>
parent, View view, int position, long id) {
Object[] objects = pd.toArray();
BluetoothDevice device =
(BluetoothDevice) objects[position];
ConnectThread connectThread = new
ConnectThread(device);
connectThread.start();

Toast.makeText(MainActivity.this,"device choosen
"+device.getName(),Toast.LENGTH_SHORT).show();
}
});
}

// class execute a connection to another bluetooth


device and do that in separate thread
private class ConnectThread extends Thread {
ConnectThread(BluetoothDevice device) {
btSocket = null;
// Get a BluetoothSocket to connect with
the given BluetoothDevice
try {
btSocket =
device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) { }
}

public void run() {


try {
btSocket.connect();
} catch (IOException connectException) {
// Unable to connect close the socket

150
and get out
try {
btSocket.close();
} catch (IOException closeException) {
}
}
}
}

// method to send data to connected device


private void send_data(String data){
byte[] msgBuffer = data.getBytes();
try {
if (btSocket!=null) {
OutputStream outStream =
btSocket.getOutputStream();
outStream.write(msgBuffer);
}
}catch (IOException e){}
}

// method to receive data from another device


private void receive_data(){
char[] buffer = new char[1024];
StringBuilder out = new StringBuilder();
try {
InputStream inStream =
btSocket.getInputStream();
for (int i=0; i<inStream.available(); i++)
{
Reader in = new
InputStreamReader(inStream, "UTF-8");
int end = in.read(buffer);
out.append(buffer);
}
textView.setText(out.toString());
} catch (IOException e) {}
}

151
‫• ال تنسى كتابة االذونات في ملف المانفيست‪.‬‬
‫• هذا المثال ال يعمل إذا كان جهاز البلوتوث مغلق‪ ،‬لذا يجب تشغيل البلوتوث قبل فتح البرنامج‪.‬‬
‫• البرنامج المكتوب في االردوينو تجده في كتاب االردوينو الذي كتبته انا‪.‬‬

‫‪The Wi-Fi‬‬
‫الكالس ‪ConnectivityManager‬‬

‫هذا الكالس مسؤول عن عرض حالة االتصال والتحكم باالتصال بالشبكة‪ ،‬والستخدام هذا الكالس علينا اخذ‬
‫االذن للوصول وتغيير الشبكة في ملف الـ ‪ manifest‬بهذا الشكل‪:‬‬

‫‪<uses-permission‬‬
‫>‪android:name=”android.permission.ACCESS_NETWORK_STATE”/‬‬
‫‪<uses-permission‬‬
‫>‪android:name=”android.permission.CHANGE_NETWORK_STATE”/‬‬
‫وللصول والتعمل مع هذا الكالس نستخدم دالته ‪ getSystemService‬ونمرر لها بين قوسيها القيمة التالية‬
‫كأسم للخادم ‪ ،Context.CONNECTIVITY_SERVICE‬بهذا الشكل‪:‬‬

‫= ‪ConnectivityManager connectivity‬‬
‫;)‪(ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE‬‬

‫اليجاد ومراقبة االتصال بالشبكة نستخدم الدالة ‪ getActiveNetworkInfo‬التابعة للكالس‬


‫‪ ConnectivityManager‬هذه الدالة تعيد تفاصيل االتصال الحالي النشط على شكل اوبجكت من النوع‬
‫‪ ،NetworkInfo‬ويمكن استخدام هذه الدالة بهذا الشكل‪:‬‬

‫;)(‪NetworkInfo activeNetwork = connectivity.getActiveNetworkInfo‬‬

‫قبل محاولة نقل البيانات عبر الشبكة يفضل التأكد من حالة االتصال ونوعه الن نقل البيانات ستستهلك من‬
‫بطارية الهاتف لذا يفضل التأكد من وجود االتصال في االول بهذا الشكل‪:‬‬

‫‪152‬‬
boolean isConnected = ((activeNetwork != null) &&
(activeNetwork.isConnectedOrConnecting()
));
.‫ هذا يعني ان الهاتف متصل بالشبكة‬true ‫ هي‬isConnected ‫حيث انه إذا أصبحت قيمة المتغير‬

WifiManager ‫الكالس‬

‫ ولكي نستخدم هذا الكالس عليها اخذ االذن في ملف الـ‬،‫ في الهاتف‬Wi-Fi ‫هذا الكالس مسوؤل عن إدارة الـ‬
:‫ بهذا الشكل‬Manifest

<uses-permission android:name=”android.permission.ACCESS_WIFI_STATE”/>
<uses-permission
android:name=”android.permission.CHANGE_WIFI_STATE”/>
‫ ونمرر لها القيمة‬getSystemService ‫وللوصول الى هذا الكالس نستخدم دالته‬
:‫ بهذا الشكل‬Context.WIFI_SERVICE

WifiManager wifi =
(WifiManager)getApplicationContext().getSystemService(Context.WIFI_SERVIC
E);
:‫لهذا الكالس عدة دوال منها‬

setWifiEnabled

false ‫ بين قوسيها للتشغيل او‬true ‫ او اطفائه وذلك بتمرير القيمة‬Wi-Fi ‫تستخدم هذه الدالة لتشغيل الـ‬
:‫ مثال‬،‫لالطفاء‬

wifi.setWifiEnabled(true);

getWifiState

:WifiManager ‫ وذلك باستخدام أحد القيم التالية مع الكالس‬Wi-Fi ‫تستخدم هذه الدالة لمعرفة حالة الـ‬

.‫ مطفي‬Wi-Fi ‫ اذا كان الـ‬WIFI_STATE_DISABLED -


.Wi-Fi ‫ اذا كان في هذه اللحظة جاري إطفاء الـ‬WIFI_STATE_DISABLING -
.‫ مشتغل‬Wi-Fi ‫ اذا كان الـ‬WIFI_STATE_ENABLED -
.‫ يتم تشغيله في هذه اللحظة‬Wi-Fi ‫ اذا كان الـ‬WIFI_STATE_ENABLING -

153
.‫ غير معروفة‬Wi-Fi ‫ اذا كانت حالة الـ‬WIFI_STATE_UNKNOWN -

:‫مثال‬

if (wifi.getWifiState() == WifiManager.WIFI_STATE_ DISABLED)


// the Wi-Fi is off do something

isWifiEnabled

:‫ مثال‬،false ‫ مشتغل وبخالف ذلك تعيد القيمة‬Wi-Fi ‫ إذا كان الـ‬true ‫تعيد هذه الدالة القيمة‬

if (wifi.isWifiEnabled())
// the Wi-Fi is on do something

getConnectionInfo

IP ‫ وعنوان الـ‬SSID ‫ مثال اسم الشيكة‬،‫تستخدم هذه الدالة للحصول على معلومات الشبكة المتصلين بها حاليا‬
‫ حيث ان هذه الدالة تعيد اوبجكت من نوع‬،BSSID ‫ وقوة اإلشارة وسرعتها وكذلك الـ‬MAC ‫وعنوان الـ‬
‫ بهذا‬،‫ وهذا الكالس يحتوي على عدة دوال من خاللها يمكن استخراج هذه المعلومات‬WifiInfo ‫الكالس‬
:‫الشكل‬
WifiInfo info = wifi.getConnectionInfo();
if (info.getBSSID() != null) {
// ‫ يمثل بعد الشبكة‬5 ‫ الى‬1 ‫في هذا السطر نحصل على رقم من‬
‫عن الجهاز‬
int strength =

WifiManager.calculateSignalLevel(info.getRssi(),
5);
int speed = info.getLinkSpeed();
int ipAddress = info.getIpAddress();
String units = WifiInfo.LINK_SPEED_UNITS;
String ssid = info.getSSID();
String macAddress= info.getMacAddress();
}

startScan

154
‫تستخدم هذه الدالة للبحث عن الشبكات المتاحة وعند اكتمال عملية البحث والحصول على النتيجة تعيد‬
‫‪ Intent‬بالقيمة ‪SCAN_RESULTS_AVAILABLE_ACTION‬‬

‫‪getScanResults‬‬

‫نستخدم هذه الدالة بعد اكتمال عملية البحث بالدالة السابقة ‪ startScan‬الستخراج النتائج التي حصلنا عليها‬
‫وهي تعيد النتائج على شكل اوبجكت من النوع ‪ ،ScanResult‬كل نتيجة من هذه النتائج تحتوي على كل‬
‫المعلومات من اسم الشبكة ونقطة االتصال وسرعته الخ‪.‬‬

‫مثال متكامل‪:‬‬

‫سنعمل برنامج يعرض شبكات الـ ‪ Wi-Fi‬المتاحة وعند الضغط على احداها يمكننا ادخال كلمة السر ومن ثم‬
‫االتصال بها‪.‬‬

‫• هذا البرنامج إذا كان يعمل على نظام اندرويد ذو ‪ 23 API‬او اعلى فيجب ان يكون الـ ‪location‬‬
‫على الجهاز يعمل واال فلن تظهر قائمة أسماء الشبكات‪.‬‬
‫‪1- MainActivity.java‬‬
‫;‪package com.example.ahmed.myapplication‬‬

‫‪import‬‬ ‫;‪android.Manifest‬‬
‫‪import‬‬ ‫;‪android.annotation.SuppressLint‬‬
‫‪import‬‬ ‫;‪android.app.Dialog‬‬
‫‪import‬‬ ‫;‪android.app.ListActivity‬‬
‫‪import‬‬ ‫;‪android.content.BroadcastReceiver‬‬
‫‪import‬‬ ‫;‪android.content.Context‬‬

‫‪155‬‬
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;

public class MainActivity extends ListActivity {


WifiManager mainWifiObj;
WifiScanReceiver wifiReciever;
ListView list;
String wifis[];
EditText pass;

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

list=getListView();
mainWifiObj = (WifiManager)
getApplicationContext().getSystemService(Context.WIFI_S
ERVICE);
wifiReciever = new WifiScanReceiver();
mainWifiObj.startScan();

// listening to single list item on click


list.setOnItemClickListener(new

156
AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?>
parent, View view, int position, long id) {

// selected item
String ssid = ((TextView)
view).getText().toString();
connectToWifi(ssid);
Toast.makeText(MainActivity.this,"Wifi
SSID : "+ssid,Toast.LENGTH_SHORT).show();

}
});
}

protected void onPause() {


unregisterReceiver(wifiReciever);
super.onPause();
}

protected void onResume() {


registerReceiver(wifiReciever, new
IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)
);
super.onResume();
if(Build.VERSION.SDK_INT >=
Build.VERSION_CODES.M)

if(checkSelfPermission(Manifest.permission.ACCESS_COARS
E_LOCATION) != PackageManager.PERMISSION_GRANTED)
requestPermissions(new
String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
87);
}
class WifiScanReceiver extends BroadcastReceiver {
@SuppressLint("UseValueOf")
public void onReceive(Context c, Intent intent)
{
List<ScanResult> wifiScanList =
mainWifiObj.getScanResults();

157
wifis = new String[wifiScanList.size()];
for(int i = 0; i < wifiScanList.size();
i++){
wifis[i] =
((wifiScanList.get(i)).toString());
}
String filtered[] = new
String[wifiScanList.size()];
int counter = 0;
for (String eachWifi : wifis) {
String[] temp = eachWifi.split(",");

filtered[counter] =
temp[0].substring(5).trim();//+"\n" +
temp[2].substring(12).trim()+"\n"
+temp[3].substring(6).trim();//0->SSID, 2->Key
Management 3-> Strength

counter++;

}
list.setAdapter(new
ArrayAdapter<String>(getApplicationContext(),R.layout.l
ist_item,R.id.label, filtered));
}
}

private void finallyConnect(String networkPass,


String networkSSID) {
WifiConfiguration wifiConfig = new
WifiConfiguration();
wifiConfig.SSID = String.format("\"%s\"",
networkSSID);
wifiConfig.preSharedKey =
String.format("\"%s\"", networkPass);

// remember id
int netId = mainWifiObj.addNetwork(wifiConfig);
mainWifiObj.disconnect();
mainWifiObj.enableNetwork(netId, true);

158
mainWifiObj.reconnect();

WifiConfiguration conf = new


WifiConfiguration();
conf.SSID = "\"\"" + networkSSID + "\"\"";
conf.preSharedKey = "\"" + networkPass + "\"";
mainWifiObj.addNetwork(conf);
}

private void connectToWifi(final String wifiSSID) {


final Dialog dialog = new Dialog(this);
dialog.setContentView(R.layout.connect);
dialog.setTitle("Connect to Network");
TextView textSSID = (TextView)
dialog.findViewById(R.id.textSSID1);

Button dialogButton = (Button)


dialog.findViewById(R.id.okButton);
pass = (EditText)
dialog.findViewById(R.id.textPassword);
textSSID.setText(wifiSSID);

// if button is clicked, connect to the


network;
dialogButton.setOnClickListener(new
View.OnClickListener() {
@Override
public void onClick(View v) {
String checkPassword =
pass.getText().toString();
finallyConnect(checkPassword,
wifiSSID);
dialog.dismiss();
}
});
dialog.show();
}
}

159
2- activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/andro
id"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>

3- list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Single List Item Design -->
<TextView
xmlns:android="http://schemas.android.com/apk/res/andro
id"
android:id="@+id/label"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dip"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="@android:color/holo_blue_dark">
</TextView>

4- connect.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/andro
id"
android:layout_width="match_parent"
android:layout_height="match_parent">

160
<TextView
android:id="@+id/textSSID1"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<EditText
android:id="@+id/textPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/textSSID1"
android:layout_marginTop="5dp"/>

<Button
android:id="@+id/okButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/textPassword"
android:text="Connect"/>
</RelativeLayout>

5- AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/andro
id"
package="com.example.ahmed.myapplication">

<uses-permission
android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission
android:name="android.permission.ACCESS_NETWORK_STATE"/
>
<uses-permission
android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission
android:name="android.permission.ACCESS_COARSE_LOCATION
" />
<uses-permission

161
android:name="android.permission.ACCESS_FINE_LOCATION"
/>

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action
android:name="android.intent.action.MAIN" />

<category
android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

Wi-Fi Direct

،‫يستخدم هذا النوع من االتصال لالتصال بين جهازين لنقل البيانات مباشرتا دون الحاجة الى شبكة خارجية‬
‫ وهذا الكالس مسؤال عن االتصال واكتشاف‬WifiP2pManager ‫ولعمل ذلك نحتاج الى استخدام الكالس‬
:‫ وللتعامل مع هذا الكالس علينا اخذ االذونات التالية‬،‫األجهزة األخرى‬

<uses-permission android:name=”android.permission.ACCESS_WIFI_STATE”/>
<uses-permission
android:name=”android.permission.CHANGE_WIFI_STATE”/>
<uses-permission android:name=”android.permission.INTERNET”/>

162
‫ ونمرر لها بين قوسيها القيمة التالية‬getSystemService ‫وللوصول الى هذا الكالس نستخدم الدالة‬
:‫ بهذا الشكل‬،Context.WIFI_P2P_SERVICE

WifiP2pManager wifiP2p =
(WifiP2pManager)getSystemService(Context.WIFI_P2P_SERVICE);

:‫هذا الكالس يحتوي على مجموعة من الدوال من أهمها‬

initialize

‫ وهي تستدعى قبل أي دالة من دوال الكالس‬Wi-Fi ‫تستخدم هذه الدالة لتهيئة التطبيق في إطار شبكة الـ‬
:‫ مثال‬،‫ وبعدها ننشأ قناة لالصغاء الى االتصال‬WifiP2pManager
private WifiP2pManager wifiP2pManager;
private WifiP2pManager.Channel wifiDirectChannel;
private void initializeWiFiDirect() {
wifiP2pManager =
(WifiP2pManager)getSystemService(Context.WIFI_P2P_SERVI
CE);
wifiDirectChannel = wifiP2pManager.initialize(this,
getMainLooper(), new WifiP2pManager.ChannelListener() {
public void onChannelDisconnected() {
initializeWiFiDirect();
}
});
}

connect

‫ مع جهاز محدد‬Wi-Fi ‫تستخدم هذا الدالة لالتصل عبر الـ‬

discoverPeers

‫ الموجودة في نطاق البحث‬Wi-Fi ‫تستخدم هذه الدالة الكتشاف كل أجهزة الـ‬

163
‫‪cancelConnect‬‬

‫هذه الدالة تستخدم اللغاء االتصال باي جهاز ‪ Wi-Fi‬اخر‬

‫‪removeGroup‬‬

‫هذه الدالة تستخدم اللغاء االتصال الحالي بين الجهازين المتصلين‬

‫مثال متكامل‪:‬‬

‫هذا المثال يرسل نص الى ‪ esp8266‬لتعرضها هي على شاشة الـ ‪ Serial Monitor‬وهي بدورها تعيد‬
‫نص الى البرنامج ليستلمه ويعرضه البرنامج (برنامج الـ ‪ esp8266‬موجود في كتاب االردوينو الذي كتبته‬
‫انا)‪ ،‬بهذا الشكل‪:‬‬

‫‪1- MainActivity.java‬‬

‫‪164‬‬
package com.example.ahmed.myapplication;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import android.annotation.SuppressLint;
import android.os.AsyncTask;
import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends Activity {

TextView textResponse;
Button buttonConnect;
EditText welcomeMsg;
String IpAddress = "192.168.0.117";
int Port = 8090;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

buttonConnect = (Button)
findViewById(R.id.connect);
textResponse = (TextView)
findViewById(R.id.response);
welcomeMsg =
(EditText)findViewById(R.id.welcomemsg);

buttonConnect.setOnClickListener(new
OnClickListener() {
@Override
public void onClick(View view) {

165
MyClientTask myClientTask = new
MyClientTask(welcomeMsg.getText().toString());
myClientTask.execute();
}
});
}

@SuppressLint("StaticFieldLeak")
public class MyClientTask extends AsyncTask<Void, Void,
Void> {
String response = "";
String msgToServer;

MyClientTask(String msgTo) {
msgToServer = msgTo;
}

@Override
protected Void doInBackground(Void... arg0) {
Socket socket = null;
DataOutputStream dataOutputStream = null;
DataInputStream dataInputStream = null;

try {
socket = new Socket(IpAddress, Port);
dataOutputStream = new
DataOutputStream(socket.getOutputStream());
dataInputStream = new
DataInputStream(socket.getInputStream());

if(!msgToServer.equals(""))

dataOutputStream.writeUTF(msgToServer+"$");

int x = dataInputStream.read();
while (x>0) {
response += Character.toString ((char)
x);
x = dataInputStream.read();
}

166
} catch (IOException e) { }
finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {}
}
if (dataOutputStream != null) {
try {
dataOutputStream.close();
} catch (IOException e) {}
}
if (dataInputStream != null) {
try {
dataInputStream.close();
} catch (IOException e) {}
}
}
return null;
}

@Override
protected void onPostExecute(Void result) {
textResponse.setText(response);
super.onPostExecute(result);
}
}
}

2- activity_main.xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/andro
id"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >

167
<EditText
android:id="@+id/welcomemsg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Say hello to server" />
<Button
android:id="@+id/connect"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Connect..."/>
<TextView
android:id="@+id/response"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

</LinearLayout>

manifest ‫وال تنسى وضع التصريح التالي في ملف الـ‬ •

<uses-permission
android:name="android.permission.INTERNET" />

:2 ‫مثال متكامل‬

‫هذا المثال هو عبارة عن برنامجين على جهازين اندرويد الرسال واستقبال البيانات حيث ان أحدهم يعمل‬
:‫ تجد المثال على هذا الرابط‬،‫كخادم واألخر يعمل كعميل‬

http://android-er.blogspot.com/2014/08/bi-directional-communication-
between.html

168
‫الفصل الثالث عشر (تحويل النص الى كالم)‬

‫في هذا الفصل سنتعلم كيف نحول النص الى كالم مسموع باستخدام محرك االندرويد ‪ TTS‬وهو اختصار‬
‫لكلمة ‪ ،Text to speech‬لكي نفهم االمر بشكل واضح سنقوم بعمل برنامج بسيط يحتوي ‪ activity‬واحدة‬
‫تعرض حقل نصي من النوع ‪ EditText‬يكتب فيه المستخدم النص المراد نطقه‪ ،‬ونعمل زر ‪ Button‬عند‬
‫الضغط عليه سيتحدث البرنامج‪ ،‬وسنقسم تصميم البرنامج الى عدة خطوات وفي األخير سأكتب كامل كود‬
‫البرنامج‪.‬‬

‫الخطوة األولى‪:‬‬
‫سنعمل الطبقة ‪ layout‬ونضع فيها حقل نصي ‪ EditText‬ونعطيه ‪ id‬باالسم ‪ ،enter‬وكذلك سنعمل زر‬
‫‪ Button‬نعطيه ‪ id‬باالسم ‪ ،speak‬عند الضغط على هذا الزر سيتم تنفيذ الدالة المسماة ‪ Speack_fun‬لذا‬
‫نضع اسمها في الخاصية ‪ onClick‬للزر‪ ،‬وبهذا نكون قد انتهينا من الطبقة ‪ layout‬وباقي عملنا سيكون على‬
‫كالس الجافا‪.‬‬

‫الخطوة الثانية‪:‬‬

‫‪169‬‬
‫ هذا االنترفيس‬،OnInitListener ‫ لالنترفيس‬implements ‫ أوال نجعله يعمل‬،‫نأتي االن الى كالس الجافا‬
.‫ لكن سنتكلم عنها في الخطوة الخامسة‬onInit ‫يتطلب منا ان نعمل دالته‬

‫ بهذا‬،myTTS ‫ وسنسميه‬TextToSpeech ‫ نقوم بتعريف اوبجكت من النوع‬onCreate ‫في خارج الدالة‬


:‫الشكل‬

private TextToSpeech myTTS;

‫ واال فلن يعمل برنامجنا لذا علينا‬TTS ‫في البداية علينا ان نتأكد من ان جهاز المستخدم فيه هذا المحرك‬
‫ (شرحنا عن هذه الدالة في‬startActivityForResult ‫ وارساله بالدالة‬intent ‫االستعالم من خالل انشاء‬
)onCreate ‫ (هذا الكود يكتب في داخل الدالة‬:‫ بهذا الشكل‬،)‫الفصل الثالث‬

Intent checkTTSIntent = new Intent();


checkTTSIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA
);
startActivityForResult(checkTTSIntent, MY_DATA_CHECK_CODE);

‫ يمثل رقم االستعالم ونعرفه‬int ‫ هو عبارة عن متغير من النوع‬MY_DATA_CHECK_CODE


:‫ بهذا الشكل‬onCreate ‫خارج الدالة‬

private int MY_DATA_CHECK_CODE = 0;

.)‫ (شرحنا عنها في الفصل الثالث‬onActivityResult ‫وسنستلم نتيجة االستعالم من خالل الدالة‬

:‫الخطوة الثالثة‬
‫ بهذا‬،‫ وفي داخل الكالس الرئيسي‬onCreate ‫ في أي مكان خارج الدالة‬onActivityResult ‫ننشأ الدالة‬
:‫الشكل‬

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == MY_DATA_CHECK_CODE) {
if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
myTTS = new TextToSpeech(this, this);
} else {
Intent installTTSIntent = new Intent( );

170
‫‪installTTSIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DAT‬‬
‫;)‪A‬‬
‫;)‪startActivity(installTTSIntent‬‬
‫}‬
‫}}‬

‫الشرط األول ‪ requestCode == MY_DATA_CHECK_CODE‬عملناه لنتأكد من ان نتيجة الطلب‬


‫جائت عن هذا االستعالم وليس غيره (شرحنا عنه في الفصل الثالث) وفي داخل هذا الشرط سنكتب كل الكود‬
‫المطلوب‪ ،‬الشرط الثاني نضعه لنتأكد من ان جهاز المستخدم يحتوي على البيانات المطلوبة للنطق‪ ،‬فاذا كان‬
‫يحتوي ننشأ االوبجكت ‪ myTTS‬الذي عملناه بالخطوة الثانية‪ ،‬اما إذا لم يكن يحتوي جهاز المستخدم على‬
‫البيانات المطلوبة سننفذ ما بداخل الـ ‪ else‬وهنا كتبنا كود لنرشد المستخدم لكي يسطب هذه البيانات على‬
‫جهازة‪.‬‬

‫الخطوة الرابعة‪:‬‬
‫االن سنقوم بانشاء الدالة ‪ Speack_fun‬والتي قلنا انه سيتم تنفيذها عند الضغط على الزر ‪ ،Button‬سنكتبها‬
‫بهذا الشكل‪:‬‬

‫{ )‪public void Speack_fun(View v‬‬


‫;)‪EditText enteredText = (EditText)findViewById(R.id.enter‬‬
‫;)(‪String words = enteredText.getText().toString‬‬
‫;)‪speakWords(words‬‬
‫}‬

‫حيث هنا سنقوم باخذ النص المكتوب في الحقل النصي ‪ EditText‬ونضعه في المتغير ‪( words‬الحظ‬
‫استخدام الدالة ‪ toString‬معه) وبعدها سنمرر هذه القيمة الى الدالة ‪ speakWords‬والتي سننشأها في‬
‫الخطوة السادسة‪.‬‬

‫الخطوة الخامسة‪:‬‬
‫قلنا في الخطوة الثانية انه علينا ان نعمل ‪ implements‬لالنترفيس ‪ ،OnInitListener‬وهذا االنترفيس‬
‫يتطلب منا ان نعمل دالته ‪ ،onInit‬االن سنتكلم عن كيفية انشاء هذه الدالة‪ ،‬بهذا الشكل‪:‬‬

‫{ )‪public void onInit(int initStatus‬‬

‫‪171‬‬
‫{ )‪if (initStatus == TextToSpeech.SUCCESS‬‬
‫‪if(myTTS.isLanguageAvailable(Locale.US)==TextToSpeech.LANG_AVAI‬‬
‫)‪LABLE‬‬
‫;)‪myTTS.setLanguage(Locale.US‬‬
‫{ )‪} else if (initStatus == TextToSpeech.ERROR‬‬
‫;)(‪Toast.makeText(this, "Sorry! failed...", Toast.LENGTH_LONG).show‬‬
‫}}‬

‫في الشرط األول ‪ initStatus == TextToSpeech.SUCCESS‬نتأكد من ان العملية ناجحة وبخالف‬


‫ذلك ن ظهر رسالة تنبيه للمستخدم‪ ،‬اما اذا كانت ناجحة فعلينا ان نحدد اللغة التي سيتكلم بها الجهاز‪ ،‬مثال هنا‬
‫جعلناه يتكلم باللغة اإلنجليزية االمريكية باستخدام الدالة ‪( setLanguage‬يمكن اختيار لغات أخرى) وهذه‬
‫الدالة وضعناها في داخل الشرط حيث ان هذا الشرط يتأكد من ان هذه اللغة بالتحديد موجودة في جهاز‬
‫المستخدم‪.‬‬

‫الخطوة السادسة‪:‬‬
‫ننشأ الدالة ‪ speakWords‬والتي كنا قد استدعيناها من داخل الدالة ‪ Speack_fun‬في الخطوة الرابعة‬
‫ونكتبها بهذا الشكل‪:‬‬

‫{ )‪private void speakWords(String words‬‬


‫;)‪myTTS.speak(words, TextToSpeech.QUEUE_FLUSH, null‬‬
‫}‬

‫هذا االمر سيجعل البرنامج ينطق الكالم الممرر له هنا باالسم ‪words‬‬

‫الخطوة السابعة‪:‬‬
‫يمكن ان نضيف العديد من الخيارات والخصائص األخرى على دوال النطق‪ ،‬مثال يمكن ان نضيف الدالة‬
‫‪ shutdown‬او الدالة ‪ stop‬والتي نعني بها ان يتوقف عن النطق‪.‬‬

‫;) (‪myTTS.shutdown‬‬
‫;) (‪myTTS.stop‬‬

‫‪172‬‬
‫ (هذا هو ملف الجافا في الـ‬:‫االن سنكتب كامل الكود الذي شرحناه في الخطوات السابقة ليكون بهذا الشكل‬
)activity

package com.example.ahmed.myapplication;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.content.Intent;
import java.util.Locale;
import android.widget.Toast;
public class MainActivity extends Activity implements OnInitListener {
//TTS object
private TextToSpeech myTTS;
//status check code
private int MY_DATA_CHECK_CODE = 0;
//create the Activity
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//check for TTS data
Intent checkTTSIntent = new Intent();
checkTTSIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_
DATA);
startActivityForResult(checkTTSIntent, MY_DATA_CHECK_CODE);
}
//respond to button clicks
public void Speack_fun(View v) {
//get the text entered
EditText enteredText = (EditText)findViewById(R.id.enter);
String words = enteredText.getText().toString();
speakWords(words);

173
}
//speak the user text
private void speakWords(String words) {
//speak straight away
myTTS.speak(words, TextToSpeech.QUEUE_FLUSH, null);
}
//act on result of TTS data check
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == MY_DATA_CHECK_CODE) {
if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
//the user has the necessary data - create the TTS
myTTS = new TextToSpeech(this, this);
} else {
//no data - install it now
Intent installTTSIntent = new Intent();

installTTSIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DAT
A);
startActivity(installTTSIntent);
}}}
//setup TTS
public void onInit(int initStatus) {
//check for successful instantiation
if (initStatus == TextToSpeech.SUCCESS) {
if(myTTS.isLanguageAvailable(Locale.US)==TextToSpeech.LANG_AVAI
LABLE)
myTTS.setLanguage(Locale.US);
} else if (initStatus == TextToSpeech.ERROR) {
Toast.makeText(this, "Sorry! failed...",
Toast.LENGTH_LONG).show();
}
}}

174
‫الفصل الرابع عشر (‪)Material Design‬‬

‫في هذا الفصل سنتكلم عن طريقة جديدة في التصميم اضافتها جوجل في الـ ‪ ،API 21‬هذه الطريقة تمكننا من‬
‫عمل تأثيرات ‪ 3D‬واشرطة تمرير الى اخره من المميزات‪ ،‬وتم إضافة هذه التأثيرات في عدة مكتبات‪.‬‬

‫انشاء ‪Recyclerview‬‬
‫ما نقصده بالـ ‪ recyclerview‬هو شريط تمرير افقي او عمودي للعناصر‪ ،‬بهذا الشكل‪:‬‬

‫‪175‬‬
‫هذه المربعات الملونة يمكن ان تكون صور او ازرار او أي عنصر ويمكن تمريرها بشكل افقي‪ ،‬سنتعلم االن‬
‫كيف نعمل مثل هذا البرنامج‪ ،‬أوال سننشأ مشروع جديد ونعمل اثنين ‪ actitity‬مع اثنين ‪ layout‬االكتفتي‬
‫األولى الرئيسية سنفترض اسمها ‪ MainActivity‬مرتبطة بالطبقة ‪ ،activity_main‬اما الـ ‪activity‬‬
‫األخرى والتي ستحمل كالس محتويات هذه القائمة اسمها ‪ Main2Activity‬والطبقة المرتبطة بها اسمها‬
‫‪.activity_main2‬‬

‫اول شيء علينا القيام به هو إضافة المكتبات التالية الى الـ ‪Gradle‬‬

‫'‪implementation 'com.android.support:appcompat-v7:27.1.1‬‬
‫'‪implementation 'com.android.support:recyclerview-v7:27.1.1‬‬

‫الضافتها في برنامج االندرويد ستوديو نذهب الى‪:‬‬

‫‪File → Project Structure → App → Dependencies‬‬

‫لتكون النافذه بهذا الشكل‪:‬‬

‫‪176‬‬
‫إذا لم تكن هاتين المكتبتين مضافات نضيفهن من عالمة الزائد الموجودة في اعلى النافذة على جهة اليمين‬
.)‫(كما موضح في الصورة السابقة‬

:‫نأتي االن الى كتابة الكود في كل واحدة منهم‬

:‫ هو‬activity_main.xml ‫الكود في داخل الطبقة‬


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/andro
id"
android:layout_width="wrap_content"
android:layout_height="wrap_content">

<android.support.v7.widget.RecyclerView
android:id="@+id/rvAnimals"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scrollbars="horizontal" />

177
</RelativeLayout>

:‫ هو‬activity_main2.xml ‫الكود في داخل الطبقة‬


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/andro
id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp">

<View
android:id="@+id/colorView"
android:layout_width="100dp"
android:layout_height="100dp" />

<TextView
android:id="@+id/tvAnimalName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp" />

</LinearLayout>

:‫ هو‬Main2Activity.java ‫الكود في داخل الـ‬


package com.example.ahmed.myapplication;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

178
import java.util.List;
import android.widget.TextView;

public class Main2Activity extends


RecyclerView.Adapter<Main2Activity.ViewHolder> {
private List<Integer> mViewColors;
private List<String> mAnimals;
private LayoutInflater mInflater;
private ItemClickListener mClickListener;

// data is passed into the constructor


Main2Activity(Context context, List<Integer> colors,
List<String> animals) {
this.mInflater = LayoutInflater.from(context);
this.mViewColors = colors;
this.mAnimals = animals;
}

// inflates the row layout from xml when needed


@Override
@NonNull
public ViewHolder onCreateViewHolder(@NonNull ViewGroup
parent, int viewType) {
View view =
mInflater.inflate(R.layout.activity_main2, parent,
false);
return new ViewHolder(view);
}

// binds the data to the view and textview in each row


@Override
public void onBindViewHolder(@NonNull ViewHolder
holder, int position) {
int color = mViewColors.get(position);
String animal = mAnimals.get(position);
holder.myView.setBackgroundColor(color);
holder.myTextView.setText(animal);
}

// total number of rows

179
@Override
public int getItemCount() {
return mAnimals.size();
}

// stores and recycles views as they are scrolled off


screen
public class ViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener {
View myView;
TextView myTextView;

ViewHolder(View itemView) {
super(itemView);
myView = itemView.findViewById(R.id.colorView);
myTextView =
itemView.findViewById(R.id.tvAnimalName);
itemView.setOnClickListener(this);
}

@Override
public void onClick(View view) {
if (mClickListener != null)
mClickListener.onItemClick(view, getAdapterPosition());
}
}

// convenience method for getting data at click


position
public String getItem(int id) {
return mAnimals.get(id);
}

// allows clicks events to be caught


public void setClickListener(ItemClickListener
itemClickListener) {
this.mClickListener = itemClickListener;
}

// parent activity will implement this method to

180
respond to click events
public interface ItemClickListener {
void onItemClick(View view, int position);
}
}

:MainActivity.java ‫وأخيرا نأتي الى الكود في الـ‬


package com.example.ahmed.myapplication;

import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import java.util.ArrayList;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity


implements Main2Activity.ItemClickListener {

private Main2Activity adapter;

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// data to populate the RecyclerView with


ArrayList<Integer> viewColors = new
ArrayList<>();
viewColors.add(Color.BLUE);
viewColors.add(Color.YELLOW);
viewColors.add(Color.MAGENTA);
viewColors.add(Color.RED);
viewColors.add(Color.BLACK);

181
ArrayList<String> animalNames = new
ArrayList<>();
animalNames.add("Horse");
animalNames.add("Cow");
animalNames.add("Camel");
animalNames.add("Sheep");
animalNames.add("Goat");

// set up the RecyclerView


RecyclerView recyclerView =
findViewById(R.id.rvAnimals);
LinearLayoutManager horizontalLayoutManagaer =
new LinearLayoutManager(MainActivity.this,
LinearLayoutManager.HORIZONTAL, false);

recyclerView.setLayoutManager(horizontalLayoutManagaer)
;
adapter = new Main2Activity(this, viewColors,
animalNames);
adapter.setClickListener(this);
recyclerView.setAdapter(adapter);
}

@Override
public void onItemClick(View view, int position) {
Toast.makeText(this, "You clicked " +
adapter.getItem(position) + " on item position " +
position, Toast.LENGTH_SHORT).show();
}
}

‫ إذا ردنا ان نعمل قائمة العرض لتكون بشكل‬،‫االن اكتمل عندنا البرنامج وسيكون مثل الصورة في األعلى‬
)MainActivity.java ‫عمودي كل ما علينا فعله هو استبدال هذين السطرين (الموجودين في الملف‬
LinearLayoutManager horizontalLayoutManagaer = new

LinearLayoutManager(MainActivity.this,

182
LinearLayoutManager.HORIZONTAL,
false);
recyclerView.setLayoutManager(horizontalLayoutManagaer)
;

:‫بهذا السطر‬
recyclerView.setLayoutManager(new
LinearLayoutManager(this));

:‫وسيكون شكل البرنامج هكذا‬

183
‫الفصل االخير (مالحظات عامة)‬

‫* اذا كان عندنا المجلد ‪( layout‬لعرض طبقة لجميع االجهزة ما عدا التابلت) والمجلد ‪layout-large‬‬
‫(لعرض طبقة فقط للتابلت) هناك ملف جافا واحد للتعامل مع هذين الطبقتين واردنا من البرنامج ان يتصرف‬
‫بسلوك معين اذا كان الجهاز تابلت وبخالف ذلك يتصرف بسلوك مختلف‪ ،‬يمكن تحديد نوع الجهاز بستخدام‬
‫الدالة ‪ findViewById‬حيث يجب ان تحتوي احدى هذه الطبقات على عنصر بـ ‪ id‬ويكون هذا العنصر‬
‫غير موجود في الطبقة االخرى‪ ،‬مثال اذا افترضنا ان ‪ id‬لعنصر موجود في طبقة التابلت هو ‪ TAB‬يمكن ان‬
‫يكون شكل الكود في ملف الجافا بهذا الشكل‪:‬‬

‫;)‪View X = findViewById(R.id.TAB‬‬
‫{ ) ‪if ( X != null‬‬
‫االن تعرض الطبقة الموجودة في المجلد ‪// layou‬‬
‫{‪} else‬‬
‫االن تعرض الطبقة الموجودة في المجلد ‪// layout-large‬‬
‫}‬

‫‪184‬‬

You might also like