如何优化Xamarin.Forms UWP应用中按钮点击时的背景色修改方案?
嘿,我太懂你这种不想用粗暴方案的心情了——网上那种直接硬改控件模板或者写一堆重复代码的做法,确实不够优雅,维护起来也麻烦。针对Xamarin.Forms UWP应用里按钮按下状态的背景色修改,这里有几个更干净、可复用的优解,你可以根据项目需求选:
方案1:平台特定全局样式(适合统一修改所有按钮)
这种方式利用Xamarin.Forms的平台资源字典特性,只在UWP项目里定义按钮的视觉状态,不会影响其他平台的样式,而且能统一应用到所有按钮上:
- 在UWP项目的
App.xaml中添加自定义按钮样式,覆盖Pressed状态的背景色:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Style TargetType="Button" BasedOn="{StaticResource DefaultButtonStyle}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Grid x:Name="RootGrid" Background="{TemplateBinding Background}"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal"/> <VisualState x:Name="Pressed"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Background"> <DiscreteObjectKeyFrame KeyTime="0" Value="#FF4CAF50"/> <!-- 替换成你想要的按下背景色 --> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <ContentPresenter x:Name="ContentPresenter" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
- 在共享项目的
App.xaml中合并这个UWP资源字典,确保样式全局生效:
<Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <!-- 其他资源字典 --> <OnPlatform x:TypeArguments="ResourceDictionary"> <On Platform="UWP" Source="ms-appx:///YourApp.UWP/Resources/ButtonStyles.xaml"/> <!-- 替换成你的UWP资源文件路径 --> </OnPlatform> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources>
方案2:自定义Effect(轻量灵活,支持单个按钮定制)
Effect是Xamarin.Forms里的轻量扩展方式,不用修改原有控件结构,就能给单个按钮定制按下背景色,复用性极强:
步骤1:共享项目中定义Effect的绑定属性
using Xamarin.Forms; namespace YourApp.Effects { public class ButtonPressedColorEffect : RoutingEffect { // 定义附加属性,用于绑定按下背景色 public static readonly BindableProperty PressedBackgroundColorProperty = BindableProperty.CreateAttached("PressedBackgroundColor", typeof(Color), typeof(ButtonPressedColorEffect), Color.Default); public static Color GetPressedBackgroundColor(BindableObject view) { return (Color)view.GetValue(PressedBackgroundColorProperty); } public static void SetPressedBackgroundColor(BindableObject view, Color value) { view.SetValue(PressedBackgroundColorProperty, value); } public ButtonPressedColorEffect() : base("YourApp.ButtonPressedColorEffect") { } } }
步骤2:UWP项目中实现平台特效
using System.Linq; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media; using Xamarin.Forms; using Xamarin.Forms.Platform.UWP; using YourApp.Effects; [assembly: ResolutionGroupName("YourApp")] [assembly: ExportEffect(typeof(YourApp.UWP.Effects.ButtonPressedColorEffectUWP), "ButtonPressedColorEffect")] namespace YourApp.UWP.Effects { public class ButtonPressedColorEffectUWP : PlatformEffect { private Color _pressedColor; private Brush _originalPressedBrush; private Button _nativeButton; protected override void OnAttached() { _nativeButton = Control as Button; if (_nativeButton == null) return; _pressedColor = ButtonPressedColorEffect.GetPressedBackgroundColor(Element); if (_pressedColor == Color.Default) return; // 获取按钮模板中的根网格 var rootGrid = _nativeButton.GetTemplateChild("RootGrid") as Grid; if (rootGrid == null) return; // 保存原有按下状态的背景 var visualStateGroup = VisualStateManager.GetVisualStateGroups(rootGrid) .OfType<VisualStateGroup>() .FirstOrDefault(g => g.Name == "CommonStates"); var pressedState = visualStateGroup?.States.OfType<VisualState>().FirstOrDefault(s => s.Name == "Pressed"); if (pressedState?.Storyboard != null) { var animation = pressedState.Storyboard.Children .OfType<ObjectAnimationUsingKeyFrames>() .FirstOrDefault(a => a.TargetProperty == "Background"); _originalPressedBrush = animation?.KeyFrames.FirstOrDefault()?.Value as Brush; } // 替换为自定义颜色 UpdatePressedColor(); } protected override void OnDetached() { if (_nativeButton == null) return; // 恢复原有背景样式 var rootGrid = _nativeButton.GetTemplateChild("RootGrid") as Grid; if (rootGrid == null) return; var visualStateGroup = VisualStateManager.GetVisualStateGroups(rootGrid) .OfType<VisualStateGroup>() .FirstOrDefault(g => g.Name == "CommonStates"); var pressedState = visualStateGroup?.States.OfType<VisualState>().FirstOrDefault(s => s.Name == "Pressed"); if (pressedState?.Storyboard != null && _originalPressedBrush != null) { var animation = pressedState.Storyboard.Children .OfType<ObjectAnimationUsingKeyFrames>() .FirstOrDefault(a => a.TargetProperty == "Background"); animation?.KeyFrames.FirstOrDefault()?.SetValue(DiscreteObjectKeyFrame.ValueProperty, _originalPressedBrush); } _nativeButton = null; } protected override void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs args) { base.OnElementPropertyChanged(args); if (args.PropertyName == ButtonPressedColorEffect.PressedBackgroundColorProperty.PropertyName) { _pressedColor = ButtonPressedColorEffect.GetPressedBackgroundColor(Element); UpdatePressedColor(); } } private void UpdatePressedColor() { if (_nativeButton == null || _pressedColor == Color.Default) return; var rootGrid = _nativeButton.GetTemplateChild("RootGrid") as Grid; if (rootGrid == null) return; var visualStateGroup = VisualStateManager.GetVisualStateGroups(rootGrid) .OfType<VisualStateGroup>() .FirstOrDefault(g => g.Name == "CommonStates"); var pressedState = visualStateGroup?.States.OfType<VisualState>().FirstOrDefault(s => s.Name == "Pressed"); if (pressedState?.Storyboard != null) { var animation = pressedState.Storyboard.Children .OfType<ObjectAnimationUsingKeyFrames>() .FirstOrDefault(a => a.TargetProperty == "Background"); if (animation != null) { animation.KeyFrames.FirstOrDefault()?.SetValue(DiscreteObjectKeyFrame.ValueProperty, new SolidColorBrush(_pressedColor.ToWindowsColor())); } } } } }
步骤3:在XAML中使用Effect
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:effects="clr-namespace:YourApp.Effects" x:Class="YourApp.MainPage"> <StackLayout> <Button Text="测试按钮" effects:ButtonPressedColorEffect.PressedBackgroundColor="#FF4CAF50"/> </StackLayout> </ContentPage>
方案3:自定义Button控件(适合大量按钮需要统一特性的场景)
如果你的项目里有很多按钮都需要自定义按下背景色的功能,可以封装一个自定义Button控件,把逻辑内置进去,使用起来更简洁:
共享项目中定义自定义Button
using Xamarin.Forms; namespace YourApp.Controls { public class CustomButton : Button { public static readonly BindableProperty PressedBackgroundColorProperty = BindableProperty.Create(nameof(PressedBackgroundColor), typeof(Color), typeof(CustomButton), Color.Default); public Color PressedBackgroundColor { get => (Color)GetValue(PressedBackgroundColorProperty); set => SetValue(PressedBackgroundColorProperty, value); } } }
UWP项目中实现自定义渲染器
可以复用上面Effect里的逻辑,把它集成到自定义渲染器中,这样使用时直接用<controls:CustomButton>即可,不用额外添加Effect。
这几个方案都比直接硬改模板要优雅得多:全局样式适合统一应用风格,Effect轻量灵活适合单个定制,自定义控件适合大规模复用。我个人更推荐Effect方案,既不侵入原有控件结构,又能灵活调整。
内容的提问来源于stack exchange,提问作者Georgi Koemdzhiev
相关产品推荐
相关产品推荐

