Xamarin.Forms:在UWP中悬停时自定义按钮样式

时间:2016-10-06 12:13:51

标签: button xamarin xamarin.forms uwp windows-10-universal

我在Xamarin Forms项目的App.xaml中定义了一些样式。但是,如果将鼠标悬停在按钮上或按下按钮,则不会影响该按钮。字体颜色在此处更改为黑色,并且按钮周围出现灰色边框。现在我要覆盖这种风格。

首先尝试:向UWP项目的App.xaml添加定义

<Application
    x:Class="YourApp.UWP.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:YourApp.UWP"
    RequestedTheme="Light">

    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.ThemeDictionaries>
                <ResourceDictionary x:Key="Light">
                    <SolidColorBrush x:Key="ButtonPointerOverBackgroundThemeBrush" Color="#00FF00" />
                </ResourceDictionary>
            </ResourceDictionary.ThemeDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

结果:根本没有变化

第二次尝试:覆盖UWP项目PointOver中的App.xaml视觉状态

<Application
    x:Class="YourApp.UWP.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:YourApp.UWP"
    RequestedTheme="Light">

    <Application.Resources>
        <ResourceDictionary>
            <Style TargetType="Button" x:Key="HoverButtonStyle">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="Button">
                            <Grid>
                                <VisualStateManager.VisualStateGroups>
                                    <VisualStateGroup x:Name="CommonStates">
                                        <VisualState x:Name="PointerOver">
                                            <Storyboard>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                                                Storyboard.TargetProperty="Background">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="#00FF00" />
                                                </ObjectAnimationUsingKeyFrames>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                                                Storyboard.TargetProperty="Foreground">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="#00FF00" />
                                                </ObjectAnimationUsingKeyFrames>
                                            </Storyboard>
                                        </VisualState>
                                    </VisualStateGroup>
                                </VisualStateManager.VisualStateGroups>
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </ResourceDictionary>
    </Application.Resources>
</Application>

结果:根本没有任何变化,我想我必须应用这种风格(如果我这样做,按钮似乎不在这里)

第三次尝试:添加完整按钮样式并应用

<Style TargetType="Button" x:Key="HoverButtonStyle">
    <Setter Property="Background" Value="{ThemeResource ButtonBackgroundThemeBrush}" />
    <Setter Property="Foreground" Value="{ThemeResource ButtonForegroundThemeBrush}"/>
    <Setter Property="BorderBrush" Value="{ThemeResource ButtonBorderThemeBrush}" />
    <Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
    <Setter Property="Padding" Value="12,4,12,4" />
    <Setter Property="HorizontalAlignment" Value="Left" />
    <Setter Property="VerticalAlignment" Value="Center" />
    <Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
    <Setter Property="FontWeight" Value="SemiBold" />
    <Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Grid>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal" />
                            <VisualState x:Name="PointerOver">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                       Storyboard.TargetProperty="Background">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ButtonPointerOverBackgroundThemeBrush}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                       Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonPointerOverForegroundThemeBrush}" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Pressed">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                       Storyboard.TargetProperty="Background">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonPressedBackgroundThemeBrush}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                       Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonPressedForegroundThemeBrush}" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Disabled">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                       Storyboard.TargetProperty="Background">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonDisabledBackgroundThemeBrush}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                                       Storyboard.TargetProperty="BorderBrush">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonDisabledBorderThemeBrush}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
                                                       Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonDisabledForegroundThemeBrush}" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="FocusStates">
                            <VisualState x:Name="Focused">
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="FocusVisualWhite"
                                         Storyboard.TargetProperty="Opacity"
                                         To="1"
                                         Duration="0" />
                                    <DoubleAnimation Storyboard.TargetName="FocusVisualBlack"
                                         Storyboard.TargetProperty="Opacity"
                                         To="1"
                                         Duration="0" />
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Unfocused" />
                            <VisualState x:Name="PointerFocused" />
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Border x:Name="Border"
                Background="{TemplateBinding Background}"
                BorderBrush="{TemplateBinding BorderBrush}"
                BorderThickness="{TemplateBinding BorderThickness}"
                Margin="3">
                        <ContentPresenter x:Name="ContentPresenter"
                              Content="{TemplateBinding Content}"
                              ContentTransitions="{TemplateBinding ContentTransitions}"
                              ContentTemplate="{TemplateBinding ContentTemplate}"
                              Margin="{TemplateBinding Padding}"
                              HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                              VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 
                              AutomationProperties.AccessibilityView="Raw"/>
                    </Border>
                    <Rectangle x:Name="FocusVisualWhite"
                   IsHitTestVisible="False"
                   Stroke="{ThemeResource FocusVisualWhiteStrokeThemeBrush}"
                   StrokeEndLineCap="Square"
                   StrokeDashArray="1,1"
                   Opacity="0"
                   StrokeDashOffset="1.5" />
                    <Rectangle x:Name="FocusVisualBlack"
                   IsHitTestVisible="False"
                   Stroke="{ThemeResource FocusVisualBlackStrokeThemeBrush}"
                   StrokeEndLineCap="Square"
                   StrokeDashArray="1,1"
                   Opacity="0"
                   StrokeDashOffset="0.5" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

自定义渲染器:

protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
    base.OnElementChanged(e);

    if (this.Element != null)
    {
        this.Control.Style = Windows.UI.Xaml.Application.Current.Resources["HoverButtonStyle"] as Windows.UI.Xaml.Style;
    }
}

结果:似乎应用了样式,但我在Xamarin Forms中定义的背景颜色不占用按钮的整个宽度。边框颜色仍然没有改变。

这是怎么做的?

2 个答案:

答案 0 :(得分:7)

现在我发现了这款造型的工作原理。首先,您必须找到基本的UWP类(通过按住 Ctrl 并单击类名或查看here)。例如。对于Picker,它是ComboBox。如果您使用Google,则可以访问this page,在那里您可以找到有关覆盖ComboBox默认布局的所有信息。对于Button,它是this page,依此类推。因此,解决方案是拥有这样的App.xaml(UWP项目)(采用您选择的颜色):

<Application
    x:Class="YourApp.UWP.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:YourApp.UWP"
    RequestedTheme="Light">

    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.ThemeDictionaries>
                <ResourceDictionary x:Key="Light">
                    <SolidColorBrush x:Key="SystemControlHighlightBaseMediumLowBrush" Color="White" />
                    <SolidColorBrush x:Key="SystemControlHighlightBaseHighBrush" Color="White" />
                </ResourceDictionary>
            </ResourceDictionary.ThemeDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

仅为某些按钮应用样式,您必须执行以下步骤:

在您的UWP项目的App.xaml中,您需要以下条目:

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Styles/DefaultButtonControlTemplate.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

在这里注册一个样式,该样式位于单独的文件中。我有一个名为Styles的文件夹,其中放置了文件 DefaultButtonControlTemplate.xaml 。从MSDN获取的文件内容如下所示:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MyApp.UWP.ControlTemplates">

    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="ColorsAndBrushes.xaml" />
    </ResourceDictionary.MergedDictionaries>

    <ControlTemplate x:Key="DefaultButtonControlTemplate" TargetType="Button">
        <!-- here is the content of the file -->
    </ControlTemplate>

</ResourceDictionary>

正如您所见,我引用了一个公共文件,其中包含我的所有颜色(或UWP世界中的画笔)。

最后,您需要一个这样的自定义渲染器:

public class DefaultButtonRenderer : ButtonRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
    {
        base.OnElementChanged(e);

        if (this.Control != null)
        {
            this.Control.Template = Windows.UI.Xaml.Application.Current.Resources["DefaultButtonControlTemplate"] as Windows.UI.Xaml.Controls.ControlTemplate;
        }
    }
}

答案 1 :(得分:1)

找到了一种将其全部保留在UWP自定义渲染器中的方法,而不必担心修改其他内容或其他按钮设置是否会冲突。就我而言,我创建了一个自定义的PillButton,因此很明显会更新您的类和颜色,否则不会。如果您在按钮上没有看到圆角半径,则使用以下按钮将是普通按钮。

[assembly: ExportRenderer(typeof(PillButton), typeof(PillButtonRenderer))]
namespace YourProject.UWP.Renderers
{
    public class PillButtonRenderer : ButtonRenderer
    {
        public PillButton PillButtonElement => Element as PillButton;

        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Button> e)
        {
            base.OnElementChanged(e);

            if (Control != null)
            {
                Windows.UI.Xaml.Controls.Button button = Control;

                Resources = (Windows.UI.Xaml.ResourceDictionary)XamlReader.Load(PillButtonStyleDictionary);

                Resources["PillCornerRadius"] = PillButtonElement.CornerRadius;
                Resources["PillBorderWidth"] = PillButtonElement.BorderWidth;

                // if hover color not supplied, then hover color will be lighter version of background color, unless background color is transparent in which case it will be the border color
                var hoverColor = PillButtonElement.UwpHoverColor != default(Color) ? PillButtonElement.UwpHoverColor
                    : (PillButtonElement.BackgroundColor == Color.Transparent
                        ? PillButtonElement.BorderColor
                        : PillButtonElement.BackgroundColor.ChangeColorBrightness(0.15));
                Resources["PillFillColorOnHover"] = new SolidColorBrush(hoverColor.ToUwp());

                // if pressed color not supplied, then make it a darker shade of the hover color
                var pressedColor = PillButtonElement.UwpPressedColor != default(Color) ? PillButtonElement.UwpPressedColor : hoverColor.ChangeColorBrightness(-0.09);
                Resources["PillFillColorOnPressed"] = new SolidColorBrush(pressedColor.ToUwp());

                // if text color on hover/press not supplied, then make it black or white depending on how dark the hover color is
                var textColor = PillButtonElement.PressedTextColor != default(Color) ? PillButtonElement.PressedTextColor : hoverColor.BlackOrWhiteForegroundTextColor();
                Resources["PillTextColorOnHoverOrPressed"] = new SolidColorBrush(textColor.ToUwp());

                // set normal style
                Resources["PillBackgroundColor"] = new SolidColorBrush(PillButtonElement.BackgroundColor.ToUwp());
                Resources["PillTextColor"] = new SolidColorBrush(PillButtonElement.TextColor.ToUwp());
                PillButtonElement.BackgroundColor = Color.Transparent; // hack

                button.Style = Resources["PillButtonStyle"] as Windows.UI.Xaml.Style;
            }
        }

        private const string PillButtonStyleDictionary = @"<ResourceDictionary
    xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
    xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">

    <x:Double x:Key=""PillCornerRadius"">0</x:Double>
    <x:Double x:Key=""PillBorderWidth"">0</x:Double>

    <SolidColorBrush
        x:Key=""PillBackgroundColor""
        Color=""Black"" />
    <SolidColorBrush
        x:Key=""PillTextColor""
        Color=""Black"" />
    <SolidColorBrush
        x:Key=""PillFillColorOnHover""
        Color=""Black"" />
    <SolidColorBrush
        x:Key=""PillFillColorOnPressed""
        Color=""Black"" />
    <SolidColorBrush
        x:Key=""PillTextColorOnHoverOrPressed""
        Color=""Black"" />

    <Style
        x:Key=""PillButtonStyle""
        TargetType=""Button"">
        <Setter
            Property=""Background""
            Value=""{ThemeResource SystemControlBackgroundBaseLowBrush}"" />
        <Setter
            Property=""Foreground""
            Value=""{ThemeResource SystemControlForegroundBaseHighBrush}"" />
        <Setter
            Property=""BorderBrush""
            Value=""{ThemeResource SystemControlForegroundTransparentBrush}"" />
        <Setter
            Property=""BorderThickness""
            Value=""{ThemeResource ButtonBorderThemeThickness}"" />
        <Setter
            Property=""Padding""
            Value=""8,4,8,4"" />
        <Setter
            Property=""HorizontalAlignment""
            Value=""Left"" />
        <Setter
            Property=""VerticalAlignment""
            Value=""Center"" />
        <Setter
            Property=""FontFamily""
            Value=""{ThemeResource ContentControlThemeFontFamily}"" />
        <Setter
            Property=""FontWeight""
            Value=""Normal"" />
        <Setter
            Property=""FontSize""
            Value=""{ThemeResource ControlContentThemeFontSize}"" />
        <Setter
            Property=""UseSystemFocusVisuals""
            Value=""True"" />
        <Setter Property=""Template"">
            <Setter.Value>
                <ControlTemplate TargetType=""Button"">
                    <Grid x:Name=""RootGrid"">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name=""CommonStates"">
                                <VisualState x:Name=""Normal"">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames
                                            Storyboard.TargetName=""Pill""
                                            Storyboard.TargetProperty=""Fill"">
                                            <DiscreteObjectKeyFrame
                                                KeyTime=""0""
                                                Value=""{StaticResource PillBackgroundColor}"" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames
                                            Storyboard.TargetName=""ContentPresenter""
                                            Storyboard.TargetProperty=""Foreground"">
                                            <DiscreteObjectKeyFrame
                                                KeyTime=""0""
                                                Value=""{StaticResource PillTextColor}"" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <PointerUpThemeAnimation Storyboard.TargetName=""RootGrid"" />
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name=""PointerOver"">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames
                                            Storyboard.TargetName=""Pill""
                                            Storyboard.TargetProperty=""Fill"">
                                            <DiscreteObjectKeyFrame
                                                KeyTime=""0""
                                                Value=""{StaticResource PillFillColorOnHover}"" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames
                                            Storyboard.TargetName=""ContentPresenter""
                                            Storyboard.TargetProperty=""Foreground"">
                                            <DiscreteObjectKeyFrame
                                                KeyTime=""0""
                                                Value=""{StaticResource PillTextColorOnHoverOrPressed}"" />
                                        </ObjectAnimationUsingKeyFrames>

