【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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM