🎉 فصل اول دوره جامع متریال دیزاین اندروید 2019 با 30% تخفیف استثنایی 🎉

شناخت فرمت تصویر وکتور در اندروید: VectorDrawable

دوشنبه ۱۰ دی ۹۷ توسط سالار ساری نوایی

آشنایی با فرمت تصویر وکتور در اندروید: VectorDrawable

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

رستر (پیکسلی) در مقابل وکتور (برداری)

اکثریت فرمت‌های تصویر (png, jpeg, bmp, gif, webp و غیره) از نوع رستر هستند که یعنی تصویر را به عنوان مجموعه‌ای از پیکسل‌های ثابت نشان می‌دهند. به همین شکل در رزولوشن به خصوصی تعریف می‌شوند و به جز رنگ هر پیکسل هیچ فهمی درباره محتوای خود ندارند. اما در تصاویر وکتور، تصویر به عنوان مجموعه‌ای از اشکال که بر روی صفحه‌ای با سایز انتزاعی تعریف شده‌اند شناخته می‌شود.

چرا وکتور؟

تصاویر وکتور سه مزیت بزرگ دارند، آن‌ها:

  • واضح هستند
  • کوچک هستند
  • پویا هستند

وضوح

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

اثر مقیاس‌دهی به بالای شدید در تصاویر رستر (چپ) و تصاویر وکتور (راست)

به همین دلیل است که در اندروید نیاز داریم چندین نسخه از هر تصویر رستر را برای تراکم صفحات مختلف آماده کنیم.

  • res/drawable-mdpi/foo.png
  • res/drawable-hdpi/foo.png
  • res/drawable-xhdpi/foo.png
  • ...

اندروید بزرگترین تراکم نزدیک به هدف را انتخاب نموده و در صورت نیاز مقیاس آن را کم می‌کند. با تمایل بازار به دستگاه‌هایی با صفحات با تراکم بالاتر، برنامه‌نویسان باید نسخه‌هایی بزرگتر از پیش از تصاویر مناسب این صفحات بسازند و آن‌ها در برنامه خود به کار گیرند. در نظر داشته باشید که بسیاری از دستگاه‌های مدرن در قالب‌های معین تراکم صفحه جای نمی‌گیرند (به عنوان مثال Pixel 3 XL با 552dpi چیزی در میان xxhdpi و xxxhdpi است) پس تصاویر غالبا مقیاس‌دهی خواهند شد.

به دلیل این که تصاویر وکتور به آسانی تغییر سایز می‌دهند، شما می‌توانید یک داده را استفاده کنید و خیالتان جمع باشد که بر روی هر صفحه‌ای با هر تراکمی به خوبی نمایش داده خواهد شد.

کوچک

تصاویر وکتور غالبا فشرده‌تر از تصاویر رستر هستند، چون تنها نیاز است که از یک نسخه استفاده کنید و این که به خوبی فشرده می‌شوند.

به عنوان مثال در اینجا تغییری در برنامه‌ی Google I/O داده شده است که تعدادی از آیکون‌ها را از فرمت رستر PNG به وکتور تغییر داده و 482 کیلوبایت حجم را کاهش دادیم. با وجود این که ممکن است چندان بزرگ به نظر نیاید، این تنها برای چند آیکون کوچک بود؛ تصاویر بزرگتر (مانند Illustration ها) کاهش حجم بیشتری خواهند یافت.

برای نمونه این Illustration از برنامه‌ی I/O سال گذشته:

Illustration ها می‌توانند کاندیدهای خوبی برای وکتور باشند

ما نتوانستیم این مورد را با یک VectorDrawable جایگزین کنیم زیرا در آن زمان گرادینت‌ها به شکل جامعی پشتیبانی نمی‌شدند (اسپویلر: الان پشتیبانی می‌شوند!) پس ما مجبور بودیم از یک نسخه‌ی رستر استفاده کنیم. اگر می‌توانستیم از وکتور استفاده کنیم، در ازای تصویری بهتر، حجمی برابر با 30% حجم اولیه داشتیم:

  • رستر: سایز دانلود = 53.9 کیلوبایت (سایر فایل Raw = 54.8 کیلوبایت)
  • وکتور: سایز دانلود = 3.7 کیلوبایت (سایز فایل Raw = 15.8 کیلوبایت)