                                        <PointerUpThemeAnimation Storyboard.TargetName=""RootGrid"" />
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name=""Pressed"">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames
                                            Storyboard.TargetName=""Pill""
                                            Storyboard.TargetProperty=""Fill"">
                                            <DiscreteObjectKeyFrame
                                                KeyTime=""0""
                                                Value=""{StaticResource PillFillColorOnPressed}"" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames
                                            Storyboard.TargetName=""ContentPresenter""
                                            Storyboard.TargetProperty=""Foreground"">
                                            <DiscreteObjectKeyFrame
                                                KeyTime=""0""
                                                Value=""{StaticResource PillTextColorOnHoverOrPressed}"" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <PointerDownThemeAnimation Storyboard.TargetName=""RootGrid"" />
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name=""Disabled"">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames
                                            Storyboard.TargetName=""Pill""
                                            Storyboard.TargetProperty=""Fill"">
                                            <DiscreteObjectKeyFrame
                                                KeyTime=""0""
                                                Value=""{ThemeResource SystemControlBackgroundBaseLowBrush}"" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames
                                            Storyboard.TargetName=""ContentPresenter""
                                            Storyboard.TargetProperty=""Foreground"">
                                            <DiscreteObjectKeyFrame
                                                KeyTime=""0""
                                                Value=""{ThemeResource SystemControlDisabledBaseMediumLowBrush}"" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames
                                            Storyboard.TargetName=""Pill""
                                            Storyboard.TargetProperty=""Stroke"">
                                            <DiscreteObjectKeyFrame
                                                KeyTime=""0""
                                                Value=""{ThemeResource SystemControlDisabledTransparentBrush}"" />
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Rectangle
                            x:Name=""Pill""
                            RadiusX=""{StaticResource PillCornerRadius}""
                            RadiusY=""{StaticResource PillCornerRadius}""
                            Stroke=""{TemplateBinding BorderBrush}""
                            StrokeThickness=""{StaticResource PillBorderWidth}"" />
                        <ContentPresenter
                            x:Name=""ContentPresenter""
                            Padding=""{TemplateBinding Padding}""
                            HorizontalContentAlignment=""{TemplateBinding HorizontalContentAlignment}""
                            VerticalAlignment=""Center""
                            VerticalContentAlignment=""{TemplateBinding VerticalContentAlignment}""
                            AutomationProperties.AccessibilityView=""Raw""
                            Content=""{TemplateBinding Content}""
                            ContentTemplate=""{TemplateBinding ContentTemplate}""
                            ContentTransitions=""{TemplateBinding ContentTransitions}"" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>";
    }
}

以防万一,这是用于代码中从Xamarin Color转换为UWP Color的ToUwp颜色扩展名:

internal static class ColorExtensions
{
    public static Color ToUwp(this Xamarin.Forms.Color color)
    {
        return Color.FromArgb((byte)(color.A * 255),
                              (byte)(color.R * 255),
                              (byte)(color.G * 255),
                              (byte)(color.B * 255));
    }
}