XAML 深入学习 (三) 高级UI开发‌

发布于:2025-07-04 ⋅ 阅读:(12) ⋅ 点赞:(0)

一、架构与设计模式

MVVM深度解耦‌

数据驱动界面‌:XAML通过声明式绑定(如{Binding Path})自动同步业务逻辑与UI状态,无需手动更新控件

例子:

MainWindow.xaml


<Window x:Class="DataBindingDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="员工信息" Height="300" Width="400">
    <StackPanel Margin="15">
        <TextBlock Text="员工姓名:"/>
        <TextBox Text="{Binding EmployeeName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
        
        <TextBlock Text="当前薪资:" Margin="0,10,0,0"/>
        <Slider Value="{Binding Salary, Mode=TwoWay}" 
                Minimum="3000" Maximum="30000" TickFrequency="1000"/>
        <TextBlock Text="{Binding Salary, StringFormat='C2'}"/>
        
        <CheckBox Content="是否经理" IsChecked="{Binding IsManager}"/>
        
        <Button Content="提交" Command="{Binding SubmitCommand}" Margin="0,20"/>
    </StackPanel>
</Window>

MainWindow.xaml.cs

using System.ComponentModel;
using System.Windows;

namespace DataBindingDemo {
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
            DataContext = new EmployeeViewModel(); // 关键:设置数据上下文
        }
    }

    public class EmployeeViewModel : INotifyPropertyChanged {
        private string _employeeName = "张三";
        private double _salary = 8000;
        private bool _isManager;

        public string EmployeeName {
            get => _employeeName;
            set { _employeeName = value; OnPropertyChanged(); }
        }

        public double Salary {
            get => _salary;
            set { _salary = value; OnPropertyChanged(); }
        }

        public bool IsManager {
            get => _isManager;
            set { _isManager = value; OnPropertyChanged(); }
        }

        public RelayCommand SubmitCommand => new RelayCommand(_ => {
            MessageBox.Show($"已提交: {EmployeeName}, 薪资: {Salary:C2}, 经理: {IsManager}");
        });

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string name = null) {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
    }

    public class RelayCommand : System.Windows.Input.ICommand {
        private readonly Action<object> _execute;
        public RelayCommand(Action<object> execute) => _execute = execute;
        public bool CanExecute(object parameter) => true;
        public void Execute(object parameter) => _execute(parameter);
        public event EventHandler CanExecuteChanged;
    }
}

命令模式集成‌:ICommand接口实现事件与逻辑分离(如异步操作封装至RelayCommand)

例子:

RelayCommandExample.xaml


<Window x:Class="CommandDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="命令模式" Height="200" Width="300">
    <StackPanel VerticalAlignment="Center">
        <Button Content="异步加载数据" 
                Command="{Binding LoadDataCommand}"
                Padding="10,5"/>
        <TextBlock Text="{Binding Status}" 
                   Margin="0,10" HorizontalAlignment="Center"/>
    </StackPanel>
</Window>

MainWindow.xaml.cs

 System.Windows;
using System.Threading.Tasks;

namespace CommandDemo {
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
            DataContext = new MainViewModel();
        }
    }

    public class MainViewModel {
        public ICommand LoadDataCommand => new RelayCommand(async _ => {
            Status = "加载中...";
            await Task.Delay(2000); // 模拟耗时操作
            Status = $"数据加载完成 {DateTime.Now:T}";
        });

        private string _status = "准备就绪";
        public string Status {
            get => _status;
            set { _status = value; OnPropertyChanged(); }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string name = null) {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
    }

    public class RelayCommand : ICommand {
        private readonly Action<object> _execute;
        public RelayCommand(Action<object> execute) => _execute = execute;
        public bool CanExecute(object parameter) => true;
        public void Execute(object parameter) => _execute(parameter);
        public event EventHandler CanExecuteChanged;
    }
}

依赖注入支持‌:结合DI容器(如Microsoft.Extensions.DependencyInjection)管理ViewModel生命周期

例子:

App.xaml.cs


using Microsoft.Extensions.DependencyInjection;
using System.Windows;

public partial class App : Application
{
    public static IServiceProvider ServiceProvider { get; private set; }

    protected override void OnStartup(StartupEventArgs e)
    {
        var services = new ServiceCollection();
        services.AddTransient<MainViewModel>(); // 每次请求新建实例
        services.AddSingleton<IDataService, MockDataService>(); // 单例服务
        ServiceProvider = services.BuildServiceProvider();
        
        var mainWindow = new MainWindow();
        mainWindow.Show();
    }
}

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = App.ServiceProvider.GetRequiredService<MainViewModel>();
    }
}

 MainViewModel.cs

public class MainViewModel
{
    private readonly IDataService _dataService;
    
    public MainViewModel(IDataService dataService)
    {
        _dataService = dataService; // 通过构造函数注入
        LoadData();
    }

    private async void LoadData()
    {
        Data = await _dataService.GetDataAsync();
    }
}

动态UI架构‌

控件模板化‌:通过重写ControlTemplate实现Material Design等复杂视觉效果(如浮动阴影、动态色彩)

例子:

MaterialButton.xaml


<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style TargetType="Button" x:Key="MaterialButton">
        <Setter Property="Background" Value="#6200EE"/>
        <Setter Property="Foreground" Value="White"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Grid>
                        <!-- 阴影层 -->
                        <Border x:Name="Shadow" 
                                CornerRadius="4" 
                                Background="Transparent"
                                Margin="0,0,0,6">
                            <Border.Effect>
                                <DropShadowEffect BlurRadius="12" 
                                                ShadowDepth="3" 
                                                Color="#40000000"/>
                            </Border.Effect>
                        </Border>
                        
                        <!-- 按钮主体 -->
                        <Border x:Name="Border" 
                                CornerRadius="4"
                                Background="{TemplateBinding Background}">
                            <ContentPresenter HorizontalAlignment="Center"
                                            VerticalAlignment="Center"/>
                        </Border>
                    </Grid>
                    
                    <ControlTemplate.Triggers>
                        <!-- 鼠标悬停效果 -->
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter TargetName="Border" Property="Background" Value="#3700B3"/>
                            <Setter TargetName="Shadow" Property="Margin" Value="0,0,0,8"/>
                            <Setter TargetName="Shadow" Property="Effect">
                                <Setter.Value>
                                    <DropShadowEffect BlurRadius="16" 
                                                    ShadowDepth="5" 
                                                    Color="#60000000"/>
                                </Setter.Value>
                            </Setter>
                        </Trigger>
                        
                        <!-- 按下效果 -->
                        <Trigger Property="IsPressed" Value="True">
                            <Setter TargetName="Border" Property="Background" Value="#03DAC6"/>
                            <Setter TargetName="Shadow" Property="Margin" Value="0,2,0,4"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

MainWindow.xaml

<Window x:Class="MaterialDesignDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Material Design" Height="200" Width="300">
    <Window.Resources>
        <ResourceDictionary Source="MaterialButton.xaml"/>
    </Window.Resources>
    
    <Grid>
        <Button Style="{StaticResource MaterialButton}"
                Content="悬浮按钮"
                Width="120" Height="40"/>
    </Grid>
</Window>


数据模板动态生成‌:DataTemplateSelector根据数据类型实时切换控件样式

例子:

MainWindow.xaml


