استخدام المخطّط المطلق باستخدام رماز XAML في تطبيقات Xamarin


حسام برهان

المخطّط المطلق Absolute Layout هو من المخطّطات المعتمدة في Xamarin، وتقوم فكرته على السماح للعناصر المرئيّة الموجودة ضمنه بأن يكون تموضعها بشكل مطلق، أي من الممكن ضبط مكان التموضع بالإضافة إلى عرض وارتفاع كل عنصر مرئي بشكل دقيق باستخدام الواحدات المستقلة عن الجهاز independent-device units. ولعلّ هذا المفهوم ليس جديدًا، بل هو من المفاهيم القديمة في عالم البرمجة، فقد كان المبرمجون قديمًا (وما زالوا في بعض الأحيان حتى اليوم) يعتمدون على بناء واجهات التطبيقات بتعريف موضع وأبعاد (العرض والارتفاع) كل عنصر بشكل دقيق ضمن الواجهة. سنطّلع في هذا الدرس ضمن سلسلة تعلّم برمجة تطبيقات أندرويد باستخدام Xamarin.Forms على كيفيّة التعامل مع هذا المخطّط باستخدام رماز XAML من خلال تطبيقين بسيطين.

main.png

تطبيق المربّعات المتداخلة

تعتمد فكرة هذا التطبيق على وجود 3 مربّعات متداخلة فيما بينها بحيث يمتلك كلّ منها شفافيّة بمقدار 50%. ستكون هذه المربّعات ضمن مخطّط مطلق Absolute Layout، وكلّ منها عبارة عن كائن ViewBox. سنعيّن مواقع وأبعاد هذه المربّعات باستخدام الواحدات المستقلة عن الجهاز independent-device units. أنشئ مشروعًا جديدًا من النوع Blank App (Xamarin.Forms Portable) وسمّه SimpleAbsoluteLayout، ثم أبق فقط على المشروعين SimpleAbsoluteLayout (Portable) و SimpleAbsoluteLayout.Droid كما وسبق أن فعلنا في هذا الدرس. بعد ذلك سنضيف صفحة محتوى تعتمد على رماز XAML كما وسبق أن فعلنا في هذا الدرس سنسمّيها SimpleAbsoluteLayoutPage. احرص على أن تكون محتويات هذه الصفحة على الشكل التالي:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="SimpleAbsoluteLayout.SimpleAbsoluteLayoutPage">
  
  <AbsoluteLayout x:Name="absLayout">
      <BoxView  x:Name="boxAccent"
                Color="Accent"
                AbsoluteLayout.LayoutBounds="25, 100, 110, 110"
                Opacity="0.5"/>

      <BoxView  x:Name="boxRed"
                Color="Red"
                AbsoluteLayout.LayoutBounds="50, 125, 110, 110"
                Opacity="0.5"/>

      <BoxView  x:Name="boxGreen"
                Color="Green"
                AbsoluteLayout.LayoutBounds="75, 150, 110, 110"
                Opacity="0.5"/>
  </AbsoluteLayout>
</ContentPage>

محتويات هذه الصفحة بسيطة وواضحة. حيث نعمل على إنشاء مخطّط مطلق باستخدام الوسم . لاحظ كيف وضعنا ضمن هذا العنصر ثلاثة عناصر من النوع BoxView. وقد عيّنّا لكل عنصر منها اسمًا لأنّنا سنحتاجه في القسم الثاني من هذا الدرس. الملفت للنظر أنّ هذه العناصر قد تمّ وضعها مباشرةً ضمن العنصر AbsoluteLayout دون الحاجة إلى وجود الوسم وهذا جائز تمامًا. لاحظ بالنسبة إلى عنصر BoxView الأوّل:

<BoxView  x:Name="boxAccent"
          Color="Accent"
          AbsoluteLayout.LayoutBounds="25, 100, 110, 110"
          Opacity="0.5"/>

الخاصيّة التي تتحكّم بموضعه هي AbsoluteLayout.LayoutBounds حيث أسندنا إليها النص:


 "25, 100, 110, 110" 

الذي يحتوي على أربعة أرقام تفصل بينها فواصل، وهي من اليسار إلى اليمين:

  • بعد الزاوية اليسرى العليا من العنصر عن الحافة اليسرى للمخطّط المطلق ورمزه X (القيمة 25).
  • بعد الزاوية اليسرى العليا من العنصر عن الحافة العليا للمخطّط المطلق ورمزه Y (القيمة 100).
  • عرض العنصر ورمزه Width (القيمة 110).
  • ارتفاع العنصر ورمزه Height (القيمة 110).

انظر الشكل التخطيطي التالي لمزيد من التوضيح:

fig01.png

في الحقيقة يمكنك اعتبار الشكل السابق كجملة إحداثية ديكارتيّة، ولكنّ محور التراتيب y-axis موجّه نحو الأسفل كما هو واضح بدلًا من الاتجاه المألوف نحو الأعلى. ينطبق نفس الأمر تمامًا على عنصريّ BoxView الباقيين:

<BoxView  x:Name="boxRed"
          Color="Red"
          AbsoluteLayout.LayoutBounds="50, 125, 110, 110"
          Opacity="0.5"/>

<BoxView  x:Name="boxGreen"
          Color="Green"
          AbsoluteLayout.LayoutBounds="75, 150, 110, 110"
          Opacity="0.5"/>

وبما أنّ لكل من هذه العناصر الثلاثة نفس القيمة للعرض وللارتفاع، إذًا فهي عبارة عن مربّعات، وهي طبوقة فيما بينها. إذا نظرت إلى الأرقام الخاصّة بالموضع وبالحجم التي اخترتها "للمربّعات" الثلاث، فستلاحظ أنّني اخترتها بعناية كي تكون متداخلة فيما بينها. وقد جعلت الشفافيّة (الخاصية Opacity) لكلّ منها 50% (القيمة 0.5) لكي تعطي ذلك التأثير اللوني الجميل عندما تتداخل مع بعضها. انتقل الآن إلى الملف App.cs واحرص على ان تكون بانية الصنف App على الشكل التالي:

public App()
{
    // The root page of your application
    MainPage = new SimpleAbsoluteLayoutPage();
}

نفّذ التطبيق باستخدام F5، لتحصل على شكل شبيه بما يلي:

fig02.png

لنحرّك المربّعات!

لنعمل على إضفاء القليل من الإثارة على التطبيق السابق. سنعمل على تحريك المربّعات السابقة بحركة رأسيّة تلقائيّة. أي أنّ المربّعات السابقة ستتحرّك نحو الأعلى حتى تصل إلى الحافة العليا للشاشة، ثم تعكس اتجاه الحركة نحو الأسفل حتى تصل إلى الحافة السفلى من الشاشة. ثم تعود من جديد لتعكس جهة الحركة لتصبح نحو الأعلى، وهكذا دواليك. في الحقيقة لن يطرأ أيّ تغيير على رماز XAML، فكل ما سنفعله هو إضافة الشيفرة البرمجيّة المناسبة لملف الشيفرة البرمجيّة الموافق لملف الرماز السابق. انتقل الآن إلى الملف SimpleAbsoluteLayoutPage.xaml.cs واحرص على أن تكون محتوياته على الشكل التالي:

1	using System;
2	using Xamarin.Forms;
3	
4	namespace SimpleAbsoluteLayout
5	{
6	    public partial class SimpleAbsoluteLayoutPage : ContentPage
7	    {
8	        private bool isUpDirection;
9	        public SimpleAbsoluteLayoutPage()
10	        {
11	            InitializeComponent();
12	
13	            isUpDirection = false;
14	
15	            Device.StartTimer(TimeSpan.FromMilliseconds(50), UpdatePositions);
16	        }
17	
18	        private bool UpdatePositions()
19	        {
20	            Rectangle rect;
21	
22	            rect = AbsoluteLayout.GetLayoutBounds(boxAccent);
23	            if (rect.Y - 10 <= 0)
24	            {
25	                isUpDirection = false;
26	            }
27	
28	            rect = AbsoluteLayout.GetLayoutBounds(boxGreen);
29	            if (rect.Y + rect.Height + 10 > this.Height)
30	            {
31	                isUpDirection = true;                
32	            }
33	
34	            BoxView[] boxViews = { boxAccent, boxRed, boxGreen };
35	
36	            for (int i = 0; i < boxViews.Length; i++)
37	            {
38	                rect = AbsoluteLayout.GetLayoutBounds(boxViews[i]);
39	
40	                if (isUpDirection)
41	                {
42	                    rect = new Rectangle(rect.X, rect.Y - 10, rect.Width, rect.Height);
43	                }
44	                else
45	                {
46	                    rect = new Rectangle(rect.X, rect.Y + 10, rect.Width, rect.Height);
47	                }
48	
49	                AbsoluteLayout.SetLayoutBounds(boxViews[i], rect);
50	            }
51	
52	            return true;
53	        }
54	    }
55	}

بالإضافة إلى بانية الصنف SimpleAbsoluteLayoutPage أنشأت تابعًا جديدًا أسميته UpdatePositions يمكنك أن تجده في الأسطر من 18 حتى 53. يُعتبر هذا التابع مسؤولًا عن تحريك المربّعات الثلاثة بالحركة الرأسيّة التي تحدثنا عنها قبل قليل. يتمّ استدعاء هذا التابع بشكل تلقائيّ كل 50 ميللي ثانيّة. التقنيّة التي استخدمناها في هذا الاستدعاء هي نفسها الموجودة في هذا الدرس. حيث استخدمنا التابع StartTimer من الصنف Device لهذا الغرض (انظر السطر 15).

لاحظ الحقل isUpDirection من النوع bool المصرّح عنه في السطر 8. يُفيد هذا الحقل في الاحتفاظ بالاتجاه الحالي للمربّعات أثناء الحركة (أي هل الحركة نحو الأعلى أم نحو الأسفل). يبدأ التابع UpdatePositions بالتصريح عن المتغيّر rect من النوع Rectangle (السطر 20). و Rectangle هو عبارة عن بنية struct أي هو من نوع قيمة value type. يُفيد هذا النوع في وصف الأشكال المستطيلة عمومًا. فهو يمتلك أربع خصائص تصف حجم وموضع أيّ مستطيل على الشاشة. هذه الخصائص هي: X و Y و Width و Height وهي تصف على الترتيب: مقدار الإزاحة الأفقيّة عن الحد الأيسر، مقدار الإزاحة الرأسيّة عن الحد الأعلى، وعرض المستطيل، وارتفاع المستطيل. وذلك بمنطق مشابه لما ورد ذكره في الشكل الأوّل الذي تعرضنا له في هذا الدرس.

نقوم في السطر 22 بالحصول على المستطيل (المربّع في واقع الأمر) الحاضن للعنصر boxAccent وهو المربّع الأوّل. ثمّ نختبر كون الإحداثي Y له قد أوشك أن يصبح سالبًا وذلك في السطر 23. فإن أوشك على ذلك فهذا يعني أنّ المربّع boxAccent قد ارتطم بالحد الأعلى للشاشة ويجب تغيير اتجاه الحركة ليصبح نحو الأسفل بدلًا من الأعلى، وهذا ما نفعله في السطر 25.

نفس المنطق السابق نطبّقه على العنصر boxGreen وهو المربّع الثالث، وذلك في السطر 28. حيث نحصل على المستطيل الحاضن للعنصر boxGreen. ثمّ نختبر كون الحافة السفلى له (تأمل التعبير rect.Y + rect.Height + 10) قد أوشكت أن تتجاوز قيمتها ارتفاع الصفحة (وهو يماثل في مثالنا الارتفاع الكامل للمخطّط المطلق) وذلك في السطر 29. فإن أوشكت على ذلك فهذا يعني أنّ المربّع boxGreen سيتجاوز الحد السفلي للشاشة ويجب تغيير اتجاه الحركة ليصبح نحو الأعلى بدلًا من الأسفل، وهذا ما نفعله في السطر 31. بعد ذلك ننشئ المصفوفة boxViews بحيث تحتوي على عناصر المربّعات الثلاثة boxAccent و boxRed و boxGreen وذلك في السطر 34.

ثمّ يدخل البرنامج حلقة for (الأسطر من 36 حتى 50) التي تنحصر وظيفتها في تحديث إحداثيات المربّعات الثلاثة بحيث تحرّكها نحو الأعلى أو نحو الأسفل بحسب قيمة الحقل isUpDirection. سيكون مقدار الانتقال الرأسي 10 وحدات في كل مرّة كما هو واضح في السطرين 42 و 46. ويتم إسناد الإحداثيّات الجديدة للمربّعات الثلاثة عن طريق استدعاء التابع الساكن AbsoluteLayout.SetLayoutBounds في السطر 49. حيث يتطلّب هذا التابع وسيطين. الأوّل هو المربّع المراد تطبيق الإحداثيات الجديدة عليه وهو موجود ضمن المصفوفة boxView، والثاني هو قيمة من النوع Rectange تحوي المستطيل (المربّع في مثالنا هذا) الحاضن الجديد.

Quote

لاحظ بأنّ استخدامنا للمصفوفة boxViews يوفّر علينا الكثير من العبارات البرمجيّة التي ستتكرّر. فمن خلال حلقة for نستطيع المرور على جميع عناصر المصفوفة والتي هي عبارة عن المربّعات التي نريد تحريكها.

 

الخلاصة

تحدثنا في هذا الدرس عن المخطّط المطلق Absolute Layout وكيفيّة استخدامه من خلال رماز XAML. يفيد هذا المخطّط كما أشرنا في التحكّم بحجم وتموضع أي عنصر مرئي على الشاشة على نحو دقيق. تناولنا تطبيقين بسيطين يوضّحان كيفيّة التحكّم بحجم وموضع كل عنصر من عناصر BoxView، كما استخدمنا التابع StartTimer من الصنف Device لتوليد استدعاءات تلقائيّة ذات أزمنة منتظمة إلى تابع مخصّص كي يتحكّم بمواضع هذه العناصر. سنتناول في الدرس القادم تطبيقًا عمليًّا هو في واقع الأمر تحديث للتطبيق الثاني في هذا الدرس لكي نتحكّم في مواضع العناصر بشكل مخصّص أكثر.





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن