در این مقاله میخواهیم یک لیست ساده را ایجاد کرده و داخل یک کنترل (View)، از نوع ListView قرار دهیم. همچنین با برخی از کنترلهای پرکاربرد، برای چیدمان کنترلها در اندروید آشنا میشویم.
قبل از شروع به طراحی UI باید کمی با واحدهای اندازه گیری در اندروید آشنا شویم. بدانید و آگاه باشید که استفاده از واحد Pixel برای تعیین اندازه در اندروید کار بسیار اشتباهی است. طراح همیشه باید Density یا تراکم صفحهی نمایش را در نظر بگیرد. تراکم صفحهی نمایش به معنای تعداد پیکسل موجود در یک اینچ میباشد. اندازهی 100 پیکسل در دستگاههای مختلف با (dpi(Dot Per Inchهای متفاوت به یک اندازه نیست.
واحد dpi: اندروید واحد dpi را برای طراحی و چیدمان Layoutها معرفی کرده است. dpi مخفف Device Independent Pixel هست و معمولا بصورت dp نوشته میشود که یک واحد پیکسلی مجازی است و بر پایهی یک صفحه نمایش با رزولوشن 160dpi طراحی شدهاست. به عبارت دیگر یک dp، یک پیکسل در یک صفحهی نمایش با رزولوشن 160dpi میباشد. این واحد این اطمینان را به شما میدهد که یک View، در صفحه نمایشهای با رزولوشن متفاوت، بطور مناسبی بزرگ یا کوچک میشود.
واحد sp: مخفف Scale Independent Pixel است و شبیه dp عمل میکند؛ با این تفاوت که تنظیمات کاربر را (مثلا شخصی که بخاطر ضعف چشم اندازهی قلم گوشی خود را بزرگ نموده) در محاسبات خود در نظر میگیرد. به دلیل آنکه از لحاظ زیبایی شناسی و همچنین چیدمان عناصر داخل UI زمانیکه از واحد اندازه گیری sp استفاده میکنیم ممکن است با مشکل مواجه شویم، بیشتر از dp استفاده میکنیم، مگر در بعضی مواقع آن هم برای مقداردهی به اندازهی قلم!
خوب! به سراغ فولدر Layout رفته و Main.axml را باز نمایید. به قسمت Source بروید.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:id="@+id/MyButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/Hello" />
</LinearLayout>
در این سند axml یک LinearLayout مشاهده مینمایید. وقتی شما View را به LinearLayout اضافه میکنید، با توجه به اینکه orientation آن را vertical یا horizontal انتخاب کرده باشید، به صورت افقی و یا عمودی طرح بندی را انجام میدهد.
layout_width و layout_height (مقداردهی آنها الزامی است) ابعاد layout ما را مشخص میکنند. مقدار fill_parent دیگر منسوخ شده و به جای آن match_parent استفاده میشود و به معنای آن است که تمام فضای موجود در کنترل را اشغال کند. مقدار دیگری که میتوان به آن نسبت داد (و در layout_height مربوط به Button مشاهده مینمایید)، wrap_content میباشد که اعلام میکند فقط به میزان مورد نیاز برای محتویات، کنترل والد را اشغال کند. البته با تغییر میزان محتویات، اندازهی کنترل متغییر است. شما میتوانید مقادیر عددی را هم با واحد dp یا حتی pixel (که اصلا توصیه نمیشد) جایگزین نمایید.
در ادامه، کنترل (که در اندروید به آن View گفته میشود) Button را حذف نمایید و به جای آن یک ListView را قرار دهید و نامی را به آن نسبت دهید. ListView از کاربردیترین و مهمترین کنترلهای اندروید میباشد. ListView شامل قسمتهای زیر است:
Rows: قسمت نمایش دهندهی دادهها.
Adapter: یک کلاس که وظیفهی انقیاد منبع داده را به ListView، بر عهده دارد.
Fast Scrolling: یک دسته(handle) که به کاربر اجازه میدهد تا در طول ListView حرکت کند.
Section Index: یک view میباشد و جایگاه لیت را هنگام اسکرول مشخص میکند و معمولا در Contacts گوشی بصورت ابتدای حروف نام مخاطبین خود مشاهده کردهاید.
Layout زیر را در نظر بگیرید:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:background="#fff"
android:id="@+id/NameListView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
به MainActivity.cs بروید و کدهای مربوط به Button قبلی را که با ListView جایگزین کردهایم، حذف نمایید. متد OnCreate به این صورت میباشد:
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
List<string> namesList = new List<string>
{
"Mohammad","Fatemeh","Ali","Hasan","Husein","Mohsen","Mahdi",
};
var namesAdapter = new ArrayAdapter<string>
(this, Android.Resource.Layout.SimpleListItem1, namesList);
var listview = FindViewById<ListView>(Resource.Id.NameListView);
listview.Adapter = namesAdapter;
}
همانطور که گفته شد SetContentView مشخص کنندهی layout مورد نظر ما برای نمایش میباشد. میتوان بدون هیچ layout خاصی با کدهای سی شارپ، کنترلهای مورد نظر را ایجاد کرد که کار زمانبری است؛ ولی بعضی مواقع مجبور به این کار هستیم.
namesList یک لیست ساده از نوع string با مقدار دهی اولیه است.
ArrayAdapter یک کلاس Adapter توکار میباشد که یک آرایه (یا لیست) را از نوع string، برای نمایش به ListView متصل میکند (bind). نوع جنریک آن یعنی <ArrayAdapter<T برای نوعهای دیگر هم استفاده میشود. در واقع Adapter با دریافت یک لیست برای نمایش و یک Layout برای تعیین نوع نمایش، به ازای هر سطر از اطلاعات یک View را با اطلاعات آن سطر به سمت ListView ارسال میکند. در اینجا ما در سازندهی ArrayAdapter با استفاده از Resourceهای توکار اندروید که از طریق Android.Resource به آنها دسترسی داریم، یک layout ساده را شامل یک TextView(مانند label و یا textBlock)، به همراه namesList، برای Adapter ارسال کردیم.
متد FindViewById با توجه به Layout معرفی شدهی به Activity، به دنبال View با Id مورد نظر میپردازد. مهم نیست که در Layoutهای جداگانه نامهای یکسانی استفاده کنید. این متد در کلاس View قرار دارد و تمام کنترل(View)ها، فرزند آن میباشند. در اینجا از نوع جنریک آن استفاده شده که عمل تبدیل View به ListView را خود متد بر عهده بگیرد.
در انتها Adapter مورد نظر به ویژگی Adpater کنترل ListView اضافه میشود.
ListView کنترل بسیار منعطفی میباشد. برخی ویژگیها آن را در زیر میتوانید مشاهده بفرمایید:
- android:dividerHeight // ارتفاع جداکنندهی سطرها
- android:divider // رنگ جداکنندهی سطرها
- android:layoutAnimation // انیمیشن برای layoutها
- android:background // رنگ ضمینه را مشخص میکند. البته میتوانید یک style را به ان نسبت دهید
خوب؛ حالا بیایید یک ListView را با ظاهر و Adapter سفارشی بسازیم.
ابتدا باید یک Layout را طراحی کنیم تا به ازای هر سطر برای ListView ارسال شود. با استفاده از Add->New item یک Layout را به فولدر layout اضافه کنید.
کد زیر را درون فایل axml مربوطه کپی کنید.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="14dp">
<TextView
android:text=""
android:gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/idTextView" />
<TextView
android:text=""
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/nameTextView"
android:layout_marginLeft="14dp" />
</LinearLayout>
کلاس زیر (یا هر کلاس دلخواه دیگری) را به عنوان مدل برنامه اضافه کنید.
namespace DotSystem.ir.App1.Model
{
public class Person
{
public int Id { get; set; }
public string PersonName { get; set; }
}
حالا باید Adapter خود را بسازیم. ابتدا کلاسی را با نام PersonAdapter به برنامه اضافه نمایید. این کلاس باید از کلاس BaseAdapter (نوع جنریک آن هم موجود میباشد) و یا فرزندان آن ArrayAdapter، CursorAdapter و ... ارث بری نماید. اگر مستقیما از BaseAdapter استفاده کنیم، به دلیل Abstract بودن تعدادی از متدها و Propertyها مجبور به override کردن آنها میشویم. ما در اینجا از BaseAdapter استفاده میکنیم. کد زیر را در نظر بگیرید:
namespace DotSystem.ir.App1.Adapters
{
public class PersonAdapter : BaseAdapter<Model.Person>
{
public override Person this[int position]
{
get
{
throw new NotImplementedException();
}
}
public override int Count
{
get
{
throw new NotImplementedException();
}
}
public override long GetItemId(int position)
{
throw new NotImplementedException();
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
throw new NotImplementedException();
}
}
}
BaseAdapter شامل یک Indexer برای دسترسی آسان به Itemهای لیست، یک ویژگی برای برگرداندن تعداد آیتمها، متدی برای برگرداندن Id هر آیتم و مهمترین بخش آن یعنی متد GetView که برای نمایش هر آیتمی یک بار اجرا میشود و Layout مورد نظر ما را با اطلاعات پر کرده و به سمت ListView میفرستد.
در اینجا ما به چند فیلد داخل کلاس احتیاج داریم.
- لیست اطلاعات مورد نظر.
- Activity جاری که Adapter را استفاده میکند.
بنابراین دو فیلد را به همراه متد سازنده، برای مقدار دهی آنها اضافه کرده و کلاس بالا را نیز تکمیل میکنیم.
namespace DotSystem.ir.App1.Adapters
{
public class PersonAdapter : BaseAdapter<Person>
{
protected Activity _activity = null;
protected List<Person> _list = null;
public PersonAdapter(Activity activity, List<Person> list)
{
_activity = activity;
_list = list;
}
public override Person this[int position]
{
get
{
return _list[position];
}
}
public override int Count
{
get
{
return _list.Count;
}
}
public override long GetItemId(int position)
{
return _list[position].Id;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
throw new NotImplementedException();
}
}
}
در این مرحله باید متد GetView را پیاده سازی کنیم. به پیاده سازی زیر دقت کنید:
public override View GetView(int position, View convertView, ViewGroup parent)
{
if (convertView == null)
convertView = _activity.LayoutInflater
.Inflate(Resource.Layout.PersonListViewItemLayout, parent, false);
var idTextView = convertView.FindViewById<TextView>(Resource.Id.idTextView);
var nameTextView = convertView.FindViewById<TextView>(Resource.Id.NameListView);
var persion = _list[position];
idTextView.Text = persion.Id.ToString();
nameTextView.Text = persion.PersonName;
return convertView;
}
در مرحلهی اول بررسی میکنیم که اگر convertView برابر با null بود، آن را مقدار دهی کند. این نکته بسیار مهم است، چرا که ListView برای کارآیی بهتر فقط آن آیتم هایی را که در دید کاربر باشد، با متد GetView لود میکند و دوباره با اسکرول لیست، عمل فراخوانی متد انجام میشود؛ البته اینبار بدون مقدار null برای convertView. بنابراین اگر دیدید که هنگام اسکرول لیست، آیتمها جابجا شدند، این بخش از متد را دوباره بررسی نمایید.
Inflate متدی است که Layout و نگه دارندهی layout را گرفته و آن را برای نمایش در Activity آماده میکند. سپس دو View را که در Layout ما وجود دارند، گرفته مقدار دهی میکنیم و در آخر هم convertView را برای نمایش به سمت ListView میفرستیم.
حال متد OnCreate را به صورت زیر بازنویسی نموده و برنامه را اجرا میکنیم.
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
List<Model.Person> personList = new List<Model.Person>
{
new Model.Person() {Id = 1, PersonName = "Mohammad", },
new Model.Person() {Id = 2, PersonName = "Ali", },
new Model.Person() {Id = 3, PersonName = "Fatemeh", },
new Model.Person() {Id = 4, PersonName = "hasan", },
new Model.Person() {Id = 5, PersonName = "Husein", },
new Model.Person() {Id = 6, PersonName = "Mohsen", },
new Model.Person() {Id = 14, PersonName = "Mahdi", },
};
var personAdapter = new Adapters.PersonAdapter(this, personList);
var listview = FindViewById<ListView>(Resource.Id.NameListView);
listview.Adapter = personAdapter;
}