399 lines
12 KiB
Markdown
399 lines
12 KiB
Markdown
# 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
|
|
|
|
1. **Compiled Bindings**: Luôn sử dụng `x:DataType` để binding tại compile-time
|
|
2. **Flat Layouts**: Tránh nested layouts, ưu tiên Grid
|
|
3. **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
|
|
|
|
```xml
|
|
<!-- ✅ ĐÚ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>
|
|
```
|
|
|
|
```xml
|
|
<!-- ❌ 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
|
|
|
|
```xml
|
|
<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
|
|
|
|
```xml
|
|
<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
|
|
|
|
```xml
|
|
<!-- ❌ 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
|
|
|
|
```xml
|
|
<!-- ✅ 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)
|
|
|
|
```xml
|
|
<!-- 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
|
|
|
|
```xml
|
|
<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
|
|
|
|
```xml
|
|
<!-- 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
|
|
|
|
```xml
|
|
<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
|
|
|
|
```xml
|
|
<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
|
|
|
|
```xml
|
|
<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
|
|
|
|
```xml
|
|
<!-- ✅ 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
|
|
|
|
```xml
|
|
<!-- ❌ 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
|
|
|
|
```xml
|
|
<!-- ❌ 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
|
|
|
|
```csharp
|
|
// ❌ 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:DataType` declared 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
|
|
|
|
---
|
|
|
|
## Resources
|
|
|
|
- [.NET MAUI Performance Best Practices](https://learn.microsoft.com/en-us/dotnet/maui/deployment/performance)
|
|
- [Compiled Bindings](https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/data-binding/compiled-bindings)
|
|
- [MVVM Rules](./mvvm-rules.md)
|
|
- [DI Setup](./di-setup.md)
|