12 KiB
12 KiB
XAML Performance / Tối Ưu XAML
Hướng dẫn chi tiết về Compiled Bindings, layout optimization và XAML best practices.
Core Principles / Nguyên Tắc Cốt Lõi
- Compiled Bindings: Luôn sử dụng
x:DataTypeđể binding tại compile-time - Flat Layouts: Tránh nested layouts, ưu tiên Grid
- Virtualization: Sử dụng CollectionView cho danh sách lớn
Compiled Bindings (BẮT BUỘC)
Why Compiled Bindings?
| Aspect | Reflection Binding | Compiled Binding |
|---|---|---|
| Performance | Slow (runtime lookup) | Fast (compile-time) |
| Error Detection | Runtime exceptions | Compile-time errors |
| NativeAOT | May not work | Full support |
| Intellisense | No | Yes |
Basic Pattern
<!-- ✅ ĐÚNG: Compiled Bindings -->
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:MyApp.ViewModels"
xmlns:models="clr-namespace:MyApp.Models"
x:Class="MyApp.Views.ProductListPage"
x:DataType="vm:ProductListViewModel">
<!-- Binding được resolve tại compile-time -->
<Label Text="{Binding Title}" />
<Label Text="{Binding Products.Count, StringFormat='Total: {0}'}" />
<CollectionView ItemsSource="{Binding Products}">
<CollectionView.ItemTemplate>
<!-- x:DataType thay đổi cho ItemTemplate -->
<DataTemplate x:DataType="models:Product">
<Grid>
<Label Text="{Binding Name}" />
<Label Text="{Binding Price, StringFormat='{0:C}'}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage>
<!-- ❌ SAI: Không có x:DataType -->
<ContentPage>
<!-- Runtime reflection, chậm và không catch lỗi -->
<Label Text="{Binding Titlee}" /> <!-- Typo không bị phát hiện! -->
</ContentPage>
Nested DataType Changes
<CollectionView ItemsSource="{Binding Categories}"
x:DataType="vm:CategoryListViewModel">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Category">
<StackLayout>
<Label Text="{Binding Name}" />
<!-- Nested collection -->
<CollectionView ItemsSource="{Binding Products}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Product">
<Label Text="{Binding Name}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Binding to Parent ViewModel from ItemTemplate
<CollectionView ItemsSource="{Binding Products}"
x:DataType="vm:ProductListViewModel">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Product">
<Grid>
<Label Text="{Binding Name}" />
<!-- Bind to parent ViewModel's command -->
<Button Text="Select"
Command="{Binding Source={RelativeSource AncestorType={x:Type vm:ProductListViewModel}}, Path=SelectProductCommand}"
CommandParameter="{Binding .}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Layout Optimization
Grid vs Nested StackLayout
<!-- ❌ SAI: Nested StackLayout (deep visual tree) -->
<StackLayout>
<StackLayout Orientation="Horizontal">
<Image Source="{Binding ImageUrl}" />
<StackLayout>
<Label Text="{Binding Name}" />
<Label Text="{Binding Description}" />
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Price}" />
<Button Text="Buy" />
</StackLayout>
</StackLayout>
</StackLayout>
</StackLayout>
<!-- Visual tree depth: 5 levels -->
<!-- ✅ ĐÚNG: Flat Grid -->
<Grid RowDefinitions="Auto,Auto,Auto"
ColumnDefinitions="80,*,Auto">
<Image Source="{Binding ImageUrl}"
Grid.RowSpan="3" />
<Label Text="{Binding Name}"
Grid.Column="1" />
<Label Text="{Binding Description}"
Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" />
<Label Text="{Binding Price}"
Grid.Row="2" Grid.Column="1" />
<Button Text="Buy"
Grid.Row="2" Grid.Column="2" />
</Grid>
<!-- Visual tree depth: 2 levels -->
Layout Performance Tips
<!-- ✅ Fixed sizes when possible -->
<Image WidthRequest="80" HeightRequest="80" Aspect="AspectFill" />
<!-- ✅ Use Grid star sizing -->
<Grid ColumnDefinitions="*,2*,*">
<!-- Proportional sizing, efficient -->
</Grid>
<!-- ❌ Avoid: AbsoluteLayout for complex UIs -->
<!-- ❌ Avoid: Deep nesting (>4 levels) -->
CollectionView Best Practices
Virtualization (Auto-enabled)
<!-- CollectionView tự động virtualize -->
<CollectionView ItemsSource="{Binding LargeList}"
ItemsUpdatingScrollMode="KeepItemsInView">
<!-- Fixed item size improves virtualization -->
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"
ItemSpacing="8" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Item">
<!-- Keep template simple for performance -->
<Grid HeightRequest="60" Padding="10">
<Label Text="{Binding Name}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
EmptyView và Loading States
<CollectionView ItemsSource="{Binding Products}">
<!-- Empty state -->
<CollectionView.EmptyView>
<ContentView>
<StackLayout VerticalOptions="Center" HorizontalOptions="Center">
<Image Source="empty_box.svg" WidthRequest="100" />
<Label Text="No products found" />
</StackLayout>
</ContentView>
</CollectionView.EmptyView>
<CollectionView.ItemTemplate>
<!-- ... -->
</CollectionView.ItemTemplate>
</CollectionView>
<!-- Loading overlay -->
<ActivityIndicator IsRunning="{Binding IsLoading}"
IsVisible="{Binding IsLoading}"
VerticalOptions="Center"
HorizontalOptions="Center" />
Resource Dictionary Patterns
App-level Resources
<!-- App.xaml -->
<Application.Resources>
<ResourceDictionary>
<!-- Merged dictionaries -->
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Colors.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<!-- Primary palette -->
<Color x:Key="Primary">#6750A4</Color>
<Color x:Key="PrimaryDark">#381E72</Color>
<Color x:Key="Secondary">#625B71</Color>
<!-- Semantic colors -->
<Color x:Key="TextPrimary">#1C1B1F</Color>
<Color x:Key="TextSecondary">#49454F</Color>
<Color x:Key="Surface">#FFFBFE</Color>
<Color x:Key="SurfaceVariant">#E7E0EC</Color>
<!-- Status colors -->
<Color x:Key="Success">#4CAF50</Color>
<Color x:Key="Warning">#FF9800</Color>
<Color x:Key="Error">#B3261E</Color>
</ResourceDictionary>
Styles.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<!-- Base styles -->
<Style TargetType="Label" x:Key="TitleLabel">
<Setter Property="FontSize" Value="24" />
<Setter Property="FontFamily" Value="InterBold" />
<Setter Property="TextColor" Value="{StaticResource TextPrimary}" />
</Style>
<Style TargetType="Label" x:Key="BodyLabel">
<Setter Property="FontSize" Value="16" />
<Setter Property="FontFamily" Value="InterRegular" />
<Setter Property="TextColor" Value="{StaticResource TextSecondary}" />
</Style>
<Style TargetType="Button" x:Key="PrimaryButton">
<Setter Property="BackgroundColor" Value="{StaticResource Primary}" />
<Setter Property="TextColor" Value="White" />
<Setter Property="FontFamily" Value="InterSemiBold" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="Padding" Value="16,12" />
</Style>
<!-- Apply style globally -->
<Style TargetType="Entry" ApplyToDerivedTypes="True">
<Setter Property="FontFamily" Value="InterRegular" />
<Setter Property="FontSize" Value="16" />
</Style>
</ResourceDictionary>
Usage in Pages
<Label Text="Welcome" Style="{StaticResource TitleLabel}" />
<Label Text="Description" Style="{StaticResource BodyLabel}" />
<Button Text="Submit" Style="{StaticResource PrimaryButton}" />
<!-- Dynamic resource (theme switching) -->
<Label TextColor="{DynamicResource TextPrimary}" />
Hot Reload Tips
<!-- ✅ Hot Reload friendly -->
<Label Text="Static text" />
<Label Text="{Binding DynamicText}" />
<!-- ⚠️ May require restart -->
<!-- Changes to: -->
<!-- - x:DataType -->
<!-- - Namespace imports -->
<!-- - Resource dictionary structure -->
Common Mistakes / Lỗi Thường Gặp
1. Missing x:DataType in ItemTemplate
<!-- ❌ SAI -->
<CollectionView ItemsSource="{Binding Products}" x:DataType="vm:ListViewModel">
<CollectionView.ItemTemplate>
<DataTemplate>
<!-- Binding uses reflection, slow! -->
<Label Text="{Binding Name}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<!-- ✅ ĐÚNG -->
<DataTemplate x:DataType="models:Product">
<Label Text="{Binding Name}" />
</DataTemplate>
2. Image Not Caching
<!-- ❌ SAI: Reload mỗi lần -->
<Image Source="{Binding ImageUrl}" />
<!-- ✅ ĐÚNG: Enable caching -->
<Image>
<Image.Source>
<UriImageSource Uri="{Binding ImageUrl}"
CachingEnabled="True"
CacheValidity="1:00:00:00" />
</Image.Source>
</Image>
3. Heavy Work in Converters
// ❌ SAI: Slow converter
public object Convert(object value, ...)
{
// Heavy computation on UI thread!
return ProcessData(value);
}
// ✅ ĐÚNG: Pre-compute in ViewModel
[ObservableProperty]
private string _processedValue; // Already computed
Performance Checklist
x:DataTypedeclared on every page and DataTemplate- No nested StackLayout deeper than 2 levels
- CollectionView used for lists (not StackLayout)
- Fixed sizes where possible (WidthRequest, HeightRequest)
- Image caching enabled for remote images
- Styles in ResourceDictionary, not inline
- StaticResource over DynamicResource when possible