1. 程式人生 > >【Win10】實現控制元件倒影效果

【Win10】實現控制元件倒影效果

原文: 【Win10】實現控制元件倒影效果

先引入個小廣告:

最近買了臺小米盒子折騰下,發覺 UI 還是挺漂亮的,特別是主頁那個倒影效果。

b94f65ec54e736d11b0f947299504fc2d5626968

(圖隨便找的,就是上面圖片底部的那個倒影效果。)

 

好了,廣告結束,迴歸正題,這個倒影效果我個人覺得是挺不錯的,那麼有沒有辦法在 Win10 中實現呢?

稍微分析了一下,大概層次是這樣的:

分析01

簡單點來說,就是倒影顯示跟控制元件顯示一樣,然後往下翻轉,再平移一下就好了。最後再對倒影加個漸變透明就 perfect 了。

 

翻轉、平移都很容易,使用 RenderTransform 就可以了。麻煩就麻煩在如何讓倒影的顯示跟控制元件的顯示相同。

在 WinRT 裡,是沒有 VisualBrush 這種東西的,因此我們得另尋他徑。俗語說:上帝關閉一扇門的同時也為你開啟一扇窗。微軟雖然去掉 VisualBrush,但是給了我們 RenderTargetBitmap 這種獲取絕大部分控制元件當前樣貌的神器。(MediaElement 獲取不了,WebView 則需另外使用 WebViewBrush 來獲取,這裡我們忽略掉這兩個不是常見需求的傢伙。)

 

那麼我們就可以將倒影設定為 Image 控制元件,然後賦值上 RenderTargetBitmap 就可以了。但問題又來了,我們應該什麼時候去抓一次控制元件的外貌?查閱 MSDN 得知,

LayoutUpdated 事件可以幫到我們。

還等什麼,立馬開始編寫我們的程式碼。

 

建立我們的專案,新建一個名字叫做 ReflectionPanel 的模板化控制元件。

然後定義我們的控制元件模板如下:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local
="using:ReflectionPanelDemo" xmlns:controls="using:ReflectionPanelDemo.Controls"> <Style TargetType="controls:ReflectionPanel"> <Setter Property="HorizontalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="controls:ReflectionPanel"> <Grid x:Name="RootLayout" Background="{TemplateBinding Background}"> <!--#region 倒影--> <!--以控制元件底部中心作為變換點--> <Image x:Name="ReflectionImage" Stretch="None" RenderTransformOrigin="0.5,1"> <Image.RenderTransform> <TransformGroup> <!--以控制元件底部反轉控制元件--> <ScaleTransform ScaleY="-1" /> <!--倒影與實際內容的間距--> <TranslateTransform x:Name="SpacingTransform" Y="0" /> </TransformGroup> </Image.RenderTransform> </Image> <!--#endregion--> <!--#region 實際內容--> <ContentControl x:Name="ContentBorder" Content="{TemplateBinding Content}" /> <!--#endregion--> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>

這樣對應上之前的分析了。

 

接下來編寫 cs 程式碼。

protected override void OnApplyTemplate()
{
    FrameworkElement rootLayout = (FrameworkElement)this.GetTemplateChild("RootLayout");

    // 實際內容容器。
    this._contentBorder = (ContentControl)this.GetTemplateChild("ContentBorder");

    // 倒影圖片。
    this._reflectionImage = (Image)this.GetTemplateChild("ReflectionImage");

    // 倒影位移。
    this._spacingTransform = (TranslateTransform)this.GetTemplateChild("SpacingTransform");
    this._spacingTransform.Y = this.ReflectionSpacing;

    if (DesignMode.DesignModeEnabled == false)
    {
        rootLayout.LayoutUpdated += this.RootLayoutChanged;
    }
}

private async void RootLayoutChanged(object sender, object e)
{
    try
    {
        // 呈現控制元件到影象源。
        RenderTargetBitmap contentRender = new RenderTargetBitmap();
        await contentRender.RenderAsync(this._contentBorder);

        // 設定倒影圖片。
        this._reflectionImage.Source = contentRender;
    }
    catch
    {
    }
}

這裡是最關鍵的程式碼。詳細可以看文章末尾提供的 demo。

 

接下來嘗試一下吧。

QQ截圖20150925220914

感覺還不錯的說。

 

最後,我們來做漸變的半透明效果。

在 WinRT 裡,由於沒了 OpacityMask 屬性,因此我們還是從圖片入手吧。

RenderTargetBitmap 有一個叫 GetPixelsAsync 的方法,可以獲取到圖片的資料,格式是以 BGRA8 的格式,這裡聯動一下老周的 blog 好了(http://www.cnblogs.com/tcjiaan/p/4231886.html)。

簡單點說,就是每 4 個 byte 代表一個畫素。我們再簡單分析下需求,那麼可以得出,圖片最頂部是最透明的,最底部是最不透明的。

經過簡單的數學計算,我們可以寫出以下程式碼:

// 獲取影象資料。
byte[] bgra8 = (await contentRender.GetPixelsAsync()).ToArray();

// 獲取影象高度和寬度。
int width = contentRender.PixelWidth;
int height = contentRender.PixelHeight;

for (int i = 0; i < bgra8.Length; i += 4)
{
    // 獲取該畫素原來的 A 通道。
    byte a = bgra8[i + 3];

    // 計算該畫素的 Y 軸座標。
    int y = (i / 4) / width;

    // 計算新的 A 通道值。
    bgra8[i + 3] = (byte)(a * y / height);
}

最後我們將修改後的 data 弄到 Image 控制元件上去就 ok 了。這裡我們使用 WriteableBitmap。

WriteableBitmap outputBitmap = new WriteableBitmap(width, height);
bgra8.CopyTo(outputBitmap.PixelBuffer);

// 設定倒影圖片。
this._reflectionImage.Source = outputBitmap;

 

大功告成,看一下 Demo 的效果。

QQ截圖20150925221952

最後附帶完整 Demo 下載:ReflectionPanelDemo.zip