كيفي. Xamarin رد فعل الأم. ثلاثة أطر - تجربة واحدة (الجزء 2)


هذه هي المقالة الثانية في سلسلة حيث قارنا Kivy و Xamarin.Forms و React Native. في ذلك ، سأحاول كتابة نفس جدولة المهام ، ولكن باستخدام Xamarin.Forms. سأرى كيف أفعل ذلك ، وما يجب أن أواجهه.

لن أكرر المعارف التقليدية ؛ يمكن رؤيتها في المقالة الأولى: Kivy. Xamarin رد فعل الأم. ثلاثة أطر - تجربة واحدة

الجزء الثالث عن React Native: Kivy. Xamarin رد فعل الأم. ثلاثة أطر - تجربة واحدة (الجزء 3)

في البداية ، سأقول بضع كلمات حول منصة Xamarin.Forms وكيف سأتعامل مع حل المهمة. Xamarin.Forms هو إضافة لـ Xamarin.iOs و Xamarin.Android. بعد التجميع ، يتم "نشر" الجزء العام إلى عناصر التحكم الأصلية القياسية ، لذلك في الأساس تحصل على تطبيقات أصلية بالكامل لجميع الأنظمة الأساسية المدعومة.

صيغة Xamarin.Forms قريبة جدًا من صيغة WPF ، والجزء العام مكتوب في .NET Standard. ونتيجة لذلك ، تحصل على فرصة لاستخدام نهج MVVM عند تطوير التطبيق ، بالإضافة إلى الوصول إلى عدد كبير من مكتبات الجهات الخارجية المكتوبة لـ .NET Standard والموجودة بالفعل في NuGet ، والتي يمكنك استخدامها بأمان في تطبيقات Xamarin.Forms الخاصة بك.

كود المصدر للتطبيق هنا متاح على GitHub .

لذا ، دعنا ننشئ تطبيق Xamarin.Forms فارغ ونبدأ. سيكون لدينا نموذج بيانات بسيط ، مع فئتين فقط من الملاحظات والمشروع:

public class Note { public string UserIconPath { get; set; } public string UserName { get; set; } public DateTime EditTime { get; set; } public string Text { get; set; } } public class Project { public string Name { get; set; } public ObservableCollection<Note> Notes { get; set; } public Project() { Notes = new ObservableCollection<Note>(); } } 

سأحاول الالتزام بنهج MVVM ، لكنني لن أستخدم أي مكتبة خاصة ، حتى لا أعقد الكود. ستطبق جميع فئات النماذج ونماذج العرض واجهة INotifyPropertyChanged. سأزيل تطبيقه في أمثلة الشفرة المحددة للإيجاز.

الشاشة الأولى التي نمتلكها هي قائمة بالمشروعات مع القدرة على إنشاء مشروع جديد أو حذف المشروع الحالي. لنصنع له نموذجاً:

 public class MainViewModel { public ObservableCollection<Project> Projects { get; set; } public MainViewModel() { Projects = Project.GetTestProjects(); } public void AddNewProject(string name) { Project project = new Project() { Name = name }; Projects.Add(project); } public void DeleteProject(Project project) { Projects.Remove(project); } } 

رمز الشاشة نفسه:

 <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:TodoList.View" x:Class="TodoList.View.ProjectsPage"> <ContentPage.ToolbarItems> <ToolbarItem Clicked="AddNew_Clicked" Icon="plus.png"/> </ContentPage.ToolbarItems> <ListView ItemsSource="{Binding Projects}" ItemTapped="List_ItemTapped"> <ListView.ItemTemplate> <DataTemplate> <TextCell Text="{Binding Name}" TextColor="Black"> <TextCell.ContextActions> <MenuItem Clicked="DeleteItem_Clicked" IsDestructive="true" CommandParameter="{Binding .}" Text="Delete"/> </TextCell.ContextActions> </TextCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </ContentPage> 

اتضح أن الترميز بسيط للغاية ، الشيء الوحيد الذي أود التركيز عليه هو تنفيذ أزرار التمرير لحذف المشاريع. في ListView ، يوجد مفهوم ContextActions ، إذا قمت بتعيينه ، فسيتم تطبيقه في iOS من خلال السحب ، في Android - من خلال النقر الطويل. يتم تطبيق هذا النهج في Xamarin.Forms ، لأنه أصلي لكل منصة. ومع ذلك ، إذا أردنا التمرير السريع في android ، فسنحتاج إلى تنفيذه بأيدينا في الجزء الأصلي من android. ليس لدي أي مهمة لقضاء الكثير من الوقت في ذلك ، لذلك كنت راضيًا عن النهج القياسي :) ونتيجة لذلك ، يتم التمرير السريع في iOS وقائمة السياق في Android بكل بساطة:

 <TextCell.ContextActions> <MenuItem Clicked="DeleteItem_Clicked" IsDestructive="true" CommandParameter="{Binding .}" Text="Delete"/> </TextCell.ContextActions> 

باستبدال بيانات الاختبار ، نحصل على القائمة التالية:



الآن دعنا ننتقل إلى معالج الأحداث. لنبدأ ببساطة - احذف المشروع:

 MainViewModel ViewModel { get { return BindingContext as MainViewModel; } } async Task DeleteItem_Clicked(object sender, EventArgs e) { MenuItem menuItem = sender as MenuItem; if (menuItem == null) return; Project project = menuItem.CommandParameter as Project; if (project == null) return; bool answer = await DisplayAlert("Are you sure?", string.Format("Would you like to remove the {0} project", project.Name), "Yes", "No"); if(answer) ViewModel.DeleteProject(project); } 

ليس من الجيد حذف شيء ما بدون سؤال المستخدم ، وفي Xamarin. من السهل القيام بذلك باستخدام طريقة DisplayAlert القياسية. بعد الاتصال به ، ستظهر النافذة التالية:



هذه النافذة من iOs. سيكون لدى Android نسخته الخاصة من نافذة مماثلة.

بعد ذلك سنقوم بتنفيذ إضافة مشروع جديد. يبدو أن هذا يتم عن طريق القياس ، ولكن في Xamarin. الأشكال لا يوجد تطبيق لحوار مشابه للحوار الذي أكدت حذفه ، ولكن يسمح لك بإدخال النص. هناك حلان ممكنان:

  • كتابة الخدمة الخاصة بك التي تثير الحوار الأصلي ؛
  • تنفيذ نوع من الحل على جانب Xamarin.Forms.

لم أكن أرغب في إضاعة الوقت في إثارة الحوار من خلال السكان الأصليين ، وقررت استخدام النهج الثاني ، الذي قمت بتطبيقه من الخيط: كيف أقوم بعمل مربع حوار بسيط لـ InputBox؟ ، وهي طريقة Task InputBox (التنقل في التنقل).

 async Task AddNew_Clicked(object sender, EventArgs e) { string result = await InputBox(this.Navigation); if (result == null) return; ViewModel.AddNewProject(result); } 

سنقوم الآن بمعالجة النقر بالصف لفتح المشروع:

 void List_ItemTapped(object sender, Xamarin.Forms.ItemTappedEventArgs e) { Project project = e.Item as Project; if (project == null) return; this.Navigation.PushAsync(new NotesPage() { BindingContext = new ProjectViewModel(project) }); } 

كما ترى من الكود أعلاه ، من أجل الانتقال إلى نافذة المشروع ، نحتاج إلى نموذج العرض الخاص به وكائن الصفحة في النافذة.

