Personalización de una ListViewCustomizing a ListView

Descargar ejemplo Descargar el ejemploDownload Sample Download the sample

Una ListView de :::no-loc(Xamarin.Forms)::: es una vista que muestra una colección de datos como una lista vertical. En este artículo se muestra cómo crear un representador personalizado que encapsula los controles de lista específica de la plataforma y los diseños de celda nativa, lo que permite tener más control sobre el rendimiento del control de lista nativa.A :::no-loc(Xamarin.Forms)::: ListView is a view that displays a collection of data as a vertical list. This article demonstrates how to create a custom renderer that encapsulates platform-specific list controls and native cell layouts, allowing more control over native list control performance.

Todas las vistas de :::no-loc(Xamarin.Forms)::: tienen un representador que las acompaña para cada plataforma y que crea una instancia de un control nativo.Every :::no-loc(Xamarin.Forms)::: view has an accompanying renderer for each platform that creates an instance of a native control. Cuando una aplicación de :::no-loc(Xamarin.Forms)::: representa un ListView, se crea en iOS la instancia de la clase ListViewRenderer, que a su vez crea una instancia del control UITableView nativo.When a ListView is rendered by a :::no-loc(Xamarin.Forms)::: application, in iOS the ListViewRenderer class is instantiated, which in turn instantiates a native UITableView control. En la plataforma de Android, la clase ListViewRenderer crea una instancia de un control ListView nativo.On the Android platform, the ListViewRenderer class instantiates a native ListView control. En Plataforma universal de Windows (UWP), la clase ListViewRenderer crea una instancia de un control ListView nativo.On the Universal Windows Platform (UWP), the ListViewRenderer class instantiates a native ListView control. Para obtener más información sobre el representador y las clases de control nativo a las que se asignan los controles de :::no-loc(Xamarin.Forms):::, vea Clases base y controles nativos del representador.For more information about the renderer and native control classes that :::no-loc(Xamarin.Forms)::: controls map to, see Renderer Base Classes and Native Controls.

El siguiente diagrama muestra la relación entre el control ListView y los controles nativos correspondientes que lo implementan:The following diagram illustrates the relationship between the ListView control and the corresponding native controls that implement it:

Relación entre el control ListView y los controles nativos de implementación

El proceso de representación puede aprovecharse para implementar las personalizaciones específicas de la plataforma creando un representador personalizado para una ListView en cada plataforma.The rendering process can be taken advantage of to implement platform-specific customizations by creating a custom renderer for a ListView on each platform. Para hacerlo, siga este procedimiento:The process for doing this is as follows:

  1. Cree un control personalizado de :::no-loc(Xamarin.Forms):::.Create a :::no-loc(Xamarin.Forms)::: custom control.
  2. Consuma el control personalizado de :::no-loc(Xamarin.Forms):::.Consume the custom control from :::no-loc(Xamarin.Forms):::.
  3. Cree el representador personalizado para el control en cada plataforma.Create the custom renderer for the control on each platform.

Ahora se analizará en detalle cada elemento, para implementar un representador NativeListView que aproveche las ventajas de los diseños de celda nativos y los controles de lista específicos de la plataforma.Each item will now be discussed in turn, to implement a NativeListView renderer that takes advantage of platform-specific list controls and native cell layouts. Este escenario es útil al migrar una aplicación nativa existente que contiene la lista y el código de la celda que se puede volver a usar.This scenario is useful when porting an existing native app that contains list and cell code that can be re-used. Además, permite la personalización detallada de las características de control de lista que pueden afectar al rendimiento, como la virtualización de datos.In addition, it allows detailed customization of list control features that can affect performance, such as data virtualization.

Crear el control ListView personalizadoCreating the Custom ListView Control

Se puede crear un control personalizado ListView mediante la creación de subclases de la clase ListView, como se muestra en el siguiente ejemplo de código:A custom ListView control can be created by subclassing the ListView class, as shown in the following code example:

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));
    }
  }
}

El NativeListView se crea en el proyecto de biblioteca de .NET Standard y define la API para el control personalizado.The NativeListView is created in the .NET Standard library project and defines the API for the custom control. Este control expone una propiedad Items que se usa para rellenar el ListView con los datos y que puede enlazarse a datos para fines de presentación.This control exposes an Items property that is used for populating the ListView with data, and which can be data bound to for display purposes. También expone un evento ItemSelected que se desencadena cada vez que se selecciona un elemento en un control de lista nativo específico de la plataforma.It also exposes an ItemSelected event that will be fired whenever an item is selected in a platform-specific native list control. Para más información sobre el enlace de datos, consulte Data Binding Basics (Aspectos básicos del enlace de datos).For more information about data binding, see Data Binding Basics.

Uso del control personalizadoConsuming the Custom Control

En XAML puede hacerse referencia al control personalizado NativeListView en el proyecto de biblioteca de .NET Standard declarando un espacio de nombres para su ubicación y usando el prefijo del espacio de nombres en el control.The NativeListView custom control can be referenced in Xaml in the .NET Standard library project by declaring a namespace for its location and using the namespace prefix on the control. El siguiente ejemplo de código muestra cómo se puede usar el control personalizado NativeListView en una página XAML:The following code example shows how the NativeListView custom control can be consumed by a XAML page:

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

El prefijo de espacio de nombres local puede tener cualquier nombre.The local namespace prefix can be named anything. Pero los valores clr-namespace y assembly tienen que coincidir con los detalles del control personalizado.However, the clr-namespace and assembly values must match the details of the custom control. Una vez que se declara el espacio de nombres, el prefijo se usa para hacer referencia al control personalizado.Once the namespace is declared, the prefix is used to reference the custom control.

El siguiente ejemplo de código muestra cómo se puede usar el control personalizado NativeListView en una página C#:The following code example shows how the NativeListView custom control can be consumed by a C# page:

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;
    }
    ...
}

El control personalizado NativeListView utiliza los representadores personalizados específicos de la plataforma para mostrar una lista de datos que se rellena mediante la propiedad Items.The NativeListView custom control uses platform-specific custom renderers to display a list of data, which is populated through the Items property. Cada fila de la lista contiene tres elementos de datos, un nombre, una categoría y un nombre de archivo de imagen.Each row in the list contains three items of data – a name, a category, and an image filename. El diseño de cada fila de la lista se define mediante el representador personalizado específico de la plataforma.The layout of each row in the list is defined by the platform-specific custom renderer.

Nota

Dado que el control personalizado NativeListView se representa mediante controles de lista específicos de la plataforma que incluyen capacidad de desplazamiento, el control personalizado no debe hospedarse en los controles de diseño desplazable, como ScrollView.Because the NativeListView custom control will be rendered using platform-specific list controls that include scrolling ability, the custom control should not be hosted in scrollable layout controls such as the ScrollView.

Ahora se puede agregar un representador personalizado a cada proyecto de aplicación para crear controles de lista específicos de la plataforma y diseños de celda nativos.A custom renderer can now be added to each application project to create platform-specific list controls and native cell layouts.

Creación del representador personalizado en cada plataformaCreating the Custom Renderer on each Platform

El proceso para crear la clase del representador personalizado es el siguiente:The process for creating the custom renderer class is as follows:

  1. Cree una subclase de la clase ListViewRenderer que representa el control personalizado.Create a subclass of the ListViewRenderer class that renders the custom control.
  2. Invalide el método OnElementChanged que representa el control personalizado y escriba lógica para personalizarlo.Override the OnElementChanged method that renders the custom control and write logic to customize it. Se llama a este método cuando se crea el correspondiente ListView de :::no-loc(Xamarin.Forms):::.This method is called when the corresponding :::no-loc(Xamarin.Forms)::: ListView is created.
  3. Agregue un atributo ExportRenderer a la clase de representador personalizada para especificar que se usará para representar el control personalizado de :::no-loc(Xamarin.Forms):::.Add an ExportRenderer attribute to the custom renderer class to specify that it will be used to render the :::no-loc(Xamarin.Forms)::: custom control. Este atributo se usa para registrar al representador personalizado con :::no-loc(Xamarin.Forms):::.This attribute is used to register the custom renderer with :::no-loc(Xamarin.Forms):::.

Nota

Proporcionar un representador personalizado en cada proyecto de la plataforma es un paso opcional.It is optional to provide a custom renderer in each platform project. Si no hay un representador personalizado registrado, se usa el representador predeterminado de la clase base de la celda.If a custom renderer isn't registered, then the default renderer for the cell's base class will be used.

El siguiente diagrama muestra las responsabilidades de cada proyecto de la aplicación de ejemplo, junto con las relaciones entre ellos:The following diagram illustrates the responsibilities of each project in the sample application, along with the relationships between them:

Responsabilidades de proyecto del representador personalizado de NativeListView

El control personalizado NativeListView se representa mediante clases de representador específicas de la plataforma, que se derivan de la clase ListViewRenderer de cada plataforma.The NativeListView custom control is rendered by platform-specific renderer classes, which all derive from the ListViewRenderer class for each platform. Esto da lugar a que cada control personalizado NativeListView se represente con diseños de celda nativos y controles de lista específicos de la plataforma, como se muestra en las siguientes capturas de pantalla:This results in each NativeListView custom control being rendered with platform-specific list controls and native cell layouts, as shown in the following screenshots:

NativeListView en cada plataforma

La clase ListViewRenderer expone el método OnElementChanged, al que se llama cuando se crea el control personalizado de :::no-loc(Xamarin.Forms)::: para representar el control nativo correspondiente.The ListViewRenderer class exposes the OnElementChanged method, which is called when the :::no-loc(Xamarin.Forms)::: custom control is created to render the corresponding native control. Este método toma un parámetro ElementChangedEventArgs que contiene propiedades OldElement y NewElement.This method takes an ElementChangedEventArgs parameter, that contains OldElement and NewElement properties. Estas propiedades representan al elemento de :::no-loc(Xamarin.Forms)::: al que estaba asociado el representador y al elemento de :::no-loc(Xamarin.Forms)::: al que está asociado el representador, respectivamente.These properties represent the :::no-loc(Xamarin.Forms)::: element that the renderer was attached to, and the :::no-loc(Xamarin.Forms)::: element that the renderer is attached to, respectively. En la aplicación de ejemplo, la propiedad OldElement es null y la propiedad NewElement contiene una referencia a la instancia de NativeListView.In the sample application, the OldElement property will be null and the NewElement property will contain a reference to the NativeListView instance.

El lugar para realizar la personalización de controles nativos es una versión reemplazada del método OnElementChanged en cada clase de representador específica de la plataforma.An overridden version of the OnElementChanged method, in each platform-specific renderer class, is the place to perform the native control customization. Una referencia con tipo para el control nativo que se usa en la plataforma puede obtenerse a través de la propiedad Control.A typed reference to the native control being used on the platform can be accessed through the Control property. Además, mediante la propiedad :::no-loc(Xamarin.Forms)::: se puede obtener una referencia al control de Element que se representa.In addition, a reference to the :::no-loc(Xamarin.Forms)::: control that's being rendered can be obtained through the Element property.

Debe tener cuidado al suscribirse a los controladores de eventos en el método OnElementChanged, como se muestra en el siguiente ejemplo de código:Care must be taken when subscribing to event handlers in the OnElementChanged method, as demonstrated in the following code example:

protected override void OnElementChanged (ElementChangedEventArgs<:::no-loc(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
  }
}

Solo se debe configurar el control nativo y suscribir a los controladores de eventos cuando se adjunta el representador personalizado a un nuevo elemento de :::no-loc(Xamarin.Forms):::.The native control should only be configured and event handlers subscribed to when the custom renderer is attached to a new :::no-loc(Xamarin.Forms)::: element. De forma similar, solo se debe cancelar la suscripción de los controladores de eventos que se han suscrito cuando cambia el elemento al que está asociado el representador.Similarly, any event handlers that were subscribed to should be unsubscribed from only when the element the renderer is attached to changes. Adoptar este enfoque facilita crear un representador personalizado que no sufra pérdidas de memoria.Adopting this approach will help to create a custom renderer that doesn't suffer from memory leaks.

Una versión invalidada del método OnElementPropertyChanged, en cada clase de representador específico de la plataforma, es el lugar para responder a los cambios de propiedad enlazable en el control personalizado de :::no-loc(Xamarin.Forms):::.An overridden version of the OnElementPropertyChanged method, in each platform-specific renderer class, is the place to respond to bindable property changes on the :::no-loc(Xamarin.Forms)::: custom control. Siempre se debe realizar una comprobación de la propiedad que ha modificado, ya que esta invalidación se puede llamar varias veces.A check for the property that's changed should always be made, as this override can be called many times.

Cada clase de representador personalizado se decora con un atributo ExportRenderer que registra el representador con :::no-loc(Xamarin.Forms):::.Each custom renderer class is decorated with an ExportRenderer attribute that registers the renderer with :::no-loc(Xamarin.Forms):::. El atributo toma dos parámetros: el nombre de tipo del control personalizado de :::no-loc(Xamarin.Forms)::: que se representa y el nombre de tipo del representador personalizado.The attribute takes two parameters – the type name of the :::no-loc(Xamarin.Forms)::: custom control being rendered, and the type name of the custom renderer. El prefijo assembly para el atributo especifica que el atributo se aplica a todo el ensamblado.The assembly prefix to the attribute specifies that the attribute applies to the entire assembly.

En las secciones siguientes se describe la implementación de cada clase de representador personalizado específico de plataforma.The following sections discuss the implementation of each platform-specific custom renderer class.

Creación del representador personalizado en iOSCreating the Custom Renderer on iOS

El siguiente ejemplo de código muestra el representador personalizado para la plataforma iOS:The following code example shows the custom renderer for the iOS platform:

[assembly: ExportRenderer (typeof(NativeListView), typeof(NativeiOSListViewRenderer))]
namespace CustomRenderer.iOS
{
    public class NativeiOSListViewRenderer : ListViewRenderer
    {
        protected override void OnElementChanged (ElementChangedEventArgs<:::no-loc(Xamarin.Forms):::.ListView> e)
        {
            base.OnElementChanged (e);

            if (e.OldElement != null) {
                // Unsubscribe
            }

            if (e.NewElement != null) {
                Control.Source = new NativeiOSListViewSource (e.NewElement as NativeListView);
            }
        }
    }
}

El control UITableView se configura mediante la creación de una instancia de la clase NativeiOSListViewSource, siempre que se adjunte el representador personalizado a un nuevo elemento de :::no-loc(Xamarin.Forms):::.The UITableView control is configured by creating an instance of the NativeiOSListViewSource class, provided that the custom renderer is attached to a new :::no-loc(Xamarin.Forms)::: element. Esta clase proporciona datos para el control UITableView invalidando los métodos RowsInSection y GetCell desde la clase UITableViewSource y exponiendo una propiedad Items que contiene la lista de datos que se mostrarán.This class provides data to the UITableView control by overriding the RowsInSection and GetCell methods from the UITableViewSource class, and by exposing an Items property that contains the list of data to be displayed. La clase también proporciona una invalidación del método RowSelected que invoca el evento ItemSelected proporcionado por el control personalizado NativeListView.The class also provides a RowSelected method override that invokes the ItemSelected event provided by the NativeListView custom control. Para obtener más información sobre las invalidaciones de método, vea Subclassing UITableViewSource (Creación de subclases de UITableViewSource).For more information about the method overrides, see Subclassing UITableViewSource. El método GetCell devuelve un UITableCellView que se rellena con datos para cada fila de la lista y se muestra en el siguiente ejemplo de código:The GetCell method returns a UITableCellView that's populated with data for each row in the list, and is shown in the following code example:

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;
}