در نظر داشته باشید با وجود این که تقسیم کردن وضعیت تراکم برنامه‌ی اندروید با به کارگیری تصویر با تراکم مورد نیاز دستگاه مزایایی در خود دارد، اما یک Vector Drawable به طور کل کوچک‌تر خواهد بود و همچنین نیاز مداوم به ساختن تصاویر رستر بزرگتر را از بین می‌برد.

پویا

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

بده‌بستان‌ها

وکتورها معایبی نیز دارند که باید در نظر گرفته شوند:

رمزگشایی

همان طور که گفته شد، وکتورها محتوای خود را تعریف می‌کنند، از این رو نیاز است که پیش از استفاده متورم شده و رسم شوند.

این کار دو مرحله دارد:

  1. تورم(Inflation): فایل وکتور شما باید خوانده شده و با نمونه‌سازی مسیرها، گروه‌ها و هر چیزی که شما اعلام می‌کنید در یک VectorDrawable تجزیه شود.
  2. رسم: سپس این اشیاء نمونه نیاز دارند که با اجرای دستورات رسم Canvas رسم شوند.

این دو بخش تنها جزئی از پیچیدگی وکتور و نوع عملیاتی که شما انجام می‌دهید هستند. اگر شما از اشکال بسیار مرکب و پیچیده‌ای استفاده کنید، مدت زمان بیشتری طول می‌کشد تا در یک مسیر تجزیه شود. مشابها، اعمال ترسیم نیز زمان بیشتری خواهند گرفت (و بعضی هزینه‌برترند مانند کارهای یک کلیپ). ما در آینده در یک پست از همین مجموعه به این موضوع بر می‌گردیم و این هزینه‌ها را دسته‌بندی می‌کنیم.

برای وکتورهای ایستا، مرحله‌ی رسم تنها نیاز است که یک بار اجرا شود و سپس می‌تواند در یک Bitmap ذخیره گردد. اما وکتورهای متحرک نمی‌توانند به این شکل بهینه‌سازی انجام دهند زیرا با تغییر جزئیات آن‌ها نیاز دارند که مجددا کار رسم را انجام دهند.

این مورد را با یک تصویر رستر مانند PNG مقایسه کنید که تنها نیاز دارد محتوای فایل را رمزگشایی کند، کاری که در طول زمان بسیار بهینه گشته است.

بده‌بستان اساسی میان رستر و وکتور این است. وکتور مزایای ذکر شده را با خود به همراه دارد اما در ازای رندر هزینه‌برتر. در دوران آغازین اندروید، دستگاه‌ها قدرت کمتری داشتند و تراکم صفحات تفاوت چندانی نداشتند. امروزه دستگاه‌های اندروید بسیار قدرتمندترند و در تراکم صفحات بسیار متنوعی ارائه می‌شوند. به این دلیل است که من باور دارم اکنون زمان آن است که تمام برنامه‌ها به سمت تصاویر وکتور حرکت کنند.

سازگاری

بنا به ماهیت این فرمت، وکتورها گزینه بسیار خوبی در تعریف برخی تصاویر مانند آیکون‌های ساده هستند. اما در رمزگذاری تصاویر عکاسی به عنوان مثال که تعریف محتوای آن به عنوان مجموعه‌ای اشکال دشوار است عملکرد بدی دارند و بهتر است که در این مواقع از فرمت رستر (مانند webp) استفاده کنیم که البته این موضوع وابسته به پیچیدگی تصویر شماست.

تبدیل

هیچ کدام از ابزارهای طراحی (که من با آن‌ها آشنایی دارم) به طور مستقیم VectorDrawable را نمی‌سازند که یعنی یک مرحله‌ی تبدیل از سایر فرمت‌ها به آن وجود دارد. این امر می‌تواند روند کاری میان طراحان و توسعه‌دهندگان را پیچیده کند. در پست‌های بعد وارد جزئیات این مبحث خواهیم شد.

