Xamarin.Forms滑块

使用滑块从一系列连续值中进行选择。

Xamarin.FormsSlider 是一个水平条,用户可操作它来从连续范围中选择一个 double 值。

Slider 定义了 double 类型的 3 个属性:

  • Minimum 是最小范围,默认值为 0。
  • Maximum 是范围最大值,默认值为 1。
  • Value 是滑块的值,其范围在 MinimumMaximum 之间,默认值为 0。

这三个属性受 BindableProperty 对象支持。 Value 属性的默认绑定模式为 BindingMode.TwoWay,这意味着它适合在使用模型-视图-视图模型 (MVVM) 体系结构的应用程序中用作绑定源。

警告

在内部,Slider 确保 Minimum 小于 Maximum。 如果曾经设置过 MinimumMaximum,使其 Minimum 不小于 Maximum,则会引发异常。 若要详细了解如何设置 MinimumMaximum 属性,请参阅下面的预防措施部分。

Slider 强制 Value 属性,使其介于 MinimumMaximum(包含端点值)之间。 如果 Minimum 属性设置为大于 Value 属性的值,Slider 会将 Value 属性设置为 Minimum。 同样,如果 Maximum 设置为小于 Value 的值,Slider 会将 Value 属性设置为 Maximum

Slider 定义一个 ValueChanged 事件;当 Value 因用户操作 Slider 或程序直接设置 Value 属性而发生变化时,会触发此事件。 如前一段所述,当 Value 属性被强制执行时,也会触发 ValueChanged 事件。

ValueChanged 事件随附的 ValueChangedEventArgs 对象有两个类型为 double 的属性:OldValueNewValue。 在事件被触发时,NewValue 的值与 Slider 对象的 Value 属性相同。

Slider 还定义了在拖动操作的开头和结尾触发的 DragStartedDragCompleted 事件。 与 ValueChanged 事件不同,DragStartedDragCompleted 事件只能通过用户操作 Slider 来触发。 触发 DragStarted 事件时,会执行类型为 ICommandDragStartedCommand。 同样,当触发 DragCompleted 事件时,会执行类型为 ICommandDragCompletedCommand

警告

请勿使用 CenterStart 或具有 SliderEnd 的无约束水平布局选项。 在 Android 和 UWP 上,Slider 折叠成一个长度为零的条形,而在 iOS 上,该条形非常短。 保留 Fill 的默认 HorizontalOptions 设置,在 Grid 布局中放置 Slider 时不要使用 Auto 的宽度。

此外,Slider 还定义了几个影响其外观的属性:

注意

ThumbColorThumbImageSource 属性是互斥的。 在这两个属性均已设置的情况下,ThumbImageSource 属性优先。

基本滑块代码和标记

该示例首先显示三个页面,这些页面在功能上相同,但以不同的方式实现。 第一个页面仅使用 C# 代码,第二个页面在代码中将 XAML 和事件处理程序结合使用,第三个页面能够在 XAML 文件中使用数据绑定来避免事件处理程序。

在代码中创建滑块

“基本滑块代码”页面演示如何在代码中创建 Slider 和两个 Label 对象:

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

会初始化 Slider,使其 Maximum 属性为 360。 SliderValueChanged 处理程序使用 slider 对象的 Value 属性来设置第一个 LabelRotation 属性,将 String.Format 方法与事件参数的 NewValue 属性结合使用来设置第二个 LabelText 属性。 这两种获取 Slider 的当前值的方法是可互换的。

下面是在 iOS 和 Android 设备上运行的程序:

“基本滑块代码”页面

第二个 Label 显示“(未初始化)”文本,直到 Slider 被操作,这会导致触发第一个 ValueChanged 事件。 请注意,在每个平台中,显示的小数位数各不相同。 这些差异与 Slider 的平台实现相关,将在本文稍后的平台实现差异部分予以讨论。

在 XAML 中创建滑块

“基本滑块 XAML”页面在功能上与“基本滑块代码”相同,但主要在 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>

代码隐藏文件包含 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);
    }
}

事件处理程序还可以通过 sender 参数获取触发事件的 SliderValue 属性包含当前值:

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

如果 Slider 对象在 XAML 文件中指定了具有 x:Name 属性的名称(例如“slider”),则事件处理程序可能直接引用该对象:

double value = slider.Value;

数据绑定滑块

“基本滑块绑定”页面演示了如何编写一个几乎等效的程序,该程序通过使用数据绑定来消除 Value 事件处理程序:

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

第一个 LabelRotation 属性绑定到 SliderValue 属性,第二个 LabelText 属性具有 StringFormat 规范。 “基本滑块绑定”页面在功能上与前面两个页面略有不同:第一个页面出现时,第二个 Label 显示具有值的文本字符串。 这是使用数据绑定的好处。 若要显示没有数据绑定的文本,需要通过从类构造函数调用事件处理程序来专门初始化 LabelText 属性或模拟 ValueChanged 事件的触发。

预防措施

Minimum 属性的值必须始终小于 Maximum 属性的值。 以下代码片段导致 Slider 引发异常:

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

