Udostępnij za pośrednictwem


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ą:

Relacja między kontrolką ListView a implementacją kontrolek natywnych

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:

  1. Utwórz kontrolkę niestandardową Xamarin.Forms .
  2. Zużyj kontrolkę niestandardową z poziomu Xamarin.Forms.
  3. 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:

  1. Utwórz podklasę ListViewRenderer klasy, która renderuje kontrolkę niestandardową.
  2. 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.
  3. 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:

NativeListView — obowiązki projektu niestandardowego modułu renderowania

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:

NativeListView na każdej platformie

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 Countmetody , GetView, GetItemIdi 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.