Personalización de una ListViewCustomizing a ListView
Descargar el ejemplo
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:
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:
- Cree un control personalizado de :::no-loc(Xamarin.Forms):::.Create a :::no-loc(Xamarin.Forms)::: custom control.
- Consuma el control personalizado de :::no-loc(Xamarin.Forms):::.Consume the custom control from :::no-loc(Xamarin.Forms):::.
- 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:
- Cree una subclase de la clase
ListViewRenderer
que representa el control personalizado.Create a subclass of theListViewRenderer
class that renders the custom control. - Invalide el método
OnElementChanged
que representa el control personalizado y escriba lógica para personalizarlo.Override theOnElementChanged
method that renders the custom control and write logic to customize it. Se llama a este método cuando se crea el correspondienteListView
de :::no-loc(Xamarin.Forms):::.This method is called when the corresponding :::no-loc(Xamarin.Forms):::ListView
is created. - 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 anExportRenderer
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:
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:
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.
Vínculos relacionadosRelated Links
- CustomRendererListView (sample) (CustomRendererListView [ejemplo])CustomRendererListView (sample)