15/9/2013 - برمجة Android - بناء قوائم ListView مُتقدمه




مرحباً بكم مره أخرى :)، نستكمل ما بدأنا به في درسنا الأول "بناء قائمة ListView بسيطة في واجهة المستخدم الرسومية"

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

المطلوب
في هذا الدرس سنقوم بالتالي، سنعرض قائمة من الأسماء، لكل إسم رقم هاتف و بريد إلكتروني يظهران أسفل الإسم، و يمكننا إختيار أكثر من إسم و ضغط زر معين يُظهر لنا الأسماء التي قمنا بإختيارها. بشكل مشابه للتالي :



الخطوة الأولى : فئة لتخزين البيانات
في البداية سنحتاج لتعريف فئة تحتوي على معلومات الأشخاص، الإسم و الرقم و البريد الإلكتروني، و يُمكننا من خلال هذه الفئة أخذ الأسماء التي قمنا بتحديدها، هذه الخطوة بسيطة :

class Person
{
private String name;
private String phone;
private String email;

Person( String name, String phone, String email )
{
this.name = name;
this.phone = phone;
this.email = email;
}

public String getName()
{
return this.name;
}

public String getPhone()
{
return this.phone;
}

public String getEmail()
{
return this.email;
}
}


لا يوجد شيء جديد هُنا، مُجرّد فئه تُمثّل شخص واحد وتحتوي على بياناته.

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

ArrayAdapter listAdapter = new ArrayAdapter( this, android.R.layout.simple_list_item_1 );


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

ما نريده الآن مُختلف بعض الشيء، فإننا نُريد في الصف الواحد في القائمة أن يعرض إسم الشخص و بجانبة صندوق إختيار، و عرض نصيّن في الأسفل يحتويان على رقم الهاتف و البريد الإلكتروني لهذا الشخص كما في الصورة في الأعلى، و بالتالي فإننا بحاجة لأن نصمم ملف XML خاص بنا يحتوي على تصميم الصف في قائمتنا الجديده.

العملية بسيطة، في المُجلّد res/layout أنشئ ملف جديد من نوع Android XML Layout File و قمت بتسميته person_row.xml، بعد إنشاءك للملف سيظهر لك مُصمم الواجهات الخاص بآندرويد.

فيما بعد كُل عنصر يتم عرضه في القائمة سيعتمد على هذا الملف في طريقة عرضه، سنُضيف في هذا التصميم التالي : صندوق إختيار واحد (CheckBox) و نصّين من الحجم الصغير أسفل هذا الصندوق (TextView)، خزّن الملف وهذا كُلّ شيء.

يُمكنك الملاحظه من خلال هذه الخطوه بإنّه بإمكانك تصميم صفوف قائمتك بالطريقة التي تشاؤها، يُمكنك مثلاً إضافة صورة لكل عنصر من عناصر القائمة و هكذا.

الخطوة الثالثه : إستخدام مُحوّلنا (Adapter) الخاص
في الدرس السابق إستخدمنا المُحوّل ArrayAdapter لعرض قائمتنا البسيطه، إختلفت الأمور الآن لأنه أصبح لدينا تصميم خاص بنا لكل عُنصر و بالتالي لابد أن نبني مُحوّلنا الخاص ليعرض البيانات بأماكنها الصحيحه (لا تقلق فالعملية ليست مُخيفه :)).

سنعتمد في محولنا على المُحوّل الموجود مُسبقاً وهو ArrayAdapter، لنبدأ ببناء شيء بسيط :

class PersonsAdapter extends ArrayAdapter
{
private Context context;
private ArrayList checkedList;

public PersonsAdapter( Context context )
{
super( context, R.layout.person_row );

this.context = context;
this.checkedList = new ArrayList();
}


الأمور بسيطه هنا، أنشأنا فئة بإسم PersonsAdapter و التي ترث الفئة ArrayAdapter و لاحظ إننا حددنا نوع ArrayAdapter على إنّه Person و هي الفئة التي قمنا بتعريفها في الخطوة الأولى.

في المُشيّد قمنا بمناداة مُشيّد الأب كما هي العادة و البارامتر الأول الذي نستقبله في مشيدنا و نرسلها إلى مُشيّد الأب واضح.

لاحظ البارامتر الثاني الذي مررناه للمشيد الأب، هنا نقطة هامة، هذا البارامتر يُمثّل التصميم الذي أنشأنها في الخطوه الثانيه، حيث إننا نُخبر الـ ArrayAdapter أن تعتمد على هذا التصميم الخاص بنا لعرض الصفوف في قائمتنا.

ستُلاحظ كذلك بأننا عرّفنا القائمة checkedList، لن أتحدث عنها الآن، سيأتي وقتها بعد قليل إن شاء الله :).

لنشرع بكتابة الدالة المُهمه في هذه الفئة و هي getView، هذه الدالة موجودة في الأساس في فئة الأب ArrayAdapter، و يتم إستدعاؤها في كُل مرّه يرغب النظام بعرض عُنصر جديد في قائمتنا، و بالتالي يُمكننا عرض التصميم الخاص بنا و عرض بيانات الشخص.

	@Override
public View getView( final int position, View convertView, ViewGroup parent )
{


لاحظ البارامتر الأول وهو position، يستقبل هذا البارامتر مكان العُنصر الذي سيتم عرضه حالياً، فإذا كان النظام يُجهّز لعرض العنصر الأول من القائمة فقيمة هذا البارامتر سيكون 0، و هكذا بالتتالي، هذا البارامتر مُهم لأنه سيُمكننا من أخذ معلومات الشخص الذي سيتم عرضه في الصف الذي يتم بناؤه حالياً من خلال النظام.

ننتقل لبناء شيفرة الدالة getView :

		LayoutInflater inflater = (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE );

View rowView = inflater.inflate( R.layout.person_row, parent, false );

// ... //


هُنا نقوم بإنشاء التصميم الموجود في person_row.xml و بالتالي يُمكننا عرضه، في نهاية الدالة سنحتاج لإعادة قيمة rowView (لن أفصّل في هذه الشيفرة لأنها خارج موضوعنا)، بعد ذلك يُمكننا أخذ الأدوات من واجهتنا التي قمنا بتصميمها للتعامل معها برمجياً بالطريقة التي إعتدنا عليها كالتالي :

		CheckBox checkBox = ( CheckBox ) rowView.findViewById( R.id.checkBox1 );
TextView txtPhone = ( TextView ) rowView.findViewById( R.id.textView1 );
TextView txtEmail = ( TextView ) rowView.findViewById( R.id.textView2 );

// ... //


نحن الآن جاهزون لعرض معلومات الشخص الذي يُمثله هذا الصف، كما أسلفت فإننا سنعتمد على البارامتر position، و لأننا في هذه الفئة نرث الفئة ArrayAdapter بالتالي يُمكننا إستخدام الدالة getItem التي إستخدمناها في الدرس السابق كالتالي :

		Person currPerson = getItem( position );

checkBox.setText( currPerson.getName() );
txtPhone.setText( currPerson.getPhone() );
txtEmail.setText( currPerson.getEmail() );

// ... //


الأمور بسيطة هنا، نأخذ الكائن الخاص بالشخص الذي يُمثله الصف الحالي الذي يتم تكوينه من خلال النظام لعرضه، ووضع معلوماته في الأماكن المُناسبة في التصميم الذي أنشأنها، هذا كُل شيء.

قبل إنهاء الدالة و إعادة قيمة rowView، هُناك خطوه أخيرة يجب القيام بها في هذه الدالة وهي تسجيل الأشخاص الذي يقوم المستخدم بوضع علامة صح بجانب أسمائهم و بالتالي نتمكن فيما بعد من عرض هذه القائمة للمستخدم.

سنقوم ببساطة بإستخدام الحَدث CheckedChangeListener الخاص بالـ checkBox كالتالي :

		checkBox.setOnCheckedChangeListener( new OnCheckedChangeListener() 
{
@Override
public void onCheckedChanged( CompoundButton parent, boolean isChecked )
{
if ( isChecked )
checkedList.add( getItem( position ) );
else
checkedList.remove( getItem( position ) );
}
});

// ... //


بالطبع تذكر القائمة checkedList و التي قلت بأنني سأذكرها فيما بعد، وظيفة هذه القائمة بسيطة، و هي تخزين أسماء الأشخاص الذين أختارهم المُستخدم، و في حال أزال المستخدم أحد المُختارين يتم حذفه من هذه القائمة، الشيفرة في الأعلى تقوم بهذا الضبط.

وأخيراً، نُعيد قيمة rowView و نُغلق الدالة ليتسنى للنظام عرض الصف :

		return rowView;
}


تبقّت لدينا خطوة أخيره في هذه الفئة وهي دالة لإرجاع قائمة الأشخاص المُختارين

	public ArrayList getCheckedList()
{
return this.checkedList;
}


هذا كُل شيء، الشيفرة الكاملة للفئة PersonsAdapter :

class PersonsAdapter extends ArrayAdapter
{
private Context context;
private ArrayList checkedList;

public PersonsAdapter( Context context )
{
super( context, R.layout.person_row );

this.context = context;
this.checkedList = new ArrayList();
}

@Override
public View getView( final int position, View convertView, ViewGroup parent )
{
LayoutInflater inflater = (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE );

View rowView = inflater.inflate( R.layout.person_row, parent, false );

// ... //

CheckBox checkBox = ( CheckBox ) rowView.findViewById( R.id.checkBox1 );
TextView txtPhone = ( TextView ) rowView.findViewById( R.id.textView1 );
TextView txtEmail = ( TextView ) rowView.findViewById( R.id.textView2 );

// ... //

Person currPerson = getItem( position );

checkBox.setText( currPerson.getName() );
txtPhone.setText( currPerson.getPhone() );
txtEmail.setText( currPerson.getEmail() );

// ... //

checkBox.setOnCheckedChangeListener( new OnCheckedChangeListener()
{
@Override
public void onCheckedChanged( CompoundButton parent, boolean isChecked )
{
if ( isChecked )
checkedList.add( getItem( position ) );
else
checkedList.remove( getItem( position ) );
}
});

// ... //

return rowView;
}

public ArrayList getCheckedList()
{
return this.checkedList;
}
}


الخطوة الرابعه و الأخيره : تعبئة المعلومات و عرض القائمة للمستخدم
مَررنا بهذا من قبل في الدرس السابق، ستكون الأمور مُشابهه مع بعض الإختلاف في التفاصيل، واجهة البرنامج ستحتوي على ListView لعرض قائمتنا و زر واحد يضغطه المستخدم عندما يختار مجموعة من الأشخاص، و يقوم هذا الزر بعرض رسالة Toast بأسماء الأشخاص الذين أختارهم المستخدم، الأمور بسيطة هنا ولا يوجد بها شيء جديد.

بالطبع عملنا الآن في الدالة onCreate الخاصّه بإنشاء الـ Activity الرئيسي للبرنامج، نأخذ الأدوات برمجياً كخطوة أولى :

ListView listView = (ListView) findViewById( R.id.listView1 );
Button btnOK = (Button) findViewById( R.id.button1 );


نُنشؤ المحوّل الذي سيحتوي على المعلومات التي ستُعرض في القائمة :

final PersonsAdapter personsAdapter = new PersonsAdapter( this );


لاحظ إننا إستخدمنا مُحولنا الخاص PersonsAdapter و الذي كتبنا شيفرته منذ قليل.

نقوم بتعبئة بعض البيانات في المُحوّل إعتماداً على الفئة Person و التي عرفناها في أوّل الدرس :

		personsAdapter.add( new Person( "Name 1", "12345678", "name1@example.com" ) );
personsAdapter.add( new Person( "Name 2", "87654321", "name2@example.com" ) );
personsAdapter.add( new Person( "Name 3", "12348765", "name3@example.com" ) );


نُخبر القائمة ListView بأنّ المُحوّل الذي يجب أن تأخذ منه المعلومات و تعرضه هو personsAdapter :

		listView.setAdapter( personsAdapter );


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

		btnOK.setOnClickListener( new OnClickListener() 
{
@Override
public void onClick( View parent )
{
String message = "";

ArrayList selectedPersons = personsAdapter.getCheckedList();

Iterator it = selectedPersons.iterator();

while ( it.hasNext() )
{
Person currPerson = it.next();

message += currPerson.getName() + ", ";
}

Toast.makeText( getApplicationContext(), message , Toast.LENGTH_LONG ).show();
}
});



وهُنا نستخدم الدالة getCheckedList التي عرفناها في مُحولنا لنأخذ قائمة الأشخاص الذين تم إختيارهم.

هذا كُل شيء، الشيفرة الكاملة للدرس :

package com.example.androidtutorials;

import java.util.ArrayList;
import java.util.Iterator;

import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

ListView listView = (ListView) findViewById( R.id.listView1 );
Button btnOK = (Button) findViewById( R.id.button1 );

// ... //

final PersonsAdapter personsAdapter = new PersonsAdapter( this );

personsAdapter.add( new Person( "Name 1", "12345678", "name1@example.com" ) );
personsAdapter.add( new Person( "Name 2", "87654321", "name2@example.com" ) );
personsAdapter.add( new Person( "Name 3", "12348765", "name3@example.com" ) );

// ... //

listView.setAdapter( personsAdapter );

// ... //

btnOK.setOnClickListener( new OnClickListener()
{
@Override
public void onClick( View parent )
{
String message = "";

ArrayList selectedPersons = personsAdapter.getCheckedList();

Iterator it = selectedPersons.iterator();

while ( it.hasNext() )
{
Person currPerson = it.next();

message += currPerson.getName() + ", ";
}

Toast.makeText( getApplicationContext(), message , Toast.LENGTH_LONG ).show();
}
});
}

@Override
public boolean onCreateOptionsMenu(Menu menu)
{
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}

class PersonsAdapter extends ArrayAdapter
{
private Context context;
private ArrayList checkedList;

public PersonsAdapter( Context context )
{
super( context, R.layout.person_row );

this.context = context;
this.checkedList = new ArrayList();
}

@Override
public View getView( final int position, View convertView, ViewGroup parent )
{
LayoutInflater inflater = (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE );

View rowView = inflater.inflate( R.layout.person_row, parent, false );

// ... //

CheckBox checkBox = ( CheckBox ) rowView.findViewById( R.id.checkBox1 );
TextView txtPhone = ( TextView ) rowView.findViewById( R.id.textView1 );
TextView txtEmail = ( TextView ) rowView.findViewById( R.id.textView2 );

// ... //

Person currPerson = getItem( position );

checkBox.setText( currPerson.getName() );
txtPhone.setText( currPerson.getPhone() );
txtEmail.setText( currPerson.getEmail() );

// ... //

checkBox.setOnCheckedChangeListener( new OnCheckedChangeListener()
{
@Override
public void onCheckedChanged( CompoundButton parent, boolean isChecked )
{
if ( isChecked )
checkedList.add( getItem( position ) );
else
checkedList.remove( getItem( position ) );
}
});

// ... //

return rowView;
}

public ArrayList getCheckedList()
{
return this.checkedList;
}
}

class Person
{
private String name;
private String phone;
private String email;

Person( String name, String phone, String email )
{
this.name = name;
this.phone = phone;
this.email = email;
}

public String getName()
{
return this.name;
}

public String getPhone()
{
return this.phone;
}

public String getEmail()
{
return this.email;
}
}


أرجو أن الدرس قد أفادك، لأي سؤال يُمكنك مُراسلتي و سأبذل جهدي للمساعدة إن شاء الله.