<Window x:Class="TemplateSelectorDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TemplateSelectorDemo"
        Title="模板选择器" Height="300" Width="400">
    <Window.Resources>
        <DataTemplate x:Key="TextTemplate">
            <Border Background="LightBlue" Padding="10">
                <TextBlock Text="{Binding Content}" FontSize="16"/>
            </Border>
        </DataTemplate>
        
        <DataTemplate x:Key="ImageTemplate">
            <Border Background="LightGreen" Padding="5">
                <Image Source="{Binding Path}" Width="100" Height="100"/>
            </Border>
        </DataTemplate>
        
        <local:CustomTemplateSelector x:Key="MySelector"
            TextTemplate="{StaticResource TextTemplate}"
            ImageTemplate="{StaticResource ImageTemplate}"/>
    </Window.Resources>
    
    <ListBox ItemsSource="{Binding Items}"
             ItemTemplateSelector="{StaticResource MySelector}"/>
</Window>

CustomTemplateSelector.cs

using System.Windows;
using System.Windows.Controls;

namespace TemplateSelectorDemo {
    public class CustomTemplateSelector : DataTemplateSelector {
        public DataTemplate TextTemplate { get; set; }
        public DataTemplate ImageTemplate { get; set; }
        
        public override DataTemplate SelectTemplate(object item, 
            DependencyObject container) {
            return item is TextItem ? TextTemplate : 
                   item is ImageItem ? ImageTemplate : null;
        }
    }
}

 ViewModels.cs

 TemplateSelectorDemo {
    public abstract class BaseItem {}
    
    public class TextItem : BaseItem {
        public string Content { get; set; } = "文本内容";
    }
    
    public class ImageItem : BaseItem {
        public string Path { get; set; } = "image.png";
    }
    
    public class MainViewModel {
        public ObservableCollection<BaseItem> Items { get; } = new() {
            new TextItem(),
            new ImageItem(),
            new TextItem()
        };
    }
}


二、高级UI表现力

声明式动画引擎‌

<Storyboard>
    <DoubleAnimation Storyboard.TargetName="MyRect" 
                     Storyboard.TargetProperty="Opacity" 
                     From="0" To="1" Duration="0:0:2"/>
</Storyboard>

XAML原生支持时间轴动画与关键帧控制

视觉层级管理‌

视觉树操作‌:VisualTreeHelper动态遍历/修改UI元素层级结构

例子:

VisualTreeHelperDemo.xaml


<Window x:Class="VisualTreeDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="视觉树操作" Height="350" Width="500">
    <StackPanel x:Name="MainPanel">
        <Button Content="查找子元素" Click="FindChildren_Click"/>
        <Button Content="修改样式" Click="ModifyStyle_Click"/>
        <ListBox x:Name="TreeViewer" Height="200"/>
    </StackPanel>
</Window>

MainWindow.xaml.cs

 System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace VisualTreeDemo {
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
            MainPanel.Children.Add(new TextBlock { Text = "原始元素1" });
            MainPanel.Children.Add(new TextBlock { Text = "原始元素2" });
        }

        private void FindChildren_Click(object sender, RoutedEventArgs e) {
            TreeViewer.Items.Clear();
            TraverseVisualTree(MainPanel, 0);
        }

        private void TraverseVisualTree(DependencyObject parent, int depth) {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) {
                var child = VisualTreeHelper.GetChild(parent, i);
                TreeViewer.Items.Add(new string(' ', depth*4) + child.GetType().Name);
                
                if (VisualTreeHelper.GetChildrenCount(child) > 0) {
                    TraverseVisualTree(child, depth + 1);
                }
            }
        }

        private void ModifyStyle_Click(object sender, RoutedEventArgs e) {
            ApplyStyleToTextBlocks(MainPanel);
        }

        private void ApplyStyleToTextBlocks(DependencyObject parent) {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) {
                var child = VisualTreeHelper.GetChild(parent, i);
                
                if (child is TextBlock tb) {
                    tb.Foreground = Brushes.Red;
                    tb.FontWeight = FontWeights.Bold;
                }
                ApplyStyleToTextBlocks(child);
            }
        }
    }
}

 混合渲染‌:集成Win2D或SkiaSharp实现GPU加速绘制(如复杂几何图形)

例子:

MainWindow.xaml


<Window x:Class="SkiaSharpDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:skia="clr-namespace:SkiaSharp.Views.WPF;assembly=SkiaSharp.Views.WPF"
        Title="SkiaSharp GPU绘制" Height="400" Width="600">
    <Grid>
        <skia:SKElement x:Name="SkiaCanvas" 
                       PaintSurface="OnPaintSurface"/>
    </Grid>
</Window>

MainWindow.xaml.cs

using SkiaSharp;
using System.Windows;

namespace SkiaSharpDemo {
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
        }

        private void OnPaintSurface(object sender, 
            SKPaintSurfaceEventArgs e) {
            var surface = e.Surface;
            var canvas = surface.Canvas;
            
            // 清空画布
            canvas.Clear(SKColors.White);
            
            // 创建渐变画笔
            using var paint = new SKPaint {
                Shader = SKShader.CreateLinearGradient(
                    new SKPoint(0, 0),
                    new SKPoint(e.Info.Width, e.Info.Height),
                    new[] { SKColors.Blue, SKColors.Red },
                    new[] { 0f, 1f },
                    SKShaderTileMode.Clamp)
            };
            
            // 绘制复杂路径
            using var path = new SKPath();
            path.MoveTo(100, 100);
            path.CubicTo(300, 50, 200, 200, 400, 150);
            path.LineTo(400, 300);
            path.ArcTo(200, 200, 0, SKPathArcSize.Large, 
                      SKPathDirection.CounterClockwise, 100, 300);
            path.Close();
            
            canvas.DrawPath(path, paint);
        }
    }
}

 App.xaml

<Application x:Class="SkiaSharpDemo.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml">
</Application>

主题与样式系统‌

全局资源字典‌:ResourceDictionary统一管理颜色、字体等设计资产,支持运行时动态切换

例子:

App.xaml


<Application x:Class="ResourceDemo.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Themes/LightTheme.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Themes/LightTheme.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Color x:Key="PrimaryColor">#FF42A5F5</Color>
    <SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource PrimaryColor}"/>
    <FontFamily x:Key="MainFont">Segoe UI</FontFamily>
    <Thickness x:Key="StandardPadding">10</Thickness>
</ResourceDictionary>

 Themes/DarkTheme.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Color x:Key="PrimaryColor">#FF1565C0</Color>
    <SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource PrimaryColor}"/>
    <FontFamily x:Key="MainFont">Consolas</FontFamily>
    <Thickness x:Key="StandardPadding">10</Thickness>
</ResourceDictionary>

MainWindow.xaml

<Window x:Class="ResourceDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="资源字典演示" Height="300" Width="400">
    <StackPanel>
        <Button Content="切换深色主题" Click="DarkTheme_Click"
                Background="{StaticResource PrimaryBrush}"
                FontFamily="{StaticResource MainFont}"
                Padding="{StaticResource StandardPadding}"/>
        <Button Content="切换浅色主题" Click="LightTheme_Click"
                Background="{StaticResource PrimaryBrush}"
                FontFamily="{StaticResource MainFont}"
                Padding="{StaticResource StandardPadding}"/>
    </StackPanel>
</Window>

MainWindow.xaml.cs

using System.Windows;
using System.Windows.Media;

namespace ResourceDemo {
    public partial class MainWindow : Window {
        public MainWindow() => InitializeComponent();

        private void ChangeTheme(string themePath) {
            var dict = new ResourceDictionary { Source = new Uri(themePath, UriKind.Relative) };
            Application.Current.Resources.MergedDictionaries[0] = dict;
        }

        private void DarkTheme_Click(object sender, RoutedEventArgs e) 
            => ChangeTheme("Themes/DarkTheme.xaml");

        private void LightTheme_Click(object sender, RoutedEventArgs e) 
            => ChangeTheme("Themes/LightTheme.xaml");
    }
}

 状态样式触发器‌:VisualStateManager根据控件状态(如Pressed、Disabled)自动切换样式

例子:

MainWindow.xaml


<Window x:Class="VisualStateDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="状态样式触发器" Height="300" Width="400">
    <Grid>
        <Button Content="交互按钮" Width="200" Height="60">
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup x:Name="CommonStates">
                    <VisualState x:Name="Normal">
                        <Storyboard>
                            <ColorAnimation Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)"
                                           To="LightBlue" Duration="0:0:0.2"/>
                        </Storyboard>
                    </VisualState>
                    <VisualState x:Name="MouseOver">
                        <Storyboard>
                            <ColorAnimation Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)"
                                           To="LightGreen" Duration="0:0:0.2"/>
                        </Storyboard>
                    </VisualState>
                    <VisualState x:Name="Pressed">
                        <Storyboard>
                            <ColorAnimation Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)"
                                           To="Orange" Duration="0:0:0.1"/>
                            <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleX"
                                            To="0.95" Duration="0:0:0.1"/>
                            <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleY"
                                            To="0.95" Duration="0:0:0.1"/>
                        </Storyboard>
                    </VisualState>
                    <VisualState x:Name="Disabled">
                        <Storyboard>
                            <ColorAnimation Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)"
                                           To="LightGray" Duration="0:0:0.2"/>
                        </Storyboard>
                    </VisualState>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
            <Button.RenderTransform>
                <ScaleTransform/>
            </Button.RenderTransform>
            <Button.Style>
                <Style TargetType="Button">
                    <Setter Property="Background" Value="LightBlue"/>
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="Button">
                                <Border Background="{TemplateBinding Background}"
                                        CornerRadius="5">
                                    <ContentPresenter HorizontalAlignment="Center"
                                                      VerticalAlignment="Center"/>
                                </Border>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </Button.Style>
        </Button>
    </Grid>
</Window>

MainWindow.xaml.cs

using System.Windows;

namespace VisualStateDemo {
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
        }
    }
}

 三、跨平台能力革新

单一代码库多端部署‌

.NET MAUI机制‌:XAML布局自适应不同平台(iOS/Android/Windows),共享核心业务逻辑

例子:

MainPage.xaml


<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiDemo.MainPage">
    <ScrollView>
        <VerticalStackLayout 
            Spacing="25" 
            Padding="30,0"
            VerticalOptions="Center">
            
            <Image Source="dotnet_bot.png"
                   SemanticProperties.Description="MAUI Logo"
                   HeightRequest="200"
                   HorizontalOptions="Center"/>
            
            <Label Text="Hello, World!"
                   Style="{StaticResource Headline}"
                   SemanticProperties.HeadingLevel="Level1"
                   HorizontalOptions="Center"/>
            
            <Label Text="Welcome to .NET MAUI!"
                   Style="{StaticResource SubHeadline}"
                   SemanticProperties.HeadingLevel="Level2"
                   HorizontalOptions="Center"/>
            
            <Button Text="Click Me" 
                    Command="{Binding CounterCommand}"
                    HorizontalOptions="Center"
                    Style="{OnPlatform Android=PrimaryButton, 
                                      iOS=SecondaryButton,
                                      WinUI=DefaultButton}"/>
            
            <Label Text="{Binding CounterText}"
                   HorizontalOptions="Center"/>
            
        </VerticalStackLayout>
    </ScrollView>
</ContentPage>

MainPage.xaml.cs

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace MauiDemo;

public partial class MainPage : ContentPage, INotifyPropertyChanged
{
    private int _count = 0;
    private string _counterText = "Click count: 0";
    
    public string CounterText {
        get => _counterText;
        set {
            _counterText = value;
            OnPropertyChanged();
        }
    }
    
    public Command CounterCommand { get; }
    
    public MainPage()
    {
        InitializeComponent();
        CounterCommand = new Command(OnCounterClicked);
        BindingContext = this;
    }
    
    private void OnCounterClicked()
    {
        _count++;
        CounterText = $"Click count: {_count}";
        
        SemanticScreenReader.Announce(CounterText);
    }
    
    public event PropertyChangedEventHandler PropertyChanged;
    
    protected void OnPropertyChanged([CallerMemberName] string name = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}

Platforms/Android/Resources/values/styles.xml

<resources>
    <style name="PrimaryButton" parent="Widget.AppCompat.Button.Colored">
        <item name="android:backgroundTint">#512BD4</item>
        <item name="android:textColor">#FFFFFF</item>
    </style>
</resources>

Platforms/iOS/Resources/SecondaryB

using Microsoft.Maui.Controls.PlatformConfiguration;
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;

namespace MauiDemo;

public static class SecondaryButtonStyle
{
    public static Button ApplySecondaryStyle(this Button button)
    {
        button.BackgroundColor = Colors.White;
        button.TextColor = Colors.Black;
        button.On<iOS>().SetUseSafeArea(true);
        return button;
    }
}


响应式布局‌:FlexLayout与Grid结合自适应规则应对屏幕尺寸变化

例子:

ResponsivePage.xaml


<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ResponsiveDemo.ResponsivePage">
    <Grid RowDefinitions="Auto,*,Auto"
          ColumnDefinitions="*,*">
        
        <!-- 顶部标题栏 - 横屏时右移 -->
        <Label Text="响应式布局演示" 
               Grid.ColumnSpan="{OnPlatform 2, 
                              Default=1, 
                              iOS=1, 
                              Android=1,
                              WinUI=2}"
               HorizontalOptions="Center"
               Style="{StaticResource TitleStyle}"/>
        
        <!-- 主内容区 - FlexLayout根据方向变化 -->
        <FlexLayout Grid.Row="1"
                    Grid.ColumnSpan="2"
                    Direction="{OnPlatform Default=Row, 
                                        iOS=Row, 
                                        Android=Column,
                                        WinUI=Row}"
                    Wrap="Wrap"
                    JustifyContent="SpaceEvenly"
                    AlignItems="Center">
            
            <BoxView Color="LightBlue" 
                     WidthRequest="{OnPlatform 150, 
                                      Default=100, 
                                      iOS=120}"
                     HeightRequest="100"/>
            <BoxView Color="LightGreen"
                     WidthRequest="{OnPlatform 150, 
                                      Default=100, 
                                      iOS=120}"
                     HeightRequest="100"/>
            <BoxView Color="LightPink"
                     WidthRequest="{OnPlatform 150, 
                                      Default=100, 
                                      iOS=120}"
                     HeightRequest="100"/>
        </FlexLayout>
        
        <!-- 底部按钮区 - 根据屏幕宽度调整布局 -->
        <StackLayout Grid.Row="2"
                     Grid.ColumnSpan="2"
                     Orientation="{OnPlatform Default=Horizontal, 
                                           iOS=Horizontal, 
                                           Android=Vertical}">
            <Button Text="确定" 
                    WidthRequest="{OnPlatform 120, 
                                      Default=100, 
                                      iOS=110}"/>
            <Button Text="取消"
                    WidthRequest="{OnPlatform 120, 
                                      Default=100, 
                                      iOS=110}"/>
        </StackLayout>
    </Grid>
</ContentPage>

ResponsivePage.xaml.cs

using Microsoft.Maui.Controls;

namespace ResponsiveDemo;

public partial class ResponsivePage : ContentPage
{
    public ResponsivePage()
    {
        InitializeComponent();
        
        // 监听尺寸变化
        this.SizeChanged += (s,e) => {
            bool isWide = this.Width > 600;
            VisualStateManager.GoToState(
                this, 
                isWide ? "WideLayout" : "NarrowLayout"
            );
        };
    }
}

 App.xaml

<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ResponsiveDemo.App">
    <Application.Resources>
        <ResourceDictionary>
            <Style x:Key="TitleStyle" TargetType="Label">
                <Setter Property="FontSize" Value="{OnPlatform 24, Default=20, iOS=22}"/>
                <Setter Property="FontAttributes" Value="Bold"/>
            </Style>
            
            <VisualStateGroup x:Key="LayoutStates">
                <VisualState x:Key="WideLayout">
                    <VisualState.Setters>
                        <Setter TargetName="mainGrid" Property="Grid.ColumnDefinitions" Value="*,*,*"/>
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Key="NarrowLayout">
                    <VisualState.Setters>
                        <Setter TargetName="mainGrid" Property="Grid.ColumnDefinitions" Value="*"/>
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </ResourceDictionary>
    </Application.Resources>
</Application>

原生性能优化‌

渲染器定制‌:通过Handler机制重写平台原生控件行为(如Android下定制按钮阴影)

例子:

CustomButton.xaml


<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="CustomRendererDemo.CustomButton">
    <Button x:Name="NativeButton"
            Text="Custom Shadow Button"
            BackgroundColor="#512BD4"
            TextColor="White"/>
</ContentView>

CustomButton.xaml.cs

using Microsoft.Maui.Controls;

namespace CustomRendererDemo;

public partial class CustomButton : ContentView
{
    public CustomButton()
    {
        InitializeComponent();
    }
}

CustomButtonHandler.cs

using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;

namespace CustomRendererDemo;

public partial class CustomButtonHandler : ViewHandler<CustomButton, Android.Widget.Button>
{
    protected override Android.Widget.Button CreatePlatformView()
    {
        var button = new Android.Widget.Button(Context);
        return button;
    }

    protected override void ConnectHandler(Android.Widget.Button platformView)
    {
        base.ConnectHandler(platformView);
        UpdateShadow();
    }

    void UpdateShadow()
    {
        if (PlatformView == null) return;
        
        PlatformView.SetShadowLayer(
            radius: 10f, 
            dx: 5f, 
            dy: 5f, 
            color: Android.Graphics.Color.Argb(100, 0, 0, 0));
    }
}

MauiProgram.cs

using Microsoft.Maui;
using Microsoft.Maui.Hosting;

namespace CustomRendererDemo;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureMauiHandlers(handlers => {
                handlers.AddHandler<CustomButton, CustomButtonHandler>();
            });

        return builder.Build();
    }
}


渲染管线接入‌:Avalonia框架支持Skia自定义绘制管线实现高性能渲染

例子:

CustomSkiaControl.cs


using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Skia;
using SkiaSharp;

public class CustomSkiaControl : Control
{
    protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
    {
        base.OnAttachedToVisualTree(e);
        InvalidateVisual();
    }

    public override void Render(DrawingContext context)
    {
        var skiaContext = context.GetFeature<ISkiaSharpApiLeaseFeature>();
        using (var lease = skiaContext.Lease())
        {
            var canvas = lease.SkCanvas;
            canvas.Clear(SKColors.White);
            
            // 绘制渐变背景
            using (var paint = new SKPaint())
            {
                var rect = new SKRect(0, 0, (float)Bounds.Width, (float)Bounds.Height);
                paint.Shader = SKShader.CreateLinearGradient(
                    new SKPoint(0, 0),
                    new SKPoint((float)Bounds.Width, (float)Bounds.Height),
                    new[] { SKColors.Blue, SKColors.Green },
                    new[] { 0f, 1f },
                    SKShaderTileMode.Clamp);
                canvas.DrawRect(rect, paint);
            }

            // 绘制文本
            using (var paint = new SKPaint())
            {
                paint.Color = SKColors.Red;
                paint.TextSize = 24;
                paint.IsAntialias = true;
                canvas.DrawText("SkiaSharp Rendering", 20, 40, paint);
            }
        }
    }
}

MainWindow.xaml

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:YourNamespace"
        Title="SkiaSharp Demo">
    <local:CustomSkiaControl Width="400" Height="300"/>
</Window>


四、性能与可维护性

渲染优化技术‌

UI虚拟化‌:VirtualizingStackPanel应对万级数据列表,仅渲染可视区域元素

例子:

MainWindow.xaml


<Window x:Class="VirtualizationDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="UI虚拟化演示" Height="450" Width="800">
    <ListView ItemsSource="{Binding Items}" 
              VirtualizingStackPanel.IsVirtualizing="True"
              VirtualizingStackPanel.VirtualizationMode="Recycling"
              ScrollViewer.CanContentScroll="True">
        <ListView.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Name}" 
                           Margin="5" FontSize="16"/>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Window>

MainWindow.xaml.cs

using System.Collections.ObjectModel;
using System.Windows;

namespace VirtualizationDemo
{
    public partial class MainWindow : Window
    {
        public ObservableCollection<DataItem> Items { get; set; }

        public MainWindow()
        {
            InitializeComponent();
            Items = new ObservableCollection<DataItem>();
            
            // 生成10万条测试数据
            for (int i = 0; i < 100000; i++)
            {
                Items.Add(new DataItem { Name = $"项目 {i + 1}" });
            }
            
            DataContext = this;
        }
    }

    public class DataItem
    {
        public string Name { get; set; }
    }
}

 异步加载策略‌:PriorityBinding优先显示关键数据,后台加载次要内容

例子:

MainWindow.xaml


<Window x:Class="PriorityBindingDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="异步加载策略" Height="350" Width="500">
    <StackPanel>
        <TextBlock Margin="10" FontWeight="Bold" Text="商品详情"/>
        <TextBlock Margin="10,5">
            <TextBlock.Text>
                <PriorityBinding>
                    <Binding Path="BasicInfo" IsAsync="False"/>
                    <Binding Path="LoadingText"/>
                </PriorityBinding>
            </TextBlock.Text>
        </TextBlock>
        
        <TextBlock Margin="10" FontWeight="Bold" Text="完整描述"/>
        <TextBlock Margin="10,5">
            <TextBlock.Text>
                <PriorityBinding>
                    <Binding Path="FullDescription" IsAsync="True"/>
                    <Binding Path="LoadingText"/>
                </PriorityBinding>
            </TextBlock.Text>
        </TextBlock>
    </StackPanel>
</Window>

MainWindow.xaml.cs

using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows;

namespace PriorityBindingDemo
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private string _loadingText = "加载中...";
        public string LoadingText {
            get => _loadingText;
            set => SetField(ref _loadingText, value);
        }

        private string _basicInfo;
        public string BasicInfo {
            get => _basicInfo ?? LoadBasicInfo();
            set => SetField(ref _basicInfo, value);
        }

        private string _fullDescription;
        public string FullDescription {
            get => _fullDescription ?? Task.Run(LoadFullDescription).Result;
            set => SetField(ref _fullDescription, value);
        }

        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;
        }

        private string LoadBasicInfo() {
            // 模拟快速加载的关键数据
            Task.Delay(300).Wait();
            return "商品名称:智能手机(基础信息已加载)";
        }

        private async Task<string> LoadFullDescription() {
            // 模拟耗时的详细数据加载
            await Task.Delay(3000);
            return "产品详情:\n- 6.5英寸AMOLED屏幕\n- 骁龙888处理器\n- 5000mAh电池\n(完整描述已加载)";
        }

        // INotifyPropertyChanged实现
        public event PropertyChangedEventHandler PropertyChanged;
        protected void SetField<T>(ref T field, T value, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) {
            field = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

 开发效率工具链‌

实时热重载‌:修改XAML即时预览效果(注:需规避XamlC编译冲突)
Live Visual Tree调试‌:运行时动态检查/修改XAML属性


网站公告

今日签到

点亮在社区的每一天
去签到