إنشاء واجهات للساعات WearOS
إنشاء واجهات Watch Face لساعات WearOS بإستخدام Watch Face Format (WFF)
سنتعرف في هذا المقال كيفية إنشاء واجهات Watch Face للساعات الذكية التي تعمل بنظام WearOS (Android).
حالياً يوجد ٣ طرق لإنشاء واجهات WearOS
وهو عبارة عن برنامج ذات واجهة رسومية GUI وتستطيع إنشاء الواجهات عبر السحب والإفلات
أبرز مزايا هذا الخيار هي عدم الحاجة لمعرفة برمجية والعناصر جاهزة للسحب والإفلات ورؤية الواجهة بشكل مباشر
عيوبها هي عدم إمكانية التخصيص الكبيرة مثل Jetpack او WFF

2. Jetpack Watch Face (Deprecated)
والذي يمكنك من إنشاء واجهات باستخدام الكود تحديداً باستخدام Canvas API
مثال مأخوذ من تطبيقي Prayer Watch Face
override fun render(
canvas: Canvas,
bounds: Rect,
zonedDateTime: ZonedDateTime,
sharedAssets: PrayerSharedAssets
) {
//Clear the canvas to prevent previous background bitmap shadow to show up.
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.MULTIPLY)
val prayerName = getPrayerNameByLocaleUseCase.getPrayerNameByLocale(
previousPrayer,
locale
)
val prayerNameWidth = prayerNameTextPaint.measureText(prayerName)
val centerX = width / 2f
val x = centerX + dpToPx(12f, context)
val textY = remainingY
canvas.withTranslation(x = x, y = textY) {
canvas.drawText(
prayerName,
0f,
0f,
prayerNameTextPaint
)
}
}
للأسف قامت Google بإيقاف الدعم لهذا SDK لأسباب كثيرة أهمها التحكم بالبطارية , وعلى الرغم أن التطبيق يعمل على الساعات باستخدام هذا SDK إلا أن التطبيق لن يتم قبوله على Google Play حيث أن Google جعلت WFF الخيار الإفتراضي للساعات التي تعمل بنظام WearOS 5 فما فوق.
من أبرز مزايا هذا الخيار هو إمكانية التخصيص الكبيرة والتحكم الكامل على الواجهة , حيث أن شيئ يمكنك رسمه باستخدام Canvas يمكنك استخدامه على الواجهة مما يعطيك إمكانية تخصيص كبيرة.
وهو الخيار الذي تم إطلاقه حديثاً من قبل Google وهو الذي سنقوم بشرحه والتركيز عليه
مثال:
<WatchFace
height="450"
width="450">
<Metadata
key="CLOCK_TYPE"
value="DIGITAL" />
<Scene>
<DigitalClock
height="450"
width="450"
x="0"
y="0">
<TimeText
format="hh:mm"
height="100"
width="450"
x="0"
y="175">
<Variant
mode="AMBIENT"
target="alpha"
value="0" />
<Font
color="#ffffffff"
family="SYNC_TO_DEVICE"
size="128" />
</TimeText>
<TimeText
alpha="0"
format="hh:mm"
height="100"
width="450"
x="0"
y="175">
<Variant
mode="AMBIENT"
target="alpha"
value="255" />
<Font
color="#ffffffff"
family="SYNC_TO_DEVICE"
size="128"
weight="THIN" />
</TimeText>
</DigitalClock>
<Group
name="hello_world"
height="450"
width="450"
x="0"
y="0">
<PartText
height="50"
width="450"
x="0"
y="285">
<Variant
mode="AMBIENT"
target="alpha"
value="0" />
<Text>
<Font
color="#ffffffff"
family="SYNC_TO_DEVICE"
size="36">
<Template>%s
<Parameter expression="greeting" />
</Template>
</Font>
</Text>
</PartText>
</Group>
</Scene>
</WatchFace>
من أبرز المزايا في هذا الخيار هو أنه الخيار المدعوم من Google , على الرغم من عدم وجود إمكانية كتابة كود برمجي في هذا الخيار, إلا أنه يتيح لك بعض التخصيص كما سنرى لاحقاً
من عيوبه أنه لا يتيح لك كامل التحكم والتخصيص مثل Jetpack Watchface
إنشاء المشروع باستخدام Watch Face Format (WFF)
في Android Studio نقوم بإنشاء مشروع جديد ونختار WearOS > Basic Watch Face

ونضع إسم للمشروع

بعد فتح المشروع سيظهر بهذا الشكل

سنلاحظ أنه لا يوجد أية ملفات داخل Java فقط يوجد ملفات XML
لنشرح محتويات المشروع الحالي
داخل ملف AndroidManifest.xml سنجد التالي
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.type.watch" />
<!-- Note: hasCode is required to be false for Watch Face Format -->
<application
android:hasCode="false"
android:label="@string/watch_face_name">
<meta-data
android:name="com.google.android.wearable.standalone"
android:value="true" />
<property
android:name="com.google.wear.watchface.format.version"
android:value="1" />
<!-- Optionally add details about the publisher. -->
<!--
<property
android:name="com.google.wear.watchface.format.publisher"
android:value="YourPublisherNameHere" />
-->
</application>
</manifest>
كما نرى فإنه يجب ان يكون
hasCode="false"
أي أنه لا يمكن إضافة أي أكواد برمجية
ونستطيع أيضاً رؤية اصدار Watch Face
<property
android:name="com.google.wear.watchface.format.version"
android:value="1" />
حيث أن كل إصدار يحتوي على مزايا مهمة كما سنرى لاحقاً, وبعض الإصدارات لا تعمل على بعض إصدارات WearOS
لمزيد من التفاصيل
يمكنك رؤية الصورة أدناه لمزيد من المعلومات

داخل المجلد res سنجد
res/drawable/preview.png
هذه الصورة التي ستظهر للمستخدم لاختيار الواجهة بين الوجهات الأخرى

داخل
res/raw/watchface.xml
هذا هو الملف الذي سنقضى كل الوقت فيه, حيث أن هذا الملف هو المسؤول عن العناصر داخل الواجهة كالألوان, النصوص, الصور, الخ..
سنعود إليه في وقت لاحق
ملف strings.xml يمكننا استخدامه لدعم عدة لغات على حسب لغة الساعة
ملف watch_face_info.xml
<WatchFaceInfo>
<!--
Preview is the only required element here.
For other elements, see:
https://developer.android.com/training/wearables/wff/setup#declare-metadata
-->
<Preview value="@drawable/preview" />
</WatchFaceInfo>
حالياً الملف بهذا الشكل , يحتوي فقط على صورة العرض
يمكننا إضافة الخيار Editable لنخبر النظام أن هذه الواجهة يمكن تخصيصها مثل تغيير الألوان والعناصر كما سنرى لاحقاً. إذا لم نقم بإضافة هذا الخيار فإن زر "التخصيص" او "Customize" لن يظهر على الساعة عند النقر المطول.
<?xml version="1.0" encoding="utf-8"?>
<WatchFaceInfo>
<Editable value="true" />
<Preview value="@drawable/preview" />
</WatchFaceInfo>
لنجرب تشغيل التطبيق- تأكد من إضافة وتحميل Wear OS Emulator , في حالتي قمت باستخدام WearOS Large Round Android 16.0

جميل جداً!
لنقم بشرح الكود خطوة بخطوة
<WatchFace
height="450"
width="450">
<Metadata
key="CLOCK_TYPE"
value="DIGITAL" />
<Metadata
key="PREVIEW_TIME"
value="06:18:00" />
</WatchFace>
WatchFace هو العنصر الRoot الذي يحتوي جميع العناصر ومقاس مساحة العمل, المقاس الإفتراضي ٤٥٠ وهو مقاس مناسب لمعظم الشاشات
Metadata الذي يحتوي على معلومات الواجهة مثل نوعها Analog or Digital ويمكننا أيضاً وضع الوقت الذي نريد للعرض على الساعة مثل 6:18
Scene والذي سيحتوي على عناصر الساعة , يمكنك أيضاً وضع backgroundColor ل scene
من الجدير بالذكر أن Scene يمكن أن يحتوي على عنصر واحد فقط
داخل Scene يمكننا وضع الساعة. حالياً يوجد نوعين من الساعات Digital و Analog. سنقوم بالعمل على DigitalClock حالياً كونها أسهل في التعامل.
<DigitalClock
height="450"
width="450"
x="0"
y="0"/>
يمكننا تخصيص الإرتفاع والعرض, وإحداثيات الساعة الرقمية مثل x,y
يجب وضع TimeText داخل DigitalClock لعرض الوقت
<TimeText
format="hh:mm"
height="100"
width="450"
x="0"
y="175">
معظم عناصر WFF تأخذ نفس البارامترات مثل الإرتفاع, العرض, x,y
بالنسبة ل TimeText يمكننا تحديد صيغة الوقت مثل
hh:mm:ssrenders as "12:34:56".h:mmrenders as "12:34"hh_10renders as "1".hh_1renders as "2".mrenders as "34"mm_10renders as "3".mm_1renders as "4".ssrenders as "56"ss_10renders as "5".ss_1renders as "6".
يمكنك تفقد الDocumentation لمعلومات أكثر عن صيغ الوقت المدعومة
يجب علينا إضافة Font للتحكم بالخط مثل نوع الخط, لونه, وحجمه.
<Font
color="#ffffffff"
family="SYNC_TO_DEVICE"
size="128" />
قمنا بوضع اللون الى اللون الأبيض
نوع الخط "Family" SYNC_TO_DEVICE
SYNC_TO_DEVICE تعني أنه سيقوم بعرض نفس الخط الموجود بالهاتف المربوط بالساعة الذكية
يمكننا وضع خط خاص عبر إنشاء مجلد font داخل res

ثم وضع family="FONT_NAME" مثل
<Font
color="#ffffffff"
family="rubik"
size="128" />
يجب أن يكون إسم الخط نفسه بدون الإمتداد
لنشغل التطبيق لنرى النتيجة
<WatchFace
height="450"
width="450">
<Metadata
key="CLOCK_TYPE"
value="DIGITAL" />
<Scene>
<DigitalClock
height="450"
width="450"
x="0"
y="0">
<TimeText
format="hh:mm"
height="100"
width="450"
x="0"
y="175">
<Font
color="#ffffffff"
family="rubik"
size="128" />
</TimeText>
</DigitalClock>
</Scene>
</WatchFace>

الآن سنقوم بوضع Variant
<Variant
mode="AMBIENT"
target="alpha"
value="0" />
الVariant سيقوم بالتبديل ما بين targets في حالتنا "alpha"
نستخدمه عادة للتبديل بين الأوضاع عندما تكون شاشة الساعة مضاءة وبين وضع AOD Always On Display
في وضع AOD يجب أن تكون الشاشة سوداء مع ألوان قريبة للأسود لتوفير البطارية.
في هذا المثال قمنا بوضع value=0 أي أنه في وضع AMBIENT سنقوم بتغيير alpha الى 0 مما يعني إخفاء هذا العنصر تماماً في وضع AOD
ولعرض الوقت عندما يكون AOD فعال سنقوم بإنشاء نص آخر بVairant معاكس
<TimeText
alpha="0" //hidden
format="hh:mm"
height="100"
width="450"
x="0"
y="175">
<Variant
mode="AMBIENT"
target="alpha"
value="255" />
<Font
color="#ffffffff"
family="SYNC_TO_DEVICE"
size="128"
/>
</TimeText>
نلاحظ أنه TimeText Alpha هو 0 , أي أنه غير ظاهر بشكل إفتراضي
ثم وضعنا Variant alpha 255 لنعرض هذا العنصر في وضع Ambient فقط
ليصبح لدينا نصين, واحد لوضع Ambient , والآخر للوضع العادي
<WatchFace
height="450"
width="450">
<Metadata
key="CLOCK_TYPE"
value="DIGITAL" />
<Scene>
<DigitalClock
height="450"
width="450"
x="0"
y="0">
<TimeText
format="hh:mm"
height="100"
width="450"
x="0"
y="175">
<Variant
mode="AMBIENT"
target="alpha"
value="0" />
<Font
color="#ffffffff"
family="SYNC_TO_DEVICE"
size="128" />
</TimeText>
<TimeText
alpha="0"
format="hh:mm"
height="100"
width="450"
x="0"
y="175">
<Variant
mode="AMBIENT"
target="alpha"
value="255" />
<Font
color="#ffffffff"
family="SYNC_TO_DEVICE"
size="128"
/>
</TimeText>
</DigitalClock>
</Scene>
</WatchFace>
لتوضيح الفكرة بشكل أكبر سنقوم بتغيير النص في الوضع العادي الى الأحمر مثلا, واللون الأبيض في وضع Ambient
<WatchFace
height="450"
width="450">
<Metadata
key="CLOCK_TYPE"
value="DIGITAL" />
<Scene>
<DigitalClock
height="450"
width="450"
x="0"
y="0">
<TimeText
format="hh:mm"
height="100"
width="450"
x="0"
y="175">
<Variant
mode="AMBIENT"
target="alpha"
value="0" />
<Font
color="#ff0000"
family="SYNC_TO_DEVICE"
size="128" />
</TimeText>
<TimeText
alpha="0"
format="hh:mm"
height="100"
width="450"
x="0"
y="175">
<Variant
mode="AMBIENT"
target="alpha"
value="255" />
<Font
color="#ffffffff"
family="SYNC_TO_DEVICE"
size="128"
/>
</TimeText>
</DigitalClock>
</Scene>
</WatchFace>

سنتابع الآن لنشرح كيفية إضافة نص ما(بغض النظر عن الوقت)
يمكننا إضافة Group وتستخدم عادة لتنظيم الكود, او لوضع مزايا معينة لهذا Group عند وضع معين في إعدادات Watch Face او حتى لوضع عناصر معينة ضمن X,Y معينة.
يجب أن يحتوي كل Group على إسم
<Group
name="hello_world"
height="450"
width="450"
x="0"
y="0">
ولوضع نص معين يمكننا إستخدام `PartText`
<PartText
height="50"
width="450"
x="0"
y="285">
داخل PartText يجب أن نضع Text وداخله يمكننا تخصيص الخط Font
يمكننا وضع النص الذي نريده بشكل مباشر داخل Font
أو يمكننا إستخدام Expressions كما سنرى لاحقاً
<Group
name="hello_world"
height="450"
width="450"
x="0"
y="0">
<PartText
height="50"
width="450"
x="0"
y="285">
<Text>
<Font
color="#ffffffff"
family="SYNC_TO_DEVICE"
size="30">
Hello 3alam Pro!
</Font>
</Text>
</PartText>
</Group>

يمكننا إستخدام strings.xml وعرض النصوص داخله عبر استخدام `Template`
<Group
name="hello_world"
height="450"
width="450"
x="0"
y="0">
<PartText
height="50"
width="450"
x="0"
y="285">
<Variant
mode="AMBIENT"
target="alpha"
value="0" />
<Text>
<Font
color="#ffffffff"
family="SYNC_TO_DEVICE"
size="36">
<Template>%s
<Parameter expression="greeting" />
</Template>
</Font>
</Text>
</PartText>
</Group>
يمكننا إستخدام Template بنفس فكرة String.format() في الأندرويد, يمكننا وضع %s لإستبدال النص strings.xml ,
يمكننا وضع اسم العنصر من strings.xml داخل expression
//strings.xml
<resources>
<string name="watch_face_name">My Watch Face</string>
<string name="greeting">Hello, 3alam Pro!</string>
</resources>
//watchface.xml
<Font
color="#ffffffff"
family="SYNC_TO_DEVICE"
size="36">
<Template>%s
<Parameter expression="greeting" />
</Template>
</Font>
للأسف حالياً اذا قمت بتشغيل التطبيق وكان هنالك بعض الأخطاء في syntax فإن التطبيق سيعمل على جميع الأحوال, أو أن الواجهة ستعمل skip لبعض الأجزاء التي يوجد فيها أخطاء ولن يظهر أي أخطاء في Logcat.
للتأكد من أن الملف يعمل بشكل طبيعي, ولنضمن من أن التطبيق سيتم قبوله في Google Play فإنه يجب علينا استخدام wff-validator والتي ستقوم بالتحقق من صلاحية ملف xml
يمكنك تحميل الأداة عبر رابط Github الرسمي
ويمكنك استخدامه بالشكل التالي
java -jar ~/Downloads/wff-validator.jar 2 /Users/user/AndroidStudioProjects/MyWatchFace/watchface/src/main/res/raw/watchface.xml
كل ما عليك هو تزويد الأداة ب رقم اصدار Watch Face الذي تريد التجربة عليه "٢" مثلا , وثم مسار ملف xml

كما نرى فإنه لاتوجد أية مشاكل في ملف xml
وللتأكد, سأقوم بتغيير داخل Font ووضع النص مباشرةً داخل Template
<Group
name="hello_world"
height="450"
width="450"
x="0"
y="0">
<PartText
height="50"
width="450"
x="0"
y="285">
<Variant
mode="AMBIENT"
target="alpha"
value="0" />
<Text>
<Font
color="#ffffffff"
family="SYNC_TO_DEVICE"
size="36">
<Template>Hello 3alam Pro! //incorrect
</Template>
</Font>
</Text>
</PartText>
</Group>
سنرى أنه يوجد خطأ مع أن التطبيق يعمل على Emulator

مصادر قد تهمك
التعليقات (0)
لايوجد لديك حساب في عالم البرمجة؟
تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !