Xamarin.Forms滑块
使用滑块从一系列连续值中进行选择。
Xamarin.FormsSlider
是一个水平条,用户可操作它来从连续范围中选择一个 double
值。
Slider
定义了 double
类型的 3 个属性:
这三个属性受 BindableProperty
对象支持。 Value
属性的默认绑定模式为 BindingMode.TwoWay
,这意味着它适合在使用模型-视图-视图模型 (MVVM) 体系结构的应用程序中用作绑定源。
警告
在内部,Slider
确保 Minimum
小于 Maximum
。 如果曾经设置过 Minimum
或 Maximum
,使其 Minimum
不小于 Maximum
,则会引发异常。 若要详细了解如何设置 Minimum
和 Maximum
属性,请参阅下面的预防措施部分。
Slider
强制 Value
属性,使其介于 Minimum
和 Maximum
(包含端点值)之间。 如果 Minimum
属性设置为大于 Value
属性的值,Slider
会将 Value
属性设置为 Minimum
。 同样,如果 Maximum
设置为小于 Value
的值,Slider
会将 Value
属性设置为 Maximum
。
Slider
定义一个 ValueChanged
事件;当 Value
因用户操作 Slider
或程序直接设置 Value
属性而发生变化时,会触发此事件。 如前一段所述,当 Value
属性被强制执行时,也会触发 ValueChanged
事件。
ValueChanged
事件随附的 ValueChangedEventArgs
对象有两个类型为 double
的属性:OldValue
和 NewValue
。 在事件被触发时,NewValue
的值与 Slider
对象的 Value
属性相同。
Slider
还定义了在拖动操作的开头和结尾触发的 DragStarted
和 DragCompleted
事件。 与 ValueChanged
事件不同,DragStarted
和 DragCompleted
事件只能通过用户操作 Slider
来触发。 触发 DragStarted
事件时,会执行类型为 ICommand
的 DragStartedCommand
。 同样,当触发 DragCompleted
事件时,会执行类型为 ICommand
的 DragCompletedCommand
。
警告
请勿使用 Center
、Start
或具有 Slider
的 End
的无约束水平布局选项。 在 Android 和 UWP 上,Slider
折叠成一个长度为零的条形,而在 iOS 上,该条形非常短。 保留 Fill
的默认 HorizontalOptions
设置,在 Grid
布局中放置 Slider
时不要使用 Auto
的宽度。
此外,Slider
还定义了几个影响其外观的属性:
MinimumTrackColor
是拇指左侧的条形颜色。MaximumTrackColor
是拇指右侧的条形颜色。ThumbColor
是拇指颜色。ThumbImageSource
是用于拇指的图像,类型为ImageSource
。
注意
ThumbColor
和 ThumbImageSource
属性是互斥的。 在这两个属性均已设置的情况下,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。 Slider
的 ValueChanged
处理程序使用 slider
对象的 Value
属性来设置第一个 Label
的 Rotation
属性,将 String.Format
方法与事件参数的 NewValue
属性结合使用来设置第二个 Label
的 Text
属性。 这两种获取 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
参数获取触发事件的 Slider
。 Value
属性包含当前值:
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>
第一个 Label
的 Rotation
属性绑定到 Slider
的 Value
属性,第二个 Label
的 Text
属性具有 StringFormat
规范。 “基本滑块绑定”页面在功能上与前面两个页面略有不同:第一个页面出现时,第二个 Label
显示具有值的文本字符串。 这是使用数据绑定的好处。 若要显示没有数据绑定的文本,需要通过从类构造函数调用事件处理程序来专门初始化 Label
的 Text
属性或模拟 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" ... />
可以将 Minimum
和 Maximum
值设置为负数,但只能按照 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 Slider
的 StepFrequency
属性设置为 Maximum
和 Minimum
之差除以 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。 当 Maximum
和 Minimum
属性之差为 10 或更大时,StepFrequency
设置为 1,并且 Value
属性具有整数值。
StepSlider 解决方案
有关更通用的 StepSlider
,可查看第 27 章:自定义呈现器。(参见《使用 Xamarin.Forms 创建移动应用》一书)。 StepSlider
类似于 Slider
,但添加了一个 Steps
属性来指定介于 Minimum
和 Maximum
之间的值数。
颜色选择滑块
示例中的最后两页都使用三个 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
值:
将滑块绑定到视图模型
“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
元素时,会从视图模型更新 BoxView
和 Label
元素:
Binding
标记扩展的 StringFormat
组件设置为“F2”格式以显示两个小数位数。 (有关数据绑定中的字符串格式设置,请参阅字符串格式设置一文。)但是,程序的 UWP 版本限制为值 0、0.1、0.2...0.9 和 1.0。 这是 UWP Slider
的实现的直接结果,如平台实现差异部分所述。