لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 10/31/16 في كل الموقع
-
يُتيح نظام تشغيل أندرويد طريقة محددة وبسيطة للتنقل بين المكونات الأساسية للتطبيقات والتي ذكرناها سابقًا وذلك عن طريق استخدام الـ Intents. Intents يُعتبر الـ Intent طريقة التواصل بين مكونات التطبيق المختلفة من أجل القيام بشيء ما، فهو يسمح لمكونات التطبيق (كالأنشطة Activities ، الخدمات Services وغيرها) من التفاعل مع بعضها البعض والقيام بإحدى المهام، فباستخدام الـ Intent يمكننا التنقل بين شاشات التطبيق المختلفة أو التطبيقات الأخرى فمثلًا عند الضغط على زر يقوم بفتح نشاط Activity جديد داخل نفس التطبيق وعرض صورة به، كما يسمح بتفاعل التطبيقات الأخرى المتواجدة على الهاتف مع تطبيقك، فيمكن عرض الصورة في تطبيق خارجي كتطبيق المعرض (Gallery). وعند استخدام الـ Intent في التنقل بين مكونات نفس التطبيق يسمى بالـ Explicit Intent، لذا يستخدم Explicit Intent في ربط مكونات التطبيق الداخلية ببعضها وسنتعرف من خلال الأمثلة القادمة على كيفية استخدام الـ Explicit Intents في التنقل بين الأنشطة وإرسال واستقبال البيانات بينهم. وعند استخدامه للتنقل إلى التطبيقات الخارجية يسمى بالـ Implicit Intent. المثال الأول الآن قم بفتح Android Studio وقم بعمل تطبيق جديد اسمه "Explicit Intent Example"، في هذا المثال سنقوم باستخدام زر للتنقل إلى نشاط جديد. أولًا نقوم بصنع واجهة المستخدم الخاصة بالنشاط الأول والرئيسي في الملف activity_main.xml: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Welcome to The First Activity" android:textSize="21sp"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="GO!" android:id="@+id/gobtn"/> </LinearLayout> ثانيًا للاستجابة للزر عند الضغط عليه نقوم باستخدام onClickListener. package apps.noby.explicitintentexample; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; public class MainActivity extends Activity { Button go; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); go =(Button) findViewById(R.id.gobtn); go.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //Create your Intent Here } }); } } وبداخلها سنقوم بكتابة الـ Intent المناسب ولكن قبل ذلك ينبغي أن نقوم بعمل نشاط جديد وربطه بواجهة استخدام جديدة ولعمل ذلك نضغط بالزر الأيمن على المجلد المسمى باسم الحزمة ثم اختر: New > Activity > Empty Activity قم بكتابة اسم النشاط الجديد وتأكد من وجود اسم الحزمة في المكان المخصص لها كما بالصورة التالية: بعد أن يقوم البرنامج بصنع ملفات النشاط الجديد من ملف activity_second.xml سنقوم بصنع واجهة المستخدم الخاصة بالنشاط الثاني. وسنضع بداخلها TextView يعرض النص "Welcome to The Second Activity": <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Welcome to The Second Activity" android:textSize="21sp"/> </LinearLayout> والآن نعود مرة أخرى للنشاط الأول لنكمل التابع ()onClick، ولصنع Intent نقوم بعمل كائن من الصنف Intent وتمرير لدالة البناء الخاصة به النشاط الذي سننتقل منه وهو MainActivity.this وتمرير النشاط الذي نريد الانتقال إليه وهو SecondActivity.class. Intent i = new Intent(MainActivity.this,SecondActivity.class); بهذا السطر نكون قد صنعنا الهدف ولكن يتبقى أن نقوم بتفعيل هذا الهدف ليستجيب له التطبيق وذلك عن طريق التابع ()startActivity وتمرير الـ Intent الذي قمنا بصناعته إليه. لتصبح الشيفرة النهائية هي: package apps.noby.explicitintentexample; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; public class MainActivity extends Activity { Button go; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); go =(Button) findViewById(R.id.gobtn); go.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //Create your Intent Here Intent i = new Intent(MainActivity.this,SecondActivity.class); startActivity(i); } }); } } والآن نقوم بتجربة التطبيق على المحاكي والتأكد من قيامه بالوظيفة المطلوبة. عند النظر إلى ملف AndroidManifest.xml نجد أنه تم إضافة تعريف لعنصر جديد من النوع Activity، ولاحظ أن بدون تعريف العنصر الجديد لن يعمل التطبيق بشكل سليم ولن يتعرف نظام التشغيل عليه. <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" 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> <activity android:name=".SecondActivity"></activity> </application> … المثال الثاني سنقوم في هذا المثال بإرسال بيانات من نشاط وعرضها في نشاط آخر. أولًا نقوم بصنع النشاط الجديد الذي سيتم عرض البيانات بداخله. ثانيا نبدأ بصنع واجهة المستخدم في ملف activity_main.xml: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Your Name" android:textSize="21sp" android:id="@+id/editt"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Send" android:id="@+id/sendbtn"/> </LinearLayout> ثم سنقوم بالاستجابة لضغط الزر كما سبق وبداخل التابع ()onClick يتم تعريف الـ Intent وحفظ البيانات المكتوبة في الـ EditText بداخله ثم استدعاء النشاط الجديد. ولإرسال البيانات داخل الـ Intent يتم ذلك عن طريق التابع ()putExtra ونمرر له عنصرين وهما المفتاح و القيمة. المفتاح هو نص نميز به القيمة التي يتم إرسالها عبر الـ Intent ويُستخدم مرة أخرى لاستعادة البيانات التي أرسلناها. والقيمة هي البيانات التي نرغب في إرسالها. package apps.noby.explicitintentexample; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; public class MainActivity extends Activity { final public static String NAME_TAG = "name"; private Button send; private EditText et; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); send =(Button) findViewById(R.id.sendbtn); et = (EditText)findViewById(R.id.editt); send.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //Create your Intent Here Intent i = new Intent(MainActivity.this, SecondActivity.class); String name = et.getText().toString(); i.putExtra(NAME_TAG,name); startActivity(i); } }); } } قمنا بتعريف المفتاح على هيئة نص ثابت وذلك لتسهيل استخدام المفتاح مرة أخرى عند استعادة البيانات في النشاط الآخر. ثم بعد ذلك نقوم بصنع واجهة المستخدم الخاصة بالنشاط الثاني: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello, " android:textSize="21sp" android:id="@+id/hellotxt"/> </LinearLayout> ولاستقبال النص الذي أرسلناه بداخل الـ Intent يجب علينا أولًا أن نستعيد الـ Intent الذي قام بفتح النشاط باستخدام التابع ()getIntent. Intent actIntent = getIntent(); ولاستعادة النص الذي حفظناه نستخدم ()getText().toString ونمرر لها المفتاح الذي قمنا بحفظ البيانات به، ثم بعد ذلك نعرض هذا النص بداخل TextView. package apps.noby.explicitintentexample; import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; public class SecondActivity extends AppCompatActivity { private TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); tv =(TextView) findViewById(R.id.hellotxt); Intent actIntent = getIntent(); String name = actIntent.getExtras().getString(MainActivity.NAME_TAG); tv.setText("Hello, " + name); } } ثم نقوم بتجربة التطبيق على المحاكي. المثال الثالث سنقوم في هذا المثال بإعادة البيانات من النشاط الثاني إلى النشاط الأول عند الانتهاء من التفاعل مع النشاط الثاني. أولًا نُنشئ تطبيقًا جديدًا به نشاطان كما في الأمثلة السابقة ونبدأ بصنع واجهة المستخدم الخاصة بالنشاط الأول. <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Register" android:id="@+id/regbtn"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="NO Name" android:textSize="30sp" android:id="@+id/regtxt"/> </LinearLayout> وبداخل النشاط الأول نقوم باستدعاء النشاط الثاني عند الضغط على الزر المتواجد بالواجهة ولكن في هذا المثال لإخبار نظام التشغيل بأننا ننتظر بيانات من النشاط الآخر سنقوم بفتح النشاط الآخر بالتابع ()startActivityForResult بدلًا من التابع ()startActivity. private static final int ACTIVITY_REQUEST_CODE = 6 ; private Button reg; private TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); reg =(Button) findViewById(R.id.regbtn); tv = (TextView)findViewById(R.id.regtxt); reg.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //Create your Intent Here Intent i = new Intent(MainActivity.this, SecondActivity.class); startActivityForResult(i,ACTIVITY_REQUEST_CODE); } }); } ونمرر للتابع ()startActivityForResult الـ Intent كالمعتاد والرقم الخاص بالطلب وهذا الرقم هو أي رقم صحيح، وفائدته عند رجوع البيانات للنشاط مرة أخرى التمييز بين أي الأنشطة التي قامت بإرسال هذه البيانات. وفي هذا المثال قمنا بتعريف رقم ثابت وتمريره للتابع وعند عودة البيانات سنقوم بالتأكد من هذا الرقم مرة أخرى. والآن نقوم بصنع الواجهة الخاصة بالنشاط الآخر في ملف activity_second.xml: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Enter Your Name" android:textSize="21sp" android:id="@+id/edttxt"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Enter" android:id="@+id/entbtn" android:layout_gravity="right"/> </LinearLayout> بعد ذلك نكتب شيفرة التحكم الخاصة بهذا النشاط. package apps.noby.explicitintentexample; import android.content.Intent; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; public class SecondActivity extends Activity { public static final String REG_NAME_TAG ="name" ; private EditText et; private Button enter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); et =(EditText) findViewById(R.id.edttxt); enter = (Button) findViewById(R.id.entbtn); enter.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String str = et.getText().toString(); Intent in = new Intent(); in.putExtra(REG_NAME_TAG,str); setResult(RESULT_OK,in); finish(); } }); } } فعند الضغط على الزر Enter نقوم بتسجيل النص المتواجد بداخل العنصر EditText، إنشاء Intent جديد وحفظ النص بداخله ثم استدعاء التابع ()setResult والذي يمكننا من إرسال البيانات التي قمنا بتخزينها داخل Intent إلى من قام باستدعاء هذا النشاط. ونمرر للتابع ()setResult الثابت RESULT_OK والمعرّف داخل الـ (API (android.app.Activity كما نمرر لها الـ Intent الذي قمنا بإنشائه وحفظ البيانات بداخله. أخيرًا نستخدم التابع ()finish وهو المسؤول عن غلق النشاط. لاستقبال هذه البيانات في النشاط الأول نقوم بتضمين التابع ()onActivityResult في النشاط كما يلي. package apps.noby.explicitintentexample; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; public class MainActivity extends Activity { private static final int ACTIVITY_REQUEST_CODE = 6 ; private Button reg; private TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); reg =(Button) findViewById(R.id.regbtn); tv = (TextView)findViewById(R.id.regtxt); reg.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //Create your Intent Here Intent i = new Intent(MainActivity.this, SecondActivity.class); startActivityForResult(i,ACTIVITY_REQUEST_CODE); } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if ( (resultCode == RESULT_OK) && (requestCode == ACTIVITY_REQUEST_CODE)){ String name = data.getExtras().getString(SecondActivity.REG_NAME_TAG); tv.setText(name); } } } وبداخل هذا التابع نتأكد من أن النشاط السابق تم إغلاقه بشكل صحيح ولم يحدث له مشكلة عن طريق التأكد من أن المتغير resultCode يساوي RESULT_OK التي قمنا بتمريرها للتابع ()setResult ثم نتأكد أيضًا من أن النشاط الذي يُعيد هذه البيانات له رقم طلب يساوي رقم الطلب الذي قمنا باستخدامه مسبقًا، بعد ذلك نبدأ في استقبال البيانات. ثم نقوم بتشغيل التطبيق على المحاكي. الأمثلة السابقة كانت باستخدام Explicit Intent للقيام بالوظيفة المطلوبة ولربط مكونات التطبيق المختلفة ببعضها فيمكنك من صنع تطبيق متعدد الأنشطة، في الأمثلة القادمة سنقوم باستخدام الـ Implicit Intents والتعامل معها. المثال الرابع في هذا المثال سنقوم بفتح موقع أكاديمية حسوب في تطبيق المتصفح، وبنفس الطريقة يمكن فتح الروابط الأخرى خارج التطبيق. قم بإنشاء تطبيق جديد يُدعى "Implicit Intent Example" ثم نبدأ بصنع واجهة المستخدم في ملف activity_main.xml: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Open Hsoub Academy" android:id="@+id/opnbtn"/> </LinearLayout> وعند الضغط على الزر نقوم بفتح الرابط. Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("http://academy.hsoub.com")); startActivity(i); ونمرر لدالة البناء الخاصة بالـ Intent نوع الفعل الذي نريده من التطبيق الخارجي وفي هذا المثال نريد تطبيق يستطيع عرض البيانات بداخله، ثم نمرر له نوع البيانات المراد عرضها وهي الرابط الخاص بموقع أكاديمية حسوب. لتصبح الشيفرة النهائية الخاصة بالبرنامج كما يلي: package apps.noby.explicitintentexample; import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; public class MainActivity extends Activity { private Button open; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); open = (Button) findViewById(R.id.opnbtn); open.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //Create your Intent Here Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("http://academy.hsoub.com")); startActivity(i); } }); } } ثم نقوم بتجربة التطبيق النهائي على المحاكي. المثال الخامس في هذا التطبيق سنقوم بالاتصال من خلال فتح تطبيق الاتصال من داخل التطبيق الخاص بنا. نبدأ أولًا بصنع واجهة المستخدم في ملف activity_main.xml: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Dial Number" android:id="@+id/dialbtn"/> </LinearLayout> وعند الضغط على الزر نقوم بالاتصال بالرقم التالي: Intent i = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:0123456789")); startActivity(i); ونمرر لدالة البناء الخاصة بالـ Intent نوع الفعل الذي نريده وهو الاتصال بالرقم، ثم نمرر له الرقم. ثم نقوم بتجربة التطبيق النهائي على المحاكي. بهذا نكون قد وصلنا إلى نهاية هذا الدرس، في انتظار تجربتكم وآرائكم.1 نقطة
-
لدى برمجة تطبيقات أندرويد، فإنّه وفي العديد من الأحيان نحتاج أن نعرض مجموعة من العناصر معًا في النشاط أمام المستخدم كعرض جهات الاتصال مثلًا أو الرسائل حيث يمكن للمستخدم أن يتصفحها ويتنقل بينها سريعًا، كما يمكنه الضغط على أي منها فيتم عرض المزيد من المعلومات عن هذا العنصر. نستخدم عنصر الواجهة ListView أو قائمة العرض للقيام بما سبق والذي يتيح عرض أكثر من عنصر في قائمة واحدة، ويتم إضافة العناصر إلى قائمة العرض تلقائيًا باستخدام كائن من الصنف Adapter والذي يستطيع جلب المحتوى من مصفوفة من البيانات أو قاعدة بيانات. ويُعتبر الصنف Adapter بمثابة حلقة الوصل بين عنصر الواجهة الخاص بقائمة العرض ومصدر البيانات المستخدم حيث يقوم بتشكيلها بالشكل المطلوب لتُمثل عنصرًا من عناصر القائمة. يوجد عدة أصناف فرعية من الصنف Adapter كي تُستخدم مع أشكال البيانات المختلفة مثل: ArrayAdapter Base Adapter SimpleCursorAdapter قوائم العرض مع كائن من ArrayAdapter تطبيق 1 يُستخدم ArrayAdapter عندما يكون المصدر البيانات هو قائمة أو مصفوفة. سنقوم بصنع تطبيق يعرض مجموعة من النصوص في قائمة العرض، لذا سنقوم بعمل مشروع جديد في Android Studio. ولتكوين قائمة العرض نقوم بالخطوات التالية: نضع عنصر من ListView في ملفات الواجهة ونشير له بـ Id مميز، ثم نربط هذا العنصر بشيفرات الجافا باستخدام التابع ()findViewById. <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/listview"/> </LinearLayout> نصنع ملف واجهة جديد يكون الجذر له عنصر من النوع TextView. ولصنع واجهة استخدام جديدة نضغط على المجلد layout المتواجد داخل res بالزر الأيمن ثم نختار: new > XML > Layout XML File ونسميه row_item. وبداخل هذا الملف نجعل عنصر الجذر من النوع TextView ونغير حجم النص الذي سيُكتب بداخله إلى 20sp ونجعل الخاصية padding لها قيمة 20sp. لاحظ أن الخاصية padding تعني الحشو أي ضع فراغًا مقداره 20sp حول النص المكتوب داخل TextView في كل الاتجاهات. <?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:textSize="20sp" android:padding="20sp"/> في شيفرة الجافا نبني كائنًا جديدًا من الصنف ArrayAdapter ونمرر لدالة البناء الخاصة به ثلاث عناصر: العنصر الأول هو this والذي يعبر عن السياق الخاص بالنشاط المكوّن للقائمة. العنصر الثاني هو TextView الذي سيُعرض فيه كل نص في القائمة. العنصر الأخير هو المصفوفة الخاصة بالنصوص التي سيتم عرضها في قائمة العرض. ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,R.layout.row_item,colorsArray); نستدعي التابع ()setAdapter باستخدام الكائن الخاص بالـ ListView ونمرر له الـ Adapter الذي قمنا بصنعه ليصبح التطبيق النهائي: package apps.noby.listviewexample; import android.app.Activity; import android.os.Bundle; import android.widget.ArrayAdapter; import android.widget.ListView; public class MainActivity extends Activity { private ListView lv; String [] colorsArray = {"Red","Yellow","Blue","Orange","Black","Green","Brown","Grey"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); lv =(ListView) findViewById(R.id.listview); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,R.layout.row_item,colorsArray); lv.setAdapter(adapter); } } ثم نقوم بتجربة التطبيق على المحاكي لنتأكد من عمله بالشكل المطلوب. تطبيق 2 في هذا التطبيق بدلًا من استخدام المصفوفة لتخزين البيانات سنقوم باستبدالها بقائمة من النوع ArrayList مع الحفاظ على باقي ملفات الواجهة السابقة لتصبح الشفرة النهائية بعد التعديل: package apps.noby.listviewexample; import android.app.Activity; import android.os.Bundle; import android.widget.ArrayAdapter; import android.widget.ListView; import java.util.ArrayList; public class MainActivity extends Activity { private ListView lv; ArrayList<String> colorsList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); colorsList = new ArrayList<String>(); colorsList.add("Red"); colorsList.add("Yellow"); colorsList.add("Blue"); colorsList.add("Orange"); colorsList.add("Black"); colorsList.add("Green"); colorsList.add("Brown"); colorsList.add("Grey"); lv =(ListView) findViewById(R.id.listview); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,R.layout.row_item,colorsList); lv.setAdapter(adapter); } } وبتجربة هذا التطبيق على المحاكي نجد أنه يقوم بنفس الوظيفة السابقة، ولكن استخدامنا للقائمة بدلًا من المصفوفة يتيح لنا القدرة على إضافة أو إزالة العناصر بسهولة وهو ما لا تسمح به المصفوفة. استجابة عناصر القائمة عند الضغط عليها تطبيق 3 سنقوم في هذا التطبيق بجعل العناصر المتواجدة بقائمة العرض تستجيب عند الضغط عليها، فكما ذكرنا سابقًا أننا استخدمنا كائنًا من نوع ArrayList لسهولة إضافة أو إزالة العناصر منه، سنقوم الآن بتطبيق هذا المفهوم حيث سنقوم بإزالة العنصر من القائمة عند الضغط عليه. كما نستخدم التابع ()setOnClickListener مع الزر للاستجابة عند الضغط عليه سنقوم هنا باستخدام التابع ()setOnItemClickListener للاستجابة عند الضغط على عناصر القائمة، وبداخله نجد الدالة ()onItemClick وبها العناصر التالية: parent وهو يُعبر عن الأب الخاص بالعنصر الذي تم الضغط عليه وفي هذا المثال فهو ListView. view ويُمثل العنصر نفسه الذي تم الضغط عليه أي TextView. position ويُعبر عن رقم العنصر في القائمة. Id وهو رقم مميز يتم تحديده لعنصر الواجهة الذي يعرض النص. لتصبح الشفرة النهائية الخاصة بهذا التطبيق: package apps.noby.listviewexample; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; import java.util.ArrayList; public class MainActivity extends Activity { private ListView lv; ArrayList<String> colorsList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); colorsList = new ArrayList<String>(); colorsList.add("Red"); colorsList.add("Yellow"); colorsList.add("Blue"); colorsList.add("Orange"); colorsList.add("Black"); colorsList.add("Green"); colorsList.add("Brown"); colorsList.add("Grey"); lv =(ListView) findViewById(R.id.listview); final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,R.layout.row_item,colorsList); lv.setAdapter(adapter); lv.setOnItemClickListener(new ListView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { String item = (String) parent.getItemAtPosition(position); colorsList.remove(item); adapter.notifyDataSetChanged(); } }); } } بداخل التابع ()onItemClick نستخدم الكائن parent لاستدعاء التابع ()getItemAtPosition وتمرير رقم العنصر له حتى يُعيد لنا النص المخزن في ذلك العنصر. ثم بعد ذلك نقوم بإزالة العنصر من القائمة واستدعاء التابع ()notifyDataSetChanged باستخدام الكائن adapter وذلك لتنفيذ التغيير الذي حدث على عناصر قائمة العرض. الآن نقوم بتجربة التطبيق على المحاكي، نجد أنه عند الضغط على أحد العناصر تختفي في الحال. يمكنك تطوير هذا التطبيق لجعله يستدعي نشاطًا جديدًا عند الضغط على أحد العناصر. البحث داخل عناصر قائمة العرض تطبيق 4 في هذا التطبيق سنقوم بالبحث عن العناصر التي تحتوي على نص معين وعرضها هي فقط وذلك لسهولة الوصول لعنصر محدد. كتطوير على التطبيق السابق سنقوم بإضافة الخاصية Orientation للعنصر LinearLayout في ملف activity_main.xml. android:orientation="vertical" ثم نضيف عنصر EditText قبل ListView وهو العنصر الذي سيقوم المستخدم بالتفاعل معه وكتابة النص الذي سيبحث عنه داخل قائمة العرض. <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:drawableLeft="@drawable/ic_search_black_24dp" android:hint="Search..." android:id="@+id/searchedittext"/> <ListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/listview"/> </LinearLayout> ولإضافة صورة يسار العنصر EditText نستخدم الخاصية drawableLeft ثم نضيف الصورة من المجلد drawable. الآن سنربط هذا العنصر بالشيفرة الرئيسية للتطبيق ثم نستدعي التابع ()addTextChangedListener والذي يستجيب لأي تغير يحدث داخل EditText، حيث يوفر ثلاث دوال: searchET.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { adapter.getFilter().filter(s); } @Override public void afterTextChanged(Editable s) { } }); كل دالة تختص بفترة محددة، وهنا نهتم بالدالة ()onTextChanged والتي يتم استدعاؤها لحظة تغير النص المكتوب بداخل EditText. أخيرًا لتنقية القائمة وترك فقط العناصر التي يتم البحث عنها نستدعي التابع ()getFilter باستخدام الكائن الخاص بالـ ArrayAdapter ثم نستدعي بعده مباشرة التابع ()filters ونمرر له المتغير s الذي يحمل بداخله النص الذي قمنا بكتابته في EditText. لتصبح الشيفرة النهائية كما يلي: package apps.noby.listviewexample; import android.app.Activity; import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.ListView; import java.util.ArrayList; public class MainActivity extends Activity { private ListView lv; private ArrayList<String> colorsList; EditText searchET; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); colorsList = new ArrayList<String>(); colorsList.add("Red"); colorsList.add("Yellow"); colorsList.add("Blue"); colorsList.add("Orange"); colorsList.add("Black"); colorsList.add("Green"); colorsList.add("Brown"); colorsList.add("Grey"); searchET = (EditText) findViewById(R.id.searchedittext); lv =(ListView) findViewById(R.id.listview); final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,R.layout.row_item,colorsList); lv.setAdapter(adapter); lv.setOnItemClickListener(new ListView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { String item = (String) parent.getItemAtPosition(position); colorsList.remove(item); adapter.notifyDataSetChanged(); } }); searchET.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { adapter.getFilter().filter(s); } @Override public void afterTextChanged(Editable s) { } }); } } بعد ذلك نقوم بتجربة التطبيق على المحاكي لنتأكد من عمله كما ينبغي. تخصيص واجهة قائمة العرض تطبيق 5 تُتيح قائمة العرض ميزة تخصيص العناصر، فيمكن صنع قائمة عرض تتكون من نص وصورة في كل صف بدلًا من نص فقط كالأمثلة السابقة. في هذا التطبيق سنقوم بصنع تطبيق يعرض قائمة كما في الصورة التالية، وعند الضغط على العنصر يعرض نشاطًا جديدًا. من Android Studio نقوم بعمل مشروع جديد، نبدأ أولًا بصنع واجهة المستخدم الرئيسية والمتكونة من ListView. <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/listview"/> </LinearLayout> نصنع ملف واجهة جديد يُعبر عن الصف ويدعى row_item وبداخله نكون الشكل المطلوب للصف في قائمة العرض كما في المخطط التالي: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/blue" android:id="@+id/imgview"/> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Title Example" android:textSize="20sp" android:textStyle="bold" android:id="@+id/titleview"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Description Example" android:textSize="15sp" android:id="@+id/descview"/> </LinearLayout> </LinearLayout> يتكون الصف في قائمة العرض من صورة، نص العنوان ونص الوصف لذا سنصنع صنفًا جديدًا يحتوي بداخله على هذه المعلومات الخاصة بالعنصر ثم نكوّن مصفوفة أو قائمة من هذه الصنف. لصنع صنفًا جديدًا من داخل المجلد java اضغط بالزر الأيمن على اسم الحزمة الخاصة بالمشروع واختر New > Java Class ثم اكتب اسم الصنف الجديد ListItem. بداخل هذا الصنف الجديد نعرّف ثلاث متغيرات، package apps.noby.customlistviewexample; public class ListItem { public int imgSrc; public String title; public String desc; } في ملف MainActivity.java نكتب الشيفرة الخاصة بالمشروع. أولًا نصنع كائنًا من الصنف ArrayList ليكون هو مصدر البيانات التي ستُعرض في قائمة العرض، ويتكون الكائن من مجموعة من الكائنات الأخرى من الصنف ListItem لكل منها صورة خاصة به، نص العنوان ونص الوصف. private ArrayList<ListItem> items; String [] colorsArray = {"Red","Yellow","Blue","Black","Green","Brown","Grey"}; int [] colorsImage = {R.drawable.red,R.drawable.yellow,R.drawable.blue,R.drawable.black,R.drawable.green,R.drawable.brown,R.drawable.grey}; items = new ArrayList<ListItem>(); for(int i=0;i<colorsArray.length;i++){ ListItem item= new ListItem(); item.imgSrc=colorsImage; item.title = colorsArray; item.desc = colorsArray + " Color!"; items.add(item); } ثانيًا بعد ذلك نصنع الكائن الخاص بالـ ArrayAdapter ولكننا لا يمكننا استخدام الصنف الأساسي من ArrayAdapter حيث أنه لا يعمل إلا مع نص واحد فقط ولن يمكننا تغيير الصورة أو النص الثاني، لذا ينبغي علينا صنع الصنف ArrayAdapter الخاص بنا. بنفس الطريقة السابقة نصنع صنف جديد من داخل المجلد Java، اضغط بالزر الأيمن على اسم الحزمة الخاصة بالمشروع واختر New > Java Class ثم اكتب اسم الصنف الجديد CustomArrayAdapter. يجب أن يرث هذا الصنف الفرعي من الصنف الأساسي ArrayAdapter. public class CustomArrayAdapter extends ArrayAdapter ثم نقوم بكتابة دالة البناء الخاصة بهذا الصنف ونستدعى في بدايتها دالة البناء الخاصة بالصنف الأب، وتحتاج دالة البناء لكائن من الصنف Context وهو السياق الذي سيعمل فيه هذا الـ Adapter أي النشاط الذي سيعمل به، كما يحتاج إلى واجهة المستخدم التي تكون شكل الصف، وقائمة البيانات التي سيتم عرضها. private Context con; private ArrayList<ListItem> data; private int resLayout; public CustomArrayAdapter(Context context, int resource, List objects) { super(context, resource,objects); con = context; resLayout = resource; data =(ArrayList) objects; } يوجد تابع داخل ArrayAdapter يُدعى ()getCount ويتم استخدامه بشكل تلقائي عندما يريد الكائن الذي سنصنعه من CustomArrayAdapter معرفة عدد العناصر التي سيقوم بعرضها داخل قائمة العرض، لذا فهو يستدعي التابع ()size باستخدام الكائن الخاص بالبيانات. @Override public int getCount() { return data.size(); } وبداخل الصنف الأب ArrayAdapter يوجد أيضًا تابع يُدعى ()getView، وهو المسؤول عن تكوين الشكل الخاص بالصف المعروض في قائمة العرض لذا كي نصنع الصف الخاص بنا يجب علينا تعديل هذا التابع. ولتعديل التابع نقوم بكتابته بطريقتنا الخاصة كما يلي: @Override public View getView(int position, View convertView, ViewGroup parent) { inflater = (LayoutInflater) con.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View rootView = inflater.inflate(resLayout,null); ImageView img = (ImageView) rootView.findViewById(R.id.imgview); TextView title = (TextView) rootView.findViewById(R.id.titleview); TextView desc = (TextView) rootView.findViewById(R.id.descview); ListItem item = data.get(position); img.setImageResource(item.imgSrc); title.setText(item.title); desc.setText(item.desc); return rootView; } داخل التابع نبدأ أولًا بطلب الكائن LayoutInflater وهو المسؤول عن تحويل ملف XML إلى كائن من النوع View وللحصول عليه نستخدم التابع ()getSystemService والذي يوفر مجموعة مختلفة من الخدمات منها الكائن LayoutInflater، ويتم تحديد نوع الخدمة المطلوبة عن طريق تمرير ثابت محدد وفي هذه الحالة هو Context.LAYOUT_INFLATER_SERVICE. بعد الحصول على هذا الكائن نستدعي التابع ()inflate ونمرر له ملف XML الذي نرغب في تحويله إلى كائن في ملف الجافا. inflater = (LayoutInflater) con.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View rootView = inflater.inflate(resLayout,null); الآن لربط عناصر الواجهة بالشيفرة نستخدم التابع ()findViewById ولكنا هنا لا نستطيع استخدامه مباشرة كما كنا نفعل عند وجودنا داخل شيفرة النشاط، لذا يجب أن نستدعي التابع باستخدام الكائن من الصنف View الذي حصلنا عليه من التابع ()inflate. ImageView img = (ImageView) rootView.findViewById(R.id.imgview); TextView title = (TextView) rootView.findViewById(R.id.titleview); TextView desc = (TextView) rootView.findViewById(R.id.descview); أخيرًا نأتي بالبيانات المناسبة للعنصر من داخل الـ ArrayList ثم نضع بداخل كل عنصر من عناصر الواجهة البيانات الخاصة به. ListItem item = data.get(position); img.setImageResource(item.imgSrc); title.setText(item.title); desc.setText(item.desc); لتصبح الشيفرة النهائية داخل الصنف CustomArrayAdapter: package apps.noby.customlistviewexample; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; import java.util.ArrayList; import java.util.List; public class CustomArrayAdapter extends ArrayAdapter { private Context con; private ArrayList<ListItem> data; private LayoutInflater inflater; private int resLayout; public CustomArrayAdapter(Context context, int resource, List objects) { super(context, resource,objects); con = context; data =(ArrayList) objects; resLayout = resource; } @Override public int getCount() { return data.size(); } @Override public View getView(int position, View convertView, ViewGroup parent) { inflater = (LayoutInflater) con.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View rootView = inflater.inflate(resLayout,null); ImageView img = (ImageView) rootView.findViewById(R.id.imgview); TextView title = (TextView) rootView.findViewById(R.id.titleview); TextView desc = (TextView) rootView.findViewById(R.id.descview); ListItem item = data.get(position); img.setImageResource(item.imgSrc); title.setText(item.title); desc.setText(item.desc); return rootView; } } نعود مجددًا إلى ملف MainActivity.java لإنشاء كائن من الصنف الذي صنعناه: private CustomArrayAdapter adapter; adapter = new CustomArrayAdapter(this,R.layout.row_item,items); ثالثًا نربط قائمة العرض بالشيفرة ونستدعي التابع ()setAdapter: private ListView customLV; customLV = (ListView) findViewById(R.id.listview); customLV.setAdapter(adapter); بهذا نكون قد انتهينا من تكوين قائمة العرض المطلوبة، يتبقى أن نجعل عناصر قائمة العرض تستجيب عند الضغط عليها وتقوم بفتح نشاط جديد، لذا أولًا نقوم بعمل نشاط جديد بالضغط بالزر اليمن على اسم الحزمة واختيار: New > Activity > Empty Activity ونسميها DescriptionActivity. ولفتح هذا النشاط عند الضغط على عناصر قائمة العرض نقوم باستدعاء التابع ()setOnItemClickListener وبداخله نفتح النشاط الجديد ونرسل إليه اسم العنصر الذي قام بفتحه حتى يعرضه. package apps.noby.customlistviewexample; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.ListView; import java.util.ArrayList; public class MainActivity extends Activity { private ListView customLV; private CustomArrayAdapter adapter; private ArrayList<ListItem> items; String [] colorsArray = {"Red","Yellow","Blue","Black","Green","Brown","Grey"}; int [] colorsImage = {R.drawable.red,R.drawable.yellow,R.drawable.blue,R.drawable.black,R.drawable.green,R.drawable.brown,R.drawable.grey}; public static final String COLOR_KEY = "colorName"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); items = new ArrayList<ListItem>(); for(int i=0;i<colorsArray.length;i++){ ListItem item= new ListItem(); item.imgSrc=colorsImage; item.title = colorsArray; item.desc = colorsArray + " Color!"; items.add(item); } customLV = (ListView) findViewById(R.id.listview); adapter = new CustomArrayAdapter(this,R.layout.row_item,items); customLV.setAdapter(adapter); customLV.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Intent intent = new Intent(MainActivity.this,DescriptionActivity.class); intent.putExtra(COLOR_KEY,colorsArray[position]); startActivity(intent); } }); } } أخيرًا نقوم بتهيئة واجهة المستخدم الخاصة بالنشاط الجديد لتعرض النص المرسل إليها. <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/txt" android:textSize="20sp"/> </LinearLayout> ولعرض النص داخل الـ TextView نقوم في الشيفرة باستدعاء الـ Intent الذي قام بفتح النشاط واستخراج منه البيانات المرسلة وكتابتها على TextView كما يلي: package apps.noby.customlistviewexample; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; public class DescriptionActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_description); String str = getIntent().getStringExtra(MainActivity.COLOR_KEY); TextView tv = (TextView) findViewById(R.id.txt); tv.setText("Welcome to " + str + " Color Home!!"); } } والآن نقوم بتجربة التطبيق على المحاكي حتى نتأكد من عمله بالشكل المطلوب. البحث داخل عناصر قائمة العرض التي تم تخصيصها تطبيق 6 في هذا التطبيق سنكرر ما قمنا به سابقًا من بحث عن نص معين داخل قائمة العرض ولكن الاختلاف هنا سيكون أن قائمة العرض ليست بهذه البساطة وتتكون من عنصر واحد فقط وهو النص، ولكنها تتكون من صور وعدة نصوص مختلفة لذا ينبغي علينا أن نصنع بأنفسنا طريقتنا الخاصة للبحث وتنقية العناصر من القائمة وترك العناصر التي يتم البحث عنها. أولًا نقوم بتغيير ملف الواجهة activity_main.xml وإضافة عنصر EditText. <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:drawableLeft="@drawable/ic_search_black_24dp" android:hint="Search..." android:id="@+id/searchedittext"/> <ListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/listview"/> </LinearLayout> ثانيًا نربط عنصر EditText في الشيفرة الأساسية ونستدعي التابع ()addTextChangedListener وبداخل الدالة ()onTextChanged نستدعي التابع الخاص بالتنقية كما سبق. package apps.noby.customlistviewexample; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.widget.AdapterView; import android.widget.EditText; import android.widget.ListView; import java.util.ArrayList; public class MainActivity extends Activity { private ListView customLV; private CustomArrayAdapter adapter; private ArrayList<ListItem> items; String[] colorsArray = {"Red", "Yellow", "Blue", "Black", "Green", "Brown", "Grey"}; int[] colorsImage = {R.drawable.red, R.drawable.yellow, R.drawable.blue, R.drawable.black, R.drawable.green, R.drawable.brown, R.drawable.grey}; public static final String COLOR_KEY = "colorName"; EditText searchET; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); items = new ArrayList<ListItem>(); for (int i = 0; i < colorsArray.length; i++) { ListItem item = new ListItem(); item.imgSrc = colorsImage; item.title = colorsArray; item.desc = colorsArray + " Color!"; items.add(item); } searchET = (EditText) findViewById(R.id.searchedittext); customLV = (ListView) findViewById(R.id.listview); adapter = new CustomArrayAdapter(this, R.layout.row_item, items); customLV.setAdapter(adapter); customLV.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Intent intent = new Intent(MainActivity.this, DescriptionActivity.class); intent.putExtra(COLOR_KEY, colorsArray[position]); startActivity(intent); } }); searchET.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { adapter.getFilter().filter(s); } @Override public void afterTextChanged(Editable s) { } }); } } أخيرًا يتبقى أن نقوم بكتابة تابع التنقية الخاص بنا، وسنقوم بكتابته داخل الصنف CustomArrayAdapter. داخل CustomArrayAdapter نقوم بإنشاء كائن آخر من ArrayList ونجعله يساوي البيانات التي سنضعها في قائمة العرض، مثله كالكائن data. private ArrayList<ListItem> dataTemp; public CustomArrayAdapter(Context context, int resource, ArrayList<ListItem> objects) { super(context, resource,objects); con = context; data = objects; resLayout = resource; dataTemp = objects; } بعد ذلك نعيد كتابة التابع الخاص بالتنقية والمتواجد أيضًا داخل الأب Arrayadapter لذا سنقوم بعمل Override لهذا التابع. والتابع هو ()getFilter ويعيد كائن من الصنف Filter وبداخل هذا الصنف تظهر لدينا دالتين جديدتين كما يلي: @Override public Filter getFilter() { return new Filter() { @Override protected FilterResults performFiltering(CharSequence constraint) { } @Override protected void publishResults(CharSequence constraint, FilterResults results) { } }; } الأولى ()performFiltering هي المسؤولة عن عملية التنقية ونكتب بداخلها كيف تتم تنقية العناصر وما هي العناصر التي ستظهر عند كتابة نص معين ويتم تخزين النص الذي تم كتابته وعلى أساسه تتم عملية التنقية داخل المتغير constraint. الثانية ()publishResults هي المسؤولة عن تغيير محتوي البيانات التي تعرضها قائمة العرض حيث تأتي النتائج بعد التنقية داخل الكائن results. سنبدأ أولًا بكتابة طريقة التنقية التي سنتبعها وهي، نتأكد أن النص الذي ستتم التنقية على أساسه وممثل في المتغير constraint ليس فارغًا وذلك عن طريق الجملة المنطقية التالية: constraint == null || constraint.length() == 0 وتعني هل المتغير constraint لم يُخزن بداخله أي بيانات أو تم تخزين نص فارغ، لذا سنضع هذه الجملة داخل الجملة الشرطية if وإن تحققت فذلك يعني أنه لا داعي للتنقية وستظل عناصر القائمة كما هي، أما إذا كان خلاف ذلك ويوجد نص فسنقوم بعملية التنقية. FilterResults results = new FilterResults(); if(constraint == null || constraint.length() == 0){ results.values = dataTemp; results.count = dataTemp.size(); } else{ } return results; لاحظ أن الكائن results هو المتغير الذي سنخزن بداخله ناتج التنقية، ويتكون من عنصرين مهمين الأول results.values وهنا يتم تخزين البيانات الجديدة و results.count ويتم تخزين عددهم. طريقة التنقية ستكون كالتالي: نصنع حاوية جديدة لتخزين العناصر الناتجة من التنقية. لكل عنصر من العناصر القديمة في قائمة العرض نقوم بمقارنته بالنص المكتوب لنرى هل يحتوي على حروفه. إن كان يحتوي على أحد حروفه ننقله إلى حاوية التخزين الجديدة التي صنعناها. بعد الانتهاء من كافة العناصر نعيد النتيجة ليتم تحديث قائمة العرض بالعناصر الجديدة. ArrayList<ListItem> filterList = new ArrayList<ListItem>(); for (int i = 0; i < dataTemp.size(); i++) { ListItem item = dataTemp.get(i); if ( (item.title.toLowerCase()).contains(constraint.toString().toLowerCase())) { ListItem filterItem = new ListItem(); filterItem.imgSrc = dataTemp.get(i).imgSrc; filterItem.title = dataTemp.get(i).title; filterItem.desc = dataTemp.get(i).desc; filterList.add(filterItem); } } results.values = filterList; results.count = filterList.size(); لاحظ أننا نقوم بالتنقية على أساس النص المخزن داخل title، وأننا نقوم بتحويل هذا النص إلى حروف صغيرة ومقارنته بالنص الذي نبحث عنه أيضًا بعد تحويله لحروف صغيرة لتوحيد طريقة كتابة الكلمة. ونستخدم التابع ()contains والذي يتأكد هل الكائن الذي قام باستدعاء هذا التابع يحتوي على النص الذي نمرره للتابع أم لا، إن كان يحتويه فسيعيد القيمة المنطقية true أما إذا كان لا يحتويه فسيعيد false. بعد ذلك ننتقل إلى الدالة الأخرى لنشر النتائج الجديدة في قائمة العرض، ونقوم بنقل البيانات المخزنة في results.values إلى المتغير data حتى يتم تحديث قائمة العرض باستدعاء التابع ()getView بعد استدعاء ()notifyDataChanged. data = (ArrayList<ListItem>) results.values; notifyDataSetChanged(); لتصبح الشيفرة النهائية للصنف CustomArrayAdapter بعد التعديل: package apps.noby.customlistviewexample; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.Filter; import android.widget.ImageView; import android.widget.TextView; import java.util.ArrayList; public class CustomArrayAdapter extends ArrayAdapter{ private Context con; private ArrayList<ListItem> data; private ArrayList<ListItem> dataTemp; private LayoutInflater inflater; private int resLayout; public CustomArrayAdapter(Context context, int resource, ArrayList<ListItem> objects) { super(context, resource,objects); con = context; data = objects; resLayout = resource; dataTemp = objects; } @Override public int getCount() { return data.size(); } @Override public View getView(int position, View convertView, ViewGroup parent) { inflater = (LayoutInflater) con.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View rootView = inflater.inflate(resLayout,null); ImageView img = (ImageView) rootView.findViewById(R.id.imgview); TextView title = (TextView) rootView.findViewById(R.id.titleview); TextView desc = (TextView) rootView.findViewById(R.id.descview); ListItem item = data.get(position); img.setImageResource(item.imgSrc); title.setText(item.title); desc.setText(item.desc); return rootView; } @Override public Filter getFilter() { return new Filter() { @Override protected FilterResults performFiltering(CharSequence constraint) { FilterResults results = new FilterResults(); if (constraint == null || constraint.length() == 0) { results.values = dataTemp; results.count = dataTemp.size(); } else{ ArrayList<ListItem> filterList = new ArrayList<ListItem>(); for (int i = 0; i < dataTemp.size(); i++) { ListItem item = dataTemp.get(i); if ( (item.title.toLowerCase()).contains(constraint.toString().toLowerCase())) { ListItem filterItem = new ListItem(); filterItem.imgSrc = dataTemp.get(i).imgSrc; filterItem.title = dataTemp.get(i).title; filterItem.desc = dataTemp.get(i).desc; filterList.add(filterItem); } } results.values = filterList; results.count = filterList.size(); } return results; } @Override protected void publishResults(CharSequence constraint, FilterResults results) { data = (ArrayList<ListItem>) results.values; notifyDataSetChanged(); } }; } } الآن قم بتجربة التطبيق على المحاكي للتأكد انه يعمل كما ينبغي. بهذا نكون قد وصلنا إلى نهاية هذا الدرس، في انتظار تجربتكم وآرائكم.1 نقطة