Ini adalah artikel kedua dalam seri di mana kami membandingkan Kivy, Xamarin.Forms, dan React Native. Di dalamnya, saya akan mencoba untuk menulis penjadwal tugas yang sama, tetapi menggunakan Xamarin. Saya akan melihat bagaimana saya melakukannya, dan apa yang harus saya hadapi.
Saya tidak akan mengulang TK, itu bisa dilihat di artikel pertama:
Kivy. Xamarin Bereaksi Asli. Tiga kerangka kerja - satu percobaanBagian ketiga adalah tentang React Native:
Kivy. Xamarin Bereaksi Asli. Tiga kerangka kerja - satu percobaan (bagian 3)Untuk memulai, saya akan mengatakan beberapa kata tentang platform Xamarin.Forms dan bagaimana saya akan mendekati solusi tugas. Xamarin.Forms adalah add-on untuk Xamarin.iOs dan Xamarin.Android. Setelah perakitan, bagian umum "dikerahkan" ke kontrol asli standar, jadi pada dasarnya Anda mendapatkan aplikasi asli sepenuhnya untuk semua platform yang didukung.
Sintaks Xamarin.Forms sangat dekat dengan sintaks WPF, dan bagian umum ditulis dalam .NET Standard. Akibatnya, Anda mendapatkan kesempatan untuk menggunakan pendekatan MVVM saat mengembangkan aplikasi, serta akses ke sejumlah besar perpustakaan pihak ketiga yang ditulis untuk .NET Standard dan sudah ada di NuGet, yang dapat Anda gunakan dengan aman di aplikasi Xamarin.Forms Anda.
Kode sumber untuk aplikasi di sini tersedia di
GitHub .
Jadi, mari kita buat aplikasi Xamarin.Forms kosong dan mulai. Kami akan memiliki model data sederhana, dengan hanya dua kelas Catatan dan Proyek:
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>(); } }
Saya akan mencoba mematuhi pendekatan MVVM, tapi saya tidak akan menggunakan perpustakaan khusus, agar tidak menyulitkan kode. Semua kelas model dan model tampilan akan mengimplementasikan antarmuka INotifyPropertyChanged. Saya akan menghapus implementasinya dalam contoh kode yang diberikan untuk singkatnya.
Layar pertama yang akan kita miliki adalah daftar proyek dengan kemampuan untuk membuat yang baru atau menghapus yang sekarang. Mari kita membuat model untuknya:
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); } }
Kode layar itu sendiri:
<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>
Markupnya ternyata cukup sederhana, satu-satunya yang ingin saya ingat adalah penerapan tombol swipe untuk menghapus proyek. Di ListView ada konsep ContextActions, jika Anda mengaturnya, maka di iOS mereka akan diimplementasikan melalui swipe, di Android - melalui ketukan panjang. Pendekatan ini diimplementasikan dalam Xamarin.Forms, karena ini asli untuk setiap platform. Namun, jika kita ingin menggesek di android, kita perlu menerapkannya dengan tangan kita di bagian asli android. Saya tidak punya tugas untuk menghabiskan banyak waktu dalam hal ini, jadi saya puas dengan pendekatan standar :) Sebagai hasilnya, geser di iOS dan menu konteks di Android diimplementasikan cukup sederhana:
<TextCell.ContextActions> <MenuItem Clicked="DeleteItem_Clicked" IsDestructive="true" CommandParameter="{Binding .}" Text="Delete"/> </TextCell.ContextActions>
Mengganti data uji, kami mendapatkan daftar berikut:

Sekarang mari kita beralih ke event handler. Mari kita mulai dengan yang sederhana - hapus proyek:
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); }
Tidak baik menghapus sesuatu tanpa bertanya kepada pengguna, dan di Xamarin. Bentuknya mudah dilakukan menggunakan metode DisplayAlert standar. Setelah memanggilnya, jendela berikut akan muncul:

