أندرويد للمبتدئين عناصر قائمة العرض لواجهة المستخدم في تطبيقات أندرويد


أحمد النوبي

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

android-listview.png

نستخدم عنصر الواجهة ListView أو قائمة العرض للقيام بما سبق والذي يتيح عرض أكثر من عنصر في قائمة واحدة، ويتم إضافة العناصر إلى قائمة العرض تلقائيًا باستخدام كائن من الصنف Adapter والذي يستطيع جلب المحتوى من مصفوفة من البيانات أو قاعدة بيانات.

1.png

ويُعتبر الصنف Adapter بمثابة حلقة الوصل بين عنصر الواجهة الخاص بقائمة العرض ومصدر البيانات المستخدم حيث يقوم بتشكيلها بالشكل المطلوب لتُمثل عنصرًا من عناصر القائمة.

يوجد عدة أصناف فرعية من الصنف Adapter كي تُستخدم مع أشكال البيانات المختلفة مثل:

  • ArrayAdapter
  • Base Adapter
  • SimpleCursorAdapter

قوائم العرض مع كائن من ArrayAdapter

تطبيق 1

يُستخدم ArrayAdapter عندما يكون المصدر البيانات هو قائمة أو مصفوفة.

2.png

سنقوم بصنع تطبيق يعرض مجموعة من النصوص في قائمة العرض، لذا سنقوم بعمل مشروع جديد في 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);
	}
}

ثم نقوم بتجربة التطبيق على المحاكي لنتأكد من عمله بالشكل المطلوب.

3.png

تطبيق 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

في هذا التطبيق سنقوم بالبحث عن العناصر التي تحتوي على نص معين وعرضها هي فقط وذلك لسهولة الوصول لعنصر محدد.

4.png

كتطوير على التطبيق السابق سنقوم بإضافة الخاصية 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.png

تخصيص واجهة قائمة العرض

تطبيق 5

تُتيح قائمة العرض ميزة تخصيص العناصر، فيمكن صنع قائمة عرض تتكون من نص وصورة في كل صف بدلًا من نص فقط كالأمثلة السابقة.

في هذا التطبيق سنقوم بصنع تطبيق يعرض قائمة كما في الصورة التالية، وعند الضغط على العنصر يعرض نشاطًا جديدًا.

6.png

  • من 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 وبداخله نكون الشكل المطلوب للصف في قائمة العرض كما في المخطط التالي:

7.png

<?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!!");
	}
}

  والآن نقوم بتجربة التطبيق على المحاكي حتى نتأكد من عمله بالشكل المطلوب.

8.png

البحث داخل عناصر قائمة العرض التي تم تخصيصها

تطبيق 6

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

9.png

أولًا نقوم بتغيير ملف الواجهة 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();
            }
		};
	}
}

الآن قم بتجربة التطبيق على المحاكي للتأكد انه يعمل كما ينبغي.

10.png

بهذا نكون قد وصلنا إلى نهاية هذا الدرس، في انتظار تجربتكم وآرائكم.



2 اشخاص أعجبوا بهذا


تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن