Anpassen einer ViewCell

Beispiel herunterladen Das Beispiel herunterladen

Das Xamarin.Forms-Element „ViewCell“ ist eine Zelle, die „ListView“ oder „TableView“ hinzugefügt werden kann und die eine vom Entwickler definierte Ansicht enthält. In diesem Artikel wird veranschaulicht, wie Sie einen benutzerdefinierten Renderer für ein ViewCell-Element erstellen können, das in einem Xamarin.Forms-ListView-Steuerelement gehostet wird. Dadurch wird verhindert, dass die Xamarin.Forms Layoutberechnungen während des ListView-Bildlaufs wiederholt aufgerufen werden.

Jede Xamarin.Forms-Zelle verfügt über einen entsprechenden Renderer für jede Plattform, die eine Instanz eines nativen Steuerelements erstellt. Beim Rendern eines ViewCell-Objekts durch eine Xamarin.Forms-App wird in iOS die ViewCellRenderer-Klasse instanziiert, wodurch wiederum ein natives UITableViewCell-Steuerelement instanziiert wird. Auf der Android-Plattform instanziiert die ViewCellRenderer-Klasse ein natives View-Steuerelement. Auf der Universellen Windows-Plattform (UWP) instanziiert die ViewCellRenderer-Klasse eine native DataTemplate-Klasse. Weitere Informationen zu den Renderern und Klassen nativer Steuerelemente, auf die Xamarin.Forms-Steuerelemente verweisen, finden Sie unter Rendererbasisklassen und native Steuerelemente.

Das folgende Diagramm veranschaulicht die Beziehungen zwischen dem ViewCell-Objekt und den entsprechenden nativen Steuerelementen, die dieses implementieren:

Beziehung zwischen dem ViewCell-Steuerelement und den implementierenden nativen Steuerelementen

Der Renderprozess kann genutzt werden, um plattformspezifische Anpassungen zu implementieren, indem für eine ViewCell-Klasse auf jeder Plattform ein benutzerdefinierter Renderer erstellt wird. Gehen Sie hierfür folgendermaßen vor:

  1. Erstellen Sie eine benutzerdefinierte Xamarin.Forms-Zelle.
  2. Nutzen Sie die benutzerdefinierte Zelle über Xamarin.Forms.
  3. Erstellen Sie den benutzerdefinierten Renderer für die Zelle auf jeder Plattform.

Jedes Element wird nun nacheinander erläutert, um einen NativeCell Renderer zu implementieren, der ein plattformspezifisches Layout für jede Zelle nutzt, die in einem Xamarin.FormsListView Steuerelement gehostet wird. Das verhindert, dass die Xamarin.Forms-Layoutberechnungen wiederholt beim Scrollen in ListView aufgerufen werden.

Erstellen der benutzerdefinierten Zelle

Eine benutzerdefinierte Zelle kann erstellt werden, indem Sie die ViewCell-Klasse wie in folgendem Codebeispiel als Unterklasse verwenden:

public class NativeCell : ViewCell
{
  public static readonly BindableProperty NameProperty =
    BindableProperty.Create ("Name", typeof(string), typeof(NativeCell), "");

  public string Name {
    get { return (string)GetValue (NameProperty); }
    set { SetValue (NameProperty, value); }
  }

  public static readonly BindableProperty CategoryProperty =
    BindableProperty.Create ("Category", typeof(string), typeof(NativeCell), "");

  public string Category {
    get { return (string)GetValue (CategoryProperty); }
    set { SetValue (CategoryProperty, value); }
  }

  public static readonly BindableProperty ImageFilenameProperty =
    BindableProperty.Create ("ImageFilename", typeof(string), typeof(NativeCell), "");

  public string ImageFilename {
    get { return (string)GetValue (ImageFilenameProperty); }
    set { SetValue (ImageFilenameProperty, value); }
  }
}

Die Klasse NativeCell wird im .NET Standard-Bibliotheksprojekt erstellt und definiert die API für die benutzerdefinierte Zelle. Die benutzerdefinierte Zelle macht die Eigenschaften Name, Category und ImageFilename verfügbar, die über eine Datenbindung angezeigt werden können. Weitere Informationen zur Datenbindung finden Sie unter Data Binding Basics (Datenbindungsgrundlagen).

Nutzen der benutzerdefinierten Zelle

Sie können auf die benutzerdefinierte Zelle NativeCell in XAML im .NET Standard-Bibliotheksprojekt verweisen, indem Sie einen Namespace für seine Position deklarieren und das Namespacepräfix für das benutzerdefinierte Zellenelement verwenden. Das folgende Codebeispiel veranschaulicht, wie die benutzerdefinierte Zelle NativeCell von der XAML-Seite genutzt werden kann:

<ContentPage ...
    xmlns:local="clr-namespace:CustomRenderer;assembly=CustomRenderer"
    ...>
    ...
    <ContentPage.Content>
          <StackLayout>
              <Label Text="Xamarin.Forms native cell" HorizontalTextAlignment="Center" />
              <ListView x:Name="listView" CachingStrategy="RecycleElement" ItemSelected="OnItemSelected">
                  <ListView.ItemTemplate>
                      <DataTemplate>
                          <local:NativeCell Name="{Binding Name}" Category="{Binding Category}" ImageFilename="{Binding ImageFilename}" />
                      </DataTemplate>
                  </ListView.ItemTemplate>
              </ListView>
          </StackLayout>
      </ContentPage.Content>
</ContentPage>

Das local-Namespacepräfix kann beliebig benannt werden. Die Werte clr-namespace und assembly müssen jedoch mit den Angaben des benutzerdefinierten Steuerelements übereinstimmen. Wenn der Namespace deklariert wurde, wird das Präfix verwendet, um auf die benutzerdefinierte Zelle zu verweisen.

Das folgende Codebeispiel veranschaulicht, wie die benutzerdefinierte Zelle NativeCell von einer C#-Seite genutzt werden kann:

public class NativeCellPageCS : ContentPage
{
    ListView listView;

    public NativeCellPageCS()
    {
        listView = new ListView(ListViewCachingStrategy.RecycleElement)
        {
            ItemsSource = DataSource.GetList(),
            ItemTemplate = new DataTemplate(() =>
            {
                var nativeCell = new NativeCell();
                nativeCell.SetBinding(NativeCell.NameProperty, "Name");
                nativeCell.SetBinding(NativeCell.CategoryProperty, "Category");
                nativeCell.SetBinding(NativeCell.ImageFilenameProperty, "ImageFilename");

                return nativeCell;
            })
        };

        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 StackLayout
        {
            Children = {
                new Label { Text = "Xamarin.Forms native cell", HorizontalTextAlignment = TextAlignment.Center },
                listView
            }
        };
        listView.ItemSelected += OnItemSelected;
    }
    ...
}

Ein Xamarin.FormsListView -Steuerelement wird verwendet, um eine Liste von Daten anzuzeigen, die über die ItemSource -Eigenschaft aufgefüllt wird. Die RecycleElement-Zwischenspeicherstrategie versucht, den ListView-Speicherbedarf sowie die Ausführungsgeschwindigkeit durch die Wiederverwendung von Listenzellen zu minimieren. Weitere Informationen finden Sie unter Zwischenspeicherstrategie.

Jede Zeile in der Liste enthält drei Datenelemente: einen Namen, eine Kategorie und einen Dateinamen für ein Bild. Das Layout jeder Zeile in der Liste wird durch eine DataTemplate definiert, auf die über die bindbare ListView.ItemTemplate-Eigenschaft verwiesen wird. Die DataTemplate definiert, dass jede Datenzeile in der Liste eine NativeCell sein wird, die die Eigenschaften Name, Category und ImageFilename über eine Datenbindung darstellt. Weitere Informationen zum ListView-Steuerelement finden Sie unter ListView.

Ein benutzerdefinierter Renderer kann nun zu jedem Anwendungsprojekt hinzugefügt werden, um das plattformspezifische Layout für jede Zelle anzupassen.

Erstellen des benutzerdefinierten Renderers auf jeder Plattform

