概述

UWP Community Toolkit  中有一个为图片或磁贴提供轮播效果的控件 - RotatorTile,本篇我们结合代码详细讲解  RotatorTile 的实现。

RotatorTile 提供了一种类似 Windows 10 磁贴的轮播方式,可以轮流播放开发者设置的内容序列,支持设置轮播方向,包括上下左右四个方向;接下来看看官方示例的截图:

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

Doc: https://docs.microsoft.com/zh-cn/windows/uwpcommunitytoolkit/controls/rotatortile

Namespace: Microsoft.Toolkit.Uwp.UI.Controls; Nuget: Microsoft.Toolkit.Uwp.UI.Controls;

开发过程

代码分析

RotatorTile 控件包括 RotatorTile.cs 和 RotatorTile.xaml,分别是控件的定义处理类和样式文件,分别来看一下:

1. RotatorTile.xaml

RotatorTile.xaml 是 RotatorTile 控件的样式文件,我们看 Template 部分,轮播效果的实现主要是靠 StackPanel 中排列的两个 Content,分别代表 current 和 next 内容,根据设置的轮播方向,设置 StackPanel 的排列方向;轮播时,使用 TranslateTransform 来实现轮播的元素切换动画;

<Style TargetType="controls:RotatorTile">
    <Setter Property="IsTabStop" Value="False"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="controls:RotatorTile">
                <Grid Background="{TemplateBinding Background}">
                    <Canvas x:Name="Scroller"
                            DataContext="{x:Null}">
                        <StackPanel x:Name="Stack">
                            <StackPanel.RenderTransform>
                                <TranslateTransform x:Name="Translate" Y="0" />
                            </StackPanel.RenderTransform>
                            <ContentPresenter x:Name="Current"
                                                Content="{Binding}"
                                                ContentTemplate="{TemplateBinding ItemTemplate}"
                                                DataContext="{x:Null}" />
                            <ContentPresenter x:Name="Next"
                                                Content="{Binding}"
                                                ContentTemplate="{TemplateBinding ItemTemplate}"
                                                DataContext="{x:Null}" />
                        </StackPanel>
                    </Canvas>
                    <Border BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="RotationDelay" Value="0:0:5" />
    <Setter Property="ExtraRandomDuration" Value="0:0:5" />
</Style>

2. RotatorTile.cs

RotatorTile 控件的定义和主要处理类,来看看类的结构:

 

首先看一下 OnApplyTemplate() 方法,他会获取控件的模板,根据当前轮播方向处理 StackPanel 容器,初始化并开始轮播动画;这也是 RotatorTile 控件的主要流程:使用 Timer,根据设置的间隔时间和轮播的方向,在 Tick 事件中不断按照某个方向去做平移动画,动画中不断更新当前显示元素为下一个元素,并不断相应中途的显示元素集合变化事件;

同时控件会响应 RotatorTile_SizeChanged 事件,根据新的尺寸去修改显示元素和容器的尺寸;响应 RotatorTile_Loaded 和 RotatorTile_Unloaded,处理 Timer 的开始和结束处理;

RotatorTile.cs 继承自 Control 类,先看一下它定义了哪些依赖属性:

  • ExtraRandomDuration - 一个随机时间区间的上限,轮播时一个 0~ExtraRandomDuration 的随机值会被作为轮播间隔使用;
  • RotationDelay - 轮播的间隔,时间修改时会触发 OnRotationDelayInSecondsPropertyChanged 事件;
  • ItemsSource - 轮播内容集合的数据源,变化时触发 OnItemsSourcePropertyChanged 事件;
  • ItemTemplate - 轮播内容的内容模板;
  • RotateDirection - 轮播的方向,分别上 下 左 右四个方向;
  • CurrentItem - 轮播时当前的 Item,变化时触发 OnCurrentItemPropertyChanged 事件;

首先来看 OnItemsSourcePropertyChanged 事件,它的主要逻辑在方法 Incc_CollectionChanged(s, e) 中:

  • 首先 action 处理会被分为 5 种:Remove,Add,Replace,Move 和 Reset;
  • 对 Remove action,根据删除后的开始索引与当前索引,结束索引之间的关系,去更新下一个元素,或设置当前索引,或更新上下文;
  • 对 Add action,根据添加后的开始索引与当前索引的关系,以及当前索引与 0 的关系,去开始轮播,或设置当前索引,或更新上下文;
  • 对 Replace action,如果当前索引介于新的开始索引和结束索引之间,则更新下一个元素;
  • 对 Move action,如果当前索引介于新的开始索引和结束索引之间,获取它的新索引;
  • 对 Reset action,重新开始轮播;
private void Incc_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.Action == NotifyCollectionChangedAction.Remove)
    {
        )
        {
            int endIndex = e.OldStartingIndex + e.OldItems.Count;
            if (_currentIndex >= e.NewStartingIndex && _currentIndex < endIndex)
            {
                // Current item was removed. Replace with the next one
                UpdateNextItem();
            }
            else if (_currentIndex > endIndex)
            {
                // Items were removed before the current item. Just update the changed index
                _currentIndex -= (endIndex - e.NewStartingIndex) - ;
            }
            )
            {
                // Upcoming item was changed, so update the datacontext
                _nextElement.DataContext = GetNext();
            }
        }
    }
    else if (e.Action == NotifyCollectionChangedAction.Add)
    {
        int endIndex = e.NewStartingIndex + e.NewItems.Count;
        )
        {
            )
            {
                // First item loaded. Start the rotator
                Start();
            }
            else if (_currentIndex >= e.NewStartingIndex)
            {
                // Items were inserted before the current item. Update the index
                _currentIndex += e.NewItems.Count;
            }
             == e.NewStartingIndex)
            {
                // Upcoming item was changed, so update the datacontext
                _nextElement.DataContext = GetNext();
            }
        }
    }
    else if (e.Action == NotifyCollectionChangedAction.Replace)
    {
        int endIndex = e.OldStartingIndex + e.OldItems.Count;
        )
        {
            // Current item was removed. Replace with the next one
            UpdateNextItem();
        }
    }
    else if (e.Action == NotifyCollectionChangedAction.Move)
    {
        int endIndex = e.OldStartingIndex + e.OldItems.Count;
        if (_currentIndex >= e.OldStartingIndex && _currentIndex < endIndex)
        {
            // The current item was moved. Get its new location
            _currentIndex = GetIndexOf(CurrentItem);
        }
    }
    else if (e.Action == NotifyCollectionChangedAction.Reset)
    {
        // Significant change or clear. Restart.
        Start();
    }
}

接着来看 OnCurrentItemPropertyChanged(d, e) 方法的处理,主要处理逻辑在 RotateToNextItem() 中:

  • 首先判断是否有两个或者更多的元素,如果没有则退出处理;
  • 定义 Storyboard,动画时间是 500ms,方向和轮播的目标属性根据当前轮播的方向去计算;
  • 在动画结束时,开始准备下一个显示的元素;
private void RotateToNextItem()
{
    // Check if there's more than one item. if not, don't start animation
    bool hasTwoOrMoreItems = false;
    ...

    if (!hasTwoOrMoreItems) { return;}

    var sb = new Storyboard();
    if (_translate != null)
    {
        var anim = new DoubleAnimation
        {
            Duration = )),
            From =
        };
        if (Direction == RotateDirection.Up)
        {
            anim.To = -ActualHeight;
        }
        else if (Direction == RotateDirection.Down) {...}
        else if (Direction == RotateDirection.Right) {...}
        else if (Direction == RotateDirection.Left) {...}

        anim.FillBehavior = FillBehavior.HoldEnd;
        anim.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut };
        Storyboard.SetTarget(anim, _translate);
        if (Direction == RotateDirection.Up || Direction == RotateDirection.Down)
        {
            Storyboard.SetTargetProperty(anim, "Y");
        }
        else
        {
            Storyboard.SetTargetProperty(anim, "X");
        }

        sb.Children.Add(anim);
    }

    sb.Completed += async (a, b) =>
    {
        if (_currentElement != null)
        {
            _currentElement.DataContext = _nextElement.DataContext;
        }

        // make sure DataContext on _currentElement has had a chance to update the binding
        // avoids flicker on rotation
        );

        // Reset back and swap images, getting the next image ready
        sb.Stop();
        if (_translate != null)
        {
            UpdateTranslateXY();
        }

        if (_nextElement != null)
        {
            _nextElement.DataContext = GetNext(); // Preload the next tile
        }
    };
    sb.Begin();
}

我们看到有两个方法中都调用了 UpdateTranslateXY() 方法,来更新平移时的 X 或 Y:

对于 Left 和 Up,只需要充值 X 或 Y 为 0;对于 Right 和 Down,需要把对应的 X 或 Y 设置为 -1 × 对应的高度或宽度,让动画从负一倍尺寸平移到 0;

private void UpdateTranslateXY()
{
    if (Direction == RotateDirection.Left || Direction == RotateDirection.Up)
    {
        _translate.X = _translate.Y = ;
    }
    else if (Direction == RotateDirection.Right)
    {
        _translate.X = - * ActualWidth;
    }
    else if (Direction == RotateDirection.Down)
    {
        _translate.Y = - * ActualHeight;
    }
}

调用示例

我们定义了一个 RotatorTile,动画间隔 1s,方向向上,来看一下 gif 图显示的运行结果:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <controls:RotatorTile x:Name="Tile1"
                                Height="200"
                                Background="LightGray"
                                RotationDelay="0:0:1"
                                ExtraRandomDuration="0:0:1"
                                Direction="Up"
                                ItemTemplate="{StaticResource PhotoTemplate}" />
</Grid>

总结

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

最后,再跟大家安利一下 UWPCommunityToolkit 的官方微博:https://weibo.com/u/6506046490大家可以通过微博关注最新动态。

衷心感谢 UWPCommunityToolkit 的作者们杰出的工作,Thank you so much, UWPCommunityToolkit authors!!!

New UWP Community Toolkit - RotatorTile的更多相关文章

  1. New UWP Community Toolkit

    概述 UWP Community Toolkit 是一个 UWP App 自定义控件.应用服务和帮助方法的集合,能够很大程度的简化和指引开发者的开发工作,相信广大 UWPer 并不陌生. 下面是截取自 ...

  2. New UWP Community Toolkit - XAML Brushes

    概述 上一篇 New UWP Community Toolkit 文章中,我们对 V2.2.0 版本的重要更新做了简单回顾.接下来会针对每个重要更新,结合 SDK 源代码和调用代码详细讲解. 本篇我们 ...

  3. New UWP Community Toolkit - Markdown

    概述 前面 New UWP Community Toolkit 文章中,我们对 V2.2.0 版本的重要更新做了简单回顾,其中简单介绍了 MarkdownTextBlock 和 MarkdownDoc ...

  4. New UWP Community Toolkit - Staggered panel

    概述 前面 New UWP Community Toolkit 文章中,我们对 2.2.0 版本的重要更新做了简单回顾,其中简单介绍了 Staggered panel,本篇我们结合代码详细讲解  St ...

  5. New UWP Community Toolkit - Carousel

    概述 New UWP Community Toolkit  V2.2.0 的版本发布日志中提到了 Carousel 的调整,本篇我们结合代码详细讲解  Carousel 的实现. Carousel 是 ...

  6. New UWP Community Toolkit - RadialProgressBar

    概述 UWP Community Toolkit  中有一个圆形的进度条控件 - RadialProgressBar,本篇我们结合代码详细讲解  RadialProgressBar 的实现. Radi ...

  7. New UWP Community Toolkit - RadialGauge

    概述 New UWP Community Toolkit  V2.2.0 的版本发布日志中提到了 RadialGauge 的调整,本篇我们结合代码详细讲解  RadialGauge 的实现. Radi ...

  8. New UWP Community Toolkit - RangeSelector

    概述 前面 New UWP Community Toolkit 文章中,我们对 V2.2.0 版本的重要更新做了简单回顾,其中简单介绍了 RangeSelector,本篇我们结合代码详细讲解一下 Ra ...

  9. New UWP Community Toolkit - ImageEx

    概述 UWP Community Toolkit  中有一个图片的扩展控件 - ImageEx,本篇我们结合代码详细讲解  ImageEx 的实现. ImageEx 是一个图片的扩展控件,包括 Ima ...

随机推荐

  1. 360加固apk并自动签名

    我们知道Android加混淆之后,代码的安全性得到了提高,即使你hook,反编译得到的也是乱码的,对于阅读性造成了影响,为了增强代码的破解难度,我们通常退对apk进行加固,常见的有腾讯,360,爱加密 ...

  2. iOS学习——UIPickerView的实现年月选择器

    最近项目上需要用到一个选择器,选择器中的内容只有年和月,而在iOS系统自带的日期选择器UIDatePicker中却只有四个选项如下,分别是时间(时分秒).日期(年月日).日期+时间(年月日时分)以及倒 ...

  3. Hadoop之Secondary NameNode

    NameNode存储文件系统的变化作为log追加在本地的一个文件里:这个文件是edits.当一个NameNode启动时,它从一个映像文件:FsImage,读取HDFS的状态,使用来自edits日志文件 ...

  4. Problem : 1008 ( Elevator )

    好操蛋啊,电梯竟然能原地不动,你大爷的,这逻辑,太弱智了.... Problem : 1008 ( Elevator )     Judge Status : Accepted RunId : 103 ...

  5. win8快捷键

    win+Q/S搜索所有位置 win+W搜索设置 win+E文件资源管理器 win+R运行 win+T选中第一个应用程序(不确定) win+U轻松使用设置中心 win+I设置 win+P投影 win+D ...

  6. 直接删除undo及temp表空间文件后的数据库恢复一例

    前几天,某用户研发找到我,说他们的研发库坏了,问我能恢复不?我问他们做了什么操作,一个小男孩儿说,看到空间满了,清除了点儿数据,我说是不是连数据库的文件也清除了,他说没有,他清除的是ORACLE_HO ...

  7. MYSQL数据库学习十七 日志管理

    17.1 MySQL软件所支持的日志 MySQL所支持的日志文件里,除了二进制日志文件外,其他日志文件都是文本文件.默认情况下,MySQL只会启动错误日志文件,其他日志文件则需要手动启动. 二进制日志 ...

  8. UITableViewStyleGrouped模式下多余间距

    第一个section上边多余间距处理 // 隐藏UITableViewStyleGrouped上边多余的间隔 _tableView.tableHeaderView = [[UIView alloc] ...

  9. 【Linux】 Linux权限管理与特殊权限

    Linux权限管理 权限管理这方面,非常清楚地记得刚开始实习那会儿是仔细研究过的,不知道为什么没有笔记留痕..除了一些基本的知识点早就忘光了,无奈只好从头开始学习一遍.. ■ 基本权限知识 这部分实在 ...

  10. 【jQuery】 jQuery基础

    jQuery 之前在JS的文章中提到过,JS虽然功能全面但是仍然比较接近底层,代码写起来很麻烦,而以jQuery为代表的JS库包装了很多功能,可以让代码更加简单.接下来就来简单地记录一下我学习和所知道 ...