وقتی نیاز دارید تصاویر ثابت را در برنامه خود نمایش دهید، میتوانید از کلاس Drawable و زیرکلاسهای آن برای رسم اشکال و تصاویر استفاده کنید. Drawable یک انتزاع کلی برای چیزی است که میتواند رسم شود . زیرکلاسهای مختلف به سناریوهای خاص تصویر کمک میکنند و میتوانید آنها را برای تعریف اشیاء رسمپذیر خود که به روشهای منحصر به فردی رفتار میکنند، گسترش دهید.
علاوه بر استفاده از سازندههای کلاس، دو راه برای تعریف و نمونهسازی یک Drawable وجود دارد:
- یک منبع تصویر (یک فایل بیتمپ) ذخیره شده در پروژه خود را inflate کنید.
- یک منبع XML که ویژگیهای ترسیمپذیر را تعریف میکند، inflate کنید.
توجه: در عوض، میتوانید از یک vector drawable استفاده کنید که تصویری را با مجموعهای از نقاط، خطوط و منحنیها، همراه با اطلاعات رنگی مرتبط، تعریف میکند. این امر به vector drawableها اجازه میدهد تا بدون از دست دادن کیفیت، برای اندازههای مختلف مقیاسبندی شوند. برای اطلاعات بیشتر، به نمای کلی vector drawableها مراجعه کنید.
ایجاد drawableها از تصاویر منابع
شما میتوانید با ارجاع به یک فایل تصویری از منابع پروژه خود، تصاویر گرافیکی را به برنامه خود اضافه کنید. انواع فایلهای پشتیبانی شده PNG (ترجیحاً)، JPG (قابل قبول) و GIF (توصیه نمیشود) هستند. آیکونهای برنامه، لوگوها و سایر گرافیکها، مانند مواردی که در بازیها استفاده میشوند، برای این تکنیک بسیار مناسب هستند.
برای استفاده از یک منبع تصویر، فایل خود را به دایرکتوری res/drawable/ پروژه خود اضافه کنید. پس از ورود به پروژه، میتوانید از کد یا طرحبندی XML خود به منبع تصویر ارجاع دهید. در هر صورت، با استفاده از شناسه منبع، که نام فایل بدون پسوند نوع فایل است، به آن ارجاع داده میشود. به عنوان مثال، به my_image.png به صورت my_image اشاره کنید.
توجه: منابع تصویری که در دایرکتوری res/drawable/ قرار میگیرند، ممکن است در طول فرآیند ساخت، به طور خودکار توسط ابزار aapt با فشردهسازی بدون اتلاف تصویر بهینهسازی شوند. به عنوان مثال، یک PNG با رنگ واقعی که به بیش از ۲۵۶ رنگ نیاز ندارد، میتواند به یک PNG 8 بیتی با پالت رنگ تبدیل شود. این منجر به تصویری با کیفیت برابر میشود اما به حافظه کمتری نیاز دارد. در نتیجه، فایلهای باینری تصویر قرار گرفته در این دایرکتوری میتوانند در زمان ساخت تغییر کنند. اگر قصد دارید یک تصویر را به عنوان یک جریان بیتی بخوانید تا آن را به یک نقشه بیتی تبدیل کنید، تصاویر خود را در پوشه res/raw/ قرار دهید، جایی که ابزار aapt آنها را تغییر نمیدهد.
قطعه کد زیر نحوه ساخت یک ImageView را نشان میدهد که از تصویری که از یک منبع drawable ایجاد شده استفاده میکند و آن را به طرحبندی اضافه میکند:
کاتلین
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) }
جاوا
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 مدیریت کنید، همانطور که در مثال زیر نشان داده شده است:
کاتلین
val myImage: Drawable = ResourcesCompat.getDrawable(context.resources, R.drawable.my_image, null)
جاوا
Resources res = context.getResources(); Drawable myImage = ResourcesCompat.getDrawable(res, R.drawable.my_image, null);
هشدار: هر منبع منحصر به فرد در پروژه شما، صرف نظر از تعداد اشیاء مختلفی که برای آن نمونهسازی میکنید، تنها میتواند یک حالت را حفظ کند. برای مثال، اگر دو شیء Drawable را از یک منبع تصویر مشابه نمونهسازی کنید و یک ویژگی (مانند آلفا) را برای یک شیء تغییر دهید، این تغییر بر شیء دیگر نیز تأثیر میگذارد. هنگام کار با چندین نمونه از یک منبع تصویر، به جای تغییر مستقیم شیء Drawable ، باید یک انیمیشن tween انجام دهید.
قطعه کد XML زیر نحوه اضافه کردن یک منبع drawable به ImageView در طرحبندی XML را نشان میدهد:
<ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/my_image" android:contentDescription="@string/my_image_desc" />
برای اطلاعات بیشتر در مورد استفاده از منابع پروژه، به بخش منابع و داراییها مراجعه کنید.
نکته: هنگام استفاده از منابع تصویری به عنوان منبع فایلهای ترسیمی خود، مطمئن شوید که تصاویر برای تراکم پیکسلهای مختلف اندازه مناسبی دارند. اگر تصاویر صحیح نباشند، برای تناسب با اندازه، بزرگنمایی میشوند که میتواند باعث ایجاد اشکال در فایلهای ترسیمی شما شود. برای اطلاعات بیشتر، بخش «پشتیبانی از تراکم پیکسلهای مختلف» را مطالعه کنید.
ایجاد drawableها از منابع XML
اگر میخواهید یک شیء 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 تعریف شده و توسط برنامه شما نمونهسازی شود.
هر کلاس drawable که از XML Inflation پشتیبانی میکند، از ویژگیهای خاص XML استفاده میکند که به تعریف ویژگیهای شیء کمک میکند. کد زیر TransitionDrawable را نمونهسازی کرده و آن را به عنوان محتوای یک شیء ImageView تنظیم میکند:
کاتلین
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.
جاوا
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 است. به همین دلیل، میتوانید هر جا که Drawable مورد انتظار است، از ShapeDrawable استفاده کنید. برای مثال، میتوانید با ارسال شیء ShapeDrawable به متد setBackgroundDrawable() از view، پسزمینه آن را تنظیم کنید. همچنین میتوانید شکل خود را به عنوان view سفارشی خودش رسم کنید و آن را به یک layout در برنامه خود اضافه کنید.
از آنجا که ShapeDrawable متد draw() مخصوص به خود را دارد، میتوانید یک زیرکلاس از View ایجاد کنید که شیء ShapeDrawable را در طول رویداد onDraw() رسم کند، همانطور که در مثال کد زیر نشان داده شده است:
کاتلین
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) } }
جاوا
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 در نمونه کد بالا مانند هر نمای سفارشی دیگری استفاده کنید. برای مثال، میتوانید آن را به صورت برنامهنویسی شده به یک activity در برنامه خود اضافه کنید، همانطور که در مثال زیر نشان داده شده است:
کاتلین
private lateinit var customDrawableView: CustomDrawableView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) customDrawableView = CustomDrawableView(this) setContentView(customDrawableView) }
جاوا
CustomDrawableView customDrawableView; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); customDrawableView = new CustomDrawableView(this); setContentView(customDrawableView); }
اگر میخواهید از نمای سفارشی در طرحبندی XML استفاده کنید، کلاس CustomDrawableView باید سازندهی View(Context, AttributeSet) را که هنگام inflate شدن کلاس از XML فراخوانی میشود، لغو کند. مثال زیر نحوهی تعریف CustomDrawableView در طرحبندی XML را نشان میدهد:
<com.example.shapedrawable.CustomDrawableView android:layout_width="fill_parent" android:layout_height="wrap_content" />
کلاس ShapeDrawable ، مانند بسیاری از انواع ترسیمپذیر دیگر در پکیج android.graphics.drawable ، به شما امکان میدهد ویژگیهای مختلف شیء را با استفاده از متدهای عمومی تعریف کنید. برخی از ویژگیهای نمونه که ممکن است بخواهید تنظیم کنید شامل شفافیت آلفا، فیلتر رنگ، لرزش، کدورت و رنگ است.
همچنین میتوانید اشکال اولیهی قابل ترسیم را با استفاده از منابع XML تعریف کنید. برای اطلاعات بیشتر، به بخش انواع منابع Drawable ، به Shape drawable مراجعه کنید.
ترسیمپذیرهای NinePatch
یک تصویر گرافیکی NinePatchDrawable یک تصویر بیتمپ قابل کشش است که میتوانید از آن به عنوان پسزمینه یک نما استفاده کنید. اندروید به طور خودکار اندازه تصویر را تغییر میدهد تا با محتوای نما سازگار شود. یک نمونه از کاربرد تصویر NinePatch، پسزمینهای است که توسط دکمههای استاندارد اندروید استفاده میشود - دکمهها باید کشیده شوند تا رشتههایی با طولهای مختلف را در خود جای دهند. یک تصویر گرافیکی NinePatch یک تصویر PNG استاندارد است که شامل یک حاشیه ۱ پیکسلی اضافی است. باید با پسوند 9.png در دایرکتوری res/drawable/ پروژه شما ذخیره شود.
از حاشیه برای تعریف نواحی قابل کشش و ثابت تصویر استفاده کنید. شما میتوانید با کشیدن یک (یا چند) خط سیاه به عرض ۱ پیکسل در قسمت چپ و بالای حاشیه، یک بخش قابل کشش را مشخص کنید (سایر پیکسلهای حاشیه باید کاملاً شفاف یا سفید باشند). میتوانید به تعداد دلخواه بخش قابل کشش داشته باشید. اندازه نسبی بخشهای قابل کشش ثابت میماند، بنابراین بزرگترین بخش همیشه بزرگترین بخش باقی میماند.
همچنین میتوانید با کشیدن یک خط در سمت راست و یک خط در پایین، یک بخش قابل ترسیم اختیاری از تصویر (در واقع خطوط فاصلهگذاری) تعریف کنید. اگر یک شیء View ، گرافیک NinePatch را به عنوان پسزمینه خود قرار دهد و سپس متن view را مشخص کند، خود را طوری کشیده میکند که تمام متن فقط ناحیهای را که توسط خطوط راست و پایین (در صورت وجود) مشخص شده است، اشغال کند. اگر خطوط فاصلهگذاری شامل نشوند، اندروید از خطوط چپ و بالا برای تعریف این ناحیه قابل ترسیم استفاده میکند.
برای روشن شدن تفاوت بین خطوط، خطوط چپ و بالا مشخص میکنند که کدام پیکسلهای تصویر مجاز به تکرار شدن برای کشش تصویر هستند. خطوط پایین و راست، ناحیه نسبی درون تصویر را که محتوای نما مجاز به اشغال آن است، تعریف میکنند.
شکل ۱ نمونهای از گرافیک NinePatch را نشان میدهد که برای تعریف یک دکمه استفاده میشود:

شکل ۱: نمونهای از گرافیک NinePatch که یک دکمه را تعریف میکند
این گرافیک NinePatch یک ناحیه قابل کشش را با خطوط چپ و بالا و ناحیه قابل ترسیم را با خطوط پایین و راست تعریف میکند. در تصویر بالا، خطوط خاکستری نقطهچین، نواحی تصویر را که برای کشش تصویر تکرار میشوند، مشخص میکنند. مستطیل صورتی در تصویر پایین، ناحیهای را که محتویات نما در آن مجاز است، مشخص میکند. اگر محتویات در این ناحیه جا نشوند، تصویر کشیده میشود تا آنها را در خود جای دهد.
ابزار Draw 9-patch با استفاده از یک ویرایشگر گرافیکی WYSIWYG، روشی بسیار مفید برای ایجاد تصاویر NinePatch شما ارائه میدهد. این ابزار حتی در صورتی که ناحیهای که برای ناحیه قابل کشش تعریف کردهاید، در معرض خطر ایجاد مصنوعات نقاشی در نتیجه تکثیر پیکسل باشد، هشدار میدهد.
نمونه طرحبندی 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 تنظیم شدهاند تا دکمه به طور مرتب در اطراف متن قرار گیرد.
شکل ۲ دو دکمه رندر شده از تصویر XML و NinePatch بالا را نشان میدهد. توجه کنید که چگونه عرض و ارتفاع دکمه با متن تغییر میکند و تصویر پسزمینه برای تطبیق با آن کشیده میشود.

شکل ۲: دکمههای رندر شده با استفاده از یک منبع XML و یک گرافیک NinePatch
ترسیمهای سفارشی
وقتی میخواهید چند ترسیم سفارشی ایجاد کنید، میتوانید این کار را با بسط دادن کلاس Drawable (یا هر یک از زیرکلاسهای آن) انجام دهید.
مهمترین متد برای پیادهسازی، draw(Canvas) است، زیرا این متد، شیء Canvas را که باید برای ارائه دستورالعملهای ترسیم خود استفاده کنید، فراهم میکند.
کد زیر یک زیرکلاس ساده از Drawable را نشان میدهد که یک دایره رسم میکند:
کاتلین
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 }
جاوا
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; } }
سپس میتوانید drawable خود را به هر جایی که میخواهید اضافه کنید، مثلاً به یک ImageView ، همانطور که در اینجا نشان داده شده است:
کاتلین
val myDrawing = MyDrawable() val image: ImageView = findViewById(R.id.imageView) image.setImageDrawable(myDrawing) image.contentDescription = resources.getString(R.string.my_image_desc)
جاوا
MyDrawable mydrawing = new MyDrawable(); ImageView image = findViewById(R.id.imageView); image.setImageDrawable(mydrawing); image.setContentDescription(getResources().getString(R.string.my_image_desc));
در اندروید ۷.۰ (سطح API 24) و بالاتر، میتوانید نمونههایی از drawable سفارشی خود را با XML به روشهای زیر تعریف کنید:
- استفاده از نام کلاس کاملاً واجد شرایط به عنوان نام عنصر XML. برای این رویکرد، کلاس drawable سفارشی باید یک کلاس سطح بالای عمومی باشد:
<com.myapp.MyDrawable xmlns:android="http://schemas.android.com/apk/res/android" android:color="#ffff0000" />
- استفاده از
drawableبه عنوان نام تگ XML و مشخص کردن نام کلاس کاملاً واجد شرایط از ویژگی class. این رویکرد میتواند هم برای کلاسهای سطح بالای عمومی و هم برای کلاسهای داخلی استاتیک عمومی استفاده شود:<drawable xmlns:android="http://schemas.android.com/apk/res/android" class="com.myapp.MyTopLevelClass$MyDrawable" android:color="#ffff0000" />
اضافه کردن رنگ به drawableها
با اندروید ۵.۰ (سطح API ۲۱) و بالاتر، میتوانید بیتمپها و نه-پچهایی را که به عنوان ماسکهای آلفا تعریف شدهاند، رنگآمیزی کنید. میتوانید آنها را با منابع رنگی یا ویژگیهای تم که به منابع رنگی تبدیل میشوند (مثلاً ?android:attr/colorPrimary ) رنگآمیزی کنید. معمولاً این داراییها را فقط یک بار ایجاد میکنید و آنها را به طور خودکار برای مطابقت با تم خود رنگ میکنید.
شما میتوانید با استفاده از متد setTint() یک ته رنگ (tint) به اشیاء BitmapDrawable ، NinePatchDrawable یا VectorDrawable اعمال کنید. همچنین میتوانید رنگ و حالت ته رنگ را در طرحبندیهای خود با استفاده از ویژگیهای android:tint و android:tintMode تنظیم کنید.
استخراج رنگهای برجسته از یک تصویر
کتابخانه پشتیبانی اندروید شامل کلاس Palette است که به شما امکان میدهد رنگهای برجسته را از یک تصویر استخراج کنید. میتوانید drawableهای خود را به عنوان یک Bitmap بارگذاری کنید و آن را به Palette منتقل کنید تا به رنگهای آن دسترسی پیدا کنید. برای اطلاعات بیشتر، بخش انتخاب رنگها با Palette API را مطالعه کنید.
