استخدام مخطط المكدّس StackLayout في Xamarin - الجزء الأوّل


حسام برهان

تعاملنا في الدروس السابقة مع عنصر واحد فقط يظهر على الشاشة. كان هذا العنصر عبارة عن لصيقة Label نُسندها إلى الخاصيّة Content لصفحة محتوى content page. في البرامج الحقيقية كما هو معروف، سنحتاج بالتأكيد إلى العديد من العناصر التي ستظهر على الشاشة لتلبية متطلّبات البرنامج. المشكلة التي تصادفنا هنا أنّ الخاصية Content لصفحة المحتوى لا تقبل سوى عنصر واحد يرث من الصنف View.
أوجدت Xamarin حلًا بسيطًا لهذه المشكلة يتمثّل في استخدام المخطّطات layouts. يمكن لأيّ مخطّط أن يستوعب أي عدد من العناصر التي يرث كل منها من الصنف View. في الحقيقة يُعتبر المخطّط بحدّ ذاته يرث من الصنف Layout<View> وهذا الصنف بدوره يرث بشكل غير مباشر من الصنف View. لذلك يمكن إسناد أيّ مخطط للخاصية Content مباشرةً. تدعم Xamarin أربعة أنواع من المخطّطات وهي:

  1. المخطّط المطلق AbsoluteLayout
  2. مخطّط الشبكة GridLayout
  3. المخطّط النسبي RelativeLayout
  4. مخطّط المكدّس StackLayout.

سنتناول ثلاثة مخطّطات في هذه السلسلة وهي: المطلق والشبكي والمكدّس. وسنبدأ في هذا المقال بمخطّط المكدّس StackLayout.

مخطّط المكدّس StackLayout

يمكن إضافة أي عدد من العناصر إلى هذا المخطّط. وسبب تسميته بهذا الاسم هو أنّه يرتّب العناصر المضافة إليه بشكل متكدّس stacked. يمتلك الصنف StackLayout خاصيتين إضافيتين عن باقي المخطّطات هما: Orientation وتُعبّر عن الاتجاه (أفقي أو رأسي) وSpacing وتمثّل التباعد بين العناصر المُضافة إلى المخطّط ولها القيمة الافتراضيّة 6.0. لنتناول الآن مثال بسيط يوضّح آلية التعامل مع هذا المخطّط المفيد.

تطبيق ColorsApp لعرض بعض الألوان

أنشئ تطبيقًا جديدًا كما تعلّمنا من الدروس السابقة بحيث يكون من النوع Blank App (Xamarin.Forms Portable) وسمّه ColorsApp. بعد إنشاء التطبيق في Visual Studio أبق فقط على المشروعين ColorsApp (Portable) و ColorsApp.Droid.
أضف إلى المشروع ColorsApp (Portable) صفحة محتوى سمّها ColorsListPage (انقر بزر الفأرة الأيمن على المشروع واختر Add ثم اختر New Item. وبعد ظهور النافذة، اختر من الجهة اليسرى Cross-Platform، ومن وسط الشاشة اختر Forms ContentPage). بعد إنشاء هذه الصفحة سنلاحظ أنّها تحتوي بالفعل على مخطّط مكدّس جاهز. انظر إلى محتويات هذه الصفحة:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Text;

using Xamarin.Forms;

namespace ColorsApp
{
    public class ColorsListPage : ContentPage
    {
        public ColorsListPage()
        {
            Content = new StackLayout
            {
                Children = {
                    new Label { Text = "Hello ContentPage" }
                },
            };
        }
    }
}

لاحظ كيف أنّنا نُسند كائنًا جديدًا من الصنف StackLayout إلى الخاصيّة Content ضمن البانية ColorsListPage. لاحظ أيضّا كيف أنّ المخطّط StackLayout يحتوي على خاصيّة اسمها Children تُعبّر عن العناصر الأبناء (كلّ منها يرث من الصنف View) التي نرغب بإضافتها إلى المكدّس. استبدل محتويات الملف ColorsListPage.cs بالشيفرة التالية:

1	using System;
2	using System.Collections.Generic;
3	using System.Linq;
4	using System.Reflection.Emit;
5	using System.Text;
6	
7	using Xamarin.Forms;
8	
9	namespace ColorsApp
10	{
11	    public class ColorInfo
12	    {
13	        public Color Color { get; set; }
14	        public string Name { get; set; }
15	    }
16	
17	    public class ColorsListPage : ContentPage
18	    {
19	        public ColorsListPage()
20	        {
21	            ColorInfo[] colors = new ColorInfo[]
22	            {
23	                new ColorInfo {Color= Color.Aqua,Name = "Aqua" },
24	                new ColorInfo {Color=Color.Blue,Name ="Blue" },
25	                new ColorInfo {Color=Color.Gray,Name ="Gray" },
26	                new ColorInfo {Color=Color.Black,Name ="Black" },
27	                new ColorInfo {Color=Color.Silver,Name ="Silver" },
28	                new ColorInfo {Color=Color.Red,Name ="Red" },
29	                new ColorInfo {Color=Color.Maroon,Name ="Maroon" },
30	                new ColorInfo {Color=Color.Yellow,Name ="Yellow" },
31	                new ColorInfo {Color=Color.Olive,Name ="Olive" },
32	                new ColorInfo {Color=Color.Lime,Name ="Lime" },
33	                new ColorInfo {Color=Color.Green,Name ="Green" },
34	                new ColorInfo {Color=Color.Navy,Name ="Navy" },
35	                new ColorInfo {Color=Color.Teal,Name ="Teal" },
36	                new ColorInfo {Color=Color.Pink,Name ="Pink" },
37	                new ColorInfo {Color=Color.Fuchsia,Name ="Fuchsia" },
38	                new ColorInfo {Color=Color.Purple,Name ="Purple" }
39	            };
40	
41	            StackLayout stackLayout = new StackLayout();
42	
43	            for (int i = 0; i < colors.Length; i++)
44	            {
45	                stackLayout.Children.Add(new Label
46	                {
47	                    Text = colors[i].Name,
48	                    TextColor = colors[i].Color,
49	                    FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label))
50	                });
51	            }
52	
53	            Padding = new Thickness(5, 5, 5, 5);
54	
55	            Content = stackLayout;
56	        }
57	    }
58	}

لقد أضفنا صنفًا بسيطًا جديدًا اسمه ColorInfo إلى هذا الملف (الأسطر من 11 حتى 15). ورغم أنّ وجود صنفين في نفس الملف هو عادة برمجيّة سيئة، إلّا أنّني آثرت ذلك طلبًا للتبسيط. تنحصر وظيفة هذا الصنف في الاحتفاظ بمعلومات بسيطة عن أيّ: قيمة اللون Color واسمه Name. أنشأنا في السطر 21 مصفوفة من الصنف ColorInfo وأسندناها إلى المتغيّر colors. ستحتوي هذه المصفوفة كما هو واضح على 16 لون. ننشئ بعد ذلك كائن جديد من الصنف StackLayout ونسنده إلى المتغيّر stackLayout (السطر 41)، ثم ندخل حلقة for (السطر 43) وظيفتها المرور على عناصر المصفوفة colors، وبحيث تُنشئ في كل دورة لصيقة Label جديدة وتُعيّن لها النص ولونه وحجمه، وتضيف هذه اللصيقة مباشرةً إلى مخطّط المكدّس stackLayout عن طريق التابع Add للخاصيّة Children منه. بعد ذلك نُحدّد مقدار الحشوة padding للصفحة (السطر 53) وفي النهاية نُسند المتغيّر stackLayout إلى الخاصية Content للصفحة (السطر 55). لتجربة هذا التطبيق، انتقل أولًا إلى الملف App.cs واحرص أن تكون بانية الصنف App على الشكل التالي:

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

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

fig01.png

نلاحظ هنا أمرين مهمّين. الأوّل أنّ بعض الألوان لا تظهر بشكل جيّد على الخلفية السوداء (وهي الخلفية الافتراضية لتطبيقات Xamarin في أندرويد) لأنّ التباين اللوني ليس جيّدًا، بل إنّ الكلمة Black لن تظهر لأنّ لونها يماثل لون الخلفية الأسود. أمّا الأمر الثاني، فلدينا بالأساس 16 لون إلّا أنّه ظهر 12 لون منها فقط. من الممكن أن يختلف عدد الألوان الظاهرة عندك بحسب الشاشة التي تستخدمها، فقد تظهر جميعها مثلًا، ولكن على كلّ الأحوال ستواجه بالتأكيد تطبيقات في المستقبل لن تظهر فيها جميع العناصر دفعةً واحدة على الشاشة. في الواقع نحتاج إلى آلية لتمرير المحتويات scrolling مثل أيّ تطبيق أندرويد آخر. سنعالج الآن كلًّا من الأمرين السابقين.

تطبيق ColorsApp المحسّن

سنعالج في البداية مسألة التباين اللوني التي من الممكن حلّها بإضافة تابع جديد إلى الصنف ColorsListPage واسمه GetSuitableBackground. تنحصر وظيفة هذا التابع بحساب مقدار الإنارة luminance وفق ثوابت عدديّة متعارف عليها على أنّها مثالية. فإذا كانت الإنارة للّون المراد إظهاره أكبر من 0.5 فستكون الخلفية سوداء، أمّا إذا كانت أقل من أو تساوي 0.5 فستكون الخلفية بيضاء. مما يمنح تباينًا مناسبًا لعرض الألوان على الشاشة. انظر الشيفرة البرمجيّة الخاصّة بهذا التابع:

private Color GetSuitableBackground(Color color)
{
    double luminance = 0.30 * color.R + 
                        0.59 * color.G + 
                        0.11 * color.B;

    return luminance > 0.5 ? Color.Black : Color.White;
}

تُعبّر الخصائص color.R و color.G و color.B عن المكوّنات الأحمر والأخضر والأزرق على الترتيب للّون الممرّر لهذا التابع والمراد إيجاد لون الخلفية المناسب له. أمّا الثوابت 0.30 و 0.59 و 0.11 فهي ثوابت تُعتبر مثالية لإيجاد إنارة اللون ويمكنك تغييرها إذا أحببت.
أمّا بالنسبة لمسألة تمرير محتويات الشاشة فحلّها بسيط، ويتمثّل في استخدام الصنف ScrollView حيث نُنشئ كائن جديد منه، ونُسند المتغيّر stackLayout إلى الخاصيّة Content من ذلك الكائن، ثم نُسند ذلك الكائن الجديد إلى الخاصيّة Content لصفحة المحتوى ColorsListPage بدلًا من إسناد stackLayout مباشرةً إليها كما كنّا نفعل في البرنامج السابق. انظر إلى الشيفرة التالية:

Content = new ScrollView
{
    Content = stackLayout
};

انظر الآن إلى الشيفرة الكاملة بعد إجراء التعديلات السابقة عليها:

 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Text;

using Xamarin.Forms;

namespace ColorsApp
{
    public class ColorInfo
    {
        public Color Color { get; set; }
        public string Name { get; set; }
    }

    public class ColorsListPage : ContentPage
    {
        public ColorsListPage()
        {
            ColorInfo[] colors = new ColorInfo[]
            {
                new ColorInfo {Color= Color.Aqua,Name = "Aqua" },
                new ColorInfo {Color=Color.Blue,Name ="Blue" },
                new ColorInfo {Color=Color.Gray,Name ="Gray" },
                new ColorInfo {Color=Color.Black,Name ="Black" },
                new ColorInfo {Color=Color.Silver,Name ="Silver" },
                new ColorInfo {Color=Color.Red,Name ="Red" },
                new ColorInfo {Color=Color.Maroon,Name ="Maroon" },
                new ColorInfo {Color=Color.Yellow,Name ="Yellow" },
                new ColorInfo {Color=Color.Olive,Name ="Olive" },
                new ColorInfo {Color=Color.Lime,Name ="Lime" },
                new ColorInfo {Color=Color.Green,Name ="Green" },
                new ColorInfo {Color=Color.Navy,Name ="Navy" },
                new ColorInfo {Color=Color.Teal,Name ="Teal" },
                new ColorInfo {Color=Color.Pink,Name ="Pink" },
                new ColorInfo {Color=Color.Fuchsia,Name ="Fuchsia" },
                new ColorInfo {Color=Color.Purple,Name ="Purple" }
            };

            StackLayout stackLayout = new StackLayout();

            for (int i = 0; i < colors.Length; i++)
            {
                stackLayout.Children.Add(new Label
                {
                    Text = colors[i].Name,
                    TextColor = colors[i].Color,
                    BackgroundColor = GetSuitableBackground(colors[i].Color),
                    FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label))
                });
            }

            Padding = new Thickness(5, 5, 5, 5);

            Content = new ScrollView
            {
                Content = stackLayout
            };
        }

        private Color GetSuitableBackground(Color color)
        {
            double luminance = 0.30 * color.R + 
                               0.59 * color.G + 
                               0.11 * color.B;

            return luminance > 0.5 ? Color.Black : Color.White;
        }
    }
}

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

fig02.png

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

 
stackLayout.Spacing = 0;

بعد السطر التالي مباشرةً:

StackLayout stackLayout = new StackLayout();

الخلاصة

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





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


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



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

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

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


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

تسجيل الدخول

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


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