تُعتبر الصور كما نعلم من العناصر الأساسيّة في أيّ تطبيق، فهي تضفي لمسة جمالية بالإضافة إلى إعطائها انطباعًا توضيحيًّا لوظائف التطبيق الأساسيّة. سنتابع عملنا في هذه السلسلة من خلال شرح كيفيّة استخدام الصور في Xamarin.Froms. يتعامل Xamarin مع الصور النقطيّة bitmaps التي قد يختلف مفهومها قليلًا بالنسبة لأنظمة تشغيل Android وiOS وWindows، ولكن إذا اعتمدت على صيغ مثل JPEG وPNG وBMP فلن تعاني من أيّة مشاكل.
يتعامل Xamarin.Forms مع الصور بأسلوبين رئيسيّين. يعتمد الأسلوب الأوّل على الصور المستقلة عن نوع الجهاز platform-independent أمّا الأسلوب الثاني فيعتمد على الصور المرتبطة بالجهاز platform-specific أو بمعنى أدق المرتبطة بنظام التشغيل الذي يعمل على الجهاز. ستناول الأسلوب الأوّل في درسنا هذا من خلال تطبيقين بسيطين يوضّحان ذلك. أمّا الأسلوب الثاني فسنؤجّل الحديث عنه لدرس آخر.
يمكن عرض الصور ضمن تطبيقات Xamarin عن طريق عنصر مرئي يمثّله الصنف Image. وكما مع بقية العناصر المرئية التي ترث من الصنف VisualElement يمتلك هذا العنصر العديد من الخصائص المشتركة بينه وبين أي عنصر مرئي آخر كاللصيقة Label مثلًا. بالإضافة إلى خصائص أخرى تميّزه كالخاصيّة Source التي تُعبّر عن مصدر الصورة التي ستُعرض ضمنه. ستكون هذه الخاصيّة محور اهتمامنا في هذا الدرس. يمكن الحصول على الصور المستقلة عن الجهاز بأسلوبين مختلفين. ستحتاج في كلّ أسلوب منهما إلى إسناد كائن من الصنف ImageSource إلى الخاصيّة Source لكائن الصورة Image.
يتمثّل الأسلوب الأوّل في الحصول على صور من الإنترنت مباشرةً، ويكون ذلك من خلال استدعاء التابع الساكن FromUri من الصنف ImageSource وتمرير عنوان Uri لملف الصورة على الإنترنت.
أمّا الأسلوب الثاني فيتمثّل باستدعاء التابع الساكن FromResource من الصنف ImageSource أيضًا وتمرير اسم المصدر resource (الصورة) إليه. سيوضّح التطبيقين البسيطين التاليّين كيفيّة فعل ذلك.
الحصول على صورة من الإنترنت
أنشئ تطبيقًا جديدًا سمّه WebImageApp من النوعBlank App (Xamarin.Forms Portable) وأبق فقط على المشروعين WebImageApp (Portable) و WebImageApp.Droid كما اتفقنا من قبل. أضف صفحة محتوى جديدة ContentPage كما فعلنا في هذا الدرس. سمّها WebImagePage واحرص على أن تكون محتويات الملف WebImagePage.cs على الشكل التالي:
using System;
using Xamarin.Forms;
namespace WebImageApp
{
public class WebImagePage : ContentPage
{
public WebImagePage()
{
Image webImage = new Image
{
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand,
Source = ImageSource.FromUri(new Uri("https://developer.xamarin.com/demo/IMG_2138.JPG?width=800")),
Aspect = Aspect.AspectFit,
BackgroundColor = Color.Accent
};
Content = new StackLayout
{
Children = {
webImage
}
};
}
}
}
ثم انتقل إلى الملف App.cs واحرص على أن تكون بانيته على الشكل التالي:
public App()
{
// The root page of your application
MainPage = new WebImagePage();
}
شيفرة التطبيق بسيطة، فهي تعمل على إنشاء كائن صورة جديد وتُسنده إلى المتغيّر webImage، ثم تعمل على جعل كائن الصورة هذا ابنًا لمخطّط المكدّس كما هو واضح.
ما يهمنا الآن هو خصائص كائن الصورة، فقد أسندنا القيمة LayoutOptions.FillAndExpand إلى كلّ من الخاصيّتين VerticalOptions وHorizontalOptions لكي تشغل الصورة كامل المساحة المتاحة لمخطّط المكدّس. أمّا بالنسبة للخاصيّة Source فهي تعبّر عن المصدر الذي سنحصل منه على الصورة. في تطبيقنا هذا سنحصل على الصورة من الإنترنت لذلك سنستخدم التابع الساكن ImageSource.FromUri الذي يُرجع كائنًا من الصف ImageSource. لاحظ أنّنا قد مرّرنا كائن جديد من النوع Uri إلى هذا التابع:
ImageSource.FromUri(new Uri("https://developer.xamarin.com/demo/IMG_2138.JPG?width=800"))
العنوان المستخدم هنا هو:
"https://developer.xamarin.com/demo/IMG_2138.JPG?width=800"
بالنسبة للخاصيّة Aspect فهي تُحدّد كيفيّة عرض الصورة ضمن عنصر الصورة Image. حيث أنّها تقبل قيمًا من المعدودة Aspect التي تحمل نفس الاسم. لهذه المعدودة ثلاثة عناصر: AspectFit وAspectFill وFill. استخدمنا في هذا التطبيق القيمة AspectFit وهي القيمة الافتراضيّة. وإليك ما تعنيه القيم الثلاث السابقة:
- AspectFit: تعني أنّ الصورة ستظهر ضمن العنصر بحيث تنسجم مع المساحة المخصّصة له، مع احترام النسبة الخاصّة بمظهر الصورة aspect ratio.
- AspectFill: في هذه الحالة ستظهر الصورة ضمن العنصر بحيث تحترم النسبة الخاصّة بمظهر الصورة aspect ratio، ولكنّها لن تهتم بالمساحة المخصّصة لهذا العنصر. لذلك فقد تظهر الصورة مقتطعةً إذا كانت أكبر من العنصر.
- Fill: تعني هذه القيمة أنّ الصورة ستظهر بحيث تشغل كامل المساحة المخصّص للعنصر بدون أيّ اعتبار النسبة الخاصّة بمظهر الصورة aspect ratio. لذلك فقط تظهر الصورة ممطوطة أفقيًّا أو رأسيًّا. لقد أسندت اللون Color.Accent إلى خاصيّة لون الخلفيّة BackgroundColor لعنصر الصورة لكي نرى المساحة الفعليّة التي يشغلها هذا العنصر. نفّذ البرنامج السابق باستخدام F5 لتحصل على شكل شبيه بما يلي، مع الانتباه إلى أنّني أجري التجارب على جهاز ذي شاشة قياسها 4.5 بوصة، لذلك قد تختلف النتائج التي تحصل عليها على جهازك في حال كان لديك شاشة ذات قياس أكبر. إذا كان الأمر كذلك فأنصح بحذف المقطع "?width=800" من عنوان Uri للصورة من الخاصيّة Source:
لاحظ كيف ظهرت الصورة بحيث بقيت مساحة فارغة من الأعلى والأسفل. هذا بسبب أنّها احترمت النسبة الخاصّة بمظهر الصورة الأصليّة aspect ratio. إذا أجريت تعديلًا على الخاصيّة Aspect لتحمل القيمة Aspect.AspectFill فستحصل في هذه الحالة على شكل شبيه بما يلي:
لاحظ هنا كيف ظهرت الصورة مقتطعة قليلًا. جرّب الآن الحالة الأخيرة، وهي إسناد القيمة Aspect.Fill إلى الخاصيّة Aspect لتحصل على شكل شبيه بما يلي:
وكما هو متوّقع ظهرت الصورة بشكل ممطوط رأسيًّا لأنّها ستملأ كامل المساحة المتاحة لعنصر الصورة دون اعتبار للنسبة الخاصّة بمظهر الصورة الأصليّة كما أشرنا.
الحصول على صورة من مصدر resource محلّي
لا يختلف التعامل مع الصور في هذا الأسلوب مع أسلوب التعامل مع الصور التي نحصل عليها من الويب. باستثناء أنّنا نستخدم التابع الساكن FromResource من الصنف ImageSource. سنتناول في هذه الفقرة تطبيقًا عمليًّا لتصفّح مجموعة من الصور التي أعددتها خصيصًا لهذا الغرض. سيتم التصفّح عن طريق زرّين: "التالي" و"السابق" بالإضافة إلى وجود لصيقة تعرض معلومة بسيطة عن رقم الصورة الحالية التي يتم عرضها على عنصر الصورة. ستكون هذه الصور موجودة محلّيًّا ضمن التطبيق. لنبدأ الآن بإنشاء تطبيق جديد اسمه ResourceImageApp من النوعBlank App (Xamarin.Forms Portable) . أبق فقط على المشروعين ResourceImageApp (Portable) و ResourceImageApp.Droid. أضف صفحة محتوى جديدة ContentPage سمّها ResourceImagePage واحرص على أن تكون محتويات الملف ResourceImagePage.cs على الشكل التالي:
1 using System;
2 using Xamarin.Forms;
3
4 namespace ResourceImageApp
5 {
6 public class ResourceImagePage : ContentPage
7 {
8 private Button btnPrev;
9 private Button btnNext;
10 private Image resourceImage;
11 private Label lblInfo;
12 private int currentIndex = 1;
13
14 public ResourceImagePage()
15 {
16 lblInfo = new Label
17 {
18 VerticalOptions = LayoutOptions.Start,
19 HorizontalOptions = LayoutOptions.FillAndExpand,
20 HorizontalTextAlignment = TextAlignment.Center,
21 TextColor = Color.Accent,
22 FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label))
23 };
24
25 resourceImage = new Image
26 {
27 VerticalOptions = LayoutOptions.FillAndExpand,
28 HorizontalOptions = LayoutOptions.FillAndExpand,
29 Aspect = Aspect.AspectFit
30 };
31
32 btnPrev = new Button
33 {
34 Text = "السابق",
35 HorizontalOptions = LayoutOptions.FillAndExpand,
36 IsEnabled = false
37 };
38 btnPrev.Clicked += BtnPrev_Clicked;
39
40 btnNext = new Button
41 {
42 Text = "التالي",
43 HorizontalOptions = LayoutOptions.FillAndExpand
44 };
45 btnNext.Clicked += BtnNext_Clicked;
46
47 StackLayout buttonsLayout = new StackLayout
48 {
49 Orientation = StackOrientation.Horizontal,
50 VerticalOptions = LayoutOptions.End,
51 HorizontalOptions = LayoutOptions.FillAndExpand,
52 Children =
53 {
54 btnNext,
55 btnPrev
56 }
57 };
58
59 Content = new StackLayout
60 {
61 Children = {
62 lblInfo,
63 resourceImage,
64 buttonsLayout
65 }
66 };
67
68 Padding = new Thickness(5, 5, 5, 5);
69
70 UpdateScreen();
71 }
72
73 private void BtnPrev_Clicked(object sender, EventArgs e)
74 {
75 currentIndex--;
76 if (currentIndex == 1)
77 {
78 btnPrev.IsEnabled = false;
79 }
80
81 btnNext.IsEnabled = true;
82
83 UpdateScreen();
84 }
85
86 private void BtnNext_Clicked(object sender, EventArgs e)
87 {
88 currentIndex++;
89 if (currentIndex == 5)
90 {
91 btnNext.IsEnabled = false;
92 }
93
94 btnPrev.IsEnabled = true;
95
96 UpdateScreen();
97 }
98
99 private void UpdateScreen()
100 {
101 lblInfo.Text = string.Format("الصورة {0} من 5", currentIndex);
102 string path = string.Format("ResourceImageApp.Images.res0{0}.jpg", currentIndex);
103 resourceImage.Source = ImageSource.FromResource(path);
104 }
105 }
106 }
ثم انتقل إلى الملف App.cs واحرص على أن تكون بانيته على الشكل التالي:
public App()
{
// The root page of your application
MainPage = new ResourceImagePage();
}
انقر بزر الفأرة الأيمن على المشروع المشترك ResourceImageApp (Portable) ثم اختر Add ومن القائمة الجانبيّة التي ستظهر اختر New Folder. سينشئ ذلك مجلّدًا جديدًا ضمن هذا المشروع، سمّه Images. ثم انقر بعد ذلك بزر الفأرة الأيمن على مجلّدنا الجديد Images واختر Add وبعدها Existing Item سيظهر مربّع حوار يسمح لك باختيار الصور المراد إضافتها كمصادر resources إلى هذا المجلّد. اختر ملفّات الصور: res01.jpg وres02.jpg وres03.jpg و res04.jpg وres05.jpg (انقر هنا لتحميلهاImages.zip). تحتوي هذه الصور بالترتيب على العبارة التالية "مرحبًا بكم في أكاديميّة حسّوب". انقر بزر الفأرة الأيمن مجدّدًا على كل اسم ملف أضفته قبل قليل إلى المشروع واختر من القائمة الأمر Properties. ستظهر خصائص الملف، اضبط الخاصيّة Build Action لتصبح Embedded Resource كما هو واضح من الشكل التالي لأحد الملفات:
نفّذ البرنامج باستخدام F5 لتحصل على شكل شبيه بما يلي:
لنلاحظ في البداية أنّ التطبيق يعرض الصورة الأولى التي يمثّلها الملف res01.jpg. كما نلاحظ أنّ زر "السابق" غير مفعّل أي أنّنا لا نستطيع الرجوع إلى الوراء، وأيضًا العبارة التوضيحيّة في الأعلى التي تعرض رقم الصورة الحاليّة. إذا جرّبت نقر زر "التالي" ستنتقل إلى الصورة رقم 2 (الملف res02.jpg) وسيصبح الزر "السابق" مفعّلًا ليسمح لنا بالعودة إلى الصورة رقم 1. إذا تابعت النقر على زر "التالي" ستستعرض تباعًا الملفات بالترتيب، حتى إذا وصلت إلى الملف رقم 5 سيصبح الزر "التالي" غير مفعّل لأنّه لا توجد أيّة صور بعد الملف رقم 5. انظر إلى الشكل التالي:
بمجرّد أن تنقر الزر "السابق" سيعود زر "التالي" مفعّلًا من جديد. لنناقش الشيفرة البرمجيّة المشغّلة لهذا التطبيق. نلاحظ في الأسطر من 8 حتى 11 أنّني قد صرّحت عن حقول خاصّة تمثّل معظم العناصر المرئيّة التي سنتعامل معها في هذا التطبيق وهي عنصر صورة وعنصر لصيقة وزرّين للتالي والسابق. كما صرّحت في السطر 12 عن متغيّر خاص currentIndex يمثّل رقم الملف الحالي الذي نرغب بعرضه ضمن ملف الصورة وأسندت له القيمة التمهيديّة 1. بالنسبة لبانية الصنف ResourceImagePage فهي تعمل على تهيئة جميع العناصر المرئيّة المستخدمة كما نعلم. حيث نضبط في الأسطر من 16 حتى 23 خصائص اللصيقة lblInfo حيث أسندنا القيمة LayoutOptions.Start للخاصيّة VerticalOptions لكي تظهر أوّل (أعلى) الشاشة. كما أسندنا القيمة TextAlignment.Center لخاصيّة محاذاة النص الأفقيّة HorizontalTextAlignment لكي يظهر النص موسّطًا بشكل أفقي. أمّا بالنسبة لعنصر الصورة resourceImage فقمنا بضبط خصائصه في الأسطر من 25 حتى 30 حيث جعلنا قيمة الخاصيّة Aspect له تساوي Aspect.AspectFit لكي تملأ الصورة الحيّز المتاح لها مع احترام النسبة الخاصّة بمظهر الصورة. تأتي بعد ذلك عمليّة ضبط الزرّين btnPrev (السابق) و btnNext (التالي) من الأسطر 32 حتى 45. سنضيف هذين الزرّين لاحقًا إلى مخطّط مكدّس خاص بهما هو buttonsLayout والسبب في ذلك أنّنا نريد أن نجعل هذين الزرّين متجاورين أفقيًّا مع تعبئة المساحة الأفقيّة المتاحة لهما (لاحظ القيمة LayoutOptions.FillAndExpand للخاصيّة HorizontalOptions لكل منهما). نصرّح عن مخطّط المكدّس buttonsLayout في الأسطر من 47 حتى 57. سنضيف لهذا المخطّط الزرّين btnPrev و btnNext كما سنضبط خاصيّة الاتجاه Orientation له لتكون أفقيّة StackOrientation.Horizontal لكي يُظهِر الزرّين السابقين بشكل أفقيّ وليس رأسيّ. سنضيف مخطّط المكدّس هذا بعد قليل إلى مخطّط المكدّس الأساسيّ للصفحة.
نضبط الخاصيّة Content للصفحة في الأسطر 59 حتى 66 بحيث ننشئ مخطّط مكدّس جديد يحتوي على العناصر: اللصيقة والصورة ومخطّط المكدّس الخاص بالأزرار على الترتيب. وهذا هو سبب ظهور العناصر السابقة بهذا الترتيب. انظر إلى المخطّط التمثيلي التالي الذي يوضّح التموضع التقريبي للعناصر السابقة على الصفحة:
المستطيل البني في الأسفل يمثّل مخطّط المكدّس buttonsLayout
بالنسبة لمعالجيّ حدثيّ النقر بالنسبة للزرّين btnPrev (الأسطر من 73 إلى 84) وbtnNext (الأسطر من 86 إلى 97) فيحتويان على منطق برمجيّ بسيط يضمن التنقّل الصحيح بين الصور الخمس الموجودة ضمن مصدر resource التطبيق. وأخيرًا يعمل التابع UpdateScreen (الأسطر من 99 إلى 104) على تحديث البيانات التي تظهر على عنصري اللصيقة والصورة عند كلّ نقر لزر "التالي" و"السابق". يتطلّب التابع FromResource من الصنف ImageSource وسيطًا نصيًّا يمثّل معرّف الملف. يتكوّن معرّف الملف من اسم التطبيق (أو بمعنى أدق التجميعة Assembly) متبوعًا بنقطة ثم اسم المجلّد الذي يحتوي على ملف الصورة متبوعًا بنقطة أخرى، ثم أخيرًا اسم الملف مع امتداده. في مثالنا هذا يكون معرّف الملف res01.jpg مثلًا على الشكل التالي:
ResourceImageApp.Images.res01.jpg
نُشكّل هذا المعرّف في السطر 102 من خلال التابع الساكن Format من الصنف string. حيث نختار الملف المطلوب من خلال قيمة المتغيّر currentIndex الذي يمثّل رقم ملف الصورة الحالي.
ربما تبدو شيفرة هذا التطبيق كبيرة نسبيًّا، ولكن أؤكّد لك أنّك ستكتب أضعافًا منها في أيّ برنامج تجاري من الممكن أن تعمل عليه! يعود سبب ذلك إلى الأسلوب الذي انتهجناه منذ بداية هذه السلسلة حتى الآن، وهو الاعتماد على الشيفرة البرمجيّة في إنشاء واجهة المستخدم بالكامل. ورغم أنّه من الممكن اتباع أساليب جيّدة تساهم في ترتيب وتنظيم هذه الشيفرة بشكل كبير، إلّا أنّ العديد من المطوّرين يفضّلون استخدام الأسلوب الآخر في تطوير تطبيقات باستخدام Xamarin.Forms والذي يتمثّل في استخدام XAML (رُماز شبيه بـ XML) والذي يسمح بفصل واجهة المستخدم عن الشيفرة البرمجيّة. سنتناول هذا الموضوع بالطبع في دروس قادمة.
الخلاصة
تعاملنا في هذا الدرس مع الصور في Xamarin.Forms. حيث ناقشنا الموضوع من جانبين مختلفين. يهتم الجانب الأوّل بتحميل الصورة من الإنترنت (ومن الممكن من شبكة محليّة أيضًا)، أمّا الجانب الثاني فيهتمّ بتحميل الصورة من مصدر محلّي. هناك المزيد للحديث عنه في هذا الموضوع، وخاصّةً فيما يتعلّق بالحصول على الصور من خلال مصادر الصور المرتبطة بالجهاز platform-specific أو بمعنى أدق المرتبطة بنظام التشغيل الذي يعمل على الجهاز والذي هو نظام أندرويد في حالتنا. أي ما يشبه الأسلوب المتبع عند العمل مع مصادر الصور من خلال Android Studio. سنغطّي هذا الموضوع لاحقًا في هذه السلسلة.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.