Jendela ini dari iOs. Android akan memiliki versi sendiri untuk jendela serupa.
Selanjutnya kita akan mengimplementasikan penambahan proyek baru. Tampaknya ini dilakukan dengan analogi, tetapi dalam Xamarin. Bentuk tidak ada implementasi dialog yang mirip dengan yang saya konfirmasi penghapusan, tetapi memungkinkan Anda untuk memasukkan teks. Ada dua solusi yang mungkin:
- tulis layanan Anda sendiri yang akan memunculkan dialog asli;
- mengimplementasikan semacam solusi di sisi Xamarin. Bentuk.
Saya tidak ingin membuang waktu meningkatkan dialog melalui asli, dan saya memutuskan untuk menggunakan pendekatan kedua, implementasi yang saya ambil dari utas:
Bagaimana cara melakukan dialog InputBox sederhana? , yaitu metode Task InputBox (navigasi navigasi).
async Task AddNew_Clicked(object sender, EventArgs e) { string result = await InputBox(this.Navigation); if (result == null) return; ViewModel.AddNewProject(result); }
Sekarang kita akan memproses ketuk demi baris, untuk membuka proyek:
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) }); }
Seperti yang dapat Anda lihat dari kode di atas, untuk menuju ke jendela proyek, kita memerlukan model tampilan dan objek halaman dari jendela tersebut.
Saya ingin mengatakan beberapa kata tentang Navigasi. Properti Navigasi didefinisikan dalam kelas VisualElement, dan memungkinkan Anda untuk bekerja dengan panel navigasi di setiap tampilan aplikasi Anda tanpa harus menggulungnya di sana dengan tangan Anda. Namun, agar pendekatan ini berhasil, Anda masih perlu membuat panel ini sendiri. Oleh karena itu, di App.xaml.cs kita menulis:
NavigationPage navigation = new NavigationPage(); navigation.PushAsync(new View.ProjectsPage() { BindingContext = new MainViewModel() }); MainPage = navigation;
Di mana ProjectsPage persis jendela yang sekarang saya jelaskan.
Jendela dengan catatan sangat mirip dengan jendela dengan proyek, jadi saya tidak akan menjelaskannya secara rinci, saya hanya akan fokus pada nuansa yang menarik.
Tata letak jendela ini ternyata lebih rumit, karena setiap baris harus menampilkan informasi lebih lanjut:
Tampilan catatan <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>
Dalam konten jendela, kita kembali memiliki ListView, yang dilampirkan pada koleksi catatan. Namun, kami ingin ketinggian sel dalam konten, tetapi tidak lebih dari 150, untuk ini kami menetapkan HasUnevenRows = "True" sehingga ListView memungkinkan sel untuk mengambil ruang sebanyak yang mereka minta. Tetapi dalam situasi ini, baris dapat meminta ketinggian lebih dari 150 dan ListView akan memungkinkan mereka untuk ditampilkan seperti ini. Untuk menghindari ini di sel, saya menggunakan pewaris saya ke panel Grid: MyCellGrid. Panel ini pada operasi pengukuran meminta ketinggian elemen internal dan mengembalikannya 150 jika lebih besar:
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 }); } }
Karena menurut TOR kita harus dapat mengedit dan menghapus, selain mengetuk dan menggesek, juga menu yang terbuka dengan mengklik tombol di sudut garis, kita akan menambahkan tombol ini ke templat sel dan berlangganan ketuk di atasnya. Dalam hal ini, jika pengguna mengklik tombol, maka itu akan mencegat isyarat dan kami tidak akan menerima acara mengklik pada baris.
<Button Grid.Row="0" Grid.Column="2" BackgroundColor="Transparent" Image="menu.png" Margin="5" HorizontalOptions="FillAndExpand" Clicked="RowMenu_Clicked"/>
Dengan data uji, formulir kami terlihat seperti ini:

Memproses tindakan pengguna dalam formulir ini sepenuhnya analog dengan apa yang ditulis untuk jendela daftar proyek. Saya ingin berhenti hanya pada menu konteks dengan tombol kami di sudut baris. Pada awalnya, saya berpikir bahwa saya akan melakukannya di Xamarin. Tingkat asrama tanpa masalah.
Memang, kita hanya perlu membuat pandangan tentang sesuatu seperti ini:
<StackLayout> <Button Text=”Edit”/> <Button Text=”Delete”/> </StackLayout>
Dan tunjukkan di sebelah tombol. Namun, masalahnya adalah kita tidak tahu persis di mana letaknya "di sebelah tombol". Menu konteks ini harus terletak di atas ListView dan, ketika dibuka, diposisikan dalam koordinat jendela. Untuk melakukan ini, Anda perlu mengetahui koordinat tombol yang ditekan relatif terhadap jendela. Kita bisa mendapatkan koordinat tombol hanya relatif terhadap ScrollView internal yang terletak di ListView. Jadi ketika garis tidak bergeser, maka semuanya baik-baik saja, tetapi ketika garis digulir, kita harus memperhitungkan berapa banyak bergulir terjadi ketika menghitung koordinat. ListView tidak memberi kita nilai gulir. Jadi itu harus ditarik keluar dari bahasa asli, yang saya benar-benar tidak ingin lakukan. Oleh karena itu, saya memutuskan untuk mengambil jalan yang lebih standar dan sederhana: tampilkan menu konteks sistem standar. Akibatnya, penangan klik tombol akan menghasilkan yang berikut:
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); } } }
Memanggil metode DisplayActionSheet hanya menampilkan menu konteks biasa:

Jika Anda perhatikan, teks catatan ditampilkan di kontrol MyLabel saya, dan tidak di Label biasa. Ini dilakukan untuk apa. Ketika pengguna mengubah teks catatan, binder dipicu, dan teks baru secara otomatis masuk dalam Label. Namun, Xamarin. Bentuk tidak menghitung ulang ukuran sel pada saat yang sama. Pengembang Xamarin mengklaim bahwa ini adalah operasi yang cukup mahal. Ya, dan ListView sendiri tidak memiliki metode apa pun yang akan membuatnya menghitung ulang ukurannya, InvalidateLayout juga tidak membantu. Satu-satunya yang mereka miliki untuk ini adalah metode ForceUpdateSize dari objek Cell. Oleh karena itu, untuk mencapainya dan mencabutnya pada saat yang tepat, saya menulis Label pewaris saya dan saya menarik metode ini untuk setiap perubahan teks:
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(); } } }
Sekarang setelah mengedit catatan, ListView akan secara otomatis menyesuaikan ukuran sel agar sesuai dengan teks baru.
Saat mengedit atau membuat catatan baru, sebuah jendela terbuka dengan Editor di konten dan tombol Simpan di toolbar:

Jendela ini sedikit berbeda dari yang kita miliki di TK: kurangnya tombol bulat di bagian bawah. Jika Anda meletakkannya di atas editor, maka itu akan diblokir oleh keyboard kiri. Pada saat yang sama, saya tidak menemukan solusi yang bagus bagaimana memindahkannya dan tidak mencari yang asli dengan pencarian cepat. Oleh karena itu, saya menghapusnya dan hanya meninggalkan tombol Simpan di panel atas. Jendela ini sendiri sangat sederhana, jadi saya akan menghilangkan deskripsinya.
Apa yang ingin saya katakan pada akhirnya.
Xamarin.Forms sangat cocok untuk mereka yang terbiasa dengan infrastruktur .NET dan telah bekerja dengannya untuk waktu yang lama. Mereka tidak perlu meningkatkan ke IDE dan kerangka kerja baru. Seperti yang Anda lihat, kode aplikasi tidak jauh berbeda dari kode aplikasi berbasis XAML lainnya. Selain itu, Xamarin memungkinkan Anda untuk mengembangkan dan membangun aplikasi iOS di Visual Studio untuk Windows. Saat mengembangkan aplikasi terakhir untuk pengujian dan pembuatannya, Anda harus terhubung ke mesin MacOS. Dan perpustakaan dapat dilakukan tanpa itu.
Untuk mulai menulis aplikasi di Xamarin. Bentuknya, Anda tidak perlu bermata merah dengan konsol. Cukup instal Visual Studio dan tulis aplikasi. Segala sesuatu yang lain telah diurus untuk Anda. Pada saat yang sama, tidak peduli bagaimana Microsoft dikaitkan dengan produk berbayar, Xamarin gratis dan ada versi gratis Visual Studio.
Fakta bahwa Xamarin.Forms menggunakan .NET Standard di bawah tenda memberi Anda akses ke banyak perpustakaan yang telah ditulis untuknya yang akan membuat hidup lebih mudah ketika mengembangkan aplikasi Anda.
Xamarin.Forms memungkinkan Anda untuk dengan mudah menambahkan sesuatu di bagian asli aplikasi Anda, jika Anda ingin mengimplementasikan sesuatu yang spesifik platform. Di sana Anda mendapatkan C # yang sama, tetapi API adalah asli untuk setiap platform.
Namun, tentu saja, ada beberapa kekurangan.
API, tersedia di bagian umum, agak sedikit, karena hanya berisi apa yang umum untuk semua platform. Misalnya, seperti yang dapat dilihat pada contoh saya, semua platform berisi pesan peringatan dan menu konteks, dan hal ini tersedia di Xamarin. Namun, menu standar yang memungkinkan Anda memasukkan teks hanya tersedia di iOS, jadi di Xamarin.
Pembatasan serupa ditemukan dalam penggunaan komponen. Sesuatu dapat dilakukan, sesuatu tidak mungkin. Sapuan yang sama untuk menghapus proyek atau catatan hanya berfungsi di iOS. Di Android, tindakan konteks ini akan disajikan sebagai menu yang muncul di ketukan panjang. Dan jika Anda ingin menggesek di android, maka selamat datang di bagian android dan tulis dengan tangan Anda.
Dan tentu saja, kinerja. Kecepatan aplikasi pada Xamarin. Dalam hal apapun, kecepatannya akan lebih rendah daripada kecepatan aplikasi asli. Jadi Microsoft sendiri mengatakan bahwa jika Anda memerlukan aplikasi tanpa embel-embel dalam hal persyaratan desain dan kinerja, maka Xamarin. Jika Anda membutuhkan kecantikan atau kecepatan, maka Anda seharusnya sudah turun ke penduduk asli. Untungnya, Xamarin juga memiliki versi asli yang sudah beroperasi segera dengan platform API asli mereka dan bekerja lebih cepat daripada formulir.