تحريك عناصر الواجهة البرمجية والتّحكم فيها في Xamarin


حسام برهان

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

main.png

وصف التطبيق

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

fig01.png

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

fig02.png

يمكنك تكرار نفس الخطوات السابقة لتحريك أيّ مربّع آخر. هذا هو مبدأ هذا البرنامج، وهو بسيط كما ترى، ولكن سنتعلّم من خلاله بعض التقنيّات المفيدة في ضبط التموضع ضمن المخطّط المطلق، بالإضافة إلى تعلّم كيفيّة إكساب عناصر BoxView قابلية الاستجابة إلى أحداث اللمس كما سنرى بعد قليل.

تطبيق تحريك المربّعات باستخدام الأزرار

أنشئ مشروعًا جديدًا من النوع Blank App (Xamarin.Forms Portable) وسمّه MoveSquaresByButtons، ثم أبق فقط على المشروعين MoveSquaresByButtons (Portable) و MoveSquaresByButtons.Droid كما وسبق أن فعلنا في هذا الدرس. بعد ذلك سنضيف صفحة محتوى تعتمد على رُماز XAML كما وسبق أن فعلنا في هذا الدرس سنسمّيها MoveSquaresByButtonsPage. احرص على أن تكون محتويات هذه الصفحة على الشكل التالي:

1	<?xml version="1.0" encoding="utf-8" ?>
2	<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
3	             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
4	             x:Class="MoveSquaresByButtons.MoveSquaresByButtonsPage"
5	             SizeChanged="PageResize">
6	
7	  <AbsoluteLayout>
8	    <BoxView  x:Name="boxAccent"
9	              Color="Accent"
10	              AbsoluteLayout.LayoutBounds="25, 100, 100, 100"
11	              Opacity="0.5" />
12	
13	    <BoxView  x:Name="boxRed"
14	              Color="Red"
15	              AbsoluteLayout.LayoutBounds="75, 150, 100, 100"
16	              Opacity="0.5"/>
17	
18	    <BoxView  x:Name="boxGreen"
19	              Color="Green"
20	              AbsoluteLayout.LayoutBounds="125, 200, 100, 100"
21	              Opacity="0.5"/>
22	
23	    <Button x:Name="btnMoveUp"
24	           StyleId="MoveBoxUp"
25	           Clicked="MoveBox_Clicked"
26	           FontSize="Large"
27	           BorderWidth="0"
28	           Text="↑"/>
29	
30	    <Button x:Name="btnMoveDown"
31	            StyleId="MoveBoxDown"
32	            Clicked="MoveBox_Clicked"
33	            FontSize="Large"
34	            BorderWidth="0"
35	            Text="↓"/>
36	
37	    <Button x:Name="btnMoveRight"
38	            StyleId="MoveBoxRight"
39	            Clicked="MoveBox_Clicked"
40	            FontSize="Large"
41	            Text="→"/>
42	
43	    <Button x:Name="btnMoveLeft"
44	            StyleId="MoveBoxLeft"
45	            Clicked="MoveBox_Clicked"
46	            FontSize="Large"
47	            Text="←"/>
48	  </AbsoluteLayout>
49	  
50	</ContentPage>

لاحظ معي أنّ عناصر BoxView الموجودة في الأسطر من 8 حتى 21 هي نفسها الموجودة في تطبيق المربّعات المتداخلة من الدرس السابق. الجديد هنا هو تعيين معالج للحدث SizeChanged للصفحة (السطر 5) الذي يُستدعى كلّما اقتضت الحاجة إلى تغيير حجم الصفحة، وهو يحدث عادةً عند البدء بتشغيل التطبيق، وأيضًا عندما يُغيّر المستخدم اتجاه الجهاز من عمودي إلى أفقي أو بالعكس. الجديد هنا أيضًا هو الأزرار الأربعة الموجودة في الأسطر من 23 حتى 47. من الواضح أنّ جميع العناصر المرئيّة السابقة موجودة ضمن مخطّط مطلق AbsoluteLayout يضمن لنا التحكّم بمواقعها وبأحجامها كما أشرنا.

تأمّل معي هذا الرماز الذي يمثّل زر الحركة نحو الأعلى:

<Button x:Name="btnMoveUp"
        StyleId="MoveBoxUp"
        Clicked="MoveBox_Clicked"
        FontSize="Large"
        BorderWidth="0"
        Text="&#8593;"/>

لقد وضعت القيمة "MoveBoxUp" للخاصيّة StyleId حيث سنستفيد منها ضمن معالج الحدث MoveBox_Clicked الذي أسندته للحدث Clicked كما هو واضح. سنرى بعد قليل الشيفرة البرمجيّة الخاصّة بهذا المعالج. الأمر الملفت هنا هو القيمة "&H8593;" التي ضبطُّها لخاصيّة النص. سيؤدي ذلك إلى وضع رمز السهم المتجه إلى أعلى ليظهر على هذا الزر. بنفس هذا الأسلوب تمّ تجهيز الأزرار الثلاثة الباقية التي سأسند إلى كلّ منها قيمة مختلفة للخاصيّة StyleId لتعبّر عن وظيفتها، ولكن مع إسناد نفس معالج الحدث MoveBox_Clicked لجميعها. لننتقل الآن إلى ملف الشيفرة البرمجية الموافق لصفحة المحتوى السابقة. احرص على أن تكون محتوياته على الشكل التالي:

1	using System;
2	using Xamarin.Forms;
3	
4	namespace MoveSquaresByButtons
5	{
6	    public partial class MoveSquaresByButtonsPage : ContentPage
7	    {
8	        private BoxView selectedBoxView;
9	        private const double BUTTON_MARGIN = 5;
10	        private const double PAGE_BUTTOM_MARGIN = 10;
11	        private const double MOVE_AMOUNT = 15;
12	
13	        public MoveSquaresByButtonsPage()
14	        {
15	            InitializeComponent();
16	
17	            TapGestureRecognizer tapGesture = new TapGestureRecognizer();
18	
19	            tapGesture.Tapped += TapGesture_Tapped;
20	
21	            boxAccent.GestureRecognizers.Add(tapGesture);
22	            boxRed.GestureRecognizers.Add(tapGesture);
23	            boxGreen.GestureRecognizers.Add(tapGesture);
24	        }
25	
26	        private void TapGesture_Tapped(object sender, EventArgs e)
27	        {
28	            selectedBoxView = (BoxView)sender;
29	
30	            btnMoveUp.BackgroundColor = selectedBoxView.Color;
31	            btnMoveDown.BackgroundColor = selectedBoxView.Color;
32	            btnMoveLeft.BackgroundColor = selectedBoxView.Color;
33	            btnMoveRight.BackgroundColor = selectedBoxView.Color;
34	        }
35	
36	
37	        private void MoveBox_Clicked(object sender, EventArgs e)
38	        {
39	            Button moveButton = (Button)sender;
40	            Rectangle rect;
41	
42	            if (selectedBoxView == null)
43	            {
44	                DisplayAlert("اختيار مربع", "اختر مربّعًا من فضلك", "موافق");
45	                return;
46	            }
47	
48	            rect = AbsoluteLayout.GetLayoutBounds(selectedBoxView);
49	
50	            if (moveButton.StyleId == "MoveBoxUp")
51	            {
52	                rect = new Rectangle(rect.X, rect.Y - MOVE_AMOUNT, rect.Width, rect.Height);
53	            }
54	            else if (moveButton.StyleId == "MoveBoxDown")
55	            {
56	                rect = new Rectangle(rect.X, rect.Y + MOVE_AMOUNT, rect.Width, rect.Height);
57	            }
58	            else if (moveButton.StyleId == "MoveBoxRight")
59	            {
60	                rect = new Rectangle(rect.X + MOVE_AMOUNT, rect.Y, rect.Width, rect.Height);
61	            }
62	            else if (moveButton.StyleId == "MoveBoxLeft")
63	            {
64	                rect = new Rectangle(rect.X - MOVE_AMOUNT, rect.Y, rect.Width, rect.Height);
65	            }
66	
67	            AbsoluteLayout.SetLayoutBounds(selectedBoxView, rect);
68	        }
69	
70	        private void PageResize(object sender, EventArgs e)
71	        {
72	            ContentPage page = (ContentPage)sender;
73	
74	            double buttonWidth = btnMoveUp.Width;
75	            double buttonHeight = btnMoveUp.Height;
76	            double buttonsAreaWidth = buttonWidth * 3 + BUTTON_MARGIN * 2;
77	            double buttonsAreaLeftDisplacement = (page.Width - buttonsAreaWidth) / 2;
78	
79	            AbsoluteLayout.SetLayoutBounds(btnMoveDown,
80	               new Rectangle(buttonsAreaLeftDisplacement + buttonWidth + BUTTON_MARGIN,
81	                   page.Height - buttonHeight - PAGE_BUTTOM_MARGIN,
82	                   buttonWidth,
83	                   buttonHeight));
84	
85	            AbsoluteLayout.SetLayoutBounds(btnMoveUp,
86	                new Rectangle(buttonsAreaLeftDisplacement + buttonWidth + BUTTON_MARGIN,
87	                    page.Height - buttonHeight * 2 - BUTTON_MARGIN - PAGE_BUTTOM_MARGIN,
88	                    buttonWidth,
89	                    buttonHeight));
90	
91	
92	            AbsoluteLayout.SetLayoutBounds(btnMoveLeft,
93	                new Rectangle(buttonsAreaLeftDisplacement,
94	                    page.Height - 3 * buttonHeight / 2 - PAGE_BUTTOM_MARGIN,
95	                    buttonWidth,
96	                    buttonHeight));
97	
98	            AbsoluteLayout.SetLayoutBounds(btnMoveRight,
99	                new Rectangle(buttonsAreaLeftDisplacement + buttonWidth * 2 + BUTTON_MARGIN * 2,
100	                    page.Height - 3 * buttonHeight / 2 - PAGE_BUTTOM_MARGIN,
101	                    buttonWidth,
102	                    buttonHeight));
103	        }
104	    }
105	}

يحتوي الصنف MoveSquaresByButtonsPage بشكل أساسيّ على بانية وعدّة توابع هي في الواقع معالجات للأحداث التي يحتاج إليها التطبيق. سنتحدّث عنها بشيء من التفصيل.

البانية

إذا نظرت إلى البانية (الأسطر من 13 حتى 24) فستجد أنّنا نعرّف ضمنها كائنًا من النوع TapGestureRecognizer (السطر 17) سنُسند هذا الكائن إلى المتغيّر tapGesture كما هو واضح. يفيد هذا الكائن في إكساب عنصر BoxView قابلية النقر لأنّه –وبشكل مغاير لعنصر الزر Button- لا يمتلكها أصلًا. نعمل في السطر 19 على تعيين معالج لحدث النقر (اللمس) Tapped للكائن السابق واسمه TapGesture_Tapped (سنتحدّث عنه بعد لحظة). العملية الأخيرة التي نجريها في البانية هي إضافة الكائن tapGesture إلى كل من المربّعات الثلاثة عن طريق التابع Add للمجموعة GestureRecognizers لكل مربع من هذه المربعات (الأسطر من 21 حتى 23).

معالج حدث النقر على المربّعات TapGesture_Tapped

يمكنك رؤية هذا المعالج في الأسطر من 26 حتى 34. الذي يحدث ضمن هذا المعالج هو أنّنا نحصل على مرجع المربّع الذي نقره المستخدم (السطر 28) ونُسنده إلى الحقل selectedBoxView وهو حقل خاص من النوع BoxView مصرّح عنه في السطر 8، ثم نلوّن أزرار التحريك الأربعة بنفس لون المربّع الذي نقره (لمسه) المستخدم.

معالج حدث النقر على الأزرار MoveBox_Clicked

بالنسبة لمعالج الحدث MoveBox_Clicked وهو موجود في الأسطر من 37 حتى 68 فهو يُستدعى عندما ينقر المستخدم أيًا من أزرار التحريك الأربعة. ووظيفته هو تحريك المربّع الذي اختاره المستخدم مسبقًا في الاتجاه المطلوب. نحصل في السطر 39 على مرجع الزر الذي تمّ نقره، ثمّ نختبر فيما إذا كان المستخدم قد قام فعلًا باختيار مربّع من قبل (الأسطر من 42 حتى 46) حيث يُعطي التطبيق تنبيهًا للمستخدم في حال لم يكن قد اختار مربّعًا من قبل. انظر الآن إلى السطر 48:


rect = AbsoluteLayout.GetLayoutBounds(selectedBoxView);

يُرجع التابع GetLayoutBounds من الصنف AbsoluteLayout بنية من النوع Rectangle لأيّ عنصر مرئي نمرّره له موجود ضمن المخطّط المطلق. نحتاج هنا إلى معرفة المستطيل المُحدّد للمربّع الذي اختاره المستخدم من قبل. يُعطينا المستطيل المحدّد معلومات حول إحداثيّي الزاوية اليسرى العليا للمربّع (الإحداثي X والإحداثي Y) بالإضافة إلى عرض Width وارتفاع Height المربّع.

تقارن بعد ذلك عبارات if الموجودة في الأسطر من 50 حتى 65 الخاصيّة StyleId للزر الذي نقره المستخدم مع عدّة نصوص معدّة سلفًا، لمعرفة الاتجاه المطلوب التحريك وفقه. لكي يتمكّن البرنامج من تعديل الإحداثي المطلوب على المستطيل المحدّد للمربّع وذلك بالانتقال المسافة المعيّنة بالثابت MOVE_AMOUNT الذي عرّفناه في السطر 11. بعد ذلك يتم تعيين المستطيل المحدّد الجديد (بعد التعديل) إلى المربّع نفسه (السطر 67) مما يؤدي إلى إعطاء انطباع بالحركة.

معالج حدث تغيير حجم الصفحة PageResize

بالنسبة للمعالج PageResize (الأسطر من 70 حتى 103) فهو يُستدعى عندما يطرأ تعديل على حجم الصفحة. وتنحصر وظيفة هذا المعالج في الحقيقة بوضع أزرار الحركة الأربعة بالشكل الذي أوضحناه أوّل الدرس. حيث يعتمد على ثابتين:

  • الثابت BUTTON_MARGIN (مصرّح عنه في السطر 9) ويمثّل التباعد بين أيّ زرّين.
  • الثابت PAGE_BUTTOM_MARGIN (مصرّح عنه في السطر 10) ويمثّل الهامش السفلي لزر الحركة نحو الأسفل وذلك عن أسفل الشاشة.

الأمر الملفت ضمن هذا المعالج هو كيفية حساب مسافة الإزاحة الأفقيّة لتوسيط مجموعة الأزرار أفقيًّا، حيث يتم ذلك بأسلوب حسابي بسيط. إذ أنّ الشيفرة تحسب العرض الإجمالي لثلاثة أزرار مضافًا إليها قيمتي هامشين (بين كل زرين هامش) وتضعه ضمن المتغيّر buttonsAreaWidth:


double buttonsAreaWidth = buttonWidth * 3 + BUTTON_MARGIN * 2;

ثمّ تقوم بطرح القيمة السابقة من عرض الصفحة page.Width وتقسّمه على 2. فنحصل على مقدار الإزاحة الأفقيّة الواجب تطبيقها من الجهة اليسرى وتضعها ضمن المتغيّر buttonsAreaLeftDisplacement:


double buttonsAreaLeftDisplacement = (page.Width - buttonsAreaWidth) / 2;

ما تبقى من عبارات برمجيّة هي بسيطة في الواقع ولا تعدو عن كونها عبارات تنظيميّة تهدف إلى وضع الأزرار على نحو متناسق.

انتقل أخيرًا إلى الملف App.cs واحرص على أن تكون بانيته على الشكل التالي:

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

أصبح التطبيق جاهزًا للتنفيذ. يمكنك تنفيذه بضغط المفتاح F5 كما تعلّمنا.

الخلاصة

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





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


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



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

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

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


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

تسجيل الدخول

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


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