استفاده از تصاویر وکتور در برنامه‌های اندروید

چهارشنبه ۲۶ دی ۹۷ توسط سالار ساری نوایی

در پست‌های قبلی به بررسی فرمت تصویر VectorDrawable در اندروید و کارهایی که می‌تواند انجام بدهد پرداختیم.

در این پست خواهیم دید که چگونه از آن‌ها در برنامه خود استفاده کنید. VectorDrawable با اندروید آبنبات‌چوبی (API21) معرفی شد و همچنین در AndroidX نیز موجود است (به عنوان VectorDrawableCompat) که باعث می‌شود تا API14 (بیش از 99% دستگاه‌ها) از آن پشتیبانی شود. این پست طریقه‌ی استفاده از VectorDrawable ها در برنامه‌هایتان را توضیح خواهد داد.

ابتدا AndroidX

از آبنبات‌چوبی به بعد، می‌توانید هر جا که از سایر انواع drawable استفاده می‌کردید، VectorDrawableها را نیز به کار بگیرید (ارجاع دادن آن‌ها با استفاده از drawable/foo@)، اما توصیه می‌کنم که همواره از پیاده‌سازی AndroidX آن بهره ببرید. این کار بازه‌ی پلتفرم‌هایی که می‌توانید از آن‌ها استفاده کنید را گسترش می‌دهد، اما همچنین قابلیت بک‌پورت کردن ویژگی‌ها و رفع باگ‌های موجود در پلتفرم‌های قدیمی‌تر را نیز مقدور می‌سازد. به عنوان مثال، استفاده از VectorDrawableCompat از AndroidX به شما امکانات زیر را می‌دهد:

  • فعال ساختن روش‌های پر کردن مسیر nonZero و evenOdd – دو روش مرسوم تعریف کردن داخل یک شکل، غالبا استفاده شده در SVGها (evenOdd در API24 به پلتفرم اضافه شد)
  • گرادینت و ColorStateList پر/خط‌کشی شده (اضافه شده به پلتفرم در API24)
  • رفع باگ‌ها

در واقع AndroidX حتی در برخی پلتفرم‌هایی که پیاده‌سازی طبیعی امکان پذیر است (در حال حاضر API های 21 تا 23)، از پیاده‌سازی Compat استفاده می‌کند تا مزایای بالا را به همراه داشته باشد. در غیر این صورت به روش پیاده‌سازی پلتفرم واگذار می‌شود تا همچنان از بهبود‌های اضافه شده در نسخه‌های جدید بهره‌مند شود (به عنوان مثال، VectorDrawable در API24 در C مجددا پیاده‌سازی شد تا عملکرد آن بهبود یابد).

به این دلایل، شما باید همیشه از AndroidX استفاده کنید، حتی اگر این قابلیت را دارید که  minSdkVersion 24 را به کار بگیرید. معایت کمی وجود دارد و اگر زمانی در آینده امکانات جدیدی به VectorDrawable اضافه شود و همچنین در AndroidX نیز موجود شود، آن‌گاه به صورت آنی و بدون نیاز به بازرسی کد در دسترستان خواهند بود.

Alex Lockwood به خوبی این قضیه را درک کرده است! (VDC = VectorDrawableCompat)

چطور؟

برای بهره بردن از قابلیت پشتیبانی وکتور در AndroidX، باید دو چیز را بدانید:

1- قابلیت پشتیبانی را فعال کنید

شما باید قابلیت پشتیبانی از وکتور AndroidX را در build.gradle برنامه‌ی خود فعال کنید.

android {
    defaultConfig {
        vectorDrawables.useSupportLibrary = true
    }
}

این فلگ مانع از این می‌شود که  اگر minSdkVersion شما کمتر از 21 است، پلاگین Gradle اندروید نسخه‌ی PNG از تصویر وکتور شما ایجاد کند – به دلیل این که از کتابخانه‌ی AndroidX استفاده می‌کنیم نیازی به آن نداریم.