چرا SVG نه؟

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

با این حال SVG مسیر ویژه‌ای را در خود دارد که چگونگی تعریف و ترسیم اشکال را معین می‌کند. با این API شما قادر هستید که اکثر شکل‌های وکتور را ایجاد کنید. در واقع این چیزی است که اندروید از ان پشتیبانی می‌کند: مسیر ویژه‌ی SVG (به علاوه چندین ضمیمه)

در نهایت با تعریف فرمت خود، VectorDrawable می‌تواند با ویژگی‌های پلتفرم اندروید ادغام شود. به عنوان مثال کار کردن با سیستم منابع اندروید برای ارجاع دادن @colors، @dimens، یا @strings، کار کردن با ویژگی‌های تم یا AnimatedVectorDrawable با استفاده از Animator های استاندارد.

قابلیت‌های VectorDrawable

همان طور که گفته شد، VectorDrawable از مسیر ویژه‌ی SVG ها پشتیبانی می‌کند و به شما اجازه می‌دهد یک یا چند شکل را انتخاب کنید تا رسم شوند. این کار در یک داکیومنت XML صورت می‌گیرد که این گونه است:

<!-- Copyright 2018 Google LLC.
     SPDX-License-Identifier: Apache-2.0 -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
  android:width="24dp"
  android:height="24dp"
  android:viewportWidth="24"
  android:viewportHeight="24">

    <path
      android:name="cross"
      android:pathData="M6.4,6.4 L17.6,17.6 M6.4,17.6 L17.6,6.4"
      android:strokeWidth="2"
      android:strokeLineCap="square"
      android:strokeColor="#999" />

</vector>

در نظر داشته باشید که شما سایز اصلی تصویر را مشخص می‌کنید که معادل با همان سایزی است که اگر آن را در یک wrap_content ImageView قرار می‌دادید. مقادیر viewport ابعاد صفحه‌ی مجازی را مشخص می‌کنند یا فضایی که تمام ترسیمات آینده در ان رخ خواهند داد را معین می‌کنند. ابعاد اصلی و viewport می‌توانند متفاوت باشند (اما باید در ضریب یکسانی قرار گیرند)- در صورت تمایل می‌توانید وکتورهایتان را در یک صفحه‌ی 1*1 تعریف کنید.

المان <vector> شامل یک یا چند المان از نوع <path> است. آن‌ها را می‌توان نامگذاری کرد (برای ارجاعات آینده مانند انیمیشن) اما الزاما می‌بایست یک المان pathData برای آن تعیین کرد که شکل را تعریف می‌کند. این string به ظاهر مرموز را می‌توان مجموعه‌ای از دستورات برای کنترل قلم روی صفحه‌ی مجازی در نظر گرفت:

عملیات مصورسازی مسیر

دستورات بالا قلم مجازی را حرکت می‌دهند و خطی به یک نقطه‌ی دیگر می‌کشند، سپس قلم را بالا می‌آورند و حرکت می‌دهند و مجددا خط دیگری می‌کشند. تنها با 4 دستور ساده می‌توانیم تقریبا هر شکلی را رسم کنیم (دستورات بیشتری وجود دارند که می‌توانید در اینجا مشاهده کنید):

  • M حرکت کن به
  • L خط بکش به
  • c (مکعب بزیه) منحنی بکش به
  • z پایان (خط به نقطه‌ی اولیه)

(دستورات حروف بزرگ از مختصات محض و حرف کوچک از مختصات نسبی استفاده می‌کنند)

شاید برایتان سوال باشد که آیا نیاز است به این سطح از جزئیات اهمیت بدهید؟ مگر این‌ها را از فایل‌های SVG نمی‌گیریم؟ با وجود این که نیازی به این نیست که یک مسیر را بخوانید و بدانید چه چیزی رسم می‌کند، اما دانش کوچکی از طرز کار یک VectorDrawable بسیار مفید است و برای فهم ویژگی‌های پیچیده‌تر که در آینده خواهیم دید ضروری می‌باشد.