Este método crea una instancia de NativeiOSListViewCell para cada fila de datos que se mostrará en la pantalla.This method creates a NativeiOSListViewCell instance for each row of data that will be displayed on the screen. La instancia de NativeiOSCell define el diseño de cada celda y los datos de la celda.The NativeiOSCell instance defines the layout of each cell and the cell's data. Cuando una celda desaparezca de la pantalla debido al desplazamiento, la celda estará disponible para su reutilización.When a cell disappears from the screen due to scrolling, the cell will be made available for reuse. Esto evita desperdiciar memoria garantizando que solo hay instancias de NativeiOSCell para los datos que se muestran en la pantalla, en lugar de todos los datos en la lista.This avoids wasting memory by ensuring that there are only NativeiOSCell instances for the data being displayed on the screen, rather than all of the data in the list. Para obtener más información sobre la reutilización de celdas, vea Cell Reuse (Reutilización de celdas).For more information about cell reuse, see Cell Reuse. El método GetCell también lee la propiedad ImageFilename de cada fila de datos, siempre que exista, y lee la imagen y la almacena como una instancia de UIImage antes de actualizar la instancia de NativeiOSListViewCell con los datos (nombre, categoría e imagen) de la fila.The GetCell method also reads the ImageFilename property of each row of data, provided that it exists, and reads the image and stores it as a UIImage instance, before updating the NativeiOSListViewCell instance with the data (name, category, and image) for the row.

La clase NativeiOSListViewCell define el diseño de cada celda y se muestra en el siguiente ejemplo de código:The NativeiOSListViewCell class defines the layout for each cell, and is shown in the following code example:

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);
  }
}

Esta clase define los controles utilizados para representar el contenido de la celda y su diseño.This class defines the controls used to render the cell's contents, and their layout. El constructor NativeiOSListViewCell crea instancias de controles de UILabel y UIImageView e inicializa su apariencia.The NativeiOSListViewCell constructor creates instances of UILabel and UIImageView controls, and initializes their appearance. Estos controles se usan para mostrar datos de cada fila, y el método UpdateCell se usa para establecer estos datos en las instancias de UILabel y UIImageView.These controls are used to display each row's data, with the UpdateCell method being used to set this data on the UILabel and UIImageView instances. El método LayoutSubviews invalidado establece la ubicación de estas instancias especificando sus coordenadas dentro de la celda.The location of these instances is set by the overridden LayoutSubviews method, by specifying their coordinates within the cell.

Responde a un cambio de propiedad en el control personalizadoResponding to a Property Change on the Custom Control

Si la propiedad NativeListView.Items cambia debido a elementos que se agregan o se quitan de la lista, el representador personalizado debe responder mostrando los cambios.If the NativeListView.Items property changes, due to items being added to or removed from the list, the custom renderer needs to respond by displaying the changes. Esto puede realizarse invalidando el método OnElementPropertyChanged, que se muestra en el siguiente ejemplo de código:This can be accomplished by overriding the OnElementPropertyChanged method, which is shown in the following code example:

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);
  }
}

El método crea una nueva instancia de la clase NativeiOSListViewSource que proporciona datos para el control UITableView, siempre que la propiedad enlazable NativeListView.Items haya cambiado.The method creates a new instance of the NativeiOSListViewSource class that provides data to the UITableView control, provided that the bindable NativeListView.Items property has changed.

Creación del representador personalizado en AndroidCreating the Custom Renderer on Android

En el ejemplo de código siguiente se muestra el representador personalizado para la plataforma Android:The following code example shows the custom renderer for the Android platform:

[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<:::no-loc(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]);
        }
    }
}

El control nativo ListView se configura siempre y cuando el representador personalizado esté asociado a un nuevo elemento de :::no-loc(Xamarin.Forms):::.The native ListView control is configured provided that the custom renderer is attached to a new :::no-loc(Xamarin.Forms)::: element. Esta configuración implica la creación de una instancia de la clase NativeAndroidListViewAdapter que proporciona datos al control ListView nativo y el registro de un controlador de eventos para procesar el evento ItemClick.This configuration involves creating an instance of the NativeAndroidListViewAdapter class that provides data to the native ListView control, and registering an event handler to process the ItemClick event. A su vez, este controlador invocará el evento ItemSelected proporcionado por el control personalizado NativeListView.In turn, this handler will invoke the ItemSelected event provided by the NativeListView custom control. Se cancela la suscripción del evento ItemClick solo si cambia el representador al que está adjunto el elemento de :::no-loc(Xamarin.Forms):::.The ItemClick event is unsubscribed from if the :::no-loc(Xamarin.Forms)::: element the renderer is attached to changes.

El NativeAndroidListViewAdapter deriva de la clase BaseAdapter y expone una propiedad Items que contiene la lista de datos que se mostrarán, además de invalidar los métodos Count, GetView, GetItemId y this[int].The NativeAndroidListViewAdapter derives from the BaseAdapter class and exposes an Items property that contains the list of data to be displayed, as well as overriding the Count, GetView, GetItemId, and this[int] methods. Para obtener más información sobre estas invalidaciones de método, vea Implementing a ListAdapter (Implementación de un ListAdapter).For more information about these method overrides, see Implementing a ListAdapter. El método GetView devuelve una vista para cada fila, que se rellena con datos y se muestra en el siguiente ejemplo de código:The GetView method returns a view for each row, populated with data, and is shown in the following code example:

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;
}

Se llama al método GetView para devolver la celda que se va a representar, como una View, para cada fila de datos en la lista.The GetView method is called to return the cell to be rendered, as a View, for each row of data in the list. Crea una instancia de View para cada fila de datos que se mostrará en la pantalla, con la apariencia de la instancia de View que se define en un archivo de diseño.It creates a View instance for each row of data that will be displayed on the screen, with the appearance of the View instance being defined in a layout file. Cuando una celda desaparezca de la pantalla debido al desplazamiento, la celda estará disponible para su reutilización.When a cell disappears from the screen due to scrolling, the cell will be made available for reuse. Esto evita desperdiciar memoria garantizando que solo hay instancias de View para los datos que se muestran en la pantalla, en lugar de todos los datos en la lista.This avoids wasting memory by ensuring that there are only View instances for the data being displayed on the screen, rather than all of the data in the list. Para obtener más información sobre la reutilización de vistas, vea Row View Re-use (Reutilización de vistas de fila).For more information about view reuse, see Row View Re-use.

El método GetView también rellena la instancia View con datos, incluyendo la lectura de los datos de imagen del nombre de archivo especificado en la propiedad ImageFilename.The GetView method also populates the View instance with data, including reading the image data from the filename specified in the ImageFilename property.

El diseño de cada celda mostrada por el ListView nativo se define en el archivo de diseño NativeAndroidListViewCell.axml, que aumenta el método LayoutInflater.Inflate.The layout of each cell dispayed by the native ListView is defined in the NativeAndroidListViewCell.axml layout file, which is inflated by the LayoutInflater.Inflate method. En el siguiente ejemplo de código se muestra la definición del diseño:The following code example shows the layout definition:

<?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>

Este diseño especifica que dos controles de TextView y un control de ImageView se usan para mostrar el contenido de la celda.This layout specifies that two TextView controls and an ImageView control are used to display the cell's content. Los dos controles de TextView están orientados verticalmente dentro de un control de LinearLayout, con todos los controles contenidos en un RelativeLayout.The two TextView controls are vertically oriented within a LinearLayout control, with all the controls being contained within a RelativeLayout.

Responde a un cambio de propiedad en el control personalizadoResponding to a Property Change on the Custom Control

Si la propiedad NativeListView.Items cambia debido a elementos que se agregan o se quitan de la lista, el representador personalizado debe responder mostrando los cambios.If the NativeListView.Items property changes, due to items being added to or removed from the list, the custom renderer needs to respond by displaying the changes. Esto puede realizarse invalidando el método OnElementPropertyChanged, que se muestra en el siguiente ejemplo de código:This can be accomplished by overriding the OnElementPropertyChanged method, which is shown in the following code example:

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);
  }
}

El método crea una nueva instancia de la clase NativeAndroidListViewAdapter que proporciona datos para el control ListView nativo, siempre que el la propiedad enlazable NativeListView.Items haya cambiado.The method creates a new instance of the NativeAndroidListViewAdapter class that provides data to the native ListView control, provided that the bindable NativeListView.Items property has changed.