همچنین به زنجیره‌ی ابزار بیلد نیز فرستاده می‌شود. در حالت عادی منابع نسخه‌های AAPT (ابزار دسته‌بندی محتوای اندروید). این یعنی اگر شما یک VectorDrawable در res/drawable/ تعریف کنید، آن را برای شما به res/drawable-v21/ انتقال خواهد داد، زیرا می‌داند که این زمانی است که کلاس VectorDrawable معرفی شده بود.

این امر از ایجاد تداخل میان ID خصوصیت‌ها جلوگیری می‌کند – خصوصیت‌هایی که در VectorDrawable ها استفاده می‌کنید (android:pathData و android:fillColor و غیره) هر کدام یک ID اینتیجر مخصوص خود دارند، که در API21 اضافه شدند. در نسخه‌های قدیمی‌تر اندروید، هیچ چیزی مانع استفاده‌ی OEM ها از ID های تصرف نشده نمی‌شد که باعث می‌شد استفاده از خصوصیت‌های جدید در نسخه‌های قدیمی‌تر پلتفرم ناامن باشد.

این نسخه‌‌گذاری مانع از این می‌شود که فایل ما در پلتفرم‌های قبلی قابل دسترسی باشد و بک‌پورت کردن را غیرممکن می‌سازد – فلگ gradle این نسخه‌گذاری را برای drawable های وکتور غیرفعال می‌کند. به همین خاطر است که به جای این که نیاز به استفاده از app:pathData و امثالش باشید، از android:pathData و امثالش استفاده می‌کنید.

2- با AndroidX بارگذاری کنید

به هنگام بارگذاری Drawable ها نیاز دارید که از متدهای AndroidX استفاده کنید که امکان بک‌پورت کردن را فراهم می‌سازند. نقطه آغاز این کار هم این است که همواره Drawable هایمان را با AppCompatResources.getDrawable بارگذاری کنیم. با وجود این که راه‌های متعددی برای بارگذاری Drawable ها در اختیار داریم، اما اگر می‌خواهید از وکتورهای compat استفاده کنید باید AppCompatResources را به کار بگیرید. اگر این کار را انجام ندهید وارد مسیر کد AndroidX نخواهید شد و برنامه‌ی شما ممکن است در صورت تلاش برای استفاده از امکاناتی که توسط پلتفرم اجرایی شما پشتیبانی نمی‌شود، کرش شود.

همچنین متد create نیز توسط VectorDrawableCompat ارائه می‌شود که البته توصیه می‌کنم همیشه از AppCompatResources استفاده کنید، چرا که این متد یک لایه‌ی کش اضافه می‌کند.

اگر می‌خواهید drawable ها را به صورت اعلامی تعریف کنید (مثلا در layout)، در این صورت appcompat خصوصیت‌های compat متعددی در اختیارتان می‌گذارد که باید به جای موارد استاندارد پلتفرم، از آن‌ها استفاده کنید.

ImageView و ImageButton:

  • نباید: android:src
  • باید: app:srcCompat

CheckBox و RadioButton:

  • نباید: android:button
  • باید: app:buttonCompat

TextView (از appcompat:1.1.0):

  • نباید: android:drawableStart android:drawableTop …
  • باید: app:drawableStartCompat app:drawableTopCompat …

از آن‌جایی که این خصوصیت‌ها بخشی از کتابخانه‌ی appcompat هستند، فراموش نکنید که از فضای نام app: استفاده کنید. این ویوهای AppCompat* در داخل خود از AppCompatResources استفاده می‌کنند تا قابلیت بارگذاری وکتورها را فعال کنند.

در عمل

این الزامات روشی که ممکن است layout بسازید یا به منابع دسترسی پیدا کنید را تحت تاثیر قرار می‌دهند. در ادامه چند نکته‌ی کاربردی بیان شده است.

ویوهایی بدون خصوصیت‌های compat

متاسفانه چندین وضعیت وجود دارد ممکن است بخواهید drawable ها را بر ویوهایی تعریف کنید که خصوصیات compat را ارائه نمی‌دهند، یعنی هر چه که در بالا لیست نشده است. البته همچنان این امکان موجود است که از وکتورهای AndroidX استفاده کنید، اما باید با این کد این کار را انجام دهید:

/* Copyright 2018 Google LLC.
   SPDX-License-Identifier: Apache-2.0 */
val progressBar = findViewById<ProgressBar>(R.id.loading)
val drawable = AppCompatResources.getDrawable(context, R.drawable.loading_indeterminate)
progressBar.indeterminateDrawable = drawable

اما اگر از Data Binding استفاده می‌کنید، آن‌گاه این کار با یک اداپتر binding به خصوص صورت می‌پذیرد:

/* Copyright 2018 Google LLC.
   SPDX-License-Identifier: Apache-2.0 */
@BindingAdapter("indeterminateDrawableCompat")
fun bindIndeterminateProgress(progressBar: ProgressBar, @DrawableRes id: Int) {
  val drawable = AppCompatResources.getDrawable(progressBar.context, id)
  progressBar.indeterminateDrawable = drawable
}

در نظر داشته باشید که ما نمی‌خواهیم Data Binding کار بارگذاری drawable را برای ما انجام دهد (چرا که در حال حاضر برای بارگذاری drawable ها از AppCompatResources استفاده نمی‌کند) و ما نمی‌توانیم مستقیما به drawable اشاره داشته باشیم (مانند @drawable/foo). به جای این کار باید شناسه‌ی drawable را به اداپتر binding بفرستیم، پس نیاز داریم کلاس R را import کنیم تا بتوانیم آن را ارجاع بدهیم:

<!-- Copyright 2018 Google LLC.
     SPDX-License-Identifier: Apache-2.0 -->
<layout ...>
  <data>
    <import type="your.package.R" alias="R" />
    ...
  </data>

  <ProgressBar ...
    app:indeterminateDrawableCompat="@{R.drawable.foo}" />

</layout>

Drawable های تودرتو

برخی از انواع drawable ها قابلیت تودرتویی دارند مثل StateListDrawable ها، InsertDrawable ها، یا LayerDrawable ها در خود فرزندان drawable دیگری نیز دارند. پشتیبانی در AndroidX با شناسایی دقیق چگونه متورم کردن المان‌های <vector> صورت می‌گیرد (همچنین animated-vector ها و animated-selector ها، اما امروز بر وکتورهای ایستا تمرکز می‌کنیم). زمانی که AppCompatResources.getDrawable را صدا می‌زنید، منبع با شناسه‌ی ذکر شده را پیدا می‌کند و اگر یک وکتور باشد، (یعنی المان ریشه‌ی آن <vector> باشد)، به صورت دستی آن را برای شما متورم می‌سازد. در غیر این صورت، آن را به پلتفرم واگذار می‌کند تا متورم شود – در حین این کار امکان ندارد که AndroidX خود را مجددا وارد ماجرا کند. این یعنی اگر شما یک InsertDrawable که شامل یک وکتور است دارید و از AppCompatResources بخواهید آن را برای شما بارگذاری کند، تگ <vector> را می‌بیند، به آن اعتنایی نمی‌کند و به پلتفرم می‌سپاردش تا بارگذاری کند. در نتیجه فرصتی پیدا نمی‌کند که <vector> تودرتو را بارگذاری کند پس یا با مشکل مواجه خواهد شد (در API کمتر از 21) یا در حد قابل پشتیبانی پلتفرم عمل خواهد کرد.

برای حل این مشکل می‌توانید drawable ها را در کد بسازید؛ یعنی از AppCompatResources برای متورم ساختن وکتورها استفاده کنید و سپس به صورت دستی InsertDrawable را بسازید.

یک مورد استثناء افزونه‌ی جدید AndroidX یعنی AnimatedStateListDrawable های بک‌پورت شده است. این مورد نسخه‌ای از StateListDrawable با قابلیت تغییر انیمیشنی میان وضعیت‌ها است (در قالب AnimatedVectorDrawable). اما هیچ چیزی شما را الزام نمی‌کند که تغییری را تعریف کنید. پس اگر تنها نیاز به یک StateListDrawable دارید که بتواند وکتورهای فرزند را با استفاده از AndroidX متورم بسازد، می‌توانید از این مورد استفاده کنید:

<!-- Copyright 2018 Google LLC.
     SPDX-License-Identifier: Apache-2.0 -->
<animated-selector ...>
  <item android:state_foo="true" android:drawable="@drawable/some_vector" />
  <item android:drawable="@drawable/some_other_vector" />
  <!-- no transitions specified -->
</animated-selector>

راهی برای استفاده از وکتورها در drawable های تودرتو با استفاده از AppCompatDelegate#setCompatVectorFromResourcesEnabled وجود دارد، اما این مورد معایبی به همراه دارد. داکیومنت مربوطه را با دقت مطالعه کنید.

بارگذاری خارج از پردازش

گاهی اوقات نیاز دارید که drawable ها را در مکان‌هایی مهیا کنید که کنترلی بر زمان و چگونگی بارگذاری آن‌ها ندارید. برای مثال: نوتیفیکیشن‌ها، ویجت‌های صفحه یا محتوای تم شما (مثلا تنظیم android:windowBackground که به هنگام نمایش پنجره‌ی جدید توسط پلتفرم بارگذاری می‌شود). در این موارد شما مسئول بارگذاری drawable نیستید، پس فرصتی برای ادغام پشتیبانی AndroidX وجود ندارد و شما نمی‌توانید از وکتورهای پیش از API21 استفاده کنید.

البته می‌توانید در API21 به بعد از وکتورها بهره ببرید اما بدانید که ممکن است از امکانات و عیب‌های برطرف شده در AndroidX لذت نبرید. برای مثال با وجود این که بسیار خوب است که AndroidX، fillType=”evenOdd” را بک‌پورت می‌کند، وکتوری که این ویژگی را خارج از پشتیبانی AndroidX بر دستگاه‌های API21-23 استفاده کند، خصوصیت آن را نخواهد فهمید. برای این مثال به خصوص، چگونگی تبدیل filType در زمان طراحی را در مقاله‌ی بعد توضیح خواهم داد. در غیر این صورت، شما نیاز دارید که منابع جایگزینی برای سطوح مختلف API فراهم کنید:

res/
  drawable-xxhdpi/
    foo.png             <-- raster
  drawable-anydpi-v21/
    foo.xml             <-- vector
  drawable-anydpi-v24/
    foo.xml             <-- vector with fancy features

توجه کنید که ما نیاز داریم توصیف کننده‌ی منبع anydpi را در اینجا و در توصیف کننده‌ی سطح API اضافه کنیم. این به خاطر روشی است که اولویت‌های توصیف کننده منبع شکل گرفته است؛ هر محتوایی در drawable-<….>dpi مورد بهتری نسبت به یک محتوا در drawable-v21 در نظر گرفته خواهد شد.

X نقطه را مشخص می‌کند

امیدوارم که این مقاله مزایای استفاده از پشتیبانی وکتور AndroidX و محدودیت‌هایی که باید از آن‌ها آگاه باشید را برجسته کرده باشد. استفاده از پشتیبانی AndroidX هم وکتورها را در نسخه‌های بیشتری از پلتفرم فعال می‌سازد و هم آن قابلیت را بک‌پورت می‌کند، اما همچنین شما را برای دریافت به‌روزرسانی‌های آینده آماده می‌سازد.


کلیدواژه: اندروید Android Vector

منابع: medium.com

ارسال دیدگاه:
برای ارسال دیگاه باید به سیستم وارد شوید و یا ثبت نام کنید. ثبت نام چند لحظه بیشتر زمان شما را نمیگیرد.