Gehen Sie folgendermaßen vor, um eine Klasse für einen benutzerdefinierten Renderer zu erstellen:

  1. Erstellen Sie eine Unterklasse der ViewCellRenderer-Klasse, die die benutzerdefinierte Zelle rendert.
  2. Überschreiben Sie die plattformspezifische Methode, die die benutzerdefinierte Zelle rendert, und schreiben Sie Logik, um dieses anzupassen.
  3. Fügen Sie der Klasse des benutzerdefinierten Renderers ein ExportRenderer-Attribut hinzu, um anzugeben, dass sie zum Rendern der benutzerdefinierten Xamarin.Forms-Zelle verwendet werden soll. Dieses Attribut wird verwendet, um den benutzerdefinierten Renderer bei Xamarin.Forms zu registrieren.

Hinweis

Bei den meisten Xamarin.Forms-Elementen ist das Angeben eines benutzerdefinierten Renderers in jedem Plattformprojekt optional. Wenn kein benutzerdefinierter Renderer registriert wurde, wird der Standardrenderer für die Basisklasse des Steuerelements verwendet. Benutzerdefinierte Renderer sind jedoch in jedem Plattformprojekt erforderlich, wenn ein ViewCell-Element gerendert wird.

Das folgende Diagramm veranschaulicht die Zuständigkeiten jedes Projekts in der Beispielanwendung sowie deren Beziehungen zueinander:

Projektaufgaben für benutzerdefinierten NativeCell-Renderer

Die benutzerdefinierte Zelle NativeCell wird von plattformspezifischen Rendererklassen gerendert, die alle von der ViewCellRenderer-Klasse für jede Plattform abgeleitet werden. Das führt dazu, dass jede benutzerdefinierte NativeCell-Zelle mit dem plattformspezifischen Layout gerendert wird. Dies wird in folgenden Screenshots veranschaulicht:

NativeCell auf jeder Plattform

Die ViewCellRenderer-Klasse macht plattformspezifische Methoden zum Rendern der benutzerdefinierten Zelle verfügbar. Auf der iOS-Plattform ist dies die GetCell-Methode, auf der Android-Plattform die GetCellCore-Methode und auf der UWP die GetTemplate-Methode.

Jede benutzerdefinierte Rendererklasse ist mit einem ExportRenderer-Attribut versehen, das den Renderer bei Xamarin.Forms registriert. Das Attribut benötigt zwei Parameter: den Typnamen der zu rendernden Xamarin.Forms-Zelle und den Typnamen des benutzerdefinierten Renderers. Das Präfix assembly für das Attribut gibt an, dass das Attribut für die gesamte Assembly gilt.

In den folgenden Abschnitten wird die Implementierung jeder plattformspezifischen, benutzerdefinierten Rendererklasse erläutert.

Erstellen des benutzerdefinierten Renderers unter iOS

Im folgenden Codebeispiel wird der benutzerdefinierte Renderer für die iOS-Plattform veranschaulicht:

[assembly: ExportRenderer(typeof(NativeCell), typeof(NativeiOSCellRenderer))]
namespace CustomRenderer.iOS
{
    public class NativeiOSCellRenderer : ViewCellRenderer
    {
        NativeiOSCell cell;

        public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
        {
            var nativeCell = (NativeCell)item;

            cell = reusableCell as NativeiOSCell;
            if (cell == null)
                cell = new NativeiOSCell(item.GetType().FullName, nativeCell);
            else
                cell.NativeCell.PropertyChanged -= OnNativeCellPropertyChanged;

            nativeCell.PropertyChanged += OnNativeCellPropertyChanged;
            cell.UpdateCell(nativeCell);
            return cell;
        }
        ...
    }
}

Die GetCell-Methode wird aufgerufen, sodass jede anzuzeigende Zelle erstellt wird. Jede Zelle ist eine NativeiOSCell-Instanz, die das Layout der Zelle und deren Daten definiert. Der Vorgang der GetCell-Methode ist von der ListView-Strategie für die Zwischenspeicherung abhängig.

  • Wenn die ListView-Strategie zur Zwischenspeicherung RetainElement lautet, wird die GetCell-Methode für jede Zelle aufgerufen. Für jede NativeCell-Instanz, die zunächst auf dem Bildschirm angezeigt wird, wird eine NativeiOSCell-Instanz erstellt. Wenn der Benutzer durch die ListView scrollt, werden NativeiOSCell-Instanzen erneut verwendet. Weitere Informationen zum Wiederverwenden von Zellen in iOS finden Sie unter Auffüllen einer Tabelle mit Daten.

    Hinweis

    Dieser benutzerdefinierte Renderercode führt einige Vorgänge zum Wiederverwenden der Zellen durch, auch wenn ListView zum Beibehalten der Zellen festgelegt wurde.

    Die von jeder NativeiOSCell-Instanz dargestellten Daten – egal, ob es sich um neu erstellte oder wiederverwendete Daten handelt – werden mit den Daten aus jeder NativeCell-Instanz mithilfe der UpdateCell-Methode aktualisiert.

    Hinweis

    Die OnNativeCellPropertyChanged-Methode wird nie aufgerufen, wenn die ListView-Strategie zur Zwischenspeicherung zum Beibehalten von Zellen festgelegt ist.

  • Wenn die ListView-Strategie zur Zwischenspeicherung RecycleElement lautet, wird die GetCell-Methode für jede Zelle aufgerufen, die ursprünglich auf dem Bildschirm angezeigt wurde. Für jede NativeCell-Instanz, die zunächst auf dem Bildschirm angezeigt wird, wird eine NativeiOSCell-Instanz erstellt. Die von jeder NativeiOSCell-Instanz dargestellten Daten werden mit den Daten aus der NativeCell-Instanz mithilfe der UpdateCell-Methode aktualisiert. Die GetCell-Methode wird allerdings nicht aufgerufen, wenn der Benutzer durch ListView scrollt. Stattdessen werden die NativeiOSCell-Instanzen wiederverwendet. PropertyChanged-Ereignisse werden für die NativeCell-Instanz ausgelöst, wenn sich deren Daten ändern, und der OnNativeCellPropertyChanged-Ereignishandler aktualisiert die Daten in jeder wiederverwendeten NativeiOSCell-Instanz.

Im folgenden Codebeispiel ist die OnNativeCellPropertyChanged-Methode dargestellt, die aufgerufen wird, wenn ein PropertyChanged-Ereignis ausgelöst wird:

namespace CustomRenderer.iOS
{
    public class NativeiOSCellRenderer : ViewCellRenderer
    {
        ...

        void OnNativeCellPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            var nativeCell = (NativeCell)sender;
            if (e.PropertyName == NativeCell.NameProperty.PropertyName)
            {
                cell.HeadingLabel.Text = nativeCell.Name;
            }
            else if (e.PropertyName == NativeCell.CategoryProperty.PropertyName)
            {
                cell.SubheadingLabel.Text = nativeCell.Category;
            }
            else if (e.PropertyName == NativeCell.ImageFilenameProperty.PropertyName)
            {
                cell.CellImageView.Image = cell.GetImage(nativeCell.ImageFilename);
            }
        }
    }
}

Mit dieser Methode werden die Daten aktualisiert, die durch wiederverwendete NativeiOSCell-Instanzen dargestellt werden. Die Eigenschaft, die geändert wird, wird überprüft, da die Methode mehrere Male aufgerufen werden kann.

Die NativeiOSCell-Klasse definiert das Layout für jede Zelle und ist im folgenden Codebeispiel dargestellt:

internal class NativeiOSCell : UITableViewCell, INativeElementView
{
  public UILabel HeadingLabel { get; set; }
  public UILabel SubheadingLabel { get; set; }
  public UIImageView CellImageView { get; set; }

  public NativeCell NativeCell { get; private set; }
  public Element Element => NativeCell;

  public NativeiOSCell(string cellId, NativeCell cell) : base(UITableViewCellStyle.Default, cellId)
  {
    NativeCell = cell;

    SelectionStyle = UITableViewCellSelectionStyle.Gray;
    ContentView.BackgroundColor = UIColor.FromRGB(255, 255, 224);
    CellImageView = 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(CellImageView);
  }

  public void UpdateCell(NativeCell cell)
  {
    HeadingLabel.Text = cell.Name;
    SubheadingLabel.Text = cell.Category;
    CellImageView.Image = GetImage(cell.ImageFilename);
  }

  public UIImage GetImage(string filename)
  {
    return (!string.IsNullOrWhiteSpace(filename)) ? UIImage.FromFile("Images/" + filename + ".jpg") : null;
  }

  public override void LayoutSubviews()
  {
    base.LayoutSubviews();

    HeadingLabel.Frame = new CGRect(5, 4, ContentView.Bounds.Width - 63, 25);
    SubheadingLabel.Frame = new CGRect(100, 18, 100, 20);
    CellImageView.Frame = new CGRect(ContentView.Bounds.Width - 63, 5, 33, 33);
  }
}

Diese Klasse definiert die Steuerelemente, die zum Rendern der Inhalte der Zellen sowie deren Layout verwendet werden. Die Klasse implementiert die Schnittstelle INativeElementView, die benötigt wird, wenn die ListView die RecycleElement-Strategie zur Zwischenspeicherung verwendet. Diese Schnittstelle gibt an, dass die Klasse die Eigenschaft Element implementieren muss. Diese wiederum muss die benutzerdefinierten Zellendaten für wiederverwendete Zellen zurückgeben.

Der NativeiOSCell-Konstruktor initialisiert die Darstellung der Eigenschaften HeadingLabel, SubheadingLabel und CellImageView. Diese Eigenschaften werden zum Anzeigen der in der NativeCell-Instanz gespeicherten Daten verwendet. Die UpdateCell-Methode wird aufgerufen, um den Wert jeder Eigenschaft festzulegen. Wenn die ListView die RecycleElement-Strategie zur Zwischenspeicherung verwendet, können darüber hinaus die Daten, die von den Eigenschaften HeadingLabel, SubheadingLabel und CellImageView dargestellt werden, von der OnNativeCellPropertyChanged-Methode im benutzerdefinierten Renderer aktualisiert werden.

Das Zellenlayout erfolgt durch die LayoutSubviews-Überschreibung, die die Koordinaten von HeadingLabel, SubheadingLabel und CellImageView innerhalb der Zelle festlegt.

Erstellen des benutzerdefinierten Renderers unter Android

Im folgenden Codebeispiel wird der benutzerdefinierte Renderer für die Android-Plattform veranschaulicht:

[assembly: ExportRenderer(typeof(NativeCell), typeof(NativeAndroidCellRenderer))]
namespace CustomRenderer.Droid
{
    public class NativeAndroidCellRenderer : ViewCellRenderer
    {
        NativeAndroidCell cell;

        protected override Android.Views.View GetCellCore(Cell item, Android.Views.View convertView, ViewGroup parent, Context context)
        {
            var nativeCell = (NativeCell)item;
            Console.WriteLine("\t\t" + nativeCell.Name);

            cell = convertView as NativeAndroidCell;
            if (cell == null)
            {
                cell = new NativeAndroidCell(context, nativeCell);
            }
            else
            {
                cell.NativeCell.PropertyChanged -= OnNativeCellPropertyChanged;
            }

            nativeCell.PropertyChanged += OnNativeCellPropertyChanged;

            cell.UpdateCell(nativeCell);
            return cell;
        }
        ...
    }
}

Die GetCellCore-Methode wird aufgerufen, sodass jede anzuzeigende Zelle erstellt wird. Jede Zelle ist eine NativeAndroidCell-Instanz, die das Layout der Zelle und deren Daten definiert. Der Vorgang der GetCellCore-Methode ist von der ListView-Strategie für die Zwischenspeicherung abhängig.

  • Wenn die ListView-Strategie zur Zwischenspeicherung RetainElement lautet, wird die GetCellCore-Methode für jede Zelle aufgerufen. Für jede NativeCell-Instanz, die zunächst auf dem Bildschirm angezeigt wird, wird eine NativeAndroidCell erstellt. Wenn der Benutzer durch die ListView scrollt, werden NativeAndroidCell-Instanzen erneut verwendet. Weitere Informationen zur Wiederverwendung der Android-Zelle finden Sie unter Auffüllen von ListView mit Daten.

    Hinweis

    Beachten Sie, dass dieser benutzerdefinierte Renderercode einige Vorgänge zum Wiederverwenden der Zellen durchführt, auch wenn ListView zum Beibehalten der Zellen festgelegt wurde.

    Die von jeder NativeAndroidCell-Instanz dargestellten Daten – egal, ob es sich um neu erstellte oder wiederverwendete Daten handelt – werden mit den Daten aus jeder NativeCell-Instanz mithilfe der UpdateCell-Methode aktualisiert.

    Hinweis

    Obwohl die OnNativeCellPropertyChanged-Methode aufgerufen wird, wenn die ListView zum Beibehalten von Zellen festgelegt wurde, werden darüber keine NativeAndroidCell-Eigenschaftswerte aktualisiert.

  • Wenn die ListView-Strategie zur Zwischenspeicherung RecycleElement lautet, wird die GetCellCore-Methode für jede Zelle aufgerufen, die ursprünglich auf dem Bildschirm angezeigt wurde. Für jede NativeCell-Instanz, die zunächst auf dem Bildschirm angezeigt wird, wird eine NativeAndroidCell-Instanz erstellt. Die von jeder NativeAndroidCell-Instanz dargestellten Daten werden mit den Daten aus der NativeCell-Instanz mithilfe der UpdateCell-Methode aktualisiert. Die GetCellCore-Methode wird allerdings nicht aufgerufen, wenn der Benutzer durch ListView scrollt. Stattdessen werden die NativeAndroidCell-Instanzen wiederverwendet. PropertyChanged-Ereignisse werden für die NativeCell-Instanz ausgelöst, wenn sich deren Daten ändern, und der OnNativeCellPropertyChanged-Ereignishandler aktualisiert die Daten in jeder wiederverwendeten NativeAndroidCell-Instanz.

Im folgenden Codebeispiel ist die OnNativeCellPropertyChanged-Methode dargestellt, die aufgerufen wird, wenn ein PropertyChanged-Ereignis ausgelöst wird:

namespace CustomRenderer.Droid
{
    public class NativeAndroidCellRenderer : ViewCellRenderer
    {
        ...

        void OnNativeCellPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            var nativeCell = (NativeCell)sender;
            if (e.PropertyName == NativeCell.NameProperty.PropertyName)
            {
                cell.HeadingTextView.Text = nativeCell.Name;
            }
            else if (e.PropertyName == NativeCell.CategoryProperty.PropertyName)
            {
                cell.SubheadingTextView.Text = nativeCell.Category;
            }
            else if (e.PropertyName == NativeCell.ImageFilenameProperty.PropertyName)
            {
                cell.SetImage(nativeCell.ImageFilename);
            }
        }
    }
}

Mit dieser Methode werden die Daten aktualisiert, die durch wiederverwendete NativeAndroidCell-Instanzen dargestellt werden. Die Eigenschaft, die geändert wird, wird überprüft, da die Methode mehrere Male aufgerufen werden kann.

Die NativeAndroidCell-Klasse definiert das Layout für jede Zelle und ist im folgenden Codebeispiel dargestellt:

internal class NativeAndroidCell : LinearLayout, INativeElementView
{
  public TextView HeadingTextView { get; set; }
  public TextView SubheadingTextView { get; set; }
  public ImageView ImageView { get; set; }

  public NativeCell NativeCell { get; private set; }
  public Element Element => NativeCell;

  public NativeAndroidCell(Context context, NativeCell cell) : base(context)
  {
    NativeCell = cell;

    var view = (context as Activity).LayoutInflater.Inflate(Resource.Layout.NativeAndroidCell, null);
    HeadingTextView = view.FindViewById<TextView>(Resource.Id.HeadingText);
    SubheadingTextView = view.FindViewById<TextView>(Resource.Id.SubheadingText);
    ImageView = view.FindViewById<ImageView>(Resource.Id.Image);

    AddView(view);
  }

  public void UpdateCell(NativeCell cell)
  {
    HeadingTextView.Text = cell.Name;
    SubheadingTextView.Text = cell.Category;

    // Dispose of the old image
    if (ImageView.Drawable != null)
    {
      using (var image = ImageView.Drawable as BitmapDrawable)
      {
        if (image != null)
        {
          if (image.Bitmap != null)
          {
            image.Bitmap.Dispose();
          }
        }
      }
    }

    SetImage(cell.ImageFilename);
  }

  public void SetImage(string filename)
  {
    if (!string.IsNullOrWhiteSpace(filename))
    {
      // Display new image
      Context.Resources.GetBitmapAsync(filename).ContinueWith((t) =>
      {
        var bitmap = t.Result;
        if (bitmap != null)
        {
          ImageView.SetImageBitmap(bitmap);
          bitmap.Dispose();
        }
      }, TaskScheduler.FromCurrentSynchronizationContext());
    }
    else
    {
      // Clear the image
      ImageView.SetImageBitmap(null);
    }
  }
}