C# 编译器生成按顺序设置这两个属性的代码,且将 Minimum 属性设置为 10 时,它大于默认 Maximum 值 1。 在这种情况下,可以通过先设置 Maximum 属性来避免异常:

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

Maximum 设置为 20 没有问题,因为它大于默认的 Minimum 值 0。 设置 Minimum 后,其值小于 Maximum 值 20。

XAML 中存在相同的问题。 设置属性是为了确保 Maximum 始终大于 Minimum

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

可以将 MinimumMaximum 值设置为负数,但只能按照 Minimum 始终小于 Maximum 的顺序设置:

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

Value 属性应始终大于或等于 Minimum 值,且小于或等于 Maximum。 如果将 Value 设置为该范围之外的值,则会强制使此值位于该范围内,但不会引发异常。 例如,以下代码不会引发异常:

Slider slider = new Slider
{
    Value = 10
};

而是会将 Value 属性的 Maximum 值强制设置为 1。

下面是上述代码片段:

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

如果 Minimum 被设置为 10,则 Value 也会被设置为 10。

如果在 Value 属性强制转换为默认值 0 以外的其他值时附加了 ValueChanged 事件处理程序,则会触发 ValueChanged 事件。 下面是 XAML 代码片段:

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

Minimum 设置为 10 时,Value 也设置为 10,并会触发 ValueChanged 事件。 在构造页面的其余部分之前,可能会发生这种情况,处理程序可能会尝试引用尚未创建的页面上的其他元素。 你需要向 ValueChanged 处理程序添加一些代码,以检查页面上其他元素的 null 值。 或者,可以在初始化 Slider 值后设置 ValueChanged 事件处理程序。

平台实现差异

上述屏幕截图显示了具有不同小数位数的 Slider。 这与如何在 Android 和 UWP 平台上实现 Slider 相关。

Android 实现

Slider 的 Android 实现基于 Android SeekBar,并且始终将 Max 属性设置为 1000。 这意味着 Android 上的 Slider 只有 1,001 个离散值。 如果设置 Slider,使其 Minimum 为 0 且 Maximum 为 5000,那么在操作 Slider 时,Value 属性的值为 0、5、10、15,以此类推。

UWP 实现

Slider 的 UWP 实现基于 UWP Slider 控件。 UWP SliderStepFrequency 属性设置为 MaximumMinimum 之差除以 10,但不大于 1。

例如,如果默认范围为 0 到 1,则 StepFrequency 属性设置为 0.1。 操作 Slider 时,Value 属性限制为 0、0.1、0.2、0.3、0.4、0.5、0.6、0.7、0.8、0.9 和 1.0。 当 MaximumMinimum 属性之差为 10 或更大时,StepFrequency 设置为 1,并且 Value 属性具有整数值。

StepSlider 解决方案

有关更通用的 StepSlider,可查看第 27 章:自定义呈现器。(参见《使用 Xamarin.Forms 创建移动应用》一书)。 StepSlider 类似于 Slider,但添加了一个 Steps 属性来指定介于 MinimumMaximum 之间的值数。

颜色选择滑块

示例中的最后两页都使用三个 Slider 实例进行颜色选择。 第一个页面处理代码隐藏文件中的所有交互,第二个页面显示如何将数据绑定与视图模型配合使用。

处理代码隐藏文件中的滑块

“RGB 颜色滑块”页面实例化了一个 BoxView 实例(用于显示颜色)、三个 Slider 实例(用于选择颜色的红色、绿色和蓝色组件)和三个 Label 元素(用于显示这些颜色值):

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

Style 将所有三个 Slider 元素的范围都设为 0 到 255 之间。 Slider 元素共享相同的 ValueChanged 处理程序,该处理程序在代码隐藏文件中实现:

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

第一个部分将其中一个 Label 实例的 Text 属性设置为一个短文本字符串,指示 Slider 的值(采用十六进制形式)。 然后,会访问所有三个 Slider 实例,以便从 RGB 组件创建 Color 值:

RGB 颜色滑块

将滑块绑定到视图模型

“HSL 颜色滑块”页面演示如何使用视图模型执行用于根据色调、饱和度和发光度值创建 Color 值的计算。 与所有视图模型一样,HSLColorViewModel 类实现 INotifyPropertyChanged 接口,并在其中一个属性发生更改时触发 PropertyChanged 事件:

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

数据绑定一文中讨论了视图模型和 INotifyPropertyChanged 接口。

HslColorSlidersPage.xaml 文件会实例化 HslColorViewModel 并将其设置为页面的 BindingContext 属性。 这使得 XAML 文件中的所有元素都能绑定到视图模型中的属性:

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

操作 Slider 元素时,会从视图模型更新 BoxViewLabel 元素:

HSL 颜色滑块

Binding 标记扩展的 StringFormat 组件设置为“F2”格式以显示两个小数位数。 (有关数据绑定中的字符串格式设置,请参阅字符串格式设置一文。)但是,程序的 UWP 版本限制为值 0、0.1、0.2...0.9 和 1.0。 这是 UWP Slider 的实现的直接结果,如平台实现差异部分所述。