أود أن أقول بضع كلمات عن التنقل. يتم تعريف خاصية التنقل في فئة VisualElement ، وتسمح لك بالعمل مع لوحة التنقل في أي عرض لتطبيقك دون الحاجة إلى لفها هناك بيديك. ومع ذلك ، لكي يعمل هذا النهج ، ما زلت بحاجة إلى إنشاء هذه اللوحة بنفسك. لذلك ، في App.xaml.cs نكتب:

 NavigationPage navigation = new NavigationPage(); navigation.PushAsync(new View.ProjectsPage() { BindingContext = new MainViewModel() }); MainPage = navigation; 

حيث تكون صفحة المشروعات هي النافذة التي أصفها الآن.

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

تبين أن تخطيط هذه النافذة أكثر تعقيدًا ، لأن كل سطر يجب أن يعرض مزيدًا من المعلومات:

عرض الملاحظات
 <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="TodoList.View.NotesPage" xmlns:local="clr-namespace:TodoList.View" xmlns:utils="clr-namespace:TodoList.Utils" Title="{Binding Project.Name}"> <ContentPage.Resources> <ResourceDictionary> <utils:PathToImageConverter x:Key="PathToImageConverter"/> </ResourceDictionary> </ContentPage.Resources> <ContentPage.ToolbarItems> <ToolbarItem Clicked="AddNew_Clicked" Icon="plus.png"/> </ContentPage.ToolbarItems> <ListView ItemsSource="{Binding Project.Notes}" x:Name="list" ItemTapped="List_ItemTapped" HasUnevenRows="True"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <local:MyCellGrid Margin="5"> <local:MyCellGrid.RowDefinitions> <RowDefinition Height="40"/> <RowDefinition Height="*"/> </local:MyCellGrid.RowDefinitions> <local:MyCellGrid.ColumnDefinitions> <ColumnDefinition Width="40"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="40"/> </local:MyCellGrid.ColumnDefinitions> <Image Grid.Row="0" Grid.Column="0" Source="{Binding UserIconPath, Converter={StaticResource PathToImageConverter}}" /> <StackLayout Grid.Row="0" Grid.Column="1"> <Label Text="{Binding UserName}" FontAttributes="Bold"/> <Label Text="{Binding EditTime}"/> </StackLayout> <Button Grid.Row="0" Grid.Column="2" BackgroundColor="Transparent" Image="menu.png" Margin="5" HorizontalOptions="FillAndExpand" Clicked="RowMenu_Clicked"/> <local:MyLabel Grid.Row="1" Grid.Column="1" Margin="0,10,0,0" Grid.ColumnSpan="2" Text="{Binding Text}"/> </local:MyCellGrid> <ViewCell.ContextActions> <MenuItem Clicked="DeleteItem_Clicked" IsDestructive="true" CommandParameter="{Binding .}" Text="Delete"/> </ViewCell.ContextActions> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </ContentPage> 


في محتوى النافذة ، لدينا مرة أخرى ListView ، مرفقة بمجموعة الملاحظات. ومع ذلك ، نريد ارتفاع الخلايا في المحتوى ، ولكن ليس أكثر من 150 ، لذلك قمنا بتعيين HasUnevenRows = "True" بحيث تسمح ListView للخلايا أن تشغل مساحة بقدر ما تطلب. ولكن في هذه الحالة ، يمكن أن تطلب الصفوف ارتفاعًا يزيد عن 150 وسيسمح ListView بعرضها بهذه الطريقة. لتجنب هذا في الخلية ، استخدمت وريثي في ​​لوحة Grid: MyCellGrid. تطلب هذه اللوحة الخاصة بعملية القياس ارتفاع العناصر الداخلية وتعيدها إما 150 إذا كانت أكبر:

 public class MyCellGrid : Grid { protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint) { SizeRequest sizeRequest = base.OnMeasure(widthConstraint, heightConstraint); if (sizeRequest.Request.Height <= 150) return sizeRequest; return new SizeRequest(new Size() { Width = sizeRequest.Request.Width, Height = 150 }); } } 

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

 <Button Grid.Row="0" Grid.Column="2" BackgroundColor="Transparent" Image="menu.png" Margin="5" HorizontalOptions="FillAndExpand" Clicked="RowMenu_Clicked"/> 

مع بيانات الاختبار ، يبدو نموذجنا كما يلي:



تشبه معالجة إجراءات المستخدم في هذا النموذج تمامًا تلك التي تمت كتابتها في نافذة قائمة المشروع. أريد التوقف فقط في قائمة السياق عن طريق زرنا في زاوية السطر. في البداية ، اعتقدت أنني سأفعل ذلك على مستوى Xamarin.Forms دون أي مشاكل.

في الواقع ، نحن بحاجة فقط إلى إنشاء عرض لشيء مثل هذا:

 <StackLayout> <Button Text=”Edit”/> <Button Text=”Delete”/> </StackLayout> 

وإظهاره بجانب الزر. ومع ذلك ، فإن المشكلة هي أننا لا نستطيع أن نعرف بالضبط مكان وجودها "بجوار الزر". يجب وضع قائمة السياق هذه أعلى ListView ، وعند فتحها ، يتم وضعها في إحداثيات النافذة. للقيام بذلك ، تحتاج إلى معرفة إحداثيات الزر المضغوط بالنسبة للنافذة. يمكننا الحصول على إحداثيات الزر فقط فيما يتعلق بـ ScrollView الداخلي الموجود في ListView. لذلك عندما لا يتم تغيير الخطوط ، فإن كل شيء على ما يرام ، ولكن عندما يتم تمرير الخطوط ، يجب أن نأخذ في الاعتبار مقدار التمرير الذي حدث عند حساب الإحداثيات. لا يعطينا ListView قيمة التمرير. لذلك يجب أن يتم سحبها من مواطنها ، وهو ما لم أرغب في فعله. لذلك ، قررت أن أسلك المسار إلى معيار أكثر بساطة: إظهار قائمة سياق النظام القياسي. ونتيجة لذلك ، سيظهر معالج النقر على الزر ما يلي:

 async Task RowMenu_Clicked(object sender, System.EventArgs e) { string action = await DisplayActionSheet("Note action:", "Cancel", null, "Edit", "Delete"); if (action == null) return; BindableObject bindableSender = sender as BindableObject; if(bindableSender != null) { Note note = bindableSender.BindingContext as Note; if (action == "Edit") { EditNote(note); } else if(action == "Delete") { await DeleteNote(note); } } } 

استدعاء طريقة DisplayActionSheet يظهر فقط قائمة السياق العادية:



إذا لاحظت ذلك ، يتم عرض نص الملاحظة في عنصر التحكم MyLabel الخاص بي ، وليس في التسمية العادية. يتم ذلك من أجل ماذا. عندما يقوم المستخدم بتغيير نص الملاحظة ، يتم تشغيل المجلدات ، ويصل النص الجديد تلقائيًا إلى Label. ومع ذلك ، لا يقوم Xamarin.Forms بإعادة حساب حجم الخلية في نفس الوقت. يدعي مطورو Xamarin أن هذه عملية مكلفة للغاية. نعم ، ولا يحتوي ListView نفسه على أي طريقة تجعله يعيد حساب حجمه ، ولا يساعد InvalidateLayout أيضًا. الشيء الوحيد الذي لديهم لهذا هو طريقة ForceUpdateSize لكائن الخلية. لذلك ، من أجل الوصول إليه ورفعه في الوقت المناسب ، كتبت وريث التسمية الخاص بي وسحب هذه الطريقة لكل تغيير في النص:

 public class MyLabel : Label { protected override void OnPropertyChanged([CallerMemberName] string propertyName = null) { base.OnPropertyChanged(propertyName); if (propertyName == "Text") { ((this.Parent as MyCellGrid).Parent as Cell).ForceUpdateSize(); } } } 

الآن بعد تحرير ملاحظة ، سيقوم ListView بضبط حجم الخلية تلقائيًا ليلائم النص الجديد.

عند تحرير أو إنشاء ملاحظة جديدة ، تفتح نافذة مع المحرر في المحتوى وزر حفظ على شريط الأدوات:



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

ما أريد أن أقوله في النهاية.

Xamarin.Forms مناسب تمامًا لأولئك الذين هم على دراية بالبنية التحتية لـ .NET ويعملون معها منذ فترة طويلة. لن يتعين عليهم الترقية إلى IDEs وأطر عمل جديدة. كما ترى ، لا يختلف رمز التطبيق كثيرًا عن رمز أي تطبيق آخر يعتمد على XAML. بالإضافة إلى ذلك ، يسمح لك Xamarin بتطوير وبناء تطبيقات iOS في Visual Studio for Windows. عند تطوير التطبيق النهائي لاختباره وبنائه ، ستحتاج إلى الاتصال بجهاز MacOS. ويمكن للمكتبات أن تتم بدونها.

لبدء كتابة التطبيقات على Xamarin.Forms ، لا تحتاج إلى أي عيون حمراء مع وحدة التحكم. فقط قم بتثبيت Visual Studio وكتابة التطبيقات. كل شيء آخر تم الاعتناء به بالفعل من أجلك. في نفس الوقت ، بغض النظر عن كيفية ارتباط Microsoft بالمنتجات المدفوعة ، فإن Xamarin مجاني وهناك إصدارات مجانية من Visual Studio.

يمنحك استخدام Xamarin.Forms .NET القياسي تحت غطاء المحرك إمكانية الوصول إلى مجموعة من المكتبات المكتوبة بالفعل من أجلها والتي ستجعل الحياة أسهل عند تطوير تطبيقاتك.

يتيح لك Xamarin.Forms إضافة شيء بسهولة في الأجزاء الأصلية من تطبيقك ، إذا كنت ترغب في تنفيذ شيء خاص بالنظام الأساسي. هناك تحصل على نفس C # ، ولكن واجهة برمجة التطبيقات (API) أصلية لكل نظام أساسي.

ومع ذلك ، بالطبع ، كانت هناك بعض أوجه القصور.

واجهة برمجة التطبيقات (API) ، المتوفرة في الجزء العام ، ضعيفة إلى حد ما ، لأنها تحتوي فقط على ما هو مشترك لجميع المنصات. على سبيل المثال ، كما هو موضح في المثال الخاص بي ، تحتوي جميع الأنظمة الأساسية على رسائل تنبيه وقوائم سياق ، وهذا الشيء متوفر في Xamarin.Forms. ومع ذلك ، فإن القائمة القياسية التي تسمح لك بإدخال النص متوفرة فقط في iOS ، لذلك في Xamarin.

تم العثور على قيود مماثلة في استخدام المكونات. يمكن القيام بشيء ما ، شيء مستحيل. يعمل التمرير السريع نفسه لحذف مشروع أو ملاحظة فقط في iOS. في Android ، سيتم تقديم إجراء السياق هذا كقائمة تظهر بنقرة طويلة. وإذا كنت تريد التمرير في android ، فمرحباً بك في جزء android واكتبه بيديك.

وبالطبع الأداء. ستكون سرعة التطبيق على Xamarin.Forms في أي حال أقل من سرعة التطبيق الأصلي. لذلك تقول Microsoft نفسها أنه إذا كنت بحاجة إلى تطبيق دون أي رتوش من حيث متطلبات التصميم والأداء ، فإن Xamarin.Forms هو لك. إذا كنت بحاجة إلى الجمال أو السرعة ، فعليك بالفعل النزول إلى المواطن الأصلي. لحسن الحظ ، يحتوي Xamarin أيضًا على إصدارات أصلية تعمل بالفعل على الفور مع واجهات برمجة التطبيقات الأساسية الخاصة بها وتعمل بشكل أسرع من النماذج.

Source: https://habr.com/ru/post/ar420999/


All Articles