Professional Documents
Culture Documents
Android
Android
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الذي سيعرض للمستخدم.
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او كتابتها بشكل مباشر.
7
><resources
></resources
حيث ان app_nameو My_TextViewتمثل اسم تعريفي للنص المكتوب بين وسمي الفتح والغلق
وتسمى هذه االسماء بـ ،resIdومن خالله يمكن الوصول للنص المراد عرضه ولكن الحظ ان الـ resId
نفسه يعتبر من النوع intوليس من النوع ،Stringاما النص الموجود بين وسمي الفتح والغلق يعتبر من
النوع Stringوهو يمثل النص المراد التعامل معه ويمكن ان يكون هذا النص اسم البرنامج او نص يعرض
على الشاشة في مربع عرض او يمكن استعماله في اي مكان نريد.
;int X = R.string.app_name
* يمكن وضع متعاقبات الهروب في داخل النص لتعرض ،مثال لطباعة سطر جديد:
*يمكن عمل مصفوفة من االسماء (مصفوفة نصية) في داخل الملف 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ايضا.
المجلد drawableنضع فيه مجلدات اليقونات البرنامج ،وملفات الصور والصوت -1
المجلد layoutنضع فيه ملفات الـ XMLالمسؤلة عن الطبقة وعرض العناصر Widgets -2
المجلد valuesفيه ملف النصوص stringsوملف االلوان colorsوملف الثيم styles -3
المجلد mipmapنضع فيه ايقونات التطبيق (لالجهزة القديمة) -4
يمكننا اضافة مصادر (مجلدات) اخرى في داخل المجلد resلكي يأخذ منها البرنامج معلوماته في حالة معينة
(كأن يكون الهاتف في وضع افقي او يعرض البرنامج من تابلت او هاتف إلخ) ،لعمل ذلك نحتاج ان ننشأ
مجلدات ونلتزم بتسميات خاصة.
Resource type
التصنيفات
انواع مصادر البيانات
9
layout -normal -mdpi -port -notlong -v17
-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ستظهر لنا نافذه منها نحدد مصدر البيانات
والتصنيفات التي نريدها ،لتوضيح االمر سنأخذ مثال لعمل مجلد نضع فيه ملف ينفذ إذا كان الهاتف بشكل
افقي:
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
ويحتوي هذا الكالس على العديد من الدوال منها:
حيث ان الباراميتر االول يمثل مصدر الرسالة (وفي العادة يكون اسم الكالس) والباراميتر الثاني يمثل نص
الرسالة التي ستعرض ،لتوضيح االمر أكثر سنأخذ مثال عن كم دالة.
الدالة d
هذه الدالة هي اختصار لكلمة debugحيث ان وظيفة هذه الدالة هي ان تعرض رسالة للمبرمج عن البرنامج،
مثال:
الدالة i
هذه الدالة هي اختصار لكلمة informationوظيفتها هي تزويد المبرمج بالمعلومات المطلوبة عن البرنامج،
مثال:
13
الكالس Handler
يستخدم هذا الكالس الدارة كود معين ليعمل فيما بعد عند مرحلة معينة ،وايضا يمكن من خالله متابعه كود
يحتاج ان يعمل على ثريد مختلفة ،الستخدام هذا الكالس نحتاج ان نغلف الكود المراد ادارته باالوبجكت
Runnableومن ثم نستخدم دوال الكالس Handlerوهي postو postDelayedلتحديد متى نريد الكود
ان ينفذ.
الدالة post
تنشر هذه الدالة الكود بمجرد ان يتاح لها ذلك ،هذه الدالة تاخذ بين قوسيها باراميتر واحد من نوع االوبجكت
،Runnableاالوبجكت Runnableوظيفته هي التشغيل حيث يتم وضع الكود المراد تشغيله داخل دالته
runوهي المسؤلة عن ذلك و Handlerيتأكد من ان الكود ينفذ في الوقت المناسب ،تكتب الدالة بهذه
الصيغة:
الدالة postDelayed
وظيفة هذه الدالة هي اعادة تنفيذ كود معين بعد فترة زمنية معينة ،وتأخذ هذه الدالة بارامترين االول هو من
نوع االوبجكت Runnableوالثاني هو ،longالبارميتر االول يمثل كود نريد ان ننفذه في داخل الدالة run
اما البارميرتر الثاني يمثل رقم بالملي ثانية نريد ان يتم اعادة تفيذ الكود بعد هذه المدة ،صيغتها العمة بهذا
الشكل:
مثال:
;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
}
;)}
}}
الطريقة االولى
هذه الطريقة يمكن تنفيذها فقط مع العناصر التي تحمل الخاصية ( 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والصيغة العامة لها هي:
حيث انها تعيد اوبجكت من النوع Viewوتأخذ بين قوسيها الـ idالخاص بالعنصر ،Widgetوالحظ ان الـ
idيعتبر من النوع intوليس ،Stringبهذا الشكل:
حيث بدال من العالمة #نضع اسم العنصر الذي نريد التعامل معه كأن يكون TextViewاو Buttonاو
CheckBoxإلخ من العناصرو IDتمثل الـ idالخاص بالعنصر و Xتمثل اسم متغير (اوبجكت) من نوع
هذا العنصر ،مثال لتحديد مربع نصي TextViewاسم الـ idالخاص به هو Yنكتب:
هنا أصبح المتغير (االوبجكت) Xيمثل المربع النصي الذي يحمل االيدي ،Yواالن سنتمكن من الوصول
الى اي خاصية من خصائص العنصر من خالل هذا االوبجكت بهذا الشكل:
;) (X.#
حيث هنا العالمة #تمثل اسم الدالة وغالبا اسم الدالة نفس او مشابه السم الخاصية (الموجودة في ملف الطبقة
)XMLالتي نريد التعامل معها مثال الدالة ( setTextSizeتحدد حجم النص) والدالة ( setPaddingتحدد
االزاحة الداخلية للعنصر) والدالة ( getTextتأخذ النص الموجود في العنصر) والدالة ( setTextتضع نص
في العنصر) إلخ من الدوال ،مثال النشاء دالة عند تنفيذها يتم وضع قيمة نصية في داخل حقل نصي
TextViewالـ idالخاص به هو :Y
بين قوسي الدالة يمكن ان نضع نص بشكل مباشر او مسار 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
}
@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تستخدم لحفظ قيمة معينة في داخل االوبجكت ونضع لهذه القيمة مفتاح خاص (كلمة نصية) من
خالله نصل للقيمة المحفوظة ،وتأخذ الدالة بين قوسيها بارامترين االول عبارة عن قيمة نصية تمثل المفتاح
والثانية تمثل القيمة التي نريد تخزينها ،وهذه الدالة ال تعيد اي شيء ،بهذا الشكل:
الدالة * getتستخدم السترجاع القيمة المحفوظة ،تأخذ بين قوسيها باراميتر واحد عبارة عن قيمة نصية وهو
المفتاح الخاص بالقيمة المحفوظة ،بهذا الشكل:
الدالة onSaveInstanceState
تستخدم هذه الدالة لكي يتم حفظ البيانات في االوبجكت Bundleفي الوقت المناسب قبل توقف البرنامج
ودخوله في وضع دورة الحياة onPauseاو onStopحيث يتم استدعائها في الوقت المناسب بشكل
افتراضي من النظام ،والصيغة العامة لها هي:
مثال:
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بهذا الشكل:
كما ذكرنا في البداية فان كل activityتعرض للمستخدم هي متكونة من ملفين االول يمثل الجزء الظاهر
على الشاشة وهو مكتوب بلغة XMLويسمى layoutاما الثاني فهو مكتوب بلغة Javaوهو يدير الجزء
االول .وكل برنامج يجب ان يتكون من activityواحدة على االقل ،وكذلك يمكننا عمل أكثر من واحدة
وجعل البرنامج يتنقل فيما بينهن.
انشاء Activityجديدة
يمكن انشاء activityجديدة في البرنامج ببساطة في برنامج االندرويد ستوديو نذهب الى نافذة الـ Project
في داخل المجلد appوفي داخل المجلد Javaنجد مجلد باسم الحزمة التي انشأناها نضغط علية بالزر االيمن
ثم نختار New → Activity → Empty Activityكما في هذه الصورة:
25
بعدها ستظهر لنا نافذة ندخل فيها اسم ملف الجافا واسم ملف الطبقة في الـ activityوكذلك نختار لها عنوان
ونضغط على .Finish
هذه الدالة ال تتصل مباشرتا بالـ activityالجديدة وانما ترسل اوبجكت من النوع Intentليتصل بالـ OS
وبالتحديد بالجزء ActivityManagerوهو بدوره يقوم باحضار الـ activityالمطلوبة ويعرف من هي
المطلوبة من خالل الباراميتر .Intent
الـ Intentهو عبارة عن اوبجكت من خالله يمكن التواصل مع ،OSويستخدم هذا االوبجكت للعديد من
االغراض وليس فقط الجل activityجديدة ،الكالس Intentله عدة constructorsوكل واحدة منها تخدم
26
غرض معين ،ولكن ما نحتاجه هنا الجل اخبار ActivityManagerاي activityنريدها لتبدأ العمل
(لعرضها) نحتاج هذه الصيغة:
الباراميتر االول يمثل الحزمة الموجودة فيها الـ 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الحالية.
27
تمرير البيانات بين الـ Activities
يتم تبادل البيانات بين اثنين activityمن خالل تحميل االوبجكت Intentقيم اضافية ،وتكون العملية
بمرحلتين ،المرحلة االولى :اعطائه قيمة في الـ activityاالول ليرسلها ،المرحلة الثانية :استالم واخذ القيمة
منه في الـ activityالثاني.
تأخذ الدالة بارامترين االول قيمة نصية عبارة عن اسم ليمثل القيمة المرسلة ويجب ان ال يتشابه مع اسم لقيمة
مرسلة اخرى ،اما الباراميتر الثاني فهو يمثل القيمة التي نريد ان نرسلها مهما كان نوعها ( intاو Stringاو
اي نوع اخر وكذلك يمكن ان تكون القيمة مفردة او مصفوفة).
مثال( :يكتب هذا الكود في الـ activityالذي نريد ارسال المعلومات منه)
* يمكن تحميل االوبجكت Intentباكثر من قيمة من خالل كتابة الدالة putExtraأكثر من مرة فكل واحدة
منها ستحمل قمية واسم خاص بهذه القيمة.
الباراميتر االول يمثل االسم الذي كتبناه مع القمية عند ارسالها والبارميتر الثاني يمثل القيمة االفتراضية التي
سيتم اعادتها في حال لم يجد االسم ،العالمة #تمثل نوع البيانات التي نريد استخراجها كأن تكون intاو
Stringإلخ من االنوع ،مثال إذا كانت القيمة التي نريد استخراجها من النوع booleanتكون صيغة الدالة
بهذا الشكل:
28
وإذا كانت القمية عبارة عن مصفوفة نضيف للنوع كلمة ،Arrayمثال إذا كانت القمية مصفوفة ومن النوع
intتكون الدالة بهذا الشكل .getIntArrayExtra
الدالة ) (getIntent
تستخدم هذه الدالة دائما عندما نريد استرجاع االوبجكت الذي بدأ الـ ،activityوهذا ما نرسلة عند استخدام
الدالة ).startActivity (Intent
مثال( :يكتب هذا الكود في الـ activityالذي نريد استالم المعلومات فيه)
هذا المثال يستلم القيمة التي ارسلها المثال السابق باالسم .KEY
إذا أردنا ان نسمع رد من الـ activityاالبن فبدال من ان نستخدم الدالة startActivityلعرض الـ activity
االبن نستخدم الدالة ،startActivityForResultالصيغة العامة لها هي:
الباراميتر االول هو نفس باراميتر الدالة ،startActivityاما الباراميتر الثاني فهو عبارة عن رقم من خالله
يمكننا ان نميز الرد من اي activityابن قد جاء (النه يمكن ان يكون في البرنامج عدة activitiesابناء)،
تكتب هذه الدالة في الـ activityاالب.
29
في الـ activityاالبن نضع القيم التي نريد ارسالها لالب في الدالة ،setResultلهذه الدالة صيغتين يمكن ان
نستخدم اي واحدة نريد حسب الحاجة:
الصيغة االولى فيها باراميتر واحد وهو عبارة عن قيمة ثابتة وله شكلين اما ان يكون
Activity.RESULT_OKاو ،Activity.RESULT_CANCELEDوضع النتيجة في هذا
البارميتر يمكن ان يكون مفيد إذا أردنا ان نعرض activityابن ومعرفة هل ضغط المستخدم على زر موافق
ام الغاء (كيف انتهى الـ .)activity
الصيغة الثانية هي نفس االولى لكن تأخذ باراميتر ثاني وهو عبارة عن اوبجكت Intentيمكن ان نحمله ما
نريد من البيانات لكي يتم ارسالها بستخدام الدالة putExtraبهذا الشكل:
يمكن وضع الكود السابق في داخل دالة ونستدعي الدالة عند الضغط على زر معين لمعرفة فيما إذا تم الضغط
على الزر ام ال.
* ليس بالضرورة ان نكتب الدالة setResultفي الـ activityاالبن فيمكن عدم كتابتها إذا لم نكن مهتمين
بالتمييز بين القيم التي تعيدها ،لذا يمكن ان ندع الـ OSيرسل قيمة الـ resultCodeاالفتراضية (دائما يتم
اعادة قمية الـ resultCodeللـ activityاالب إذا بدأ عرض الـ activityاالبن بستخدام الدالة
)startActivityForResultلذا إذا لم نكتب الدالة setResultفانه بمجرد ان يضغط المستخدم على زر
الرجوع من الهاتف سترسل القيمة Activity.RESULT_CANCELEDللـ activityاالب.
الدالة onActivityResult
عندما يضغط المستخدم زر الرجوع من الهاتف سيتم تنفيذ هذه الدالة بشكل اوتوماتيكي ،ولكن الحظ ان هذه
الدالة تكتب في الـ activityاالب النها تستخدم الستقبال البيانات المرسلة من الـ activityاالبن ،وقبل
كتابتها يجب ان نكتب @Overrideالنها موجودة افتراضيا ونحن سنعمل تكرار لها ،الصيغة العامة لها
هي:
30
والذي قلنا انهstartActivityForResult لها ثالثة بارامترات االول هو الرقم الذي ارسلناه من خالل الدالة
الباراميترين الثاني والثالث هما نفس البيانات التي، ابن نستلم منه البياناتactivity يستخدم لمعرفة اي
. من خالل بارامتراتهاsetResult ارسلتهما الدالة
مثال كاستخراج المعلومات من االوبجكت،في داخل هذه الدالة نكتب ما نريد عمله مع البيانات المستلمة
. والتي شرحنا عنها سابقا في هذا الفصلget#Extra بواسطة الدالة
:مثال
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);
}}}
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 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
} }
;}
34
المثال السابق سينفذ كود معين عند الضغط على اول عنصر ،لكننا لم نبين له العنصر االول من اي قائمة! لذا
يجب ان نربط االوبجكت الذي عملناه itemClickListenerمع قائمة العرض التي عندنا ListViewمن
خالل الدالة setOnItemClickListenerبهذا الشكل:
االن سينفذ الكود في داخل ifعند الضغط على العنصر االول في القائمة ( ،)Drinksويمكن تكرار عبارة
الشرط لالستجابة الى بقية عناصر القائمة.
* يمكن كتابة نفس المثال السابق لكن بطريقة مختلفة اي بدال من ان نعمل اوبجكت من الكالس
AdapterView.OnItemClickListenerسنعمل كالس اعتيادي وهو يعمل ( )implementsللكالس
،AdapterView.OnItemClickListenerبهذا الشكل:
يمكن ربط الكالس الذي عملناه myClassمع قائمة العرض التي عندنا ListViewمن خالل الدالة
setOnItemClickListenerبهذا الشكل:
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
الدالة getListView
عندما نستخدم list activityفاننا ال نمتلك ملف طبقة ،XMLونحن نعلم انها تمتلك عنصر ListView
واحد ،للوصول لهذا العنصر نستخدم هذه الدالة وهي تعيد اوبجكت من النوع ،ListViewسابقا في الـ
activityاالعتيادية كنا نكتب هذا الكود لتحديد الـ :ListView
لنحصل على نفس هذا االوبجكت في الـ list activityنكتب هذا الكود:
مثال:
{)public void onListItemClick(ListView listView, View view, int position, long id
36
// Do something
}
الدالة )setListAdapter(adapter
يمرر لها كباراميتر اوبجكت (يحمل مصفوفة) من نوع الكالس ArrayAdapterلتضيف قيم المصفوفة الى
العنصر ListViewالموجود في الطبقة ( XMLوالتي قلنا انها ستنشأ فيما بعد برمجيا) لتظهر على شكل
قائمة.
مثال:
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تخبر البرنامج ان
يعرض كل عنصر بشكل منفرد لكن عند الضغط على العنصر يبقى العنصر مفعل لتمييزة عن بقية العناصر.
ما يفعله الكالس ArrayAdapterفي الحقيقة هو يأخذ كل عنصر في المصفوفة ويحوله الى String
باستخدام الدالة toStringويضع كل نتيجة في مربع عرض نصي text viewثم يظهر كل مربع عرض
نصي في صف منفرد في قائمة العرض.
الدالة setAdapter
تستخدم هذه الدالة لتمرر البيانات التي جلبها الـ adapterالى العنصر widgetالذي سيعرض البيانات.
مثال :إذا كانت عندنا قائمة عرض ListViewفي ملف الطبقة XMLواردنا هذه القائمة ان تعرض
مصفوفة مكتوبة في كالس في ملف جافا ،سنكتب الكود التالي في ملف الجافا التابع للـ activityالتي تعرض
القائمة:
حيث ان Drinksيمثل اسم مصفوفة نصية و List_Nameيمثل الـ idالخاص بالقائمة .ListView
38
الكالس ArrayList
هذا الكالس من النوع Genericوهو موجود في الحزمة ، java. utilوظيفة هذا الكالس هو ان يحمل قمة
من نوع ما على شكل قائمة ،صيغته العامة بهذا الشكل:
حيث ان DataTypeتمثل نوع البيانات التي نريد ان نضيفها للقائمة ( String, Integerاو اي نوع
اوبجكت اخر) والـ Obهو االسم الذي نختاره ،مثال:
لكي نضيف قيم الى هذه القائمة نستخدم الدالة addبهذا الشكل:
;)"mList.add("Ahmed
;)"mList.add("Ali
ويمكن ان نستخرج البيانات من هذه القائمة من خالل الدالة getحيث ان هذه الدالة تأخذ باراميتر واحد هو
عبارة عن رقم يمثل تسلسل العنصر الذي نريد استخراجه في القائمة (اول عنصر في القائمة يأخذ الرقم ،)0
بهذا الشكل:
حيث ان الدالة sizeتعيد رقم عناصر القائمة (اول عنصر في القائمة يأخذ الرقم )0
39
الفصل الخامس ()Fragments
الـ Fragmentهو عبارة عن واجهه (إطار) يتم عرضها للمستخدم من خالل اي activityفي البرنامج
ويمكن ان يعرض الـ activityالواحد عدة ،Fragmentsالـ Fragmentتتكون من واجهة عرض
( layoutعبارة عن ملف )XMLوملف جافا.
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في برنامج االندرويد ستوديو نذهب الى:
ستظهر لنا نافذة منها نعطي اسم للـ Fragmentونعلم على خيار create layout XMLونعطي اسم لملف
الـ XMLويمكن ان نلغي العالمة من الخيارات االخرى إذا لم نرد ان ينشأ لنا بشكل اوتوماتيكي دوال
وانترفيس بداخل الـ ( Fragmentليكون الـ Fragmentفارغ ونحن نضيف به ما نريد) ثم نضغط على
Finishليتم انشاء ملفين االول ملف جافا والثاني ملف .XML
االول ملف الـ :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);
}}
الذي سميناه ويتم انشاء الكالس بنفس االسم وهذا الكالس يجب انFragment تمثل اسم الـWork حيث ان
android.app الموجود في الحزمةFragment ) للكالسextends( يرث
الخاصة بالـlayout يحتاج ان يستدعيها االندرويد في كل مرة يريد عرض الطبقةonCreateView الدالة
الكود التالي في داخلها، النه من خاللها نحدد اي طبقة نريد عرضها،fragment
يمثل اسم الطبقة) اي هذه العبارة اشبه بالدالةfragment_work( هو المسؤل عن الطبقة المراد عرضها
.activity التي تحدد الطبقة التي تعرض في الـsetContentView
) وبدون بارامترات وإال فانه سيحدثpublic( عامةconstructor يجب ان يحتوي على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
43
حيث ان Workيمثل اسم الكالس الرئيسي لملف الجافا للـ ،fragmentوالـ detail_fragتمثل الـ idالذي
اعطيناه للـ fragmentفي ملف الـ XMLالخاص بالـ ،activityفي هذه الجملة سيتكون عندنا اوبجكت
اسمة Xمن نوع الكالس ( Workكالس الـ )fragmentوباالستعانة بهذا االوبجكت يمكن التعامل مع
الدوال الموجودة في كالس الـ ،fragmentمثال للوصول الى دالة في الكالس Workاسمها methوتأخذ
باراميتر واحد من النوع intنكتب التالي:
;)X.meth(5
@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 عنصر (مهما كان نوع العنصر) من خالل الدالة
@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 نستخدم الدالة
@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ونلغي العالمات من انشاء طبقة 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
}}
الدالة onCreateViewهي اختيارية اي يمكن عدم وضعها إذا لم نكن نحتاج الكود ان ينفذ في لحظة انشاء
الـ .Fragment
46
في الـ ListFragmentال نحتاج الى ملف الـ layout XMLولكي نضع المعلومات في القائمة التي ستنشأ
في الطبقة عند العرض نستخدم ملف الجافا الذي انشأناه للوصول لذلك.
* يعرض الـ ListFragmentفي الـ activityكأي Fragmentاعتيادي وكما ذكرنا سابقا اي من خالل
الوسم < >fragmentالذي نكتبه في ملف الـ XMLالخاص بالـ .activity
الدالة getListView
عندما نستخدم ListFragmentفاننا ال نمتلك ملف طبقة ،XMLونحن نعلم انها تمتلك عنصر ListView
واحد ،للوصول لهذا العنصر نستخدم هذه الدالة وهي تعيد اوبجكت من النوع ،ListViewبهذا الشكل:
هذا الكود هو تقريبا نفس الكود التالي الذي كنا نكتبه للوصول الى العناصر Widgetsفي الـ Fragment
االعتيادية:
مثال:
{)public void onListItemClick(ListView listView, View view, int position, long id
// Do something
}
47
setListAdapter(adapter) الدالة
لتضيف قيم المصفوفة الىArrayAdapter يمرر لها كباراميتر اوبجكت (يحمل مصفوفة) من نوع الكالس
(والتي قلنا انها ستنشأ فيما بعد برمجيا) لتظهر على شكلXML الموجود في الطبقةListView العنصر
.قائمة
يمكن ان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
ثانيا :نبدأ بالحصول على FragmentTransactionمن الـ ،fragment managerيتم ذلك بهذا الشكل
حيث ان الباراميتر االول للدالة يمثل الـ idالذي اعطيناه للوسم < ،>FrameLayoutوالباراميتر الثاني
يمثل اوبجكت كالس الـ ( fragmentوالذي عملناه في الخطوة اوال وسميناه .)F
رابعا (اختياري) :استخدام بعض الدوال االخرى مثل الدالة addاو الدالة removeالضافة او حذف
،fragmentبهذا الشكل:
;)T.add(R.id.fragment_container, F
49
;)T.remove(F
كذلك يمكن استخدام الدالة setTransitionلتحديد ما نوع االنتقال (التحول) الذي نريد ،تأخذ بين قوسيها
باراميتر يحدد نوع االنيميشن (تاثير الحركة) الذي نريده وهناك أربع انواع هي:
;)T. setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE
نستخدم الدالة addToBackStackالضافة خاصية الى زر الرجوع في الهاتف إذا ضغط علية المستخدم
ليرجعه الى الـ fragmentالذي كان معروض قبل هذا الـ ،fragmentهذه الدالة تاخذ باراميتر واحد من
النوع Stringيحدد ماذا نريد منها ،إذا أردنا ان يرجع المستخدم الى الـ fragmentالسابق عند الضغط على
زر الرجوع في الهاتف نمرر لها ،nullمثال:
;)T. addToBackStack(null
إذا لم نستخدم هذه الدالة فانه بشكل افتراضي عند الضغط على زر الرجوع في الهاتف لن نعود الى الـ
fragmentالسابق وانما سنعود الى الـ activityالسابق وان لم يوجد سنخرج من البرنامج.
خامسا :بعد ان نعمل كل التأثيرات وما نريد على عملية النقل نستخدم الدالة commitلتطبيق التأثيرات على
الـ ،activityمثال:
مثال( :يكتب هذا الكود في داخل ملف الجافا التابع للـ activityالتي ستحتوي على الـ )Fragment
50
معرفة الـ Fragmentالمعروضة حاليا
إذا كان عندنا اكثرمن Fragmentتعرض في الـ activityواردنا ان نعرف الـ Fragmentالمعروضة
حاليا يمكننا ذلك من خالل اضافة تاك عن طريق الدالة ( replaceوالتي شرحنا عنها قبل قليل) حيث يضاف
هذا التاك كباراميتر ثالث للدالة ونعطيه اي اسم ونضعه بين عالمتي تنصيص ،بهذا الشكل:
يمكننا تحديد الـ Fragmentالمعروضة حاليا من خالل هذا التاك وذلك عن طريق الدالة
findFragmentByTagوهي ستعيد لنا اوبجكت يمثل الـ 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
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الداخلية.
53
الفصل السادس ()action bars
action barهو الشريط الموجود في اعلى التطبيق والذي يظهر فيه اسم البرنامج وفي الغالب نضع فيه
خيار البحث او زر لالعدادات او الظهار االختصارات للتنقل بين البرنامج او ماشابه ،ويمكن ان يبقى ثابت
في التطبيق عند التنقل بين الـ .activitys
لعمل الـ action barنحتاج ان نطبق ثيم كامل على الـ activityاو على كامل التطبيق ،هناك مجموعة
متنوعة من الثيمات مثال يوجد الثيم AppCompatوالذي ياتي مع حزم جوجل supportوهذه الحزم تدعم
االنظمة القديمة من انظمة الهواتف من API level 7فما فوق او الثيم Holoالذي يدعم االصدارات من
API level 17فما فوق او الثيم Materialالذي يدعم االصدارات API level 21فما فوق.
يكتب الثيم في ملف ،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
استخدام هذه الخاصية مع العديد من العناصر النه يمكن
ان تتضارب العناصر فيما بينها)
"<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"/
ومن ثم نضع كل هذه المجلدات في داخل المجلد ،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هكذا:
"<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من خالل الدالة ،setTypeتكون بهذا الشكل (إذا كانت القيمة المرسلة
نصية):
;)"intent.setType("text/plain
ثالثا نحدد العنصر المراد التعامل معه للنشر من خالل idالعنصر الذي اعطيناه له في الوسم > <itemبهذا
الشكل:
رابعا نوضح للنظام ان هذا العنصر هو عنصر نشر من خالل الوصول للـ action providerلكي يظهر
النظام البرامج التي من الممكن النشر بها وذلك من خالل الدالة getActionProviderوالتي ستعيد قيمة من
النوع ShareActionProviderلتكون مرجع لالوبجكت ،Intentوال تنسى ان تعمل استدعاء ()import
للحزمة ،android.widget.ShareActionProviderليكون شكل الكود بهذا الشكل:
= ShareActionProvider shareActionProvider
;)((ShareActionProvider) menuItem.getActionProvider
سادسا نمرر االوبجكت 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> العنصر
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
* الضافة زر الرجوع في الـ action barال نحتاج ان نضيف اي عنصر الى الملف main_menu.xml
وانما فقط من خالل ملف الجافا التابع للـ activityالذي سيظهر فيه هذا الزر.
في ملف الجافا التابع للـ activityالذي سيظهر فيه هذا الزر ،نكتب في داخل الدالة ،onCreateفي االول
نأخذ مرجع للـ action barباستخدام الدالة getActionBarلنستخدمه مع الدالة
setDisplayHomeAsUpEnabledوالتي سنمرر لها القيمة ،trueبهذا الشكل:
61
تغييير عناصر الـ action barاثناء تشغيل البرنامج
يمكن تغيير عناصر الـ action barاثناء عمل البرنامج مثال كأن نجعل زر Buttonعند الضغط عليه
يختفي عنصر من عناصر الـ ،action barيمكننا عمل اي تغيير نريد في اي وقت نريد من خالل استدعاء
الدالة invalidateOptionsMenuهذه الدالة تخبر االندرويد ان عناصر الـ action barبحاجة الى
التغيير ،مثال إذا كان عندنا زر عند الضغط عليه سيتم تنفيذ دالة اسمها changeفاننا سنكتب الدالة
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لجعله
غير مرئي.
62
الدالة setDisplayHomeAsUpEnabled
الظهار زر الرجوع في الـ ( action barوالتي شرحنا عنها سابقا في هذا الفصل).
الدالة setTitle
الظهار عنوان في الـ action barوإذا كان عنوان الـ activityهو الظاهر فهذه الدالة ستستبدله بالعنوان
الممرر لها ،مثال:
;)"getActionBar().setTitle("Title
;”String X = “Title
;)(ActionBar actionBar = getActionBar
;)actionBar.setTitle(X
ا لقائمة الجانبية هي القائمة التي تظهر من جانب الشاشة عند سحب الشاشة من الجانب او عند الضغط على
ايقونتها ،وهي تحتوي على روابط الهم عناصر البرنامج لينتقل لها المستخدم.
63
يتم تنفيذ القائمة الجانبية باستخدام طبقة خاصة تدعى DrawerLayoutوهي موجودة في الكالس
،android.support.v4.widget.DrawerLayoutيجب ان يكون المشروع الذي نعمل به يدعم المكتبة
support.v7.app.AppCompatActivityوللتأكد من ان مشروعنا يدعم هذه المكتبة في برنامج
االندرويد ستوديو نذهب الى:
64
إذا لم تكن المكتبة support.v7.app.AppCompatActivityمضافة نضيفها من عالمة الزائد الموجودة
في اعلى النافذة على جهة اليمين (كما موضح في الصورة السابقة).
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 من خالل الكالس
الخطوة الثالثة
في هذه الخطوة نكتب كود لالستجابة عند الضغط على عناصر القائمة ونعمل ذلك من خالل الكالس
نكتب هذا الكود في ملف الجافا التابع للـ،) (والذي شرحنا عنه في الفصل الرابعonItemClickListener
: بهذا الشكل،MainActivity
68
drawerList.setOnItemClickListener(new DrawerItemClickListener());
selectItem وسيتم تنفيذ الدالة، هو اوبجكت القائمة النصية وقد عرفناه في الخطوة الثانيةdrawerList
:عند الضغط على اي عنصر في القائمة ويمكن ان يكون شكل هذه الدالة هكذا
التي ستعرض بشكل افتراضي عند فتح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وقد
شرحنا عنهم في الفصل السادس) ،بهذا الشكل:
70
}
حيث ان titlesهي مصفوفة نصية عرفناها في الخطوة الثانية ووضعنا فيها عناصر القائمة الجانبية ،واالن
نستدعي هذه الدالة من داخل الدالة selectItemلتنفذ عند الضغط على اي عنصر من القائمة الجانبية بهذا
الشكل:
;)setActionBarTitle(position
حيث ان positionيمثل باراميتر الدالة selectItemوالذي يحمل رقم تسلسل العنصر في القائمة.
الخطوة السادسة
في هذه الخطوة نريد ان نجري تعديالت بناءا على ما اذا كانت القائمة الجانبية مفتوحة (مسحوبة) او مغلقة
وهذا االمر مهم لعمل العديد من االشاء ،اوال نحتاج الى استخدام ActionBarDrawerToggleوهو
مخصص للتعامل مع القائمة الجانبية واالصغاء الحداثها كالغضط على عنصر او اي حدث ممكن يحدث لها،
نعمل ActionBarDrawerToggleجديد وتمرير اربع بارامترات له االول يمثل الـ Contextالحالي
(يمكن كتابة thisاذا كنا في داخل )activityاما الباراميتر الثاني هو DrawerLayoutاما الثالث والرابع
هما مصدر لقيم نصية تكتب في المف النصي ،stringيمكننا ان نستخدم الدالتين التابعتين له لتحدديد ما اذا
كانت القائمة الجانبية مفتوحة او مغلقة ،الدالة onDrawerClosedيتم تنفيذ ما بداخلها اذا كانت القائمة
الجانبية مغلقة onDrawerOpened ،يتم تنفيذ ما بداخلها اذا كانت القائمة الجانبية مفتوحة ،يكتب الكود بهذا
الشكل:
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
}
الخطوة السابعة
في هذه الخطوة نريد ان نجعل المستخدم يفتح ويغلق القائمة الجانبية من خالل ايقونة تظهر في الـ 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
}
االن أصبح عندنا زر رجوع عند الضغط عليه تظهر القائمة الجانبية وعند الضغط علية مرة اخرى تغلق
القائمة الجانبية ،ما نريده االن هو ان نغير شكل ايقونة زر الرجوع لتتناسب مع حالة القائمة الجانبية إذا كانت
مغلقة تظهر ايقونة بشكل ثالثة خطوط افقية اما إذا كانت مفتوحة تكون االيقونة بشكل سهم ،لعمل ذلك نحتاج
ان نستخدم الدالة onPostCreateومن داخل هذه الدالة نستدعي الدالة ،syncStateنكتب الكود بهذا
الشكل( :يكتب هذا الكود خارج الدالة )onCreate
@Override
{)protected void onPostCreate(Bundle savedInstanceState
;)super.onPostCreate(savedInstanceState
;)(drawerToggle.syncState
}
@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
: نعمل ذلك بهذا الشكل،عرضها من جديد بعد تدوير الهاتف
75
}
…
}
السابقة وذلك عنFragment ثابت عند الرجوع بزر الهاتف للـaction bar ثانيا نحل مشكلة بقاء عنوان الـ
فيFragment المعروضة حاليا واختبار ما معروض وما عندنا منFragment طريق اضافة تاك للـ
(شرحنا عن هذه االشياءFragmentManager.OnBackStackChangedListener داخل االنترفيس
(هذه الدالة قد كتبناها في الخطوة الثالثةreplace في البداية نضيف التاك داخل الدالة،)في الفصل الخامس
:وهنا فقط نضيف لها اسم التاك كباراميتر ثالث) بهذا الشكل
76
ثالثا حل مشكلة بقاء التاشير على العنصر الحالي في القائمة الجانبية عند الرجوع بزر الهاتف للـ Fragment
السابقة ،حل هذه المشكلة بسيط وذلك من خالل الدالة ، setItemCheckedهذه الدالة تستخدم لجعل عنصر
في القائمة ListViewيكون مفعل ( كانه تم الضغط عليه ) او الغاء التفعيل من العنصر ،وهي تاخذ
بارامترين االول هو رقم موقع العنصر ( الذي نريد ان نجعله مفعل ) في القائمة ListViewوالثاني عبارة
عن قيمة booleanاذا كان trueفانه سيتم تفعيله اما اذا كانت falseفسيتم الغاء التفعيل ،بهذا الشكل ( :
نكتب هذا الكود في داخل الدالة onBackStackChangedالتي عملناها قبل قليل لنضمن انه يتم تفعيل
العنصر المطلوب في كل مرة يحدث فيها تغيير في زر رجوع الـ ) Fragment
;)drawerList.setItemChecked(currentPosition, true
ن ظام االندرويد يخزن قواعد البيانات على شكل ملفات في مجلد داخل النظام ،كل قاعدة بيانات تتكون من
ملفين االول هو database fileوله نفس اسم البرنامج وهو الملف الرئيسي لقاعدة البيانات حيث ان كل
البيانات يتم تخزينها هنا ،الثاني هو journal fileيحمل نفس اسم البرنامج ويضاف له الالحقة -journal
هذا الملف يحمل كل التغييرات التي تحدث في قاعدة البيانات على سبيل المثال إذا حدثت مشكلة فان نظام
االندرويد سيستخدم هذا الملف ليرجع الى اخر تغيير.
االندرويد ياتي معد عدة كالسات لتسمح لنا بالتعامل مع قواعد البيانات ،SQLiteهناك ثالثة انواع من
الكالسات الريسية تقوم بكل العمل هي:
77
الكالس SQLiteDatabaseيعطينا صالحية النفوذ (االتصال) بقواعد البيانات.
Cursors
الكالس Cursorيسمح لنا بالقراءة والكتابة على قواعد البيانات.
الدالة 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
بعض االعمدة يمكن ان تعين على انها ،primary keyوهي ال يمكن ان تأخذ قيم متكررة ،نظام االندرويد
يتوقع ان يكون هناك عمود primary keyاسمه _idفيه ارقام متسلسلة ،لذا من االفضل عمل هذا العمود
لتجنب بعض االخطاء (هذا العمود نعمله لكن ال نضيف له القيم وانما هي تضاف اوتوماتيكيا).
كل عمود في الجدول مصمم ليحمل نوع معين من البيانات ،مثال ارقام او نصوص ،انواع البيانات هي:
* نحن نعمل قواعد البيانات باستخدام لغة ) ،Structured Query Language (SQLنحن نستخدم اوامر
هذه اللغة لعمل الجداول ،مثال لعمل الجدول السابق (جدول طالب المدرسة) نكتب:
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يمثل االوبجكت ،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وهي تستخدم لتغيير قيمة بيانات موجودة في جدول قاعدة
البيانات وهي تعيد رقم يمثل رقم تسجيل تحديث ،صيغتها العامة بهذا الشكل:
حيث ان الباراميتر االول يمثل اسم الجدول المراد تحيدث بياناته ،الباراميتر الثاني يمثل االوبجكت
( ContentValuesشرحنا عنه قبل قليل في الدالة )insertوهو سيحمل القيمة الجديدة مع اسم العمود،
82
الباراميتر الثالث يمثل الشرط في التحديث ،الباراميتر الرابع يمثل قيمة الشرط ،لتوضيح الفكرة لنأخذ مثال
لتغيير قيمة الجدول الذي عملناه قبل قليل (في الدالة :)insert
* إذا وضعنا قيمة البارامترين الثالث والرابع 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
* إذا وضعنا قيمة البارامترين الثاني والثالث nullفهذا يعني اننا سنحذف كل البيانات في الجدول.
في هذا الكود اضفنا الى الجدول الذي اسمه STUDENTعمود اسمه CLASSويحمل قيم رقمية ،ولكي
نضيف هذا الكود نحتاج الى استخدام الدالة ( execSQLشرحنا عنها مسبقا في هذا الفصل) بهذا الشكل:
84
اعادة تسمية الجدول
لكي نعيد تسمية جدول قاعدة بيانات موجود مسبقا نستخدم امر لغة ،SQLبهذا الشكل:
في هذا الكود غيرنا اسم الجدول STUDENTالى االسم ،SCHOOLولكي نضيف هذا الكود نحتاج الى
استخدام الدالة ( execSQLشرحنا عنها مسبقا في هذا الفصل) بهذا الشكل:
حذف الجدول
لكي نحذف جدول قاعدة بيانات بالكامل نحتاج الى استخدام امر لغة ،SQLبهذا الشكل:
هذا االمر سيحذف الجدول المسمى STUDENTوهذا االمر يمكن ان يكون مفيد اذا لم نعد بحاجة الى
الجدول واردنا تقليل مساحة التخزين ،ولكي نضيف هذا الكود نحتاج الى استخدام الدالة ( execSQLشرحنا
عنها مسبقا في هذا الفصل) بهذا الشكل:
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بهذا الشكل:
الجزء الثاني
في هذا الجزء سنتعلم كيف نربط قاعدة البيانات مع البرنامج الخاص بنا الستخراج البيانات وعرضها وكذلك
السماح للمستخدم باضافة بيانات لقاعدة البيانات ،نعمل كل ذلك من خالل الكالس .Cursors
86
الدالة query
هذه الدالة موجودة في الكالس ،SQLiteDatabaseمن خاللها نحدد بالضبط البيانات التي نريد ان نجلبها
من قاعدة البيانات ،كان تكون جدول بالكامل او عمود في هذا الجدول او االسماء التي تبدأ بالحرف Bمثال او
اي شرط نضعه لناخذ البيانات المطلوبة بالضبط ،وهي تعيد اوبجكت من النوع Cursorوالـ activitiesفي
برنامجنا يمكنها ان تستخدمه للوصول للبيانات ،الصيغة العامة لهذه الدالة هي:
الباراميتر االول للدالة من خالله نحدد اسم الجدول ،الباراميتر الثاني يمثل اسم العمود (او االعمدة)،
الباراميتر الثالث نضع فيه الشرط ،الباراميتر الرابع نضع فيه قيمة الشرط ،البارامترين الخامس والسادس
نستخدمهم إذا أردنا ان نجمع البيانات ،الباراميتر السابع نستخدمه إذا أردنا البيانات المعادة ان تترتب بشكل
معين (مثال بشكل ابجدي) ،هناك امكانيات اخرى لهذه الدالة تمكننا من اضافة شروط اخرى لتحديد البيانات
العائدة ،لمزيد من المعلومات يمكنك زيارة الموقع التالي:
https://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.ht
ml
* لتوضيح الية عمل هذه الدالة سنأخذ بعض االمثلة بشروط مختلفة العادة بيانات من الجدول STUDENT
التالي:
87
حيث ان dbيمثل االوبجكت .SQLiteDatabase
مثال العادة قيمة من عمود عندما تكون قيمة عمود اخر تساوي قيمة نحن نحددها ،في هذا الكود سنعيد اسم
مدرسة الطالب :Ali
هذا يعني ان يعيد قيمة العمود NAMEوالعمود SCHOOL_NAMEعندما تكون قيمة العمود NAME
هي ،Aliاي انه سيعيد القيمة Aliوالقيمة ،Motfoqinالعالمة ? تعني (قيمة ما) وهذه القيمة نحددها في
الباراميتر الرابع ،والتي حددناها هنا .Ali
هنا سيعيد قيمة العمود SCHOOL_NAMEوقيمة العمود SCOREعندما تكون قيمة العمود NAME
هي ،Aliاي انه سعيد القيمة Motfoqinوالقيمة .95
هنا ستعيد القيمة إذا كانت قيمة العمود SCHOOL_NAMEهي AL Edriseاوقيمة العمود NAME
هي ،Aliلذا فانه سيعيد القيمتان Ahmedو AL Edriseوالقيمتان Aliو.Motfoqin
* يجب ان تكون الشروط عبارة عن قيم نصية فاذا كان الشرط الذي نريد ان نضعه قيمة عددية يجب ان
نحولة الى نص ،بهذا الشكل:
88
new String[ ] {“NAME”, “SCHOOL_NAME”},
“_id = ?”,
new String[ ] { Integer.toString(1) },
;)null, null, null
مثال إذا أردنا ان نرجع البيانات لتترتب ابجديا (بشكل افتراضي البيانات تترتب على حسب قيمة _id
تصاعديا) ،يمكن ان نكتب التالي:
هذا المثال يعني ان يجلب لنا كل البيانات في العمود NAMEوالعمود SCOREوترتب البيانات ابجديا
بشكل تصاعدي اي من Aالى ( Zوإذا كانت ارقام فمن أصغر رقم الى اكبرها) وهذا بحسب قيم العمود
،NAMEوإذا أردنا ان ترتب القيم تنازليا نستبدل الكلمة ASCبالكلمة .DESC
مثال لترتيب البيانات القادمة لكن بحسب قيم أكثر من عمود ،اي في االول يرتب حسب قيم العمود االول وإذا
تكررت القيم سترتب القيم المكررة على حسب العمود الثاني ،بهذا الشكل:
في هذا المثال سترتب البيانات تنازليا على حسب العمود SCOREوإذا كان هناك ارقام متساوية في هذا
العمود فانها سترتب تصاعديا على حسب العمود .NAME
89
MAX أكبر قيمة
MIN أصغر قيمة
مثال ليعيد عدد الطالب في الجدول (عدد الصفوف في الجدول )STUDENTباالعتماد على العمود _id
من خالل الدالة ،COUNTبهذا الشكل:
هذه الدالة ستعيد الرقم ( 3حيث انها ستعمل عمود جديد اسمه my_ countوتضع فيه القيمة)
مثال العادة متوسط القيمة لدرجات الطالب من خالل الدالة ،AVGبهذا الشكل:
مثال سنستخدم فيه الباراميتر السادس للدالة ( )GROUP BYمن خالله يمكن ان نقسم النتائج العائدة على
حسب قيم عمود ما ،مثال إذا أردنا ان نعرف كم طالب في كل مدرسة (كل مدرسة كم مرة متكرر اسمها)،
يمكن ان نكتب الكود التالي:
SCHOOL_NAME my_count
AL Edrise 1
Motfoqin 2
90
الدالة getReadableDatabaseوالدالة getWritableDatabase
كما ذكرنا قبل قليل لجلب البيانات نحتاج الى استخدام الدالة queryوهذه الدالة موجودة في الكالس
SQLiteDatabaseفللوصول لها نحتاج الى مرجع لقاعدة البيانات ،الكالس SQLiteOpenHelper
يحتوي على بعض الدوال يمكنها ان تساعدنا وهذه الدوال هي getReadableDatabase
و 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يعيد اي قيمة.
الدالة moveToPrevious
هذه الدالة تنقلنا الى الصف السابق من البيانات (الجدول) وهي تعيد trueإذا وجدت قيمة وانتقلت بشكل
صحيح او تعيد falseإذا فشلت في االنتقال او إذا لم تكن هناك بيانات.
{ )) (if (cursor.moveToPrevious
//Do something
;}
92
الدالة moveToNext
هذه الدالة تنقلنا الى الصف القادم من البيانات (الجدول) وهي تعيد trueإذا وجدت قيمة وانتقلت بشكل
صحيح او تعيد falseإذا فشلت في االنتقال او إذا لم تكن هناك بيانات.
* يجب االنتقال الى الصف المناسب من جدول البيانات الذي يعيده االوبجكت Cursorحتى لو كانت القيمة
المعادة هي عبارة عن صف واحد يجب تحديد هذا الصف بهذه الدوال (دوال التنقل) ثم بداخل ifالشرط
الخاص بالدالة نكتب كود اخذ البيانات من االوبجكت .Cursor
NAME SCHOOL_NAME
93
Ahmed AL Edrise
:فاذا كتبنا الكود التالي
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عليه ان يستخدم الخذ البيانات منه وماهي االعمدة التي نريد عرضها وفي اي عنصر عرض ،بهذه
الصيغة:
حيث ان الباراميتر االول يشير الى المحتوى contextالحالية ،الباراميتر الثاني يشير الى كيفية عرض
العناصر في القائمة يمكن مثال استخدام android.R.layout.simple_list_item_1لعرض كل عنصر
في القائمة بسطر منفصل ،الباراميتر الثالث يمثل االوبجكت Cursorالذي انشأناه لجلب البيانات من قاعدة
البيانات وكما قلنا فهو يجب ان يحتوي على العمود _idاضافة الى البيانات التي نريد استخراجها ،الباراميتر
95
الرابع يمثل االعمدة التي نريد استخراج البيانات منها ووضعها في عنصر العرض ،الباراميتر الخامس يمثل
عنصر العرض ،اما الباراميتر السادس يستخدم لتحديد سلوك االوبجكت cursorوفي الغالب نضع له القيمة
.0
* كما ذكرنا سابقا فانه عندما نفتح منفذ لقاعدة البيانات وكذلك عند عمل االوبجكت cursorيجب علينا
غلقهما وذلك لتحرير الذاكرة ،وهنا ايضا يجب ان نغلقهما عند استخدام CursorAdapterلكن ما يجب علينا
االنتباه له هو انه يجب ان نغلقهما عندما ننتهي تماما من عرض البينانات لذلك يفضل ان تتم عملية االغالق
في داخل الدالة onDestroyالتابعة للـ activityلكي يتم الغلق بعد ان تحطم الـ activityوذلك الن قائمة
العرض ListViewلن تقوم باخذ كل البيانات دفعة واحدة من االوبجكت cursorوانما تاخذ فقط ما سيظهر
على الشاشة للمستخدم وعندما يحرك المستخدم شريط التمرير في الشاشة ليستعرض بقية القائمة هنا في هذه
اللحظة ستعود الـ ListViewالى االوبجكت cursorالخذ ما سيظهر فقط.
مثال( :يكتب في ملف الجالفا الخاص بالـ activityالذي سيعرض القائمة )ListView
96
;)(toast.show
}}
@Override
{)(public void onDestroy
;)(super.onDestroy
;)(cursor.close
;)(db.close
}
* كما تالحظ لقد استخدمنا الدالة setAdapterلوضع البيانات في القائمة ListViewوذلك النه يمكننا
استخدام كل دوالها (والتي شرحنا عنها سابقا) مع CursorAdapterكما استخدمناها مع .ArrayAdapter
لنأخذ مثال ونفترض ان عندنا العنصر CheckBoxظاهر للمستخدم وعند الضغط علية تنفذ الدالة
onFavoriteClickedونريد ان نخزن القيم العائدة من هذا العنصر (تعود القيم من هذا العنصر باستخدام
الدالة isCheckedوهي تعيد اما 0وتعني العنصر غير مفعل او 1وتعني العنصر مفعل) في عمود في
جدول في قاعدة البيانات اسمة ،FAVORITEيمكن ان يكون شكل الدالة onFavoriteClickedهكذا:
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الصيغة العامة لهذه الدالة هي:
تاخذ باراميتر واحد هو عبارة عن اوبجكت cursorالجديد (الذي يحمل البيانات المحدثة) الذي نريد استبداله
باالوبجكت cursorالسابق.
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
وهو سيكون حامال للنتيجة التي تعيدها هذه الدالة بشكل اوتوماتيكي) ،مثال:
;)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
هذا الشكل بالنسبة لألجهزة التي نظامها API 26فما فوق ،اما األجهزة التي نظامها اقل نكتب لها هذا الكود:
;NotificationCompat.Builder builder
االن أصبح عندنا اوبجكت اسمه 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 داخلي لنحدد الـintent انشاء-1
: هكذا،MainActivity اننا نريد ان نعرض
105
: بهذا الشكلsetFlags نستخدم الدالة-2
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_SINGLE_TOP);
لكي تبدأ الـsetContentIntent الى االشعار باستخدام الدالةpending intent اخيرا نضيف الـ-4
: بهذا الشكل، عند الضغط على االشعارactivity
.setContentIntent(pendingIntent);
ارسال االشعارات
وذلكbuilt-in services اوال علينا النفوذ الى الـ،notification service نرسل االشعارات بستخدام الـ
الذي نريدservice وهي تأخذ باراميتر واحد هو عبارة عن اسم الـgetSystemService باستخدام الدالة
: ليكون شكل الكود بهذا الشكل،)notification service استخدامه (في حالتنا هذه هو
لتحديد االشعار فاذا ارسلنا اشعار اخر بنفس هذا الرقم سيستبدل االشعار السابقNOTIFY_ID نستخدم
هوbuilder و،)باالشعار الجديد (قد يكون هذا مفيد إذا أردنا تحديث اشعار موجود بمعلومات جديدة
نرسل االشعارnotify االوبجكت الذي انشأناه قبل قليل وهنا سنبنيه ومن ثم بالدالة
مثال متكامل
106
االن سنأخذ مثال متكامل لبناء اشعار وسيعمل على جميع األجهزة ومن خالله أيضا سنتعلم كيفية بناء قنياة
حيث انه بمجردcreateNotification سنضع كل محتويات االشعار في داخل دالة اسمها،Channel
: مثال،استدعائها سيظهر االشعار للمستخدم
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
هناك أنواع عديدة من النوافذ المنبثفة يمكن انشائها ،في هذا الجزء سنأخذ البعض منها.
تستخدم هذه النافذة المنبثقة الظهار تلميح ما للمستخدم ،يمكن ان نعمل ذلك من خالل الدالة makeText
الموجودة في الكالس Toastبهذه الصيغة العامة:
حيث ان الباراميتر االول هو مثال من الكالس Activityوالذي هو بدوره كالس فرعي من الكالس
Contextفيمكن ان يحمل القيمة thisاو اسم الـ activityاو نجلبة باستخدام الدالة
، getApplicationContextالباراميتر الثاني هو عبارة عن مسار الـ idلنص الرسالة الذي سيعرض اذا
كان مكتوب في ملف الـ stringوالحظ ان المسار resIdيعتبر من النوع intاو نكتب الرسالة بشكل مباشر
هنا في هذه الدالة بوضعها بين عالمتي تنصيص ،البارميتر الثالث يمثل المدة الزمنية التي تعرض بها الرسالة
ثم تختفي وهو اما يأخذ القيمة LENGTH_SHORTاو القيمة . LENGTH_LONG
109
او يمكن كتابة السطر السابق بهذا الشكل:
;Import android.widget.Toast
نافذة هذه الرسالة تظهر بشكل افتراضي في أسفل الشاشة (كما في الصورة في األعلى) لكن لكي نجعلها
تظهر في مكان اخر من الشاشة علينا ان نستخدم الدالة setGravityحيث ان هذه الدالة تأخذ ثالثة
بارامترات األول يمثل محتوى الـ Gravityوالثاني يمثل محور االحداثيات Xوالباراميتر الثالث يمثل محو
االحداثيات ،yمثال لعرض النافذة في منتصف اعلى الشاشة نكتب الكود التالي:
110
لعمل مثل هذا الـ Dialogسنفترض ان عندنا زر اعتيادي عند الضغط علية سيتم تنفيذ الدالة
،show_dialogسنعمل طبقة layoutإضافية (غير تلك المرتبطة بالـ activityلنظع بداخلها ازرار
والحقول التي ستظهر في الرسالة) لنفترض ان اسم الطبقة الجديدة هو my_layoutويمكنك ان تضع فيها ما
تشاء كأي طبقة اعتيادية.
االن أصبح عندنا اوبجكت اسمه 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 واالن نريد ان ننفذ الدالة،في األعلى
112
{ )public void onClick(View view
// User clicked on cancel button
;)(mydialog.dismiss
}
;)}
;) (mydialog.show
}
الحظ هنا عندما أردنا الوصول الى الحقول النصية واالزرار لم نستخدم فقط الدالة findViewByIdبل
سبقناها باسم االوبجكت .mydialog
النشاء هكذا نوع من رسائل التنبيه علينا أوال ان ننشأ اوبجكت من ،AlertDialogبهذا الشكل:
113
االن أصبح عندنا اوبجكت اسمه alertDialogBuilderيمكن ان نضيف له الدوال لوضع الخصائص على
الرسالة ،من هذه الدوال:
الدالة setTitle
من خالل هذه الدالة يمكننا ان نضع عنوان لرسالة التنبيه ،بهذا الشكل:
;)"alertDialogBuilder.setTitle("Your Title
الدالة setIcon
من خالل هذه الدالة نضع ايقونة لتظهر مع الرسالة ،بهذا الشكل:
;)alertDialogBuilder.setIcon(R.drawable.ahmed
الدالة setMessage
الدالة setCancelable
هذه الدالة اما تأخذ القيمة ( trueهذا يعني انه عند الضغط على المساحات الفارغة خارج الرسالة سوف
تذهب رسالة التنبيه) او القيمة ( falseهذا يعني انه عند الضغط على المساحات الفارغة خارج الرسالة سوف
لن تذهب رسالة التنبيه) ،مثال:
;)alertDialogBuilder.setCancelable(false
الدالة setPositiveButton
تستخدم هذه الدالة للتعامل مع الزر موافق وماذا نريد من البرنامج ان يفعل عند الضغط على هذا الزر ،مثال:
alertDialogBuilder.setPositiveButton("Yes", new
{ )(DialogInterface.OnClickListener
}
114
;)}
الدالة setNegativeButton
تسخدم هذه الدالة للتعامل مع زر اإللغاء وماذا نريد من البرنامج ان يفعل إذا ضغط المستخدم على هذا الزر،
مثال:
alertDialogBuilder.setNegativeButton("No", new
{ )(DialogInterface.OnClickListener
;)(dialog.cancel
}
;)}
تستخدم الدالة cancelلكي نخفي ظهور الرسالة ووضعناها في داخل الزر Noاللغاء الرسالة عند الضغط
على هذا الزر.
الدالة create
بعد ان جمعنا البيانات التي نريد عرضها في االوبجكت alertDialogBuilderفانه حان الوقت لبناء هذا
االوبجكت بستخدام هذه الدالة ،بهذا الشكل:
الدالة show
بعد ان نبني االوبجكت بالدالة createحان االن وقت عرض الرسالة ،تستخدم هذه الدالة لهذا الغرض ،بهذا
الشكل:
;)(alertDialog.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();
}
116
الفصل العاشر ()Services
serviceهو مكون من مكونات التطبيق مثل الـ activityلكن بدون واجهة مستخدم ولديه دورة حياة ابسط
من الـ activityوهي تأتي بحزمة من المزاية لكتابة الكود ليعمل بينما المستخدم يفعل اشياء اخرى في
الهاتف ،احيانا نريد بعض العمليات ان تعمل بغض النظر عن اي برنامج علية التركيز في الهاتف ،مثال كنا
نريد تحميل ملف ضخم او تشغيل مقطع موسيقي في الخلفية او االنتظار لوصول رسالة من االنترنت (حتى
لو كان البرنامج مغلق) هنا نحتاج ان نتعامل مع الـ .services
* عندما نعمل serviceبهذه الطريقة فان برنامج االندرويد ستوديو بشكل اوتوماتيكي يصرح في ملف الـ
AndroidManifest.xmlعن الـ serviceمن خالل الوسم < >serviceبهذا الشكل:
117
<service
"android:name=".MyService
>"android:exported="false
></service
كما تالحظ فان لهذا الوسم خاصيتين االولى هي nameوهي تحمل اسم الـ serviceويجب ان يكون االسم
مسبوق بنقطية لكي يتمكن من دمجه مع الحزمة ،اما الخاصية الثانية هي تحدد فيما إذا كان الـ service
سيستخدم في برامج اخرى إذا كانت القيمة falseفهذا يعني انه فقط هذا البرنامج يستطيع استخدامه.
* سنقسم هذا الفصل الى جزئين الجزء االول نتكلم فيه عن عمل النوع االول started serviceاما الجزء
الثاني سنتكلم فيه عن عمل النوع الثاني .bound service
;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
@Override
{ )protected void onHandleIntent(Intent intent
;)"String text = intent.getStringExtra("mess
;)Log.v("DelayedMessageService", text
}
كما ذكرنا سابقا فان ما في داخل الدالة onHandleIntentسيعمل في ثريد في الخلفية ،ولكي نعرض اي
كود للمستخدم يحتاج هذا الكود ان يعمل في الثريد الرئسي ،لذا نستخدم الكالس ( handlerشرحنا عنه سابقا)
في داخل الدالة onStartCommandوهي موجودة في داخل الـ serviceوتنفذ في الثريد الرئيسي وهي
119
ليكون كود،intent service حيث يتم استدعائها في كل مرة يبدأ فيها الـonHandleIntent تنفذ قبل الدالة
: بهذا الشكلservice الـ
وفي، لكي تنفذ في الخلفية عند حدوث امر ما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علينا ان ننشأ االوبجكت Binderونمرره للدالة
onBindلتعيده لكي تستلمه الـ .activity
121
يجب ان ننشأ الـ Binderبانفسنا في الـ Serviceالذي انشأناه قبل قليل ،نحن سننشأ الـ Binderعلى شكل
كالس داخلي في داخل الـ serviceوسوف نسميه ،MyBinderليكون كامل شكل كود الـ serviceهكذا:
عندما ترتبط الـ activityمع الـ serviceفان االتصال سيستدعي الدالة onBindوالتي ستعيد االوبجكت
MyBinderفعندما تستلم الـ activityاالوبجكت MyBinderمن االتصال فهي ستستخدم الدالة X
لتحصل على االوبجكت .MyService
عندما انشأنا الـ bound serviceجعلناه يرث الكالس ،Serviceوهذا الكالس لديه بعض الدوال المهمة
منها:
122
تستخدم وهي على وشك ان
تتحطم
واالوبجكتservice يتم استدعاء هذه الدالة عندما ينشأ االتصال بالـonServiceConnected الدالة
.service يستلم والذي من خالله نأخذ مرجع للـBinder
النBinder بهذا الشكل (راجع الكود السابق في انشاء الـactivity يمكن ان يكون شكل الكود في داخل الـ
:) واالوبجكت والدوال داخلهservice هذا الكود يعتمد عليه في اسم الـ
123
@Override
{)(protected void onStart
;)(super.onStart
;)Intent intent = new Intent(this, MyService.class
;)bindService(intent, connection, Context.BIND_AUTO_CREATE
}
* يفضل ان نلغي الربط بالـ serviceإذا كانت الـ activityغير مرئية (دخلت في دورة الحياة )stopمن
خالل الدالة ،unbindServiceيمكن ان يكون الكود بهذا الشكل:
@Override
{)(protected void onStop
;)(super.onStop
{)if (bound
;)unbindService(connection
;bound = false
}
}
الحظ عملنا ifالشرط لنتأكد من ان االتصال كان قد تم عند الربط ولم يفشل bound ،هو اسم متغير انشأناه
قبل قليل ،و connectionهو اسم اوبجكت انشأناه قبل قليل.
الموقع Location
إذا أردنا تحدد موقع الجهاز نستخدم location serviceوهو سيستخدم المعلومات من GPSالنظام ،من
خالل الخطوتين التاليتين:
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يمكن ان
نضع الكود الذي نريد تنفيذه حسب الحالة في الدالة المناسبة اما الدالة التي النحتاج ان نكتب فيها كود يمكن
ان نتركها فارغة لكن يجب ان تكتب كل هذه الدوال حتى لو لم نكن نحتاجها.
= LocationManager lm
)(LocationManager
;)getSystemService(Context.LOCATION_SERVICE
وقد رأينا سابقا في االشعارت كيف استخدمنا الدالة getSystemServiceواالن نستخدمها ايضا لنحدد نوع
الـ SERVICEوفي حالتنا هو ،LOCATIONبعد ان ننشأ هذا االوبجكت علينا ان نستخدمة دالته
requestLocationUpdatesلنحدد معايير تحديث الـ ؤ وهي تأخذ اربع بارامترات االول يمثل مزود الـ
،GPSاما الثاني فيمثل اقل وقت بين كل تحديث للموقع بالملي ثانية ،اما الثالث فهو يمثل اقصر مسافة
لتحديث الموقع بالمتر ،اما الرابع فيمثل الـ ،LocationListenerيمكن ان يكون شكل الدالة هكذا:
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هي المتحسسات الموجودة في الهاتف مثل متحسس درجة االضاءة ومتحسس االتجاه واالرتفاع
إلخ ،سنتعلم في هذا الفصل كيف نتحكم ونتعامل معها ،انواع المستشعرات هي:
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شرحنا عنها في الفصل التاسع) ،بهذا الشكل:
للوصول الى مستشعر محدد نستخدم االوبجكت ،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الكود التالي:
لكن هذه الطريقة ال تحدد كل التفاصيل التي نريد ان نعرفها عن هذا المستشعر فقط تحدد ان كان موجود ام
ال.
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بهذا الشكل:
االن أصبح عندنا مصفوفة اسمها 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
هذه الدالة تصغي الى اي تغير ممكن ان يحدث في مستشعر معين ،ويمكن ان نحدد لها المدة التي نريدها ان
تخبرنا عن التغيرات التي تحدث في المستشعر ،من خالل أحد هذه القيم:
130
( SENSOR_DELAY_GAME -3تخبرنا عن التغير كل 20,000ملي ثانية)
( SENSOR_DELAY_FASTEST -4تخبرنا عن التغير باقصى سرعة ممكنة)
حيث ان mgrيمثل مرجع للـ SensorManagerوالباراميتر الثاني Lgيمثل اوبجكت المستشعر الذي
حددناه عبر الدالة .getDefaultSensor
الدالتين onSensorChangedوonAccuracyChanged
يجب كتابة هاتين الدالتين الننا جعلنا الـ activityترث االنترفيس ،SensorEventListenerحيث ان
الدالة onAccuracyChangedتقيس قيم المستشعر وهي تاخذ باراميتر واحد هو عبارة عن اوبجكت من
النوع SensorEventسنشرح عنه بعد قليل ،الدالة onAccuracyChangedتؤدي رد فعل عند حدوث
تغير في قيمة المستشعر وهي تأخذ بارامتيرن االول عبارة عن اوبجكت من النوع Sensorاما الباراميترر
الثاني هو عبارة عن متغير من النوع .int
* يمكن ان نصغي الى المستشعرات في داخل الـ activityبدون ان نجعل الـ activityترث
( )implementsاالنترفيس SensorEventListenerوذلك من خالل كتابة هذا االنترفيس في داخلها على
شكل كالس داخلي ،بهذا الشكل:
131
values -4مصفوفة عددية من النوع floatتحتوي على القيمة او القيم الجديدة المالحظة ،يمكن ان
تكون القيم (عناصر المصفوفة) مختلفة من مستشعر الخر ،الجدوال تالي يوضح قيمها:
132
الدالة unregisterListener
تستخدم هذه الدالة عندما نريد التوقف لالصغاء الى المستشعر (حفاضا على البطارية وسرعة البرنامج) وهي
تأخذ بارامترين ،االول يمثل المحتوى الحالي اما الثاني يمثل اوبجكت المستشعر الذي عملناه له من خالل
الدالة ،getDefaultSensorإذا أردنا ان نوقف االصغاء الى مستشعر الضوء مثال (اسم اوبجكته )Lg
نكتب الكود التالي:
;)mgr.unregisterListener(this, Lg
133
المستشعرات Accelerometersو compassesو gyroscopesتعطينا االمكانية لتنفيذ وظيفة معينة
باالعتماد على ميالن الجهاز ودورانة ،فباالعتماد على هذه المستشعرات يمكننا ان ننفذ طرق ادخال مبتكرة
باالظافة الى لمس الشاشة والكيبورد ،يمكننا بهذه المميزات ان نؤدي وظائف ال حصر لها.
قبل البدء بقياس تحرك وميالن الجهاز علينا ان نعرض الوضع االعتيادي للجهاز ،حيث تكون قيمة المحاور
الثالثة ،0الوضع الطبيعي للجهاز يختلف من جهاز الخر لكن بالنسبة الغلب الهواتف الذكية يكون عندما
يكون الجهاز موضوع على ظهره (الشاشة لالعلى) على طاولة واعلى الجهاز يشير باتجاه الشمال،
المستشعرات ي مكن ان تعيد لنا قيم المحاور باالعتماد على الوضع الطبيعي للجهاز او باالعتماد على وضع
العرض للجهاز (الوضع الذي يمسكه به المستخدم ليتصفح الجهاز ،يمكن ان يكون بشكل عمودي او افقي)
يمكننا ان نجد دوران الشاشة الحالي باستخدام الدالة getRotationالتابعة لالوبجكت ،Displayبهذا
الشكل:
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بهذا الشكل:
هذا الكود سيعيد لنا القيمة 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
هذا الكود سيظهر رسالة للمستخدم (فقط إذا كان البلوتوث مغلق) تطلب منه ان يفتح البلوتوث فاذا ضغط على
موافق سيتم تشغيل البلوتوث اما إذا ضغط على ال لن يتم تشغيله ،تكون الرسالة بهذا الشكل:
الدالة 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 والدالة
SCAN_MODE_CONNECTABLE_DISCOVERABLE -1
وهذا يعني ان الجهاز مرئي ويمكن،هذه القيمة تعني ان تحقق البحث وصفحة البحث كالهما مفعل
.رؤيته من قبل االجهزة االخرى لالتصال به
SCAN_MODE_CONNECTABLE -2
137
هذه القيمة تعني ان تحقق الصفحة مفعل فقط ،وهذا يعني انه يمكن لالجهزة التي كان عندها ارتباط
مع هذا البلوتوث ان تجده وتتواصل معه اما االجهزة االخرى فال يمكنها ان تراه عند البحث.
SCAN_MODE_NONE -3
هذه القيمة تعني ان الجهاز غير مرئي وال يمكن الي جهاز اخر ان يراه عند البحث.
بشكل افتراضي تكون بلوتوثات اجهزة االندرويد غير مرئية لجعلها مرئية علينا طلب االذن من المستخدم
عبر رسالة منبثقة (الرسالة المنبثقة تعتبر activityابن) ،نعمل ذلك من خالل عمل Intentجديد ونمرر له
القيمة ACTION_REQUEST_DISCOVERABLEوبعدها نمرر هذا الـ Intentالى الدالة
( startActivityForResultشرحنا عنها في الفصل الثالث) ،ليكون الكود بهذا الشكل:
إذا ضغط المستخدم على الزر موفق في الرسالة سيكون البلوتوث مرئي لالجهزة االخرى لمدة 120ثانية
وبعدها يعود غير مرئي ،وهذا الوقت هو بشكل افتراضي اما إذا أردنا ان نغير هذا الوقت يمكننا ان نرسل
المدة الزمنية التي نريدها عبر اضافتها الى الـ Intentالذي ارسلناه بستخدام الدالة putExtraحيث
باراميترها االول يكون BluetoothAdapter.EXTRA_DISCOVERABLE_DURATIONاما
باراميترها الثاني يكون رقم يمثل الوقت الذي نريده بالثانية ،بهذا الشكل:
138
لكي نعرف إذا ضغط المستخدم على الزر موافق او رفض جعل الجهاز مرئي نستخدم الدالة
( onActivityResultوالتي شرحنا عنها في الفصل الثالث وضيفتها استقبال النتائج التي تعيدها الـ
activityاالبن) ،بهذا الشكل:
@Override
{ )protected void onActivityResult(int requestCode, int resultCode, Intent data
{ )if (requestCode == 0 && resultCode == RESULT_CANCELED
// Discovery canceled by user
}}
كل ما نحتاجة عندما نريد ان نبدأ اتصال مع اي جهاز هو الـ ،MAC addressيمكن عرض االجهزة
المرتبطة في الجهاز والتي استخرجناها على شكل مصفوفة في قائمة عرض ListViewباستخدام
ArrayAdapterليراها المستخدم (شرحنا عنهما في فصول سابقه).
139
cancelDiscoveryالتابعة للكالس ،BluetoothAdapterاي ال يمكن ان نعمل عملية االتصال باي
جهاز والتطبيق ال يزال يبحث عن (يستكشف) االجهزة االخرى ،ولكي نحدد فيما اذا كان التطبيق ال يزال
يجري عملية البحث عن االجهزة ام ال نستخدم الدالة isDiscoveringالتابعة للكالس
BluetoothAdapterحيث ستعيد القيمة trueاذا كان التطبيق يجري عملية االستكشاف حاليا وبخاللف
ذلك تعيد القيمة .false
عملية البحث واستكتشاف االجهزة االخرى غير متزامنه ،اي بمعنى اننا إذا أردنا ان نعرف حالة تقدم عملية
االستكشاف (بدأ العملية ،انتهاء العملية ،ايجاد جهاز) علينا ان ننشأ الكالس BroadcastReceiverثم نسجل
الدخول له وبعد االنتهاء نسجل الخروج منه.
سيكون شكل الـ BroadcastReceiverوالدالة onReceiveداخله بهذا الشكل (يكتب خارج الدالة
onCreateالتابعة للـ :)activity
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
وكذلك يحمل هذا الـ intentمعلومات مفصلة عن الجهاز تأتي على شكل قيمة اوبجكت parcelable
ونستخرجها بهذا الشكل (يكتب هذا الكود في داخل الدالة :)onReceive
= BluetoothDevice rDv
;)intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE
االن أصبح عندنا االوبجكت rDvيحمل معلومات عن الجهاز الذي وجدناه مثل اسم وعنوان الجهاز ،ويمكن
ان نستخرج هذه المعلومات منه من خالل هذه الدوال بهذا الكشل:
االسم هو نفسه الذي استخرجناه عن طريق الـ EXTRA_NAMEقبل قليل اي ان المتغير dName
والمتغير nameسيحملون نفس القيمة.
بعد ان نستخرج معلومات الجهاز يمكن ان نضعها في قائمة عرض ListViewلعرض االجهزة التي نجدها
للمستخدم لكي يراها.
141
بعد ان انشأنا الـ BroadcastReceiverعلينا ان نسجل الدخول له ،يتم ذلك من خالل الدالة
registerReceiverوالتي تأخذ بارامترين االول هو عبارة عن اسم الـ BroadcastReceiverالذي
انشأناه قبل قليل اما الباراميتر الثاني فهو عبارة عن IntentFilterجديد نعمله ونمرر له بين قوسيه أحد هذه
القيم الثالثة بحسب السبب الذي نسجل الدخول الجله:
BluetoothAdapter.ACTION_DISCOVERY_STARTED -1
BluetoothAdapter.ACTION_DISCOVERY_FINISHED -2
BluetoothDevice.ACTION_FOUND -3
يمكن ان يكون شكل عملية تسجيل الدخول للـ BroadcastReceiverبهذا الشكل (يمكن ان يكتب في داخل
الدالة onCreateالتابعة للـ :)activity
;)unregisterReceiver(bReciever
142
الدالة استخدام يجب البيانات الستالم كخادم الجهاز لجعل
listenUsingRfcommWithServiceRecordالتابعة للكالس BluetoothServerSocketلالصغاء
الى أي طلب اتصال قادم ،هذه الدالة ستعيد اوبجكت من نوع الكالس ،BluetoothServerSocketهذه
الدالة تأخذ بين قوسيها متغيرين األول هو عبارة عن اسم نحن نختاره والثاني عبارة عن رمز يكون فريد من
نوعه يكون من نوع االكالس ،UUIDوالحظ ان الكالس BluetoothSocketفي الجهاز العميل
(المرسل) يجب ان يعرف هذا الرمز لكي تحدث عملية االتصال.
عند اجراء عملية اتصال ألول مرة بين جهازين غير مرتبطين ( )not pairingستظهر نافذة حوار للمستخدم
تطلب منه ربط الجهازين بهذا الشكل:
إذا نجح طلب االتصال فأن الدالة acceptالتابعة للكالس BluetoothServerSocketستعيد اوبجكت من
نوع الكالس BluetoothSocketومن خالل هذا االوبجكت نتمكن من ارسال البيانات في قناة االتصال
هذه.
• عند استخدام الدالة acceptيفضل اجراء عملية االصغاء لالتصاالت القادمة في ثريد في الخلفية
background threadبدال من الثريد UIحتى حدوث عملية االتصال ،ويجب االنتباه أيضا الى ان
الجهاز يكون مرئي عند محاولة االتصال به.
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;
}
يفضل اجراء عملية ارسال طلب االتصاالت في ثريد في الخلفيةconnect • عند استخدام الدالة
. حتى حدوث عملية االتصالUI بدال من الثريدbackground thread
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);
}
}
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
}
}
مثال متكامل
هذا المثال يعرض أجهزة البلوتوث المحفوظة مسبقا في الجهاز لنختار منها الجهاز الذي نريد االتصال به
ويوجد حقل نصي الرسال ما مكتوب فيه من نص الى الجهاز الذي اخترناه وأيضا استالم نص من الجهاز
االخر ،تم تجربة هذا المثال لالتصال باالردوينو الرسال واستالم النص ،شكل هذا المثال:
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;
@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();
}
});
149
}
Toast.makeText(MainActivity.this,"device choosen
"+device.getName(),Toast.LENGTH_SHORT).show();
}
});
}
150
and get out
try {
btSocket.close();
} catch (IOException closeException) {
}
}
}
}
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
قبل محاولة نقل البيانات عبر الشبكة يفضل التأكد من حالة االتصال ونوعه الن نقل البيانات ستستهلك من
بطارية الهاتف لذا يفضل التأكد من وجود االتصال في االول بهذا الشكل:
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 تستخدم هذه الدالة لمعرفة حالة الـ
153
. غير معروفةWi-Fi اذا كانت حالة الـWIFI_STATE_UNKNOWN -
:مثال
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;
@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();
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();
}
});
}
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));
}
}
// remember id
int netId = mainWifiObj.addNetwork(wifiConfig);
mainWifiObj.disconnect();
mainWifiObj.enableNetwork(netId, true);
158
mainWifiObj.reconnect();
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
discoverPeers
163
cancelConnect
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;
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>
<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 يتطلب منا ان نعمل دالته
واال فلن يعمل برنامجنا لذا عليناTTS في البداية علينا ان نتأكد من ان جهاز المستخدم فيه هذا المحرك
(شرحنا عن هذه الدالة فيstartActivityForResult وارساله بالدالةintent االستعالم من خالل انشاء
)onCreate (هذا الكود يكتب في داخل الدالة: بهذا الشكل،)الفصل الثالث
.) (شرحنا عنها في الفصل الثالث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
}
}}
الخطوة الرابعة:
االن سنقوم بانشاء الدالة Speack_funوالتي قلنا انه سيتم تنفيذها عند الضغط على الزر ،Buttonسنكتبها
بهذا الشكل:
حيث هنا سنقوم باخذ النص المكتوب في الحقل النصي EditTextونضعه في المتغير ( wordsالحظ
استخدام الدالة toStringمعه) وبعدها سنمرر هذه القيمة الى الدالة speakWordsوالتي سننشأها في
الخطوة السادسة.
الخطوة الخامسة:
قلنا في الخطوة الثانية انه علينا ان نعمل implementsلالنترفيس ،OnInitListenerوهذا االنترفيس
يتطلب منا ان نعمل دالته ،onInitاالن سنتكلم عن كيفية انشاء هذه الدالة ،بهذا الشكل:
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
}}
الخطوة السادسة:
ننشأ الدالة speakWordsوالتي كنا قد استدعيناها من داخل الدالة Speack_funفي الخطوة الرابعة
ونكتبها بهذا الشكل:
هذا االمر سيجعل البرنامج ينطق الكالم الممرر له هنا باالسم 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
176
إذا لم تكن هاتين المكتبتين مضافات نضيفهن من عالمة الزائد الموجودة في اعلى النافذة على جهة اليمين
.)(كما موضح في الصورة السابقة
<android.support.v7.widget.RecyclerView
android:id="@+id/rvAnimals"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scrollbars="horizontal" />
177
</RelativeLayout>
<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>
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;
179
@Override
public int getItemCount() {
return mAnimals.size();
}
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());
}
}
180
respond to click events
public interface ItemClickListener {
void onItemClick(View view, int position);
}
}
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;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
181
ArrayList<String> animalNames = new
ArrayList<>();
animalNames.add("Horse");
animalNames.add("Cow");
animalNames.add("Camel");
animalNames.add("Sheep");
animalNames.add("Goat");
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