概述 UWP Community Toolkit 中有一個圖片的擴展控制項 - ImageEx,本篇我們結合代碼詳細講解 ImageEx 的實現。 ImageEx 是一個圖片的擴展控制項,包括 ImageEx 和 RoundImageEx,它可以在非同步載入圖片源時顯示載入狀態,也可以在載入前使用占點陣圖片 ...
概述
UWP Community Toolkit 中有一個圖片的擴展控制項 - ImageEx,本篇我們結合代碼詳細講解 ImageEx 的實現。
ImageEx 是一個圖片的擴展控制項,包括 ImageEx 和 RoundImageEx,它可以在非同步載入圖片源時顯示載入狀態,也可以在載入前使用占點陣圖片,在下載完成後可以在應用內緩存,避免了重覆載入的過程。我們來看一下官方的介紹和官網示例中的展示:
Doc: https://docs.microsoft.com/zh-cn/windows/uwpcommunitytoolkit/controls/imageex
Namespace: Microsoft.Toolkit.Uwp.UI.Controls; Nuget: Microsoft.Toolkit.Uwp.UI.Controls;
開發過程
代碼分析
我們來看一下 ImageEx 控制項的結構:
- ImageEx.Members.cs - ImageEx 控制項部分類的成員變數類
- ImageEx.cs - ImageEx 控制項部分類的定義類
- ImageEx.xaml - ImageEx 控制項樣式文件
- ImageExBase.Members.cs - ImageEx 控制項基類部分類的成員變數類
- ImageExBase.Placeholder.cs - ImageEx 控制項基類部分類的占位符類
- ImageExBase.Source.cs - ImageEx 控制項基類部分類的圖片源類
- ImageExBase.cs - ImageEx 控制項基類部分類的定義類
- ImageExFailedEventArgs.cs - ImageEx 控制項的失敗事件參數類
- ImageExOpenedEventArgs.cs - ImageEx 控制項的打開事件參數類
- RoundImageEx.Members.cs - RoundImageEx 控制項部分類的成員變數類
- RoundImageEx.cs - RoundImageEx 控制項部分類的定義類
- RoundImageEx.xaml - RoundImageEx 控制項樣式文件
下麵把幾個重點的類詳細分析一下:
1. ImageEx.xaml
ImageEx 控制項的樣式文件,來看一下 Template 部分,包含了三層的控制項:PlaceHolderImage,Image 和 Progress,這樣就可以完成載入中或失敗時顯示 PlaceHolder 和 Progress,載入成功後顯示 Image;同時樣式在 Failed,Loading,Loaded 和 Unloaded 狀態時,也會切換不同層的顯示來完成狀態切換;
<Style TargetType="controls:ImageEx"> <Setter Property="Background" Value="Transparent" /> <Setter Property="Foreground" Value="{ThemeResource ApplicationForegroundThemeBrush}" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="controls:ImageEx"> <Grid Background="{TemplateBinding Background}" CornerRadius="{TemplateBinding CornerRadius}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <Image Name="PlaceholderImage" Opacity="1.0" .../> <Image Name="Image" NineGrid="{TemplateBinding NineGrid}" Opacity="0.0" .../> <ProgressRing Name="Progress" Margin="16" HorizontalAlignment="Center" VerticalAlignment="Center" Background="Transparent" Foreground="{TemplateBinding Foreground}" IsActive="False" Visibility="Collapsed" /> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Failed"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Image" Storyboard.TargetProperty="Opacity"> <DiscreteObjectKeyFrame KeyTime="0" Value="0" /> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlaceholderImage" Storyboard.TargetProperty="Opacity"> <DiscreteObjectKeyFrame KeyTime="0" Value="1" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="Loading" .../> <VisualState x:Name="Loaded" .../> <VisualState x:Name="Unloaded" .../> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style>
2. ImageExBase.Members.cs
ImageEx 控制項的定義和功能實現主要在 ImageExBase 類中,而 ImageExBase.Members.cs 主要定義了類的成員,具體如下:
- Stretch - 獲取或設置控制項的拉伸屬性
- CornerRadius - 獲取或設置控制項的圓角半徑,用於 Rounded 或 Circle 圖片控制項
- DecodePixelHeight - 獲取或設置控制項的解碼像素高度
- DecodePixelType - 獲取或設置控制項的解碼像素類型
- DecodePixelWidth - 獲取或設置控制項的解碼像素寬度
- IsCacheEnabled - 獲取或設置緩存是否可用
另外還定義了 ImageFailed、ImageOpened、ImageExInitialized 事件,以及 GetAlphaMask() 方法,用於獲取 alpha 通道的蒙板;
3. ImageExBase.Placeholder.cs
主要定義了 ImageExBase 類的占位符成員,具體如下:
- PlaceholderStretch - 獲取或設置占位符的拉伸屬性
- PlaceholderSource - 獲取或設置占位符的圖像源,ImageSource 類型,改變時會觸發 PlaceholderSourceChanged(d, e) 方法;
4. ImageExBase.Source.cs
主要定義了 ImageExBase 類的圖像源,除了定義 Source 外,還實現了以下幾個方法:
① SetSource(source)
初始化 token 後,如果 source 為空,則進入 Unloaded 狀態;否則進入 Loading 狀態;判斷 source 是 ImageSource 類型且有效,則賦值,然後進入 Loaded 狀態;如果 source 是 Uri 類型但無效,或 ImageSource 類型無效,則進入 Failed 狀態;如果 Uri 有效,判斷為 httpUri 則進入 LoadImageAsync(uri) 方法,否則直接拼接 ms-appx:/// 資源格式載入給控制項;
private async void SetSource(object source) { if (!IsInitialized) { return;} this._tokenSource?.Cancel(); this._tokenSource = new CancellationTokenSource(); AttachSource(null); if (source == null) { VisualStateManager.GoToState(this, UnloadedState, true); return; } VisualStateManager.GoToState(this, LoadingState, true); var imageSource = source as ImageSource; if (imageSource != null) { AttachSource(imageSource); ImageExOpened?.Invoke(this, new ImageExOpenedEventArgs()); VisualStateManager.GoToState(this, LoadedState, true); return; } _uri = source as Uri; if (_uri == null) { var url = source as string ?? source.ToString(); if (!Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out _uri)) { VisualStateManager.GoToState(this, FailedState, true); return; } } _isHttpSource = IsHttpUri(_uri); if (!_isHttpSource && !_uri.IsAbsoluteUri) { _uri = new Uri("ms-appx:///" + _uri.OriginalString.TrimStart('/')); } await LoadImageAsync(_uri); }
② LoadImageAsync(imageUri)
非同步載入圖片方法,在緩存可用且是 httpUri 時,從緩存裡加載圖片資源,根據 token 載入;然後載入對應資源後,進入 Loaded 狀態;如果遇到一場,則進入 Failed 狀態;如果是本地資源,或 http 資源不允許緩存,則直接實例化,不做緩存操作;
private async Task LoadImageAsync(Uri imageUri) { if (_uri != null) { if (IsCacheEnabled && _isHttpSource) { try { var propValues = new List<KeyValuePair<string, object>>(); // Add DecodePixelHeight, DecodePixelWidth, DecodePixelType into propValues ... var img = await ImageCache.Instance.GetFromCacheAsync(imageUri, true, _tokenSource.Token, propValues); lock (LockObj) { if (_uri == imageUri) { AttachSource(img); ImageExOpened?.Invoke(this, new ImageExOpenedEventArgs()); VisualStateManager.GoToState(this, LoadedState, true); } } } catch (OperationCanceledException) { // nothing to do as cancellation has been requested. } catch (Exception e) { lock (LockObj) { if (_uri == imageUri) { ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(e)); VisualStateManager.GoToState(this, FailedState, true); } } } } else { AttachSource(new BitmapImage(_uri)); } } }
5. ImageExBase.cs
類中定義了 ImageEx Template 定義欄位對應的變數,包括 Image,Progress,CommonStates,Loading 等等;
此外在 AttachImageOpened,RemoveImageOpened 時設置附加對應的 handler;在 AttachImageFailed,RemoveImageFailed 時設置解除對應的 handler;分別觸發對應的事件,並把 VisualState 設置為對應的狀態;
6. RoundImageEx.xaml
我們看到,PlaceHolder 和 Image 都是用矩形來實現的,定義了 RadiusX 和 RadiusY 來實現圓角,Fill 使用 ImageBrush 來載入圖像;實現圓角或圓形的圖片控制項;
另外需要註意的是,從 16299 開始,CornerRadius 屬性也能適用於 ImageEx 控制項,實現圓角矩形圖片;如果系統低於 16299,不會引發異常,但是設置會不生效;
<Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="controls:RoundImageEx"> <Grid Width="{TemplateBinding Width}" Height="{TemplateBinding Height}"> <Rectangle x:Name="PlaceholderRectangle" RadiusX="{TemplateBinding CornerRadius}" RadiusY="{TemplateBinding CornerRadius}"...> <Rectangle.Fill> <ImageBrush x:Name="PlaceholderImage" ImageSource="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PlaceholderSource}" Stretch="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PlaceholderStretch}" /> </Rectangle.Fill> </Rectangle> <Rectangle x:Name="ImageRectangle" RadiusX="{TemplateBinding CornerRadius}" RadiusY="{TemplateBinding CornerRadius}"...> <Rectangle.Fill> <ImageBrush x:Name="Image" Stretch="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Stretch}" /> </Rectangle.Fill> </Rectangle> <ProgressRing Name="Progress" ... /> <VisualStateManager.VisualStateGroups> ... </VisualStateManager.VisualStateGroups> </Grid> </ControlTemplate> </Setter.Value> </Setter>
調用示例
我們創建了兩個控制項,ImageEx 和 RoundImageEx,如下圖一是載入中的過渡狀態,圖二是正常顯示的狀態;如果 Source 設置有誤,則會出現圖三隻顯示 PlaceHolder 的情況,實際應用中,在圖片載入失敗時我們應該有對應的顯示方法;
<controls:ImageEx Name="ImageExControl" IsCacheEnabled="True" Width="200" Height="200" PlaceholderSource="/assets/LockScreenLogo.scale-200.png" Source="/assets/01.jpg"/> <controls:RoundImageEx Name="RoundImageExControl" IsCacheEnabled="True" Width="200" Height="200" Stretch="UniformToFill" PlaceholderSource="/assets/01.jpg" Source="/assets/02.jpg" CornerRadius="999"/>
總結
到這裡我們就把 UWP Community Toolkit 中的 ImageEx 控制項的源代碼實現過程和簡單的調用示例講解完成了,希望能對大家更好的理解和使用這個控制項有所幫助。歡迎大家多多交流,謝謝!
最後,再跟大家安利一下 UWPCommunityToolkit 的官方微博:https://weibo.com/u/6506046490, 大家可以通過微博關註最新動態。
衷心感謝 UWPCommunityToolkit 的作者們傑出的工作,Thank you so much, UWPCommunityToolkit authors!!!