Xamarin.Forms Control deslizante

Usar un Slider para seleccionar desde un rango de valores continuos.

El Xamarin.FormsSlider es una barra horizontal que el usuario puede manipular para seleccionar un valor double desde un rango de valores continuos.

El Slider define tres propiedades del tipo double:

  • Minimum es el valor mínimo del rango, con un valor predeterminado de 0.
  • Maximum es el máximo del rango, con un valor predeterminado de 1.
  • Value es el valor del slider, que puede oscilar entre Minimum y Maximum y tiene un valor predeterminado de 0.

Las tres propiedades están respaldadas por objetos BindableProperty. La propiedad Value tiene un modo de enlace predeterminado de BindingMode.TwoWay, lo que significa que es adecuado como origen de enlace en una aplicación que usa la arquitectura Model-View-ViewModel (MVVM).

Advertencia

Internamente, Slider garantiza que Maximum sea menor que Minimum. Si Minimum o Maximum se establecen alguna vez para que Maximum no sea menor que Minimum, se genera una excepción. Consulte la siguiente sección Precauciones para obtener más información sobre cómo establecer las propiedades Minimum y Maximum.

El Slider reprime la propiedad Value para que esté entre Minimum y Maximum, ambos inclusive. Si la propiedad Minimum se establece en un valor mayor que la propiedad Value, Slider establece la propiedad Value en Minimum. Del mismo modo, si Maximum se establece en un valor menor que Value, Slider establece la propiedad Value en Maximum.

Slider define un evento de ValueChanged que se desencadena cuando cambia el Value, ya sea mediante la manipulación del usuario de la Slider o cuando el programa establece la propiedad Value directamente. También se desencadena un evento ValueChanged cuando la propiedad Value se coacciona como se describe en el párrafo anterior.

El objeto ValueChangedEventArgs que acompaña al evento ValueChanged tiene dos propiedades, de tipo double: OldValue y NewValue. En el momento en que se desencadena el evento, el valor NewValue es el mismo que la propiedad Value del objetoSlider.

Slider también define eventos DragStarted y DragCompleted, que se desencadenan al principio y al final de la acción de arrastrar. A diferencia del evento ValueChanged, los eventos DragStarted y DragCompleted solo se desencadenan a través de la manipulación por parte del usuario del Slider. Cuando se desencadena el evento DragStarted, se ejecuta DragStartedCommand de tipo ICommand. Del mismo modo, cuando se desencadena el evento DragCompleted, se ejecuta DragCompletedCommand de tipo ICommand.

Advertencia

No uses opciones de diseño horizontal sin restricciones de Center, Start o End con Slider. Tanto en Android como en UWP, el Slider se contrae a una barra de longitud cero y, en iOS, la barra es muy corta. Mantén la configuración predeterminada HorizontalOptions de Fill y no uses un ancho de Auto al colocar Slider en un diseño Grid.

El Slider también define varias propiedades que afectan a su apariencia:

Nota:

Las propiedades ThumbColor y ThumbImageSource se excluyen mutuamente. Si se establecen ambas propiedades, la propiedad ThumbImageSource tendrá precedencia.

Código y marcado del Slider básico

La muestra comienza con tres páginas que son funcionalmente idénticas, pero se implementan de maneras diferentes. La primera página usa solo código de C#, el segundo usa XAML con un controlador de eventos en el código y el tercero puede evitar el controlador de eventos mediante el enlace de datos en el archivo XAML.

Creación de un Slider en el código

En la página Código deslizante básico se muestra cómo crear un Slider y dos objetos Label en el código:

public class BasicSliderCodePage : ContentPage
{
    public BasicSliderCodePage()
    {
        Label rotationLabel = new Label
        {
            Text = "ROTATING TEXT",
            FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
            HorizontalOptions = LayoutOptions.Center,
            VerticalOptions = LayoutOptions.CenterAndExpand
        };

        Label displayLabel = new Label
        {
            Text = "(uninitialized)",
            HorizontalOptions = LayoutOptions.Center,
            VerticalOptions = LayoutOptions.CenterAndExpand
        };

        Slider slider = new Slider
        {
            Maximum = 360
        };
        slider.ValueChanged += (sender, args) =>
        {
            rotationLabel.Rotation = slider.Value;
            displayLabel.Text = String.Format("The Slider value is {0}", args.NewValue);
        };

        Title = "Basic Slider Code";
        Padding = new Thickness(10, 0);
        Content = new StackLayout
        {
            Children =
            {
                rotationLabel,
                slider,
                displayLabel
            }
        };
    }
}

El Slider se inicializa para tener una propiedad Maximum de 360. El controlador ValueChanged del Slider usa la propiedad Value del objeto slider para establecer la propiedad Rotation del primero Label y usa el método String.Format con la propiedad NewValue de los argumentos del evento para establecer la propiedad Text del segundo Label. Estos dos enfoques para obtener el valor actual de Slider son intercambiables.

Este es el programa que se ejecuta en dispositivos iOS y Android:

Código de control deslizante básico

El segundo Label muestra el texto "(sin inicializar)" hasta que Slider se manipula, lo que hace que se desencadene el primer evento ValueChanged. Observe que el número de posiciones decimales que se muestran es diferente para cada plataforma. Estas diferencias están relacionadas con las implementaciones de la plataforma del Slider y se describen más adelante en este artículo en la sección Diferencias de implementación de la plataforma.

Creación de un Slider en XAML

La página XAML del slider básico es funcionalmente la misma que código del slider básico, pero se implementa principalmente en XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="SliderDemos.BasicSliderXamlPage"
             Title="Basic Slider XAML"
             Padding="10, 0">
    <StackLayout>
        <Label x:Name="rotatingLabel"
               Text="ROTATING TEXT"
               FontSize="Large"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />

        <Slider Maximum="360"
                ValueChanged="OnSliderValueChanged" />

        <Label x:Name="displayLabel"
               Text="(uninitialized)"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />
    </StackLayout>
</ContentPage>

El archivo de código subyacente contiene el controlador para el evento ValueChanged:

public partial class BasicSliderXamlPage : ContentPage
{
    public BasicSliderXamlPage()
    {
        InitializeComponent();
    }

    void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
    {
        double value = args.NewValue;
        rotatingLabel.Rotation = value;
        displayLabel.Text = String.Format("The Slider value is {0}", value);
    }
}

También es posible que el controlador de eventos obtenga el Slider que desencadena el evento a través del argumento sender. La propiedad Value contiene el valor actual:

double value = ((Slider)sender).Value;

Si el objeto Slider recibió un nombre en el archivo XAML con un atributo x:Name (por ejemplo, "slider"), el controlador de eventos podría hacer referencia directamente a ese objeto:

double value = slider.Value;

Enlace de datos del Slider

La página Enlaces de slider básico muestra cómo escribir un programa casi equivalente que elimina el controlador de eventos de Value mediante enlace de datos:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="SliderDemos.BasicSliderBindingsPage"
             Title="Basic Slider Bindings"
             Padding="10, 0">
    <StackLayout>
        <Label Text="ROTATING TEXT"
               Rotation="{Binding Source={x:Reference slider},
                                  Path=Value}"
               FontSize="Large"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />

        <Slider x:Name="slider"
                Maximum="360" />

        <Label x:Name="displayLabel"
               Text="{Binding Source={x:Reference slider},
                              Path=Value,
                              StringFormat='The Slider value is {0:F0}'}"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />
    </StackLayout>
</ContentPage>

La propiedad Rotation del primer Label está enlazada a la propiedad Value de Slider, tal como es la propiedad Text del segundo Label con una especificación StringFormat. La página Enlaces de slider básico funciona de manera un poco diferente a las dos páginas anteriores: cuando aparece la página por primera vez, la segunda Label muestra la cadena de texto con el valor. Esta es una ventaja de usar el enlace de datos. Para mostrar texto sin enlace de datos, tendrías que inicializar específicamente la propiedad Text de Label o simular una activación del evento ValueChanged mediante una llamada al controlador de eventos desde el constructor de clase.

Precauciones

El valor de la propiedad Minimum debe ser siempre inferior que el valor de la propiedad Maximum. El siguiente fragmento de código hace que Slider genere una excepción:

// Throws an exception!
Slider slider = new Slider
{
    Minimum = 10,
    Maximum = 20
};

El compilador de C# genera código que establece estas dos propiedades en secuencia y, cuando la propiedad Minimum se establece en 10, es mayor que el valor predeterminado Maximum de 1. Puedes evitar la excepción en este caso estableciendo primero la propiedad Maximum:

Slider slider = new Slider
{
    Maximum = 20,
    Minimum = 10
};

Establecer Maximum en 20 no es un problema porque es mayor que el valor predeterminado Minimum de 0. Cuando Minimum se establece, el valor es menor que el valor Maximum de 20.

El mismo problema existe en XAML. Establece las propiedades en un orden que garantice que Maximum siempre sea mayor que Minimum:

<Slider Maximum="20"
        Minimum="10" ... />

Luego, puedes establecer los valores Minimum y Maximum en números negativos, pero solo en un orden donde Minimum siempre sea menor que Maximum:

<Slider Minimum="-20"
        Maximum="-10" ... />

La propiedad Value siempre es mayor o igual que el valor Minimum y menor o igual que Maximum. Si Value se establece en un valor fuera de ese intervalo, el valor se reprimirá para que se encuentre dentro del intervalo, pero no se genera ninguna excepción. Por ejemplo, este código no generará una excepción:

Slider slider = new Slider
{
    Value = 10
};

En su lugar, la propiedad Value se convierte en el valor Maximum de 1.

Este es un fragmento de código que se muestra anteriormente:

Slider slider = new Slider
{
    Maximum = 20,
    Minimum = 10
};

Cuando Minimum se establece en 10, Value también se establece en 10.

Si un controlador de eventos ValueChanged se ha adjuntado en el momento en que la propiedadValue está coaccionada a algo distinto de su valor predeterminado de 0, se desencadena un evento ValueChanged. Este es un fragmento de código XAML:

<Slider ValueChanged="OnSliderValueChanged"
        Maximum="20"
        Minimum="10" />

Cuando Minimum se establece en 10, Value también se establece en 10 y el evento ValueChanged se desencadena. Esto puede ocurrir antes de que se haya construido el resto de la página y el controlador podría intentar hacer referencia a otros elementos de la página que aún no se han creado. Es posible que desees agregar código al controlador ValueChanged que comprueba si hay valores null de otros elementos en la página. O bien, puedes establecer el controlador de eventos ValueChanged después de inicializar los valores Slider.

Diferencias de implementación de la plataforma

Las capturas de pantalla mostradas anteriormente muestran el valor del Slider con un número diferente de puntos decimales. Esto se relaciona con cómo se implementa el Slider en las plataformas Android y UWP.

La implementación de Android

La implementación de Android de Slider se basa en Android SeekBar y siempre establece la propiedad Max en 1000. Esto significa que el Slider en Android tiene solo 1001 valores discretos. Si establece el Slider para que tenga un Minimum de 0 y un Maximum de 5000, a medida que se manipula la Slider, la propiedad Value tiene valores de 0, 5, 10, 15, etc.

La implementación de UWP

La implementación de UWP de Slider se basa en el control Slider de UWP. La StepFrequency propiedad de UWP Slider se establece en la diferencia de las propiedades Maximum y Minimum divididas por 10, pero no mayores que 1.

Por ejemplo, para el rango predeterminado de 0 a 1, la propiedad StepFrequency se establece en 0,1. A medida que se manipula el Slider, la propiedad Value está restringida a 0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9 y 1.0. Cuando la diferencia entre las propiedades Maximum y Minimum es 10 o superior, StepFrequency se establece en 1 y la propiedad Value tiene valores enteros.

La solución StepSlider

Se analiza un StepSlider más versátil en el capítulo 27 de . Representadores personalizados del libro Creación de aplicaciones móviles con Xamarin.Forms. El StepSlider es similar a Slider, pero agrega una propiedad Steps para especificar el número de valores entre Minimum y Maximum.

Sliders para la selección de colores

Las dos páginas finales del ejemplo usan tres instancias Slider para la selección de colores. La primera página controla todas las interacciones del archivo de código subyacente, mientras que la segunda página muestra cómo usar el enlace de datos con un ViewModel.

Control de Sliders en el archivo de código subyacente

La página Sliders de color RGB crea una instancia de un BoxView para mostrar un color, tres instancias de Slider para seleccionar los componentes rojo, verde y azul del color y tres elementos Label para mostrar esos valores de color:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="SliderDemos.RgbColorSlidersPage"
             Title="RGB Color Sliders">
    <ContentPage.Resources>
        <ResourceDictionary>
            <Style TargetType="Slider">
                <Setter Property="Maximum" Value="255" />
            </Style>

            <Style TargetType="Label">
                <Setter Property="HorizontalTextAlignment" Value="Center" />
            </Style>
        </ResourceDictionary>
    </ContentPage.Resources>

    <StackLayout Margin="10">
        <BoxView x:Name="boxView"
                 Color="Black"
                 VerticalOptions="FillAndExpand" />

        <Slider x:Name="redSlider"
                ValueChanged="OnSliderValueChanged" />

        <Label x:Name="redLabel" />

        <Slider x:Name="greenSlider"
                ValueChanged="OnSliderValueChanged" />

        <Label x:Name="greenLabel" />

        <Slider x:Name="blueSlider"
                ValueChanged="OnSliderValueChanged" />

        <Label x:Name="blueLabel" />
    </StackLayout>
</ContentPage>

Un Style proporciona a los tres elementos Slider un intervalo de 0 a 255. Los elementos Slider comparten el mismo controlador de ValueChanged, que se implementa en el archivo de código subyacente:

public partial class RgbColorSlidersPage : ContentPage
{
    public RgbColorSlidersPage()
    {
        InitializeComponent();
    }

    void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
    {
        if (sender == redSlider)
        {
            redLabel.Text = String.Format("Red = {0:X2}", (int)args.NewValue);
        }
        else if (sender == greenSlider)
        {
            greenLabel.Text = String.Format("Green = {0:X2}", (int)args.NewValue);
        }
        else if (sender == blueSlider)
        {
            blueLabel.Text = String.Format("Blue = {0:X2}", (int)args.NewValue);
        }

        boxView.Color = Color.FromRgb((int)redSlider.Value,
                                      (int)greenSlider.Value,
                                      (int)blueSlider.Value);
    }
}

La primera sección establece la propiedad Text de una de las instancias de Label en una cadena de texto corta que indica el valor del Slider en hexadecimal. A continuación, se accede a las tres instancias de Slider para crear un valor de Color a partir de los componentes RGB:

Controles deslizantes de color RGB

Enlazar el control deslizante a un ViewModel

La página Sliders de color HSL muestra cómo usar un ViewModel para realizar los cálculos utilizados para crear un valor de Color a partir de valores de tono, saturación y luminosidad. Al igual que todos los ViewModels, la clase HSLColorViewModel implementa la interfaz INotifyPropertyChanged y desencadena un evento PropertyChanged cada vez que una de las propiedades cambia:

public class HslColorViewModel : INotifyPropertyChanged
{
    Color color;

    public event PropertyChangedEventHandler PropertyChanged;

    public double Hue
    {
        set
        {
            if (color.Hue != value)
            {
                Color = Color.FromHsla(value, color.Saturation, color.Luminosity);
            }
        }
        get
        {
            return color.Hue;
        }
    }

    public double Saturation
    {
        set
        {
            if (color.Saturation != value)
            {
                Color = Color.FromHsla(color.Hue, value, color.Luminosity);
            }
        }
        get
        {
            return color.Saturation;
        }
    }

    public double Luminosity
    {
        set
        {
            if (color.Luminosity != value)
            {
                Color = Color.FromHsla(color.Hue, color.Saturation, value);
            }
        }
        get
        {
            return color.Luminosity;
        }
    }

    public Color Color
    {
        set
        {
            if (color != value)
            {
                color = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Hue"));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Saturation"));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Luminosity"));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Color"));
            }
        }
        get
        {
            return color;
        }
    }
}

ViewModels y la interfaz INotifyPropertyChanged se describen en el artículo Enlace de datos.

El archivo HslColorSlidersPage.xaml crea una instancia del HslColorViewModel y lo establece en la propiedad BindingContext de la página. Esto permite que todos los elementos del archivo XAML se enlacen a las propiedades de ViewModel:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:SliderDemos"
             x:Class="SliderDemos.HslColorSlidersPage"
             Title="HSL Color Sliders">

    <ContentPage.BindingContext>
        <local:HslColorViewModel Color="Chocolate" />
    </ContentPage.BindingContext>

    <ContentPage.Resources>
        <ResourceDictionary>
            <Style TargetType="Label">
                <Setter Property="HorizontalTextAlignment" Value="Center" />
            </Style>
        </ResourceDictionary>
    </ContentPage.Resources>

    <StackLayout Margin="10">
        <BoxView Color="{Binding Color}"
                 VerticalOptions="FillAndExpand" />

        <Slider Value="{Binding Hue}" />
        <Label Text="{Binding Hue, StringFormat='Hue = {0:F2}'}" />

        <Slider Value="{Binding Saturation}" />
        <Label Text="{Binding Saturation, StringFormat='Saturation = {0:F2}'}" />

        <Slider Value="{Binding Luminosity}" />
        <Label Text="{Binding Luminosity, StringFormat='Luminosity = {0:F2}'}" />
    </StackLayout>
</ContentPage>

A medida que se manipulan los elementos Slider, los elementos BoxView y Label se actualizan desde ViewModel:

Controles deslizantes de color HSL

El componente StringFormat de la extensión de marcado de Binding se establece para un formato de "F2" para mostrar dos posiciones decimales. (El formato de cadena en los enlaces de datos se describe en el artículo Formato de cadena.) Sin embargo, la versión de UWP del programa se limita a los valores de 0, 0.1, 0.2, ... 0.9 y 1.0. Este es un resultado directo de la implementación de la Slider para UWP tal y como se ha descrito anteriormente en la sección Diferencias de implementación de la plataforma.