Diese Klasse definiert die Steuerelemente, die zum Rendern der Inhalte der Zellen sowie deren Layout verwendet werden. Die Klasse implementiert die Schnittstelle INativeElementView, die benötigt wird, wenn die ListView die RecycleElement-Strategie zur Zwischenspeicherung verwendet. Diese Schnittstelle gibt an, dass die Klasse die Eigenschaft Element implementieren muss. Diese wiederum muss die benutzerdefinierten Zellendaten für wiederverwendete Zellen zurückgeben.

Der NativeAndroidCellKonstruktor vergrößert das NativeAndroidCell-Layout und initialisiert die Eigenschaften HeadingTextView, SubheadingTextView und ImageView der Steuerelemente im vergrößerten Layout. Diese Eigenschaften werden zum Anzeigen der in der NativeCell-Instanz gespeicherten Daten verwendet. Die UpdateCell-Methode wird aufgerufen, um den Wert jeder Eigenschaft festzulegen. Wenn die ListView die RecycleElement-Strategie zur Zwischenspeicherung verwendet, können darüber hinaus die Daten, die von den Eigenschaften HeadingTextView, SubheadingTextView und ImageView dargestellt werden, von der OnNativeCellPropertyChanged-Methode im benutzerdefinierten Renderer aktualisiert werden.

Im folgenden Codebeispiel ist die Layoutdefinition für die Layoutdatei NativeAndroidCell.axml dargestellt:

<?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/HeadingText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#FF7F3300"
            android:textSize="20dip"
            android:textStyle="italic" />
        <TextView
            android:id="@+id/SubheadingText"
            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>

Dieses Layout gibt an, dass zwei TextView-Steuerelemente sowie ein ImageView-Steuerelement zum Anzeigen des Zelleninhalts verwendet werden. Die zwei TextView-Steuerelemente sind innerhalb eines LinearLayout-Steuerelements vertikal ausgerichtet, wobei alle Steuerelemente innerhalb einer RelativeLayout enthalten sind.

Erstellen des benutzerdefinierten Renderers auf der UWP

Im folgenden Codebeispiel wird der benutzerdefinierte Renderer für die UWP veranschaulicht:

[assembly: ExportRenderer(typeof(NativeCell), typeof(NativeUWPCellRenderer))]
namespace CustomRenderer.UWP
{
    public class NativeUWPCellRenderer : ViewCellRenderer
    {
        public override Windows.UI.Xaml.DataTemplate GetTemplate(Cell cell)
        {
            return App.Current.Resources["ListViewItemTemplate"] as Windows.UI.Xaml.DataTemplate;
        }
    }
}

Die GetTemplate-Methode wird aufgerufen, um die Zelle zurückzugeben, die für jede Datenzeile in der Liste gerendert werden soll. Für jede NativeCell-Instanz wird eine DataTemplate erstellt, die auf dem Bildschirm angezeigt wird, und die DataTemplate definiert die Darstellung sowie die Inhalte der Zelle.

Die DataTemplate wird im Ressourcenverzeichnis auf Anwendungsebene gespeichert und ist im folgenden Codebeispiel dargestellt:

<DataTemplate x:Key="ListViewItemTemplate">
    <Grid Background="LightYellow">
        <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>

Die DataTemplate gibt die zum Anzeigen der Zelleninhalte verwendeten Steuerelemente sowie deren Layout und Darstellung an. Zwei TextBlock-Steuerelemente sowie ein Image-Steuerelement werden zum Anzeigen des Zelleninhalts über die Datenbindung verwendet. Zusätzlich wird eine Instanz von ConcatImageExtensionConverter verwendet, um die Dateierweiterung .jpg an jeden Bilddateinamen zu ketten. So wird sichergestellt, dass das Steuerelement Image das Bild laden und rendern kann, wenn dessen Source-Eigenschaft festgelegt ist.

Zusammenfassung

In diesem Artikel wurde gezeigt, wie Sie einen benutzerdefinierten Renderer für ein ViewCell in einem Xamarin.FormsListView Steuerelement gehostetes Renderer erstellen. Das verhindert, dass die Xamarin.Forms-Layoutberechnungen wiederholt beim Scrollen in ListView aufgerufen werden.