عندما تحتاج إلى عرض صور ثابتة في تطبيقك، يمكنك استخدام فئة Drawable وفئاتها الفرعية لرسم الأشكال والصور. Drawable هو تجريد عام
لشيء يمكن رسمه. تساعد الفئات الفرعية المختلفة في سيناريوهات صور معيّنة، ويمكنك توسيعها لتحديد عناصر قابلة للرسم خاصة بك تتصرف بطرق فريدة.
هناك طريقتان لتحديد Drawable وإنشائه بالإضافة إلى استخدام الدوال الإنشائية للفئة:
- تضخيم مورد صورة (ملف صورة نقطية) تم حفظه في مشروعك
- تضخيم مورد XML يحدّد خصائص العنصر القابل للرسم
ملاحظة: قد تفضّل بدلاً من ذلك استخدام رسم متّجهي، وهو يحدّد صورة باستخدام مجموعة من النقاط والخطوط والمنحنيات، بالإضافة إلى معلومات الألوان المرتبطة بها. يتيح ذلك إمكانية تغيير حجم الرسومات المتجهة لتناسب أحجامًا مختلفة بدون فقدان الجودة. لمزيد من المعلومات، يُرجى الاطّلاع على نظرة عامة على الرسومات المتجهة.
إنشاء عناصر قابلة للرسم من صور الموارد
يمكنك إضافة رسومات إلى تطبيقك من خلال الإشارة إلى ملف صورة من موارد مشروعك. أنواع الملفات المتوافقة هي PNG (مفضَّل) وJPG (مقبول) وGIF (غير مستحسَن). تُعد رموز التطبيقات وشعاراتها والرسومات الأخرى، مثل تلك المستخدَمة في الألعاب، مناسبة تمامًا لهذه التقنية.
لاستخدام مصدر صورة، أضِف ملفك إلى دليل res/drawable/ في مشروعك. بعد الدخول إلى مشروعك، يمكنك الرجوع إلى مورد الصورة من الرمز البرمجي أو تنسيق XML. في كلتا الحالتين، تتم الإشارة إلى الملف باستخدام معرّف المورد، وهو اسم الملف بدون امتداد نوع الملف. على سبيل المثال، يمكنك الإشارة إلى my_image.png باسم my_image.
ملاحظة: يمكن أن يتم تحسين موارد الصور الموضوعة في الدليل res/drawable/ تلقائيًا باستخدام أداة aapt أثناء عملية التصميم من خلال ضغط الصور بدون فقدان البيانات. على سبيل المثال، يمكن تحويل ملف PNG بألوان حقيقية لا يتطلّب أكثر من 256 لونًا إلى ملف PNG بـ 8 بت مع لوحة ألوان. ويؤدي ذلك إلى إنشاء صورة
بالجودة نفسها ولكنها تتطلّب مساحة أقل من الذاكرة. نتيجةً لذلك، يمكن أن تتغيّر ملفات الصور الثنائية
الموجودة في هذا الدليل في مدّة التصميم. إذا كنت تخطط لقراءة صورة كتدفق بتات من أجل تحويلها إلى صورة نقطية، ضَع صورك في المجلد res/raw/ بدلاً من ذلك، حيث لا تعدّلها الأداة aapt.
يوضّح مقتطف الرمز البرمجي التالي كيفية إنشاء ImageView يستخدم صورة تم إنشاؤها من مورد قابل للرسم وإضافتها إلى التصميم:
Kotlin
private lateinit var constraintLayout: ConstraintLayout override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Instantiate an ImageView and define its properties val i = ImageView(this).apply { setImageResource(R.drawable.my_image) contentDescription = resources.getString(R.string.my_image_desc) // set the ImageView bounds to match the Drawable's dimensions adjustViewBounds = true layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) } // Create a ConstraintLayout in which to add the ImageView constraintLayout = ConstraintLayout(this).apply { // Add the ImageView to the layout. addView(i) } // Set the layout as the content view. setContentView(constraintLayout) }
Java
ConstraintLayout constraintLayout; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Create a ConstraintLayout in which to add the ImageView constraintLayout = new ConstraintLayout(this); // Instantiate an ImageView and define its properties ImageView i = new ImageView(this); i.setImageResource(R.drawable.my_image); i.setContentDescription(getResources().getString(R.string.my_image_desc)); // set the ImageView bounds to match the Drawable's dimensions i.setAdjustViewBounds(true); i.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); // Add the ImageView to the layout and set the layout as the content view. constraintLayout.addView(i); setContentView(constraintLayout); }
في حالات أخرى، قد تحتاج إلى التعامل مع مورد الصورة كعنصر Drawable، كما هو موضّح في المثال التالي:
Kotlin
val myImage: Drawable = ResourcesCompat.getDrawable(context.resources, R.drawable.my_image, null)
Java
Resources res = context.getResources(); Drawable myImage = ResourcesCompat.getDrawable(res, R.drawable.my_image, null);
تحذير: يمكن لكل مورد فريد في مشروعك الاحتفاظ بحالة واحدة فقط، بغض النظر عن عدد العناصر المختلفة التي تنشئها له. على سبيل المثال، إذا أنشأت مثيلَين من كائن Drawable من مصدر الصورة نفسه وغيّرت إحدى السمات (مثل قناة ألفا) لأحد الكائنَين، سيؤثّر ذلك أيضًا في الكائن الآخر. عند التعامل مع عدة مثيلات لمورد صورة، بدلاً من تحويل عنصر Drawable مباشرةً، يجب تنفيذ رسوم متحركة
بينيتينغ.
يوضّح مقتطف XML أدناه كيفية إضافة مورد قابل للرسم إلى ImageView في تنسيق XML:
<ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/my_image" android:contentDescription="@string/my_image_desc" />
لمزيد من المعلومات حول استخدام موارد المشاريع، يُرجى الاطّلاع على الموارد ومواد العرض.
ملاحظة: عند استخدام موارد الصور كمصدر للعناصر القابلة للرسم، احرص على أن تكون الصور بالحجم المناسب لمختلف كثافات البكسل. إذا لم تكن الصور صحيحة، سيتم تكبيرها لتناسب الشاشة، ما قد يؤدي إلى ظهور تشوّهات في الرسومات القابلة للرسم. لمزيد من المعلومات، يُرجى الاطّلاع على مقالة توافُق التطبيق مع كثافات البكسل المختلفة.
إنشاء عناصر قابلة للرسم من موارد XML
إذا كان هناك عنصر Drawable
تريد إنشاءه، ولا يعتمد في البداية على المتغيرات المحدّدة بواسطة الرمز Drawable أو تفاعل المستخدم، فإنّ تحديد Drawable في XML هو خيار جيد. حتى إذا كنت تتوقّع أن يغيّر Drawable خصائصه أثناء تفاعل المستخدم مع تطبيقك، عليك التفكير في تحديد العنصر في XML، لأنّه يمكنك تعديل الخصائص بعد إنشاء مثيل للعنصر.
بعد تحديد Drawable في XML، احفظ الملف في الدليل res/drawable/ الخاص بمشروعك. يوضّح المثال التالي رمز XML الذي يحدّد مورد TransitionDrawable
الذي يرث من Drawable:
<!-- res/drawable/expand_collapse.xml --> <transition xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/image_expand"/> <item android:drawable="@drawable/image_collapse"/> </transition>
بعد ذلك، استرجِع العنصر وأنشئ مثيلاً له من خلال استدعاء
Resources#getDrawable()
وتمرير معرّف المورد لملف XML. يمكن تحديد أي فئة فرعية من Drawable تتوافق مع طريقة inflate() في ملف XML وإنشاؤها من خلال تطبيقك.
يستخدم كل فئة قابلة للرسم تتيح توسيع XML سمات XML محددة
تساعد في تحديد خصائص العنصر. ينشئ الرمز التالي مثيلاً من
TransitionDrawable
ويضبطه كمحتوى لكائن
ImageView:
Kotlin
val transition= ResourcesCompat.getDrawable( context.resources, R.drawable.expand_collapse, null ) as TransitionDrawable val image: ImageView = findViewById(R.id.toggle_image) image.setImageDrawable(transition) // Description of the initial state that the drawable represents. image.contentDescription = resources.getString(R.string.collapsed) // Then you can call the TransitionDrawable object's methods. transition.startTransition(1000) // After the transition is complete, change the image's content description // to reflect the new state.
Java
Resources res = context.getResources(); TransitionDrawable transition = (TransitionDrawable) ResourcesCompat.getDrawable(res, R.drawable.expand_collapse, null); ImageView image = (ImageView) findViewById(R.id.toggle_image); image.setImageDrawable(transition); // Description of the initial state that the drawable represents. image.setContentDescription(getResources().getString(R.string.collapsed)); // Then you can call the TransitionDrawable object's methods. transition.startTransition(1000); // After the transition is complete, change the image's content description // to reflect the new state.
لمزيد من المعلومات حول سمات XML المتوافقة، يُرجى الرجوع إلى الفئات المذكورة أعلاه.
عناصر قابلة للرسم على شكل أشكال
يمكن أن يكون كائن ShapeDrawable خيارًا جيدًا
عندما تريد رسم رسم ثنائي الأبعاد بشكل ديناميكي. يمكنك رسم أشكال أساسية بشكل آلي على عنصر ShapeDrawable وتطبيق الأنماط التي يحتاجها تطبيقك.
ShapeDrawable هو فئة فرعية من Drawable. لهذا السبب، يمكنك استخدام ShapeDrawable في أي مكان يُتوقّع فيه استخدام Drawable. على سبيل المثال، يمكنك استخدام كائن ShapeDrawable لضبط خلفية عرض من خلال تمريره إلى طريقة setBackgroundDrawable() الخاصة بالعرض. يمكنك أيضًا رسم الشكل كطريقة عرض مخصّصة خاصة به وإضافته إلى تنسيق في تطبيقك.
بما أنّ ShapeDrawable يتضمّن طريقة draw() خاصة به، يمكنك إنشاء فئة فرعية من View ترسم العنصر ShapeDrawable أثناء الحدث onDraw()، كما هو موضّح في مثال الرمز البرمجي التالي:
Kotlin
class CustomDrawableView(context: Context) : View(context) { private val drawable: ShapeDrawable = run { val x = 10 val y = 10 val width = 300 val height = 50 contentDescription = context.resources.getString(R.string.my_view_desc) ShapeDrawable(OvalShape()).apply { // If the color isn't set, the shape uses black as the default. paint.color = 0xff74AC23.toInt() // If the bounds aren't set, the shape can't be drawn. setBounds(x, y, x + width, y + height) } } override fun onDraw(canvas: Canvas) { drawable.draw(canvas) } }
Java
public class CustomDrawableView extends View { private ShapeDrawable drawable; public CustomDrawableView(Context context) { super(context); int x = 10; int y = 10; int width = 300; int height = 50; setContentDescription(context.getResources().getString( R.string.my_view_desc)); drawable = new ShapeDrawable(new OvalShape()); // If the color isn't set, the shape uses black as the default. drawable.getPaint().setColor(0xff74AC23); // If the bounds aren't set, the shape can't be drawn. drawable.setBounds(x, y, x + width, y + height); } protected void onDraw(Canvas canvas) { drawable.draw(canvas); } }
يمكنك استخدام الفئة CustomDrawableView في عينة التعليمات البرمجية أعلاه كما تستخدم أي عرض مخصّص آخر. على سبيل المثال، يمكنك إضافته آليًا إلى نشاط في تطبيقك، كما هو موضّح في المثال التالي:
Kotlin
private lateinit var customDrawableView: CustomDrawableView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) customDrawableView = CustomDrawableView(this) setContentView(customDrawableView) }
Java
CustomDrawableView customDrawableView; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); customDrawableView = new CustomDrawableView(this); setContentView(customDrawableView); }
إذا أردت استخدام العرض المخصّص في تنسيق XML بدلاً من ذلك، يجب أن تتجاوز الفئة CustomDrawableView الدالة الإنشائية View(Context, AttributeSet)، التي يتم استدعاؤها عند إنشاء الفئة من XML. يوضّح المثال التالي كيفية تعريف
CustomDrawableView في تنسيق XML:
<com.example.shapedrawable.CustomDrawableView android:layout_width="fill_parent" android:layout_height="wrap_content" />
يتيح لك الصف ShapeDrawable، مثل العديد من أنواع العناصر القابلة للرسم الأخرى في الحزمة android.graphics.drawable، تحديد خصائص مختلفة للعنصر باستخدام طرق عامة. تتضمّن بعض الأمثلة على الخصائص التي قد تحتاج إلى تعديلها شفافية قناة ألفا وفلتر الألوان والتمويه والعتامة واللون.
يمكنك أيضًا تحديد أشكال رسومات أساسية قابلة للرسم باستخدام موارد XML. لمزيد من المعلومات، يُرجى الاطّلاع على عنصر قابل للرسم على شكل في أنواع عناصر الموارد القابلة للرسم.
NinePatch drawables
NinePatchDrawable الرسم هو
صورة نقطية قابلة للتمديد يمكنك استخدامها كخلفية لعرض. يعمل نظام التشغيل Android على تغيير حجم الرسم تلقائيًا لاستيعاب محتوى العرض. من الأمثلة على استخدام صورة NinePatch الخلفية التي تستخدمها أزرار Android العادية، إذ يجب أن تتمدد الأزرار لاستيعاب سلاسل بأطوال مختلفة. رسومات NinePatch هي صور PNG عادية تتضمّن حدًا إضافيًا بعرض بكسل واحد.
يجب حفظه باستخدام الإضافة 9.png في الدليل res/drawable/ الخاص بمشروعك.
استخدِم الحدود لتحديد المساحات القابلة للتمديد والثابتة في الصورة. يمكنك الإشارة إلى قسم قابل للتمديد من خلال رسم خط أسود واحد (أو أكثر) بعرض بكسل واحد في الجزء الأيمن والعلوي من الحد (يجب أن تكون وحدات البكسل الأخرى للحد شفافة تمامًا أو بيضاء). يمكنك إضافة أي عدد تريده من الأقسام القابلة للتوسيع. ويظل الحجم النسبي للأقسام القابلة للتمديد كما هو، وبالتالي يظل القسم الأكبر هو الأكبر دائمًا.
يمكنك أيضًا تحديد قسم اختياري قابل للرسم من الصورة (بشكل فعّال، أسطر المساحة المتروكة) من خلال رسم خط على اليسار وخط في الأسفل. إذا كان عنصر View يضبط صورة NinePatch كخلفية له ثم يحدّد نص العرض، سيتم تمديده بحيث يشغل كل النص المساحة المحدّدة فقط بواسطة الخطوط اليمنى والسفلية (إذا كانت مضمّنة).
إذا لم يتم تضمين خطوط المساحة المتروكة، يستخدم نظام التشغيل Android الخطوط اليمنى والعلوية لتحديد مساحة الرسم هذه.
لتوضيح الفرق بين الخطوط، يحدّد الخطان الأيمن والأعلى وحدات البكسل المسموح بتكرارها في الصورة من أجل توسيعها. يحدّد الخطان السفلي والأيسر المساحة النسبية داخل الصورة التي يُسمح للمحتوى الخاص بالعرض بشغلها.
يعرض الشكل 1 مثالاً على رسم NinePatch مستخدَم لتحديد زر:
الشكل 1: مثال على رسم NinePatch يحدد زرًا
يحدّد رسم NinePatch البياني مساحة واحدة قابلة للتمديد باستخدام الخطوط اليسرى والعلوية، ومساحة قابلة للرسم باستخدام الخطوط السفلية واليمنى. في الصورة العلوية، تحدّد الخطوط الرمادية المنقّطة المناطق التي تم تكرارها في الصورة لتوسيعها. يحدّد المستطيل الوردي في الصورة السفلية المنطقة التي يُسمح فيها بعرض محتوى النافذة. إذا لم يكن المحتوى مناسبًا لهذه المنطقة، يتم تمديد الصورة لتلائمها.
توفّر أداة Draw 9-patch طريقة مفيدة للغاية لإنشاء صور NinePatch، وذلك باستخدام محرِّر رسومات WYSIWYG. بل إنّها تصدر تحذيرات إذا كانت المنطقة التي حدّدتها للمساحة القابلة للتمديد معرّضة لخطر إنتاج تشوّهات في الرسم نتيجة لتكرار وحدات البكسل.
يوضّح نموذج XML لتصميم التنسيق التالي كيفية إضافة رسم NinePatch إلى زرَّين. يتم حفظ صورة NinePatch في
res/drawable/my_button_background.9.png.
<Button android:id="@+id/tiny" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerInParent="true" android:text="Tiny" android:textSize="8sp" android:background="@drawable/my_button_background"/> <Button android:id="@+id/big" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerInParent="true" android:text="Biiiiiiig text!" android:textSize="30sp" android:background="@drawable/my_button_background"/>
يُرجى العِلم أنّه تم ضبط السمتَين layout_width وlayout_height على wrap_content لجعل الزر يتناسب بشكل أنيق مع النص.
يعرض الشكل 2 الزرَّين المعروضَين من ملف XML وصورة NinePatch الموضّحة أعلاه. لاحظ كيف يختلف عرض الزر وارتفاعه باختلاف النص، وكيف تمتد صورة الخلفية لتلائم النص.
الشكل 2: أزرار معروضة باستخدام مصدر XML ورسم NinePatch
العناصر القابلة للرسم المخصّصة
عندما تريد إنشاء بعض الرسومات المخصّصة، يمكنك إجراء ذلك من خلال توسيع الفئة Drawable (أو أي من فئاتها الفرعية).
الطريقة الأكثر أهمية التي يجب تنفيذها هي draw(Canvas)
لأنّها توفّر العنصر Canvas الذي يجب استخدامه لتقديم
تعليمات الرسم.
يوضّح الرمز التالي فئة فرعية بسيطة من Drawable
ترسم دائرة:
Kotlin
class MyDrawable : Drawable() { private val redPaint: Paint = Paint().apply { setARGB(255, 255, 0, 0) } override fun draw(canvas: Canvas) { // Get the drawable's bounds val width: Int = bounds.width() val height: Int = bounds.height() val radius: Float = Math.min(width, height).toFloat() / 2f // Draw a red circle in the center canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, redPaint) } override fun setAlpha(alpha: Int) { // This method is required } override fun setColorFilter(colorFilter: ColorFilter?) { // This method is required } override fun getOpacity(): Int = // Must be PixelFormat.UNKNOWN, TRANSLUCENT, TRANSPARENT, or OPAQUE PixelFormat.OPAQUE }
Java
public class MyDrawable extends Drawable { private final Paint redPaint; public MyDrawable() { // Set up color and text size redPaint = new Paint(); redPaint.setARGB(255, 255, 0, 0); } @Override public void draw(Canvas canvas) { // Get the drawable's bounds int width = getBounds().width(); int height = getBounds().height(); float radius = Math.min(width, height) / 2; // Draw a red circle in the center canvas.drawCircle(width/2, height/2, radius, redPaint); } @Override public void setAlpha(int alpha) { // This method is required } @Override public void setColorFilter(ColorFilter colorFilter) { // This method is required } @Override public int getOpacity() { // Must be PixelFormat.UNKNOWN, TRANSLUCENT, TRANSPARENT, or OPAQUE return PixelFormat.OPAQUE; } }
بعد ذلك، يمكنك إضافة العنصر القابل للرسم إلى أي مكان تريده، مثل ImageView كما هو موضّح هنا:
Kotlin
val myDrawing = MyDrawable() val image: ImageView = findViewById(R.id.imageView) image.setImageDrawable(myDrawing) image.contentDescription = resources.getString(R.string.my_image_desc)
Java
MyDrawable mydrawing = new MyDrawable(); ImageView image = findViewById(R.id.imageView); image.setImageDrawable(mydrawing); image.setContentDescription(getResources().getString(R.string.my_image_desc));
في نظام التشغيل Android 7.0 (المستوى 24 لواجهة برمجة التطبيقات) والإصدارات الأحدث، يمكنك أيضًا تحديد مثيلات للعنصر القابل للرسم المخصّص باستخدام XML بالطرق التالية:
- استخدام اسم الفئة المؤهَّل بالكامل كاسم لعنصر XML في هذا النهج، يجب أن تكون فئة العنصر القابل للرسم المخصّص فئة عامة ذات مستوى أعلى:
<com.myapp.MyDrawable xmlns:android="http://schemas.android.com/apk/res/android" android:color="#ffff0000" />
- استخدام
drawableكاسم علامة XML وتحديد اسم الفئة المؤهَّل بالكامل من سمة الفئة يمكن استخدام هذا الأسلوب لكل من الفئات العامة ذات المستوى الأعلى والفئات الثابتة العامة الداخلية:<drawable xmlns:android="http://schemas.android.com/apk/res/android" class="com.myapp.MyTopLevelClass$MyDrawable" android:color="#ffff0000" />
إضافة لون إلى العناصر القابلة للرسم
في الإصدار 5.0 من نظام التشغيل Android (مستوى واجهة برمجة التطبيقات 21) والإصدارات الأحدث، يمكنك تلوين الصور النقطية وصور Nine-patch المحدّدة كأقنعة ألفا. يمكنك تلوينها باستخدام موارد الألوان أو سمات المظهر التي يتم تحويلها إلى موارد ألوان (مثل ?android:attr/colorPrimary). وعادةً، يتم إنشاء مواد العرض هذه مرة واحدة فقط وتلوينها تلقائيًا لتتطابق مع المظهر.
يمكنك تطبيق لون على الكائنات BitmapDrawable أو NinePatchDrawable أو VectorDrawable باستخدام الطريقة setTint(). يمكنك أيضًا ضبط لون ووضع التظليل في تنسيقاتك باستخدام السمتَين android:tint وandroid:tintMode.
استخراج الألوان البارزة من صورة
تتضمّن "مكتبة دعم Android" الفئة Palette التي تتيح لك استخراج الألوان البارزة من صورة.
يمكنك تحميل عناصر قابلة للرسم كـ Bitmap وتمريرها إلى Palette للوصول إلى ألوانها.
لمزيد من المعلومات، يُرجى الاطّلاع على اختيار الألوان باستخدام Palette API.