Dostosowywanie obiektu ListView
Xamarin.Forms Element ListView to widok, który wyświetla kolekcję danych jako pionową listę. W tym artykule pokazano, jak utworzyć niestandardowy moduł renderujący, który hermetyzuje kontrolki listy specyficzne dla platformy i natywne układy komórek, co pozwala na większą kontrolę nad natywną wydajnością kontroli listy.
Każdy Xamarin.Forms widok ma towarzyszący moduł renderujący dla każdej platformy, który tworzy wystąpienie kontrolki natywnej. Gdy element ListView
jest renderowany przez aplikację Xamarin.Forms , w systemie iOS ListViewRenderer
klasę tworzy wystąpienie, co z kolei tworzy wystąpienie kontrolki natywnej UITableView
. Na platformie ListViewRenderer
Android klasa tworzy wystąpienie natywnej ListView
kontrolki. W platforma uniwersalna systemu Windows (UWP) ListViewRenderer
klasa tworzy wystąpienie kontrolki natywnejListView
. Aby uzyskać więcej informacji na temat klasy renderera i natywnych kontrolek mapowanych Xamarin.Forms na, zobacz Renderer Base Classes and Native Controls (Klasy bazowe modułu renderowania i kontrolki natywne).
Na poniższym diagramie przedstawiono relację między kontrolką ListView
a odpowiednimi natywnymi kontrolkami, które ją implementują:
Proces renderowania można wykorzystać w celu zaimplementowania dostosowań specyficznych dla platformy przez utworzenie niestandardowego modułu renderowania dla elementu na ListView
każdej platformie. Proces wykonywania tej czynności jest następujący:
- Utwórz kontrolkę niestandardową Xamarin.Forms .
- Zużyj kontrolkę niestandardową z poziomu Xamarin.Forms.
- Utwórz niestandardowy moduł renderowania dla kontrolki na każdej platformie.
Każdy element zostanie omówiony z kolei, aby zaimplementować NativeListView
moduł renderujący korzystający z kontrolek listy specyficznych dla platformy i natywnych układów komórek. Ten scenariusz jest przydatny podczas przenoszenia istniejącej aplikacji natywnej zawierającej listę i kod komórki, które można ponownie użyć. Ponadto umożliwia szczegółowe dostosowywanie funkcji kontroli listy, które mogą mieć wpływ na wydajność, taką jak wirtualizacja danych.
Tworzenie niestandardowej kontrolki ListView
Kontrolkę niestandardową ListView
można utworzyć przez podklasę ListView
klasy, jak pokazano w poniższym przykładzie kodu:
public class NativeListView : ListView
{
public static readonly BindableProperty ItemsProperty =
BindableProperty.Create ("Items", typeof(IEnumerable<DataSource>), typeof(NativeListView), new List<DataSource> ());
public IEnumerable<DataSource> Items {
get { return (IEnumerable<DataSource>)GetValue (ItemsProperty); }
set { SetValue (ItemsProperty, value); }
}
public event EventHandler<SelectedItemChangedEventArgs> ItemSelected;
public void NotifyItemSelected (object item)
{
if (ItemSelected != null) {
ItemSelected (this, new SelectedItemChangedEventArgs (item));
}
}
}
Element NativeListView
jest tworzony w projekcie biblioteki .NET Standard i definiuje interfejs API dla kontrolki niestandardowej. Ta kontrolka uwidacznia Items
właściwość, która jest używana do wypełniania ListView
za pomocą danych, i które mogą być danymi powiązanymi do celów wyświetlania. Uwidacznia również zdarzenie, które zostanie wyzwolone ItemSelected
za każdym razem, gdy element zostanie wybrany w kontrolce listy natywnej specyficznej dla platformy. Aby uzyskać więcej informacji na temat powiązania danych, zobacz Podstawy powiązań danych.
Korzystanie z kontrolki niestandardowej
Do NativeListView
kontrolki niestandardowej można odwoływać się w języku Xaml w projekcie biblioteki .NET Standard, deklarując przestrzeń nazw dla swojej lokalizacji i używając prefiksu przestrzeni nazw w kontrolce. Poniższy przykład kodu przedstawia sposób NativeListView
korzystania z kontrolki niestandardowej przez stronę XAML:
<ContentPage ...
xmlns:local="clr-namespace:CustomRenderer;assembly=CustomRenderer"
...>
...
<ContentPage.Content>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Text="{x:Static local:App.Description}" HorizontalTextAlignment="Center" />
<local:NativeListView Grid.Row="1" x:Name="nativeListView" ItemSelected="OnItemSelected" VerticalOptions="FillAndExpand" />
</Grid>
</ContentPage.Content>
</ContentPage>
Prefiks local
przestrzeni nazw może mieć nazwę dowolnych elementów. clr-namespace
Jednak wartości i assembly
muszą być zgodne ze szczegółami kontrolki niestandardowej. Po zadeklarowaniu przestrzeni nazw prefiks jest używany do odwołwania się do kontrolki niestandardowej.
Poniższy przykład kodu pokazuje, jak kontrolka NativeListView
niestandardowa może być zużywana przez stronę języka C#:
public class MainPageCS : ContentPage
{
NativeListView nativeListView;
public MainPageCS()
{
nativeListView = new NativeListView
{
Items = DataSource.GetList(),
VerticalOptions = LayoutOptions.FillAndExpand
};
switch (Device.RuntimePlatform)
{
case Device.iOS:
Padding = new Thickness(0, 20, 0, 0);
break;
case Device.Android:
case Device.UWP:
Padding = new Thickness(0);
break;
}
Content = new Grid
{
RowDefinitions = {
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = new GridLength (1, GridUnitType.Star) }
},
Children = {
new Label { Text = App.Description, HorizontalTextAlignment = TextAlignment.Center },
nativeListView
}
};
nativeListView.ItemSelected += OnItemSelected;
}
...
}
Kontrolka niestandardowa NativeListView
używa niestandardowych renderatorów specyficznych dla platformy do wyświetlania listy danych, które są wypełniane za pomocą Items
właściwości . Każdy wiersz na liście zawiera trzy elementy danych — nazwę, kategorię i nazwę pliku obrazu. Układ każdego wiersza na liście jest definiowany przez niestandardowy moduł renderowania specyficzny dla platformy.
Uwaga
Ponieważ kontrolka niestandardowa NativeListView
będzie renderowana przy użyciu kontrolek listy specyficznych dla platformy, które obejmują możliwość przewijania, kontrolka niestandardowa nie powinna być hostowana w kontrolkach układu przewijania ScrollView
, takich jak .
Niestandardowy moduł renderowania można teraz dodać do każdego projektu aplikacji w celu utworzenia kontrolek listy specyficznych dla platformy i natywnych układów komórek.
Tworzenie niestandardowego modułu renderowania na każdej platformie
Proces tworzenia niestandardowej klasy renderera jest następujący:
- Utwórz podklasę
ListViewRenderer
klasy, która renderuje kontrolkę niestandardową. - Zastąpi metodę
OnElementChanged
, która renderuje kontrolkę niestandardową i zapisuje logikę, aby ją dostosować. Ta metoda jest wywoływana po utworzeniu odpowiedniego Xamarin.FormsListView
elementu. ExportRenderer
Dodaj atrybut do niestandardowej klasy renderera, aby określić, że będzie on używany do renderowania kontrolki niestandardowejXamarin.Forms. Ten atrybut służy do rejestrowania niestandardowego modułu renderowania za pomocą Xamarin.Formspolecenia .
Uwaga
Opcjonalne jest udostępnienie niestandardowego modułu renderowania w każdym projekcie platformy. Jeśli niestandardowy moduł renderowania nie jest zarejestrowany, zostanie użyty domyślny moduł renderowania dla klasy bazowej komórki.
Na poniższym diagramie przedstawiono obowiązki każdego projektu w przykładowej aplikacji wraz z relacjami między nimi:
Kontrolka niestandardowa NativeListView
jest renderowana przez klasy renderer specyficzne dla platformy, które pochodzą z ListViewRenderer
klasy dla każdej platformy. Powoduje to renderowanie każdej NativeListView
niestandardowej kontrolki z kontrolkami listy specyficznymi dla platformy i natywnymi układami komórek, jak pokazano na poniższych zrzutach ekranu:
Klasa ListViewRenderer
uwidacznia metodę OnElementChanged
, która jest wywoływana podczas tworzenia niestandardowej Xamarin.Forms kontrolki w celu renderowania odpowiedniej kontrolki natywnej. Ta metoda przyjmuje ElementChangedEventArgs
parametr zawierający OldElement
właściwości i NewElement
. Te właściwości reprezentują Xamarin.Forms element dołączony do modułu renderowania, a Xamarin.Forms element, do którego jest dołączony moduł renderujący . W przykładowej aplikacji OldElement
właściwość będzie mieć null
wartość , a NewElement
właściwość będzie zawierać odwołanie do NativeListView
wystąpienia.
Zastąpiona wersja metody , w każdej klasie modułu OnElementChanged
renderowania specyficznego dla platformy, to miejsce do wykonania dostosowania natywnej kontrolki. Za pośrednictwem właściwości można uzyskać dostęp do wpisanego odwołania do natywnej kontrolki używanej Control
na platformie. Ponadto można uzyskać odwołanie do Xamarin.Forms renderowanej kontrolki Element
za pośrednictwem właściwości .
Podczas subskrybowania programów obsługi zdarzeń w metodzie OnElementChanged
należy zachować ostrożność, jak pokazano w poniższym przykładzie kodu:
protected override void OnElementChanged (ElementChangedEventArgs<Xamarin.Forms.ListView> e)
{
base.OnElementChanged (e);
if (e.OldElement != null) {
// Unsubscribe from event handlers and cleanup any resources
}
if (e.NewElement != null) {
// Configure the native control and subscribe to event handlers
}
}
Kontrolka natywna powinna być skonfigurowana i programy obsługi zdarzeń subskrybowane tylko wtedy, gdy niestandardowy moduł renderowania jest dołączony do nowego Xamarin.Forms elementu. Podobnie wszystkie programy obsługi zdarzeń, które zostały subskrybowane, powinny zostać anulowane tylko wtedy, gdy element renderujący jest dołączony do zmian. Wdrożenie tego podejścia pomoże utworzyć niestandardowy moduł renderujący, który nie cierpi na przecieki pamięci.
Przesłoniętą wersję OnElementPropertyChanged
metody w każdej klasie renderowania specyficznej dla platformy jest miejscem reagowania na zmiany właściwości, które można powiązać w kontrolce niestandardowej Xamarin.Forms . Należy zawsze sprawdzić zmienioną właściwość, ponieważ to zastąpienie może być wywoływane wiele razy.
Każda niestandardowa klasa modułu renderowania jest ozdobiona atrybutem ExportRenderer
, który rejestruje program renderujący za pomocą Xamarin.Formspolecenia . Atrybut przyjmuje dwa parametry — nazwę Xamarin.Forms typu renderowanej kontrolki niestandardowej i nazwę typu niestandardowego modułu renderowania. Prefiks assembly
atrybutu określa, że atrybut ma zastosowanie do całego zestawu.
W poniższych sekcjach omówiono implementację poszczególnych niestandardowych klas renderer specyficznych dla platformy.
Tworzenie niestandardowego modułu renderowania w systemie iOS
Poniższy przykład kodu przedstawia niestandardowy moduł renderowania dla platformy iOS:
[assembly: ExportRenderer (typeof(NativeListView), typeof(NativeiOSListViewRenderer))]
namespace CustomRenderer.iOS
{
public class NativeiOSListViewRenderer : ListViewRenderer
{
protected override void OnElementChanged (ElementChangedEventArgs<Xamarin.Forms.ListView> e)
{
base.OnElementChanged (e);
if (e.OldElement != null) {
// Unsubscribe
}
if (e.NewElement != null) {
Control.Source = new NativeiOSListViewSource (e.NewElement as NativeListView);
}
}
}
}
Kontrolka UITableView
jest konfigurowana przez utworzenie wystąpienia NativeiOSListViewSource
klasy, pod warunkiem, że niestandardowy moduł renderujący jest dołączony do nowego Xamarin.Forms elementu. Ta klasa udostępnia dane do kontrolki UITableView
, przesłaniając RowsInSection
metody i GetCell
z UITableViewSource
klasy, a także uwidaczniając właściwość zawierającą Items
listę danych do wyświetlenia. Klasa udostępnia również przesłonięć metodę RowSelected
, która wywołuje ItemSelected
zdarzenie udostępniane przez kontrolkę niestandardową NativeListView
. Aby uzyskać więcej informacji na temat przesłonięć metod, zobacz Podklasowanie UITableViewSource. Metoda GetCell
zwraca UITableCellView
obiekt wypełniony danymi dla każdego wiersza na liście i jest wyświetlany w poniższym przykładzie kodu:
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
// request a recycled cell to save memory
NativeiOSListViewCell cell = tableView.DequeueReusableCell (cellIdentifier) as NativeiOSListViewCell;
// if there are no cells to reuse, create a new one
if (cell == null) {
cell = new NativeiOSListViewCell (cellIdentifier);
}
if (String.IsNullOrWhiteSpace (tableItems [indexPath.Row].ImageFilename)) {
cell.UpdateCell (tableItems [indexPath.Row].Name
, tableItems [indexPath.Row].Category
, null);
} else {
cell.UpdateCell (tableItems [indexPath.Row].Name
, tableItems [indexPath.Row].Category
, UIImage.FromFile ("Images/" + tableItems [indexPath.Row].ImageFilename + ".jpg"));
}
return cell;
}
Ta metoda tworzy NativeiOSListViewCell
wystąpienie dla każdego wiersza danych, które będą wyświetlane na ekranie. Wystąpienie NativeiOSCell
definiuje układ każdej komórki i danych komórki. Gdy komórka zniknie z ekranu z powodu przewijania, komórka zostanie udostępniona do ponownego użycia. Pozwala to uniknąć marnowania pamięci przez upewnienie się, że na ekranie są wyświetlane tylko NativeiOSCell
wystąpienia danych, a nie wszystkie dane na liście. Aby uzyskać więcej informacji na temat ponownego użycia komórek, zobacz Ponowne użycie komórek. Metoda GetCell
odczytuje ImageFilename
również właściwość każdego wiersza danych, pod warunkiem, że istnieje, i odczytuje obraz i zapisuje go jako UIImage
wystąpienie, przed zaktualizowaniem NativeiOSListViewCell
wystąpienia danymi (nazwa, kategoria i obraz) dla wiersza.
Klasa NativeiOSListViewCell
definiuje układ dla każdej komórki i jest wyświetlany w poniższym przykładzie kodu:
public class NativeiOSListViewCell : UITableViewCell
{
UILabel headingLabel, subheadingLabel;
UIImageView imageView;
public NativeiOSListViewCell (NSString cellId) : base (UITableViewCellStyle.Default, cellId)
{
SelectionStyle = UITableViewCellSelectionStyle.Gray;
ContentView.BackgroundColor = UIColor.FromRGB (218, 255, 127);
imageView = new UIImageView ();
headingLabel = new UILabel () {
Font = UIFont.FromName ("Cochin-BoldItalic", 22f),
TextColor = UIColor.FromRGB (127, 51, 0),
BackgroundColor = UIColor.Clear
};
subheadingLabel = new UILabel () {
Font = UIFont.FromName ("AmericanTypewriter", 12f),
TextColor = UIColor.FromRGB (38, 127, 0),
TextAlignment = UITextAlignment.Center,
BackgroundColor = UIColor.Clear
};
ContentView.Add (headingLabel);
ContentView.Add (subheadingLabel);
ContentView.Add (imageView);
}
public void UpdateCell (string caption, string subtitle, UIImage image)
{
headingLabel.Text = caption;
subheadingLabel.Text = subtitle;
imageView.Image = image;
}
public override void LayoutSubviews ()
{
base.LayoutSubviews ();
headingLabel.Frame = new CoreGraphics.CGRect (5, 4, ContentView.Bounds.Width - 63, 25);
subheadingLabel.Frame = new CoreGraphics.CGRect (100, 18, 100, 20);
imageView.Frame = new CoreGraphics.CGRect (ContentView.Bounds.Width - 63, 5, 33, 33);
}
}
Ta klasa definiuje kontrolki używane do renderowania zawartości komórki i ich układu. Konstruktor NativeiOSListViewCell
tworzy wystąpienia UILabel
kontrolek i i UIImageView
inicjuje ich wygląd. Te kontrolki są używane do wyświetlania danych każdego wiersza z UpdateCell
metodą używaną do ustawiania tych danych w UILabel
wystąpieniach i UIImageView
. Lokalizacja tych wystąpień jest ustawiana przez metodę przesłoniętą LayoutSubviews
przez określenie ich współrzędnych w komórce.
Odpowiadanie na zmianę właściwości w kontrolce niestandardowej
NativeListView.Items
Jeśli właściwość ulegnie zmianie, ze względu na dodanie lub usunięcie elementów z listy, niestandardowy moduł renderujący musi odpowiedzieć, wyświetlając zmiany. Można to zrobić, przesłaniając metodę OnElementPropertyChanged
, która jest pokazana w poniższym przykładzie kodu:
protected override void OnElementPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged (sender, e);
if (e.PropertyName == NativeListView.ItemsProperty.PropertyName) {
Control.Source = new NativeiOSListViewSource (Element as NativeListView);
}
}
Metoda tworzy nowe wystąpienie NativeiOSListViewSource
klasy, która dostarcza dane do kontrolki UITableView
, pod warunkiem, że właściwość powiązana NativeListView.Items
uległa zmianie.
Tworzenie niestandardowego modułu renderowania w systemie Android
W poniższym przykładzie kodu pokazano niestandardowy moduł renderowania dla platformy Android:
[assembly: ExportRenderer(typeof(NativeListView), typeof(NativeAndroidListViewRenderer))]
namespace CustomRenderer.Droid
{
public class NativeAndroidListViewRenderer : ListViewRenderer
{
Context _context;
public NativeAndroidListViewRenderer(Context context) : base(context)
{
_context = context;
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.ListView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// unsubscribe
Control.ItemClick -= OnItemClick;
}
if (e.NewElement != null)
{
// subscribe
Control.Adapter = new NativeAndroidListViewAdapter(_context as Android.App.Activity, e.NewElement as NativeListView);
Control.ItemClick += OnItemClick;
}
}
...
void OnItemClick(object sender, Android.Widget.AdapterView.ItemClickEventArgs e)
{
((NativeListView)Element).NotifyItemSelected(((NativeListView)Element).Items.ToList()[e.Position - 1]);
}
}
}
Skonfigurowano kontrolkę natywną ListView
, pod warunkiem, że niestandardowy moduł renderujący jest dołączony do nowego Xamarin.Forms elementu. Ta konfiguracja obejmuje utworzenie wystąpienia NativeAndroidListViewAdapter
klasy, które dostarcza dane do kontrolki natywnej ListView
, i zarejestrowanie programu obsługi zdarzeń w celu przetworzenia ItemClick
zdarzenia. Z kolei ta procedura obsługi wywoła ItemSelected
zdarzenie dostarczone przez kontrolkę niestandardową NativeListView
. Zdarzenie ItemClick
jest anulowane, jeśli Xamarin.Forms element renderator jest dołączony do zmian.
Element NativeAndroidListViewAdapter
pochodzi z BaseAdapter
klasy i uwidacznia właściwość zawierającą Items
listę danych do wyświetlenia, a także zastępuje Count
metody , GetView
, GetItemId
i this[int]
. Aby uzyskać więcej informacji na temat tych przesłonięć metod, zobacz Implementowanie elementu ListAdapter. Metoda GetView
zwraca widok dla każdego wiersza, wypełniony danymi i jest wyświetlany w poniższym przykładzie kodu:
public override View GetView (int position, View convertView, ViewGroup parent)
{
var item = tableItems [position];
var view = convertView;
if (view == null) {
// no view to re-use, create new
view = context.LayoutInflater.Inflate (Resource.Layout.NativeAndroidListViewCell, null);
}
view.FindViewById<TextView> (Resource.Id.Text1).Text = item.Name;
view.FindViewById<TextView> (Resource.Id.Text2).Text = item.Category;
// grab the old image and dispose of it
if (view.FindViewById<ImageView> (Resource.Id.Image).Drawable != null) {
using (var image = view.FindViewById<ImageView> (Resource.Id.Image).Drawable as BitmapDrawable) {
if (image != null) {
if (image.Bitmap != null) {
//image.Bitmap.Recycle ();
image.Bitmap.Dispose ();
}
}
}
}
// If a new image is required, display it
if (!String.IsNullOrWhiteSpace (item.ImageFilename)) {
context.Resources.GetBitmapAsync (item.ImageFilename).ContinueWith ((t) => {
var bitmap = t.Result;
if (bitmap != null) {
view.FindViewById<ImageView> (Resource.Id.Image).SetImageBitmap (bitmap);
bitmap.Dispose ();
}
}, TaskScheduler.FromCurrentSynchronizationContext ());
} else {
// clear the image
view.FindViewById<ImageView> (Resource.Id.Image).SetImageBitmap (null);
}
return view;
}
Metoda GetView
jest wywoływana, aby zwrócić komórkę do renderowania jako View
, dla każdego wiersza danych na liście. View
Tworzy wystąpienie dla każdego wiersza danych, które będą wyświetlane na ekranie z wyglądem View
wystąpienia zdefiniowanego w pliku układu. Gdy komórka zniknie z ekranu z powodu przewijania, komórka zostanie udostępniona do ponownego użycia. Pozwala to uniknąć marnowania pamięci przez upewnienie się, że na ekranie są wyświetlane tylko View
wystąpienia danych, a nie wszystkie dane na liście. Aby uzyskać więcej informacji na temat ponownego użycia widoku, zobacz Ponowne użycie widoku wiersza.
Metoda GetView
wypełnia View
również wystąpienie danymi, w tym odczytywanie danych obrazu z nazwy pliku określonej we ImageFilename
właściwości .
Układ każdej komórki nieopłaconej przez macierzysty ListView
jest zdefiniowany w NativeAndroidListViewCell.axml
pliku układu, który jest zawyżony przez metodę LayoutInflater.Inflate
. Poniższy przykład kodu przedstawia definicję układu:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:background="@drawable/CustomSelector">
<LinearLayout
android:id="@+id/Text"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dip">
<TextView
android:id="@+id/Text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FF7F3300"
android:textSize="20dip"
android:textStyle="italic" />
<TextView
android:id="@+id/Text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14dip"
android:textColor="#FF267F00"
android:paddingLeft="100dip" />
</LinearLayout>
<ImageView
android:id="@+id/Image"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="5dp"
android:src="@drawable/icon"
android:layout_alignParentRight="true" />
</RelativeLayout>
Ten układ określa, że dwie TextView
kontrolki i kontrolka ImageView
są używane do wyświetlania zawartości komórki. Te dwie TextView
kontrolki są zorientowane w pionie w obrębie LinearLayout
kontrolki, a wszystkie kontrolki znajdują się w obiekcie RelativeLayout
.
Odpowiadanie na zmianę właściwości w kontrolce niestandardowej
NativeListView.Items
Jeśli właściwość ulegnie zmianie, ze względu na dodanie lub usunięcie elementów z listy, niestandardowy moduł renderujący musi odpowiedzieć, wyświetlając zmiany. Można to zrobić, przesłaniając metodę OnElementPropertyChanged
, która jest pokazana w poniższym przykładzie kodu:
protected override void OnElementPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged (sender, e);
if (e.PropertyName == NativeListView.ItemsProperty.PropertyName) {
Control.Adapter = new NativeAndroidListViewAdapter (_context as Android.App.Activity, Element as NativeListView);
}
}
Metoda tworzy nowe wystąpienie NativeAndroidListViewAdapter
klasy, które dostarcza dane do kontrolki natywnej ListView
, pod warunkiem, że właściwość powiązana NativeListView.Items
uległa zmianie.
Tworzenie niestandardowego modułu renderowania na platformie UWP
W poniższym przykładzie kodu pokazano niestandardowy moduł renderowania dla platformy UWP:
[assembly: ExportRenderer(typeof(NativeListView), typeof(NativeUWPListViewRenderer))]
namespace CustomRenderer.UWP
{
public class NativeUWPListViewRenderer : ListViewRenderer
{
ListView listView;
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.ListView> e)
{
base.OnElementChanged(e);
listView = Control as ListView;
if (e.OldElement != null)
{
// Unsubscribe
listView.SelectionChanged -= OnSelectedItemChanged;
}
if (e.NewElement != null)
{
listView.SelectionMode = ListViewSelectionMode.Single;
listView.IsItemClickEnabled = false;
listView.ItemsSource = ((NativeListView)e.NewElement).Items;
listView.ItemTemplate = App.Current.Resources["ListViewItemTemplate"] as Windows.UI.Xaml.DataTemplate;
// Subscribe
listView.SelectionChanged += OnSelectedItemChanged;
}
}
void OnSelectedItemChanged(object sender, SelectionChangedEventArgs e)
{
((NativeListView)Element).NotifyItemSelected(listView.SelectedItem);
}
}
}
Skonfigurowano kontrolkę natywną ListView
, pod warunkiem, że niestandardowy moduł renderujący jest dołączony do nowego Xamarin.Forms elementu. Ta konfiguracja obejmuje ustawienie sposobu, w jaki kontrolka natywna ListView
będzie reagować na wybrane elementy, wypełniając dane wyświetlane przez kontrolkę, definiując wygląd i zawartość każdej komórki oraz rejestrując procedurę obsługi zdarzeń w celu przetworzenia SelectionChanged
zdarzenia. Z kolei ta procedura obsługi wywoła ItemSelected
zdarzenie dostarczone przez kontrolkę niestandardową NativeListView
. Zdarzenie SelectionChanged
jest anulowane, jeśli Xamarin.Forms element renderator jest dołączony do zmian.
Wygląd i zawartość każdej komórki natywnej ListView
są definiowane przez DataTemplate
nazwę ListViewItemTemplate
. Jest on DataTemplate
przechowywany w słowniku zasobów na poziomie aplikacji i jest wyświetlany w poniższym przykładzie kodu:
<DataTemplate x:Key="ListViewItemTemplate">
<Grid Background="#DAFF7F">
<Grid.Resources>
<local:ConcatImageExtensionConverter x:Name="ConcatImageExtensionConverter" />
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.40*" />
<ColumnDefinition Width="0.40*"/>
<ColumnDefinition Width="0.20*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.ColumnSpan="2" Foreground="#7F3300" FontStyle="Italic" FontSize="22" VerticalAlignment="Top" Text="{Binding Name}" />
<TextBlock Grid.RowSpan="2" Grid.Column="1" Foreground="#267F00" FontWeight="Bold" FontSize="12" VerticalAlignment="Bottom" Text="{Binding Category}" />
<Image Grid.RowSpan="2" Grid.Column="2" HorizontalAlignment="Left" VerticalAlignment="Center" Source="{Binding ImageFilename, Converter={StaticResource ConcatImageExtensionConverter}}" Width="50" Height="50" />
<Line Grid.Row="1" Grid.ColumnSpan="3" X1="0" X2="1" Margin="30,20,0,0" StrokeThickness="1" Stroke="LightGray" Stretch="Fill" VerticalAlignment="Bottom" />
</Grid>
</DataTemplate>
Określa DataTemplate
kontrolki używane do wyświetlania zawartości komórki oraz ich układu i wyglądu. Dwie TextBlock
kontrolki i kontrolka Image
są używane do wyświetlania zawartości komórki za pomocą powiązania danych. Ponadto wystąpienie obiektu ConcatImageExtensionConverter
służy do łączenia .jpg
rozszerzenia pliku z każdą nazwą pliku obrazu. Dzięki temu kontrolka Image
może ładować i renderować obraz po ustawieniu jej Source
właściwości.
Odpowiadanie na zmianę właściwości w kontrolce niestandardowej
NativeListView.Items
Jeśli właściwość ulegnie zmianie, ze względu na dodanie lub usunięcie elementów z listy, niestandardowy moduł renderujący musi odpowiedzieć, wyświetlając zmiany. Można to zrobić, przesłaniając metodę OnElementPropertyChanged
, która jest pokazana w poniższym przykładzie kodu:
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == NativeListView.ItemsProperty.PropertyName)
{
listView.ItemsSource = ((NativeListView)Element).Items;
}
}
Metoda ponownie wypełnia kontrolkę natywną ListView
zmienionymi danymi, pod warunkiem że właściwość powiązana NativeListView.Items
uległa zmianie.
Podsumowanie
W tym artykule pokazano, jak utworzyć niestandardowy moduł renderujący, który hermetyzuje kontrolki listy specyficzne dla platformy i natywne układy komórek, co pozwala na większą kontrolę nad wydajnością natywnej kontroli listy.