Creación del representador personalizado en UWPCreating the Custom Renderer on UWP

En el siguiente ejemplo de código se muestra el representador personalizado para UWP:The following code example shows the custom renderer for UWP:

[assembly: ExportRenderer(typeof(NativeListView), typeof(NativeUWPListViewRenderer))]
namespace CustomRenderer.UWP
{
    public class NativeUWPListViewRenderer : ListViewRenderer
    {
        ListView listView;

        protected override void OnElementChanged(ElementChangedEventArgs<:::no-loc(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);
        }
    }
}

El control nativo ListView se configura siempre y cuando el representador personalizado esté asociado a un nuevo elemento de :::no-loc(Xamarin.Forms):::.The native ListView control is configured provided that the custom renderer is attached to a new :::no-loc(Xamarin.Forms)::: element. Esta configuración implica configurar el modo en que el control ListView nativo responderá a los elementos seleccionados, rellenar los datos mostrados por el control, definir la apariencia y el contenido de cada celda y registrar un controlador de eventos para procesar el evento SelectionChanged.This configuration involves setting how the native ListView control will respond to items being selected, populating the data displayed by the control, defining the appearance and contents of each cell, and registering an event handler to process the SelectionChanged event. A su vez, este controlador invocará el evento ItemSelected proporcionado por el control personalizado NativeListView.In turn, this handler will invoke the ItemSelected event provided by the NativeListView custom control. Se cancela la suscripción del evento SelectionChanged solo si cambia el representador al que está adjunto el elemento de :::no-loc(Xamarin.Forms):::.The SelectionChanged event is unsubscribed from if the :::no-loc(Xamarin.Forms)::: element the renderer is attached to changes.

La apariencia y el contenido de cada celda ListView nativa se definen mediante un DataTemplate denominado ListViewItemTemplate.The appearance and contents of each native ListView cell are defined by a DataTemplate named ListViewItemTemplate. Este DataTemplate se almacena en el diccionario de recursos de nivel de aplicación y se muestra en el siguiente ejemplo de código:This DataTemplate is stored in the application-level resource dictionary, and is shown in the following code example:

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

El DataTemplate especifica los controles utilizados para mostrar el contenido de la celda y su diseño y apariencia.The DataTemplate specifies the controls used to display the contents of the cell, and their layout and appearance. Dos controles de TextBlock y un control de Image se usan para mostrar el contenido de la celda mediante el enlace de datos.Two TextBlock controls and an Image control are used to display the cell's content through data binding. Además, una instancia de ConcatImageExtensionConverter se utiliza para concatenar la extensión de archivo .jpg para cada nombre de archivo de imagen.In addition, an instance of the ConcatImageExtensionConverter is used to concatenate the .jpg file extension to each image file name. Esto garantiza que el control Image puede cargar y representar la imagen cuando se establece su propiedad Source.This ensures that the Image control can load and render the image when it's Source property is set.

Responde a un cambio de propiedad en el control personalizadoResponding to a Property Change on the Custom Control

Si la propiedad NativeListView.Items cambia debido a elementos que se agregan o se quitan de la lista, el representador personalizado debe responder mostrando los cambios.If the NativeListView.Items property changes, due to items being added to or removed from the list, the custom renderer needs to respond by displaying the changes. Esto puede realizarse invalidando el método OnElementPropertyChanged, que se muestra en el siguiente ejemplo de código:This can be accomplished by overriding the OnElementPropertyChanged method, which is shown in the following code example:

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;
    }
}

El método rellena el control ListView nativo con los datos modificados, siempre que la propiedad NativeListView.Items enlazable haya cambiado.The method re-populates the native ListView control with the changed data, provided that the bindable NativeListView.Items property has changed.

ResumenSummary

En este artículo se mostró cómo crear un representador personalizado que encapsula los controles de lista específica de la plataforma y los diseños de celda nativa, lo que permite tener más control sobre el rendimiento del control de lista nativa.This article has demonstrated how to create a custom renderer that encapsulates platform-specific list controls and native cell layouts, allowing more control over native list control performance.