New UWP Community Toolkit - RotatorTile

来源:https://www.cnblogs.com/shaomeng/archive/2018/04/07/RotatorTile.html
-Advertisement-
Play Games

UWP Community Toolkit 中有一個為圖片或磁貼提供輪播效果的控制項 - RotatorTile,本篇我們結合代碼詳細講解 RotatorTile 的實現。 RotatorTile 提供了一種類似 Windows 10 磁貼的輪播方式,可以輪流播放開發者設置的內容序列,支持設置輪... ...


概述

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)
    {
        if (e.OldItems?.Count > 0)
        {
            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) - 1;
            }
            else if (e.NewStartingIndex == _currentIndex + 1)
            {
                // Upcoming item was changed, so update the datacontext
                _nextElement.DataContext = GetNext();
            }
        }
    }
    else if (e.Action == NotifyCollectionChangedAction.Add)
    {
        int endIndex = e.NewStartingIndex + e.NewItems.Count;
        if (e.NewItems?.Count > 0)
        {
            if (_currentIndex < 0)
            {
                // 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;
            }
            else if (_currentIndex + 1 == 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;
        if (_currentIndex >= e.OldStartingIndex && _currentIndex < endIndex + 1)
        {
            // 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 = new Duration(TimeSpan.FromMilliseconds(500)),
            From = 0
        };
        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
        await System.Threading.Tasks.Task.Delay(50);

        // 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 = 0;
    }
    else if (Direction == RotateDirection.Right)
    {
        _translate.X = -1 * ActualWidth;
    }
    else if (Direction == RotateDirection.Down)
    {
        _translate.Y = -1 * 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!!!

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 聊到二進位以及位運算就不得不說說,原碼,反碼,補碼了,網上對於原碼反碼補碼的解釋過於複雜,我這裡把教程里的一些總結搬出來讓大家參考一下:對於有符號的而言; 1.二進位最高位是符號位,0表示正數,1表示負數; 2.正數的原碼反碼補碼都一樣; 3.負數的反碼等於它的原碼符號位不變,其他位取反,1變0,0 ...
  • 參考資料:網易雲網課李興華:http://study.163.com/course/courseMain.htm?courseId=1455026 一、字元串一旦定義不可改變 一開始也許並不太好理解,先觀察以下代碼 結果: 以上代碼似乎主觀上覺得String內容不是改變了嗎,但並不是這樣的,下麵通過 ...
  • 回顧TCP粘包/拆包問題解決方案 上文詳細說了TCP粘包/拆包問題產生的原因及解決方式,並以LineBasedFrameDecoder為例演示了粘包/拆包問題的實際解決方案,本文再介紹兩種粘包/拆包問題的解決方案:分隔符和定長解碼器。在開始本文之前,先回顧一下解決粘包/拆包問題的幾個方式: 消息長度 ...
  • 當需要定時修改資料庫時,一般我們都選擇起一個定時進程去改庫。如果將這種定時任務寫入業務中,寫成一個介面呢,定時進程顯得有些不太合適?如果需要定時修改100次資料庫,常規做法會啟動100個進程,雖然這種進程非常輕量級,但還是會感覺不爽。實際上我們可以使用threading.Timer創建相應的線程來執 ...
  • 概述 UWP Community Toolkit 中有一個開發者工具集 DeveloperTools,可以幫助開發者在開發過程中進行 UI 和功能的調試,本篇我們結合代碼詳細講解 DeveloperTools 的實現。 DeveloperTools 中目前包括了兩個工具: AlignmentGrid ...
  • 通常說,a++是先取值後運算,++a是先運算後取值。 ++ 是一個“自增運算符”,自增運算符有兩種形式:首碼自增(++a)和尾碼自增(a++)。 運算符和操作數合起來就是一個表達式(a++、++a都是表達式,a就是操作數)。註意:每一個表達式本身都有值(和其類型),有的表達式還有“副作用”。比如自增 ...
  • 沒有找到可以直接禁止的屬性,但是找到兩個間接禁止的方式。 方式一: //onClickRow: function (rowIndex, rowData) { // $(this).datagrid('unselectRow', rowIndex); //}, 方式二: onClickRow: fun ...
  • 1、兩種種註釋符: //單行註釋; /* */多行註釋 2、幾種常見的變數: int (整數) double(浮點數)decimal(常用於金錢這個精密計算) string(字元串 )char(單個字元) 變數的命名:以字母或者下劃線開始,但命名要有意義,便於理解;Pascal規範用於類或方法的命名 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...