مسیرها به خودی خود چیزی رسم نمی‌کنند، نیاز دارند که خط‌کشی یا پر شوند.

<!-- Copyright 2018 Google LLC.
     SPDX-License-Identifier: Apache-2.0 -->
<vector ...>

    <path
      android:pathData="..."
      android:fillColor="#ff00ff"
      android:strokeColor="#999"
      android:strokeWidth="2"
      android:strokeLineCap="square" />

</vector>

بخش دوم این مجموعه وارد جزئیات روش‌های مختلف خط‌کشی یا پر کردن مسیرها می‌شود.

شما همچنین می‌توانید گروهی از مسیرها را تعریف کنید. این کار به شما اجازه می‌دهد که تغییراتی را مشخص کنید که برای تمام مسیرهای درون گروه اعمال شود.

<!-- Copyright 2018 Google LLC.
     SPDX-License-Identifier: Apache-2.0 -->
<vector ...>

    <path .../>

    <group
        android:name="foo"
        android:pivotX="12"
        android:pivotY="0"
        android:rotation="45"
        android:scaleX="1.2"
        android:translateY="-4">

        <path ... />

    </group>

</vector>

در نظر داشته باشید که شما نمی‌توانید مسیرهای منحصربفرد را چرخش/مقیاس/انتقال دهید. اگر می‌خواهید این گونه باشد نیاز است که آن‌ها را در یک گروه قرار دهید. این تغییرات برای تصاویر ایستا منطقی نیستند اما برای ساخت انیمیشن بسیار مفیدند.

شما این امکان را دارید که clip-path نیز تعریف کنید که محیطی که سایر مسیرهای یک گروه می‌توانند می‌توانند در آن رسم شوند را پوشش می‌دهد. آن‌ها دقیقا مانند مسیرها تعریف می‌شوند.

<!-- Copyright 2018 Google LLC.
     SPDX-License-Identifier: Apache-2.0 -->
<vector ...>
  
  <clip-path
    android:name="mask"
    android:pathData="..." />

  <path .../>

</vector>

یک محدودیت قابل ذکر این است که clip-path ها حذف پله‌پلگی (anti-aliased) نشده‌اند.

نمایش clip-path با عدم حذف پله‌پلگی

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

مسیرها می‌توانند برش داده شوند؛ این یعنی تنها بخشی از کل مسیر رسم شود. شما می‌توانید مسیرهای پر شده را برش دهید اما نتایج آن شما را سورپرایز خواهد کرد! بهتر است که مسیرهای خط‌کشی‌شده را برش داد.

<!-- Copyright 2018 Google LLC.
     SPDX-License-Identifier: Apache-2.0 -->
<vector...>

  <path
    android:pathData="..."
    android:trimPathStart="0.1"
    android:trimPathEnd="0.9" />

</vector>

شما می‌توانید از ابتدا یا انتهای یک مسیر عمل برش را انجام دهید یا آفستی را به هر برشی اعمال کنید. آن‌ها به عنوان بخشی از مسیر تعریف می‌شوند [0,1]. ببینید که چطور تعیین مقادیر مختلف برش، بخشی از خط که رسم می‌شود را تغییر می‌دهد. مجددا، این ویژگی برای تصاویر ایستا کاربردی ندارند و برای انیمیشن به درد بخور است.

المان ریشه‌ی vector از یک مشخصه‌ی alpha پشتیبانی می‌کند [0,1]. گروه‌ها این مشخصه را ندارند اما مسیرهای منحصربفرد از fillAlpha/strokeAlpha پشتیبانی می‌کنند.

<!-- Copyright 2018 Google LLC.
     SPDX-License-Identifier: Apache-2.0 -->
<vector ...
  android:alpha="0.7">

  <path
    android:pathData="..."
    android:fillColor="#fff"
    android:fillAlpha="0.5" />

</vector>

اعلام استقلال کنید

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


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

منابع: medium.com

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