مشخصات مقاله
-
1974
-
0.0
-
8881
-
0
-
0
ساخت و پیاده سازی view های سفارشی و ترکیبی در اندروید
View های اختصاصی
View های پیش فرض محیط اندروید
چارچوب نرم افزاری (framework) اندروید تعدادی view درون ساخته و پیش فرض ارائه می دهد که توسعه دهنده می تواند از آن ها به صورت آماده استفاده نماید. کلاسی که تمامی view ها (همان کنترل ها و المان های رابط کاربری) از آن مشتق و ارث بری می شوند، View می باشد.
View ها موظف هستند خود و تمامی المان های داخل خود (view های فرزند که در ViewGroup با آن مواجه می شوید) را اندازه گیری، طرح بندی (layout) و ترسیم نمایند.
View ها همچنین می بایست اطلاعات مربوط به وضعیت (state) UI را ذخیره کرده و event هایی که با تعامل کاربر با المان های رابط کاربری (لمس نمایشگر) فعال می شوند را مدیریت نمایند.
البته توسعه دهندگان این امکان را هم دارند که view های اختصاصی تعریف نموده و آن را در اپلیکیشن خود بکار ببرند.
به منظور تعریف view های سفارشی و دلخواه خود می توانید به یکی از روش های زیر اقدام نمایید:
- Compound view – تلفیق view ها با اتصال پیش فرض (default wiring)
- Custom view – ساخت view های اختصاصی و دلخواه خود
- با ارث بری از یک view ی آماده همچون Button
- با ارث بری از کلاس View
تصویر زیر زنجیره ی ارث بری و سلسله مراتب view های پیش فرض اندروید را به نمایش می گذارد.
View های سفارشی معمولا با هدف ارائه ی تجربه ی کاربری ویژه و دلخواه که با view های پیش فرض و آماده ی خود اندروید امکان پذیر نیست ساخته می شوند. بعلاوه پیاده سازی view های اختصاصی این امکان را برای توسعه دهنده فراهم می کند تا با ابتکار خود کارایی را افزایش دهد برای مثال در خصوص پیاده سازی layout اختصاصی، برنامه نویس می تواند layout manager را با توجه به نیاز خود تنظیم و بهینه نماید.
اندروید چگونه view hierarchy را ترسیم می نماید!
پس از اینکه انتخاب و تمرکز UI بر روی یک activity متمرکز می شود، در همان لحظه بایستی root node (گره یا عنصر اصلی و آغازین) سلسه مراتب layout خود را در اختیار سیستم اندروید قرار دهد. پس از آن سیستم اندروید فرایند ترسیم را آغاز می نماید.
روند ترسیم layout دو مرحله را پشت سر می گذارد:
- measuring pass (مرحله ی سنجش و اندازه گیری) – توسط متد `measure(int, int)` پیاده سازی می شود. این مرحله به صورت پیمایش از بالای سلسله مراتب view تا پایین آن اتفاق می افتد. هر view اندازه های خود را ذخیره می کند.
- layout pass (مرحله ی تنظیم چیدمان و طرح بندی) – این مرحله توسط متد layout(int, int, int, int) پیاده سازی می شود. پیمایش مرحله ی جاری نیز از بالای سلسله مراتب view به پایین آن رخ می دهد. طی این مرحله هر layout manager مسئول چیدمان و موقعیت دهی فرزندان خود (view های داخل خود) می باشد. لازم به ذکر است که متد فوق برای تنظیم موقعیت view ها از اندازه های بدست آمده در مرحله ی اول استفاده می کند.
مرحله ی measure (اندازه گیری) و layout (چیدمان) همیشه همزمان اتفاق می افتند.
Layout manager می تواند مرحله ی اندازه گیری را چندین بار اجرا نماید. برای مثال، LinearLayout از attribute ای به نام weight پشتیبانی می کند که فضای خالی باقی مانده را بین view ها پخش نموده و RelativeLayout تمامی المان یا view های فرزند خود را چندین بار اندازه گیری می کند تا constraint های تعیین شده در فایل layout همگی برآورده شوند.
View یا activity هر یک می توانند مرحله ی اندازه ی گیری و چیدمان را با فراخوانی متد requestLayout() راه اندازی نماید.
پس از انجام محاسبات مربوط به اندازه گیری و تنظیم چیدمان المان ها، view ها اقدام به ترسیم خود می نمایند. جهت راه اندازی این عملیات کافی است متد invalidate() از کلاس View فراخوانی شود.
استفاده از view های جدید در فایل های layout
View های اختصاصی و ترکیبی (پیچیده) می توانند در فایل های layout تعریف و استفاده شوند. برای نیل به این هدف لازم است اسم view ها را به صورت تمام و کامل در فایل ذکر شده قید نمایید. برای مثال می بایست اسم کلاس و پکیج (پوشه ی اصلی پروژه) را ارائه کنید.
در صورت تمایل می توانید name space خود را در فایل layout اعلان نمایید، مانند name space اندروید.
Alternatively you can also declare you name space in the layout file, similar to the Android name space
تهیه ی تصویر آنی (screenshot) از view ها
تمامی کلاس های View این قابلیت را دارند از وضعیت و ظاهر کنونی خود تصویر آنی تهیه نمایند.
# Build the Drawing Cache view.buildDrawingCache(); # Create Bitmap Bitmap cache = view.getDrawingCache(); # Save Bitmap saveBitmap(cache); view.destroyDrawingCache();
view های ترکیبی (Compound views)
View های ترکیبی (یا کامپوننت های ترکیبی) به Viewهای گفته می شوند که از ترکیب چند view دیگر پدیده آمده باشد.
Compound view ها به شما این امکان را می دهند تا APIها و توابع اختصاصی خود را جهت بروز رسانی و کوئری گرفتن از اطلاعات مربوط به وضعیت view بکار ببرید.
برای این control یک فایل layout تعریف می کنید و آن را به compound view خود تخصیص می دهید. در پیاده سازی compound view، بایستی ارتباط متقابل view ها را تعریف کرده باشید. ابتدا یک فایل layout تعریف می کنید که اعضا و توابع کلاس ViewGroup مربوطه را به ارث می برد. در این کلاس فایل layout را بارگذاری نموده (inflate) و منطق (کد) اتصال و ارتباط View را پیاده سازی می نمایید.
برای افزایش کارایی و سرعت اجرا، بهتر است view سفارشی که از کلاس View ارث بری کرده ایجاد نمایید. از این طریق می توانید سلسله مراتب view خود و زیرشاخه های آن را به صورت خطی نمایش دهید (flatten view). چرا که در این حالت ترسیم view به پیمایش کمتری نیاز دارد و در صورتی که به شکل درستی پیاده سازی شود، بسیار سریع تر اجرا خواهد شد.
ساخت view های اختصاصی
پیاده سازی view های اختصاصی
با ارث بری از کلاس View یا یکی از کلاس های مشتق آن (subclass)، شما می توانید view دلخواه خود را ایجاد نمایید.
برای ترسیم view می توانید متد onDraw() را بکار ببرید. در این متد یک آبجکت Canvas به عنوان ورودی دریافت می کنید. آبجکت نام برده به شما امکان می دهد تا عملیات ترسیم همچون کشیدن خط، دایره، درج متن یا bitmap را در سطح آن انجام دهید. در صورتی که view مجددا ترسیم گردد، شما می توانید متد invalidate() را فراخوانی کنید که خود سبب فراخوانی متد onDraw() این view می شود.
در صورت تعریف view های اختصاصی، حتما کلاس ViewConfiguration را بررسی نمایید چرا که این کلاس تعدادی ثابت (constant) درون ساخته دارد که شما می توانید برای تعریف view ها مورد استفاده قرار دهید.
برای ترسیم Views توسعه دهندگان اغلب از 2D Canvas API استفاده می کنند.
اندازه گیری view ها
Layout manager متد onMeasure() از view مورد نظر را صدا می زند. View سپس پارامترهای layout را از layout manager دریافت می کند. layout manager وظیفه ی تعیین اندازه ی تمامی view های داخل خود را بر عهده دارد.
View می بایست متد setMeasuredDimension (int, int) را با نتیجه مورد نظر فراخوانی نماید.
تعریف layout manager اختصاصی
برای تعریف layout manager اختصاصی خود می توانید اعضا و توابع کلاس ViewGroup را به ارث ببرید. یا انجام کار فوق این امکان برای شما فراهم می شود تا layout manger های کارامد و بهینه تری را پیاده سازی نموده یا افکت های دیداری را خلق کنید که در محیط (platform) اندروید وجود ندارد.
یک layout manager سفارشی، به تبع پیاده سازی توابع onMeasure() و onLayout() را بازنویسی (override) نموده و عملیات محاسبه ی (اندازه ی) المان های فرزند خود را نیز به صورت اختصاصی انجام می دهد. برای مثال ممکن است استفاده از ویژگی سنگین و زمان بر layout_weight از کلاس LinearLayout را کنار بگذارد.
به منظور محاسبه ی اندازه ی المان محصور (فرزند) کافی است متد measureChildWithMargins() از کلاس ViewGroup را فراخوانی نمایید.
توصیه می شود تمامی پارامترهای اضافی layout را در یک کلاس داخلی درون نمونه ی پیاده سازی شده ی خود از ViewGroup قرار دهید. برای مثال کلاس LayoutParams به عنوان یک ویژگی در کلاس ViewGroup ویژگی های command، layout و parameters را درون خود کپسوله کرده است و کلاس LinearLayout نیز علاوه بر این ویژگی ها، ویژگی layout_weight را به این ویژگی اضافه نموده است.
Life Cycle
event ها و توابع مربوط به مدیریت چرخه ی حیات window
یک view زمانی نمایش داده می شود که به layout hierarchy متصل باشد. layout hierarchy نیز خود به تبع به window وصل می باشد. هر view تعداد زیادی تابع hook مدیریت چرخه ی حیات دارد.
Life cycle
رویداد LifeCycle وابسته باز بود ن در پنجره مربوطه می باشد
یک View در یک سلسله مراتب به پنجره جاری پیوسته است . View بسته به یک Lifecycle است .
متد onAttachedToWindow() زمانی فراخوانی می شود که پنجره در حافظه بارگذاری شده و در دسترس باشد.
متد onDetachedFromWindow() زمانی فراخوانی می شود که view از میزبان (parent) خود جدا شده باشد (و البته میزبان نیز خود به یک window وصل باشد). این اتفاق، برای مثال، زمانی رخ می دهد که activity (به عنوان مثال با صدا خورده شدن متد finished()) یا view بازیافت شده باشند.
متد onAttachedToWindow() زمانی فراخوانی می شود که یک پنجره در دسترس باشد .
متد onDetachedFromWindow() از والد View پس از حذف استفاده می نماید(حتی اگر به یک پنجره دیگر پیوست باشد) . این رویداد نیز برای Activity به صورت باز پس گیری (حتی وقتی متد finished() فراخوانی شده باشد( روی می دهد .
متد onDetachedFromWindow() را می توان جهت متوقف کردن انیمیشن ها و پاک سازی و آزاد نمودن منابع مورد استفاده ی view فراخوانی نمود.
Event های مربوط به life cycle به صورت ترتیبی/پیمایشی (Traversal life cycle event)
رخدادهای چرخه ی حیات به ترتیب عبارت اند از:
- Animate
- Measure
- Layout
- Draw
لازم است view ها با نحوه ی اندازه گیری و چیدمان (layout) خود آشنا باشند. متد requestLayout() به view اعلان می کند که خود را اندازه گرفته و موقعیت دهی (layout) نمایند. از آنجایی که این عملیات ممکن است layout دیگر view ها را نیز متاثر کند، متد requestLayout() پدر (parent) نیز فراخوانی می شود.
زمانی که چندین layout را داخل هم قرار داده و آن ها را زیاد تودرتو می نمایید، این امر سبب به اجرا در آمدن الگوریتم فراخوانی بازگشتی می شود. حال زمانی که عمق ساختار درختی یا سلسله مراتب زیاد بوده و در این حین لازم باشد سلسله مراتب مجددا محاسبه شود، عملیات اندازه گیری و موقعیت دهی ناگذیر سنگین و طولانی خواهد بود.
متد onMeasure() اندازه ی view و المان های محصور آن (view های فرزند) را مشخص می کند. سپس با فراخوانی setMeasureDimension() داخل بدنه ی خود و قبل از اجرای دستور return، اندازه ی آن ها را تنظیم می نماید.
متد onLayout() تمامی view ها را بر اساس نتیجه یا خروجی متد onMeasure() موقعیت دهی می نماید. این فراخوانی معمولا تنها یکبار انجام می شود در حالی که onMeasure() می تواند چندین بار صدا خورده شود.
چرخه ی حیات activity
View ها به event های چرخه ی حیات activity ها دسترسی ندارند. اگر لازم باشد که view ها از رخداد این event ها باخبر شوند، در آن صورت می بایست یک interface در view ایجاد نمایید (interface مربوطه را در بدنه ی کلاس view پیاده سازی کرده) و متدهای مربوط به مدیریت چرخه ی حیات activity را از طریق آن فراخونی نمایید.
تعریف attribute های بیشتر برای view های اختصاصی
شما می توانید attribute های اضافی بر سازمان برای view های ترکیبی و اختصاصی خود تعریف نمایید. برای این منظور ابتدا بایستی یک فایل به نام attrs.xml در پوشه ی res/values خود ایجاد نمایید. در زیر مثالی را می بینید که برای view جدیدی به نام ColorOptionsView تعدادی attribute جدید تعریف شده است.
جهت استفاده از attribute های نام برده در فایل layout، می بایست آن ها را در بخش header فایل XML اعلان نمایید. در کد زیر این کار توسط دستور xmlns:custom صورت گرفته است. مقدار attribute هایی که زیر این دستور درج می شوند، متعاقبا به view مورد نظر نیز اعمال می شوند.
xmlns:custom="http://schemas.android.com/apk/res/com.vogella.android.view.compoundview" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" >
مثال زیر نشان می دهد چگونه کامپوننت های شما می توانند به این attribute ها دسترسی داشته باشند.
package com.vogella.android.view.compoundview;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
public class ColorOptionsView extends View {
private View mValue;
private ImageView mImage;
public ColorOptionsView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.Options, 0, 0);
String titleText = a.getString(R.styleable.Options_titleText);
int valueColor = a.getColor(R.styleable.Options_valueColor, android.R.color.holo_blue_light);
a.recycle();
// more stuff
}
}
تمرین: ساخت و پیاده سازی یک view ترکیبی
ایجاد پروژه
یک پروژه ی جدید اندروید با داده ها و مقادیر زیر ایجاد نمایید.
تعریف و استفاده از attribute های جدید
یک فایل حامل attribute های جدید به نام attr.xml را داخل پوشه ی res/values ایجاد نمایید.
محتوای فایل layout ای که توسط کلاس activity برای نمایش در UI فراخوانی می شود را به صورت زیر ویرایش نمایید.
ساخت View ترکیبی
فایل layout زیر به نام view_color_options را برای view اختصاصی خود ایجاد نمایید.
View اختصاصی خود را به صورت زیر پیاده سازی نمایید.
package com.vogella.android.customview.compoundview;
import com.vogella.android.view.compoundview.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
public class ColorOptionsView extends LinearLayout {
private View mValue;
private ImageView mImage;
public ColorOptionsView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ColorOptionsView, 0, 0);
String titleText = a.getString(R.styleable.ColorOptionsView_titleText);
int valueColor = a.getColor(R.styleable.ColorOptionsView_valueColor, android.R.color.holo_blue_light);
a.recycle();
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.CENTER_VERTICAL);
LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.view_color_options, this, true);
TextView title = (TextView) getChildAt(0);
title.setText(titleText);
mValue = getChildAt(1);
mValue.setBackgroundColor(valueColor);
mImage = (ImageView) getChildAt(2);
}
public ColorOptionsView(Context context) {
this(context, null);
}
public void setValueColor(int color) {
mValue.setBackgroundColor(color);
}
public void setImageVisible(boolean visible) {
mImage.setVisibility(visible ? View.VISIBLE : View.GONE);
}
}
تنظیم و ویرایش activity
دنه ی activity را به صورت زیر ویرایش نموده و سپس اپلیکیشن خود را اجرا نمایید.
package com.vogella.android.customview.compoundview;
import com.vogella.android.view.compoundview.R;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
public void onClicked(View view) {
String text = view.getId() == R.id.view1 ? "Background" : "Foreground";
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
}
}
اپلیکیشن در زمان اجرا می بایست ظاهری مشابه زیر داشته باشد.
Canvas API (توابع مرتبط با کلاس Canvas)
شرح مفهوم Canvas API
Canvas API به شما اجازه می دهد تا افکت های دیداری و گرافیکی منحصر بفردی خلق نمایید.
کلاس Canvas تعدادی تابع جهت ترسیم اشکال در اختیار دارد. برای انجام عملیات ترسیم، توسعه دهنده به 4 مولفه ی ابتدایی احتیاج دارد: 1. یک bitmap جهت میزبانی پیکسل ها 2. یک Canvas برای نگه داشتن توابع draw (جهت ترسیم و نوشتن در bitmap) 3. یک کلاس جهت ترسیم مانند Rect، Path، text، Bitmap 4. یک کلاس paint به منظور تعریف رنگ ها و style های المان های گرافیکی.
در واقع شما بر روی سطح Bitmap اشکال را ترسیم می کنید، توابع ترسیم اشکال را از کلاس Canvas وام گرفته و در نهایت با استفاده از کلاس Paint رنگ و استایل اشکال مورد نظر بر روی سطح canvas را تعیین می نمایید.
کلاس Canvas
آبجکت Canvas آن فایل تصویری پیکسلی یا bitmap ای که اشکال بر روی آن ترسیم می شوند را در برمی گیرد. علاوه بر آن توابعی را ویژه ی تنظیم رنگ (drawARGB())، ترسیم عکس پیکسلی (drawBitmap() )، درج متن (drawText())، ترسیم مستطیل با گوشه های گرد (drawRoundRect()) و غیره ... در اختیار برنامه نویس قرار می دهد.
کلاس Paint
برای تنظیم رنگ، فونت و غیره ... بر روی آبجکت Canvas به یک آبجکت از جنس کلاس Paint نیاز دارید.
کلاس Paint در واقع این قابلیت را در اختیار شما قرار می دهد تا رنگ، فونت و برخی از افکت ها را برای انجام عملیات ترسیم مشخص نمایید.
متد setStyle() به شما اجازه می دهد تا به سیستم اعلان نمایید آیا تنها outline و خط پیرامون ( Paint.Style.STROKE ) باید ترسیم شود یا تنها بخش رنگ شده (Paint.Style.FILL) و یا هر دو بخش مزبور.
برای تنظیم کانال آلفا Paint کافی است متد setAlpha() را فراخوانی نمایید.
جهت تنظیم رنگ های بیشتر برای آبجکت Paint () می توانید از Shaders استفاده نمایید.
Shader
آبجکت shader این امکان را فراهم می کند تا برای آبجکت Paint محتوایی که باید ترسیم شوند را مشخص نمایید. برای مثال، می توانید با استفاده از BitmapShader که کلاس مشتق از Shader هست، یک عکس پیکسلی (Bitmap) تعریف نمایید و سپس در آن شکل هندسی دلخواه را ترسیم نمایید. بدین وسیله شما می توانید به عنوان نمونه، تصویری با گوشه های گرد بکشید. کافی است یک BitmapShader برای آبجکت Paint تعریف نموده و با فراخوانی تابع drawRoundRect()، شکل مسطتیل را با گوشه های گرد ترسیم نمایید.
محیط اندروید (platform) تعدادی کلاس مشتق از Shader جهت ترسیم و پیاده سازی شیب رنگ ارائه می دهد. این کلاس ها عبارتند از: LinearGradient، RadialGradient و SweepGradient.
به منظور استفاده از Shader لازم است آن را از طریق متد setShader() به آبجکت Paint انتساب دهید.
در صورتی که ناحیه ی پر شده بزرگ تر از Shaders هست، آنگاه می توانید از طریق Shader tile model مشخص کنید که باقی ناحیه ی مربوطه چگونه پر شود. به طور مثال، ثابت Shader.TileMode.CLAMP به سیستم دستور می دهد که رنگ مربوط به گوشه ها (edge corner) بایستی برای پر کردن فضای اضافی مورد استفاده قرار گیرد. در حالی که ثابت Shader.TileMode.MIRROR اعلان می کند که تصویر مورد نظر بایستی قرینه سازی شده و Shader.TileMode.REPEAT نیز به اندروید اعلان می کند که عکسی را تکرار کند.
ذخیره ی دائمی و ماندگارسازی داده های view
اغلب view های متعارف اندروید قادرند داده های مربوط به وضعیت خود را نگه دارند. این داده ها سپس توسط سیستم به صورت دائمی ذخیره می شوند. سیستم اندروید جهت ذخیره ی داده های مربوط به وضعیت view متد onSaveInstanceState() را صدا می زند و جهت ذخیره ی دائمی و بازگردانی این داده ها متد onRestoreInstanceState(Parcable) را فراخوانی می کند.
برای ماندگار سازی داده ها، مرسوم است که کلاس View.BasedSavedState را داخل view به صورت یک کلاس درونی static پیاده سازی نمایید.
متد View.BasedSavedState یک قرارداد شخصی برای نگه داری داده ها می باشد .
اندروید یک view را بر اساس ID آن در فایل layout میزبان پیدا کرده و سپس آبجکت Bundle را (که حاوی داده های مربوط به وضعیت view می باشد) به view ارسال می کند. view با داده های کپسوله شده در این آبجکت وضعیت خود را بازگردانی می نماید.
شما می بایست اطلاعات UI را دقیقا در همان وضعیتی که کاربر آن را ترک کرده، نگه دارید و بعد مجددا آن را بازگردانی نمایید. برای مثال موقعیت نوار پیمایش یا انتخاب کاربر را به حالت قبلی آن بازگردانید.