第十八章:MVVM(五)

简介:

一个Color ViewModel
Color始终提供了探索图形用户界面功能的好方法,因此您可能不会惊讶于Xamarin.FormsBook.Toolkit库包含一个名为ColorViewModel的类。
ColorViewModel类公开Color属性,但也显示Red,Green,Blue,Alpha,Hue,Saturation和Luminosity属性,所有这些属性都是可单独设置的。这不是Xamarin.Form Color结构提供的功能。从Color构造函数或Color中的其中一个方法创建Color值时,以Add,From,Multiply或With开头,它是不可变的。
ColorViewModel类因其Color属性和所有组件属性的相互关系而变得复杂。例如,假设已设置Color属性。该类应该不仅为Color而且还为任何也发生变化的组件(如Red或Hue)触发PropertyChanged处理程序。同样,如果Red属性发生变化,那么该类应该为Red和Color以及可能的Hue,Saturation和Luminosity触发PropertyChanged事件。
ColorViewModel类通过仅为Color属性存储支持字段来解决此问题。通过调用Color.FromRgba或Color.FromHsla使用传入值,各个组件的所有set访问器都会创建一个新Color。此新Color值设置为Color属性而不是color字段,这意味着新Color值将在Color属性的set访问器中进行处理:

public class ColorViewModel : INotifyPropertyChanged
{
    Color color;
    public event PropertyChangedEventHandler PropertyChanged;
    public double Red
    {
        set
        {
            if (Round(color.R) != value)
                Color = Color.FromRgba(value, color.G, color.B, color.A);
        }
        get
        {
            return Round(color.R); 
        }
    }
    public double Green
    {
        set
        {
            if (Round(color.G) != value)
                Color = Color.FromRgba(color.R, value, color.B, color.A);
        }
 
        get
        {
            return Round(color.G);
        }
    }
    public double Blue
    {
        set
        {
            if (Round(color.B) != value)
                Color = Color.FromRgba(color.R, color.G, value, color.A);
        }
        get
        {
            return Round(color.B);
        }
    }
    public double Alpha
    {
        set
        {
            if (Round(color.A) != value)
                Color = Color.FromRgba(color.R, color.G, color.B, value);
        }
        get
        {
            return Round(color.A);
        }
    }
    public double Hue
    {
        set
        {
            if (Round(color.Hue) != value)
                Color = Color.FromHsla(value, color.Saturation, color.Luminosity, color.A);
        }
        get
        {
            return Round(color.Hue);
        }
    }
    public double Saturation
    {
        set
        {
            if (Round(color.Saturation) != value)
                Color = Color.FromHsla(color.Hue, value, color.Luminosity, color.A);
        }
        get
        { 
            return Round(color.Saturation);
        }
    }
    public double Luminosity
    {
        set
        {
            if (Round(color.Luminosity) != value)
                Color = Color.FromHsla(color.Hue, color.Saturation, value, color.A);
        }
        get
        {
            return Round(color.Luminosity);
        }
    }
    public Color Color
    {
        set
        {
            Color oldColor = color;
            if (color != value)
            {
                color = value;
                OnPropertyChanged("Color");
            }
            if (color.R != oldColor.R)
                OnPropertyChanged("Red");
            if (color.G != oldColor.G)
                OnPropertyChanged("Green");
            if (color.B != oldColor.B)
                OnPropertyChanged("Blue");
            if (color.A != oldColor.A)
                OnPropertyChanged("Alpha");
            if (color.Hue != oldColor.Hue)
                OnPropertyChanged("Hue");
            if (color.Saturation != oldColor.Saturation)
                OnPropertyChanged("Saturation");
            if (color.Luminosity != oldColor.Luminosity)
                OnPropertyChanged("Luminosity");
        }
        get
        {
            return color;
        }
    }
    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    double Round(double value)
    {
        return Device.OnPlatform(value, Math.Round(value, 3), value);
    }
}

Color属性的set访问器负责根据属性的更改来触发所有PropertyChanged事件。
请注意类底部的设备相关Round方法及其在set中的使用以及前七个属性的get访问器。 当第23章“触发器和行为”中的MultiColorSliders示例显示出问题时,会添加此项。 Android似乎在内部舍入颜色组件,导致传递给它的属性之间的不一致
Color.FromRgba和Color.FromHsla方法以及由此产生的Color值的属性,从而导致无限集和get循环。
HslSliders程序在Grid.BindingContext标记之间实例化ColorViewModel,使其成为Grid中所有Slider和Label元素的BindingContext:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:toolkit=
                 "clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
             x:Class="HslSliders.HslSlidersPage"
             SizeChanged="OnPageSizeChanged">
 
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness"
                    iOS="0, 20, 0, 0" />
    </ContentPage.Padding>
    <Grid x:Name="mainGrid">
        <Grid.BindingContext>
            <toolkit:ColorViewModel Color="Gray" />
        </Grid.BindingContext>
        <Grid.Resources>
            <ResourceDictionary>
                <Style TargetType="Label">
                    <Setter Property="FontSize" Value="Large" />
                    <Setter Property="HorizontalTextAlignment" Value="Center" />
                </Style>
            </ResourceDictionary>
        </Grid.Resources>
        <!-- Initialized for portrait mode. -->
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="0" />
        </Grid.ColumnDefinitions>
        <BoxView Color="{Binding Color}"
                 Grid.Row="0" Grid.Column="0" />
        <StackLayout x:Name="controlPanelStack"
                     Grid.Row="1" Grid.Column="0"
                     Padding="10, 5">
 
            <StackLayout VerticalOptions="CenterAndExpand">
                <Slider Value="{Binding Hue}" />
                <Label Text="{Binding Hue, StringFormat='Hue = {0:F2}'}" />
            </StackLayout>
            <StackLayout VerticalOptions="CenterAndExpand">
                <Slider Value="{Binding Saturation}" />
                <Label Text="{Binding Saturation,StringFormat='Saturation = {0:F2}'}" />
            </StackLayout>
            <StackLayout VerticalOptions="CenterAndExpand">
                <Slider Value="{Binding Luminosity}" />
                <Label Text="{Binding Luminosity, StringFormat='Luminosity = {0:F2}'}" />
            </StackLayout>
        </StackLayout>
    </Grid>
</ContentPage>

请注意,实例化ColorViewModel时,ColorViewModel的Color属性已初始化。 然后滑块的双向绑定将获取Hue,Saturation和Luminosity属性的结果值。
如果您想要实现红色,绿色和蓝色的十六进制值的显示,则可以使用与前一章中的GridRgbSliders程序相关的DoubleToIntConverter类。
HslSliders程序实现了与纵向和横向模式之间切换相同的技术,如GridRgbSliders程序。 代码隐藏文件处理此开关的机制:

public partial class HslSlidersPage : ContentPage
{
    public HslSlidersPage()
    {
        InitializeComponent();
    }
    void OnPageSizeChanged(object sender, EventArgs args)
    {
        // Portrait mode.
        if (Width < Height)
        {
            mainGrid.RowDefinitions[1].Height = GridLength.Auto;
            mainGrid.ColumnDefinitions[1].Width = new GridLength(0, GridUnitType.Absolute);
            Grid.SetRow(controlPanelStack, 1);
            Grid.SetColumn(controlPanelStack, 0);
        }
        // Landscape mode.
        else
        {
            mainGrid.RowDefinitions[1].Height = new GridLength(0, GridUnitType.Absolute);
            mainGrid.ColumnDefinitions[1].Width = new GridLength(1, GridUnitType.Star);
            Grid.SetRow(controlPanelStack, 0);
            Grid.SetColumn(controlPanelStack, 1);
        }
    }
}

这个代码隐藏文件不像只调用InitializeComponent的文件那么漂亮,但即使在MVVM的上下文中,纵向和横向模式之间的切换也是代码隐藏文件的合法使用,因为它仅用于 用户界面而不是底层业务逻辑。
这是HslSliders计划的实际应用:
2018_10_12_095158

目录
相关文章
|
10月前
|
存储 SQL 前端开发
借一个项目谈Android应用软件架构,你还在套用MVP 或MVVM吗
借一个项目谈Android应用软件架构,你还在套用MVP 或MVVM吗
|
前端开发 JavaScript Android开发
|
前端开发 JavaScript Android开发
|
前端开发 JavaScript Android开发
|
前端开发 Android开发 Windows
|
前端开发 JavaScript Android开发
|
JavaScript 前端开发 Android开发
|
JavaScript 前端开发 Android开发
|
前端开发 Android开发