• 【华为云技术分享】New UWP Community Toolkit


    概述

    UWP Community Toolkit 中有一个图片的扩展控件 - ImageEx,本篇我们结合代码详细讲解 ImageEx 的实现。

    ImageEx 是一个图片的扩展控件,包括 ImageEx 和 RoundImageEx,它可以在异步加载图片源时显示加载状态,也可以在加载前使用占位图片,在下载完成后可以在应用内缓存,避免了重复加载的过程。我们来看一下官方的介绍和官网示例中的展示:

    Source: https://github.com/Microsoft/UWPCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/ImageEx

    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 状态时,也会切换不同层的显示来完成状态切换;

     1 <Style TargetType="controls:ImageEx">
     2     <Setter Property="Background" Value="Transparent" />
     3     <Setter Property="Foreground" Value="{ThemeResource ApplicationForegroundThemeBrush}" />
     4     <Setter Property="Template">
     5         <Setter.Value>
     6             <ControlTemplate TargetType="controls:ImageEx">
     7                 <Grid Background="{TemplateBinding Background}" CornerRadius="{TemplateBinding CornerRadius}" 
     8                         BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
     9                     <Image Name="PlaceholderImage" Opacity="1.0" .../>
    10                     <Image Name="Image" NineGrid="{TemplateBinding NineGrid}" Opacity="0.0" .../>
    11                     <ProgressRing Name="Progress" Margin="16" HorizontalAlignment="Center" VerticalAlignment="Center"
    12                                     Background="Transparent" Foreground="{TemplateBinding Foreground}" IsActive="False" Visibility="Collapsed" />
    13                     <VisualStateManager.VisualStateGroups>
    14                         <VisualStateGroup x:Name="CommonStates">
    15                             <VisualState x:Name="Failed">
    16                                 <Storyboard>
    17                                     <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Image"
    18                                                                 Storyboard.TargetProperty="Opacity">
    19                                         <DiscreteObjectKeyFrame KeyTime="0"
    20                                                             Value="0" />
    21                                     </ObjectAnimationUsingKeyFrames>
    22                                     <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlaceholderImage"
    23                                                                 Storyboard.TargetProperty="Opacity">
    24                                         <DiscreteObjectKeyFrame KeyTime="0"
    25                                                             Value="1" />
    26                                     </ObjectAnimationUsingKeyFrames>
    27                                 </Storyboard>
    28                             </VisualState>
    29                             <VisualState x:Name="Loading" .../>
    30                             <VisualState x:Name="Loaded" .../>
    31                             <VisualState x:Name="Unloaded" .../>
    32                         </VisualStateGroup>
    33                     </VisualStateManager.VisualStateGroups>
    34                 </Grid>
    35             </ControlTemplate>
    36         </Setter.Value>
    37     </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:/// 资源格式加载给控件;

     1 private async void SetSource(object source)
     2 {    if (!IsInitialized) { return;}    this._tokenSource?.Cancel();    this._tokenSource = new CancellationTokenSource();
     3 
     4     AttachSource(null);    if (source == null)
     5     {
     6         VisualStateManager.GoToState(this, UnloadedState, true);        return;
     7     }
     8 
     9     VisualStateManager.GoToState(this, LoadingState, true);    var imageSource = source as ImageSource;    if (imageSource != null)
    10     {
    11         AttachSource(imageSource);
    12 
    13         ImageExOpened?.Invoke(this, new ImageExOpenedEventArgs());
    14         VisualStateManager.GoToState(this, LoadedState, true);        return;
    15     }
    16 
    17     _uri = source as Uri;    if (_uri == null)
    18     {        var url = source as string ?? source.ToString();        if (!Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out _uri))
    19         {
    20             VisualStateManager.GoToState(this, FailedState, true);            return;
    21         }
    22     }
    23 
    24     _isHttpSource = IsHttpUri(_uri);    if (!_isHttpSource && !_uri.IsAbsoluteUri)
    25     {
    26         _uri = new Uri("ms-appx:///" + _uri.OriginalString.TrimStart('/'));
    27     }    await LoadImageAsync(_uri);
    28 }

    ② LoadImageAsync(imageUri)

    异步加载图片方法,在缓存可用且是 httpUri 时,从缓存里加载图片资源,根据 token 加载;然后加载对应资源后,进入 Loaded 状态;如果遇到一场,则进入 Failed 状态;如果是本地资源,或 http 资源不允许缓存,则直接实例化,不做缓存操作;

     1 private async Task LoadImageAsync(Uri imageUri)
     2 {    if (_uri != null)
     3     {        if (IsCacheEnabled && _isHttpSource)
     4         {            try
     5             {                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)
     6                 {                    if (_uri == imageUri)
     7                     {
     8                         AttachSource(img);
     9                         ImageExOpened?.Invoke(this, new ImageExOpenedEventArgs());
    10                         VisualStateManager.GoToState(this, LoadedState, true);
    11                     }
    12                 }
    13             }            catch (OperationCanceledException)
    14             {                // nothing to do as cancellation has been requested.            }            catch (Exception e)
    15             {                lock (LockObj)
    16                 {                    if (_uri == imageUri)
    17                     {
    18                         ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(e));
    19                         VisualStateManager.GoToState(this, FailedState, true);
    20                     }
    21                 }
    22             }
    23         }        else
    24         {
    25             AttachSource(new BitmapImage(_uri));
    26         }
    27     }
    28 }

    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,不会引发异常,但是设置会不生效;

     1 <Setter Property="Template">
     2     <Setter.Value>
     3         <ControlTemplate TargetType="controls:RoundImageEx">
     4             <Grid Width="{TemplateBinding Width}"
     5                 Height="{TemplateBinding Height}">
     6                 <Rectangle x:Name="PlaceholderRectangle" RadiusX="{TemplateBinding CornerRadius}" RadiusY="{TemplateBinding CornerRadius}"...>
     7                     <Rectangle.Fill>
     8                         <ImageBrush x:Name="PlaceholderImage"
     9                                 ImageSource="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PlaceholderSource}"
    10                                 Stretch="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PlaceholderStretch}" />
    11                     </Rectangle.Fill>
    12                 </Rectangle>
    13                 <Rectangle x:Name="ImageRectangle" RadiusX="{TemplateBinding CornerRadius}" RadiusY="{TemplateBinding CornerRadius}"...>
    14                     <Rectangle.Fill>
    15                         <ImageBrush x:Name="Image"
    16                                 Stretch="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Stretch}" />
    17                     </Rectangle.Fill>
    18                 </Rectangle>
    19                 <ProgressRing Name="Progress" ... />
    20 
    21                 <VisualStateManager.VisualStateGroups>
    22                     ...                </VisualStateManager.VisualStateGroups>
    23             </Grid>
    24         </ControlTemplate>
    25     </Setter.Value></Setter>

    调用示例

    我们创建了两个控件,ImageEx 和 RoundImageEx,如下图一是加载中的过渡状态,图二是正常显示的状态;如果 Source 设置有误,则会出现图三只显示 PlaceHolder 的情况,实际应用中,在图片加载失败时我们应该有对应的显示方法;

    1 <controls:ImageEx Name="ImageExControl"
    2     IsCacheEnabled="True" Width="200" Height="200"
    3     PlaceholderSource="/assets/LockScreenLogo.scale-200.png"
    4     Source="/assets/01.jpg"/><controls:RoundImageEx Name="RoundImageExControl"
    5     IsCacheEnabled="True" Width="200" Height="200" Stretch="UniformToFill"
    6     PlaceholderSource="/assets/01.jpg"
    7     Source="/assets/02.jpg"
    8     CornerRadius="999"/>

    总结

    到这里我们就把 UWP Community Toolkit 中的 ImageEx 控件的源代码实现过程和简单的调用示例讲解完成了,希望能对大家更好的理解和使用这个控件有所帮助。欢迎大家多多交流,谢谢!

  • 相关阅读:
    Asp.net Core依赖注入(Autofac替换IOC容器)
    .NET Core WEB API接口参数模型绑定
    .net core docker容器编排部署(linux)
    asp .net core发布订阅kafka
    asp.net Core依赖注入(自带的IOC容器)
    VS2017 GIT推送错误:Authentication failed解决办法
    《ASP.NET Core 开发实战》
    《Entity Framework 实用精要》
    《C# 敏捷开发实践》
    《ASP.NET 框架应用程序实战》
  • 原文地址:https://www.cnblogs.com/huaweicloud/p/12384516.html
Copyright © 2020-2023  润新知