UWP组合 - 将不透明蒙版应用于ListView的前30px

时间:2016-07-13 20:24:22

标签: c# uwp alpha composition win2d

如何将效果应用于ListView,其中前30px从完全透明到完全不透明?这个想法是,当你向下滚动时,顶部的项目逐渐消失。

我正在构建一个UWP应用程序,其中设计要求ListView的前30px从不透明度0开始并转换为不透明度1.从概念上讲,我想象的是一个不透明蒙版,它将应用于SpriteVisual的顶部但是我无法弄清楚如何实现这一目标。

我正在尝试使用Windows 10,Composition和Win2D的周年纪念版。

编辑:一张图片可能会刷1000字:

Example of the fade

如果查看此图片,左下角和右下角有两个内容元素。虽然背景看起来是黑色的,但它实际上是一个渐变。如果您检查两个元素的顶部,它们会变得更加透明,通过背景显示。这就是我想要达到的效果。

编辑2: 为了显示我正在寻找的效果的结果,这里有一个GIF,如果我使用重叠的位图显示效果: Scrolling Images

标题背景图片是: header background

较低的30px具有alpha渐变并显示在gridview上方,使得网格视图项目的明显效果逐渐淡出并在背景下滑动。

XAML布局如下:

<Page
x:Class="App14.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App14"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="150" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <Image Source="/Assets/background.png"
           Grid.Row="0"
           Grid.RowSpan="2"
           VerticalAlignment="Top"
           Stretch="None" />

    <GridView Grid.Row="1"
              Margin="96,-30,96,96">
        <GridView.Resources>
            <Style TargetType="Image">
                <Setter Property="Height" Value="400" />
                <Setter Property="Width" Value="300" />
                <Setter Property="Margin" Value="30" />
            </Style>
        </GridView.Resources>
        <Image Source="Assets/1.jpg" />
        <Image Source="Assets/2.jpg" />
        <Image Source="Assets/3.jpg" />
        <Image Source="Assets/4.jpg" />
        <Image Source="Assets/5.jpg" />
        <Image Source="Assets/6.jpg" />
        <Image Source="Assets/7.jpg" />
        <Image Source="Assets/8.jpg" />
        <Image Source="Assets/9.jpg" />
        <Image Source="Assets/10.jpg" />
        <Image Source="Assets/11.jpg" />
        <Image Source="Assets/12.jpg" />
    </GridView>

    <!-- Header above content -->

    <Image Grid.Row="0" Source="/Assets/header_background.png"
           Stretch="None" />

    <TextBlock x:Name="Title"
               Grid.Row="0"
               FontSize="48"
               Text="This Is A Title"
               HorizontalAlignment="Center"
               VerticalAlignment="Center"
               Foreground="White" />


</Grid>

3 个答案:

答案 0 :(得分:4)

因此,在Windows UI开发实验室问题列表的@sohcatt的帮助下,我已经构建了一个可行的解决方案。

enter image description here

这是XAML:

    <Grid x:Name="LayoutRoot">

    <Image x:Name="BackgroundImage"
           ImageOpened="ImageBrush_OnImageOpened"
           Source="../Assets/blue-star-background-wallpaper-3.jpg"
           Stretch="UniformToFill" />

    <GridView x:Name="Posters" Margin="200,48">
        <GridView.Resources>
            <Style TargetType="ListViewItem" />
            <Style TargetType="Image">
                <Setter Property="Stretch" Value="UniformToFill" />
                <Setter Property="Width" Value="300" />
                <Setter Property="Margin" Value="12" />
            </Style>
        </GridView.Resources>
        <GridViewItem>
            <Image Source="Assets/Posters/1.jpg" />
        </GridViewItem>
        <GridViewItem>
            <Image Source="Assets/Posters/2.jpg" />
        </GridViewItem>
        <GridViewItem>
            <Image Source="Assets/Posters/3.jpg" />
        </GridViewItem>
        <GridViewItem>
            <Image Source="Assets/Posters/4.jpg" />
        </GridViewItem>
        <GridViewItem>
            <Image Source="Assets/Posters/5.jpg" />
        </GridViewItem>
        <GridViewItem>
            <Image Source="Assets/Posters/6.jpg" />
        </GridViewItem>
        <GridViewItem>
            <Image Source="Assets/Posters/7.jpg" />
        </GridViewItem>
        <GridViewItem>
            <Image Source="Assets/Posters/8.jpg" />
        </GridViewItem>
        <GridViewItem>
            <Image Source="Assets/Posters/9.jpg" />
        </GridViewItem>
        <GridViewItem>
            <Image Source="Assets/Posters/10.jpg" />
        </GridViewItem>
        <GridViewItem>
            <Image Source="Assets/Posters/11.jpg" />
        </GridViewItem>
        <GridViewItem>
            <Image Source="Assets/Posters/12.jpg" />
        </GridViewItem>
    </GridView>
</Grid>

以下是代码:

        private bool _imageLoaded;

    // this is an initial way of handling resize 
    // I will investigate expressions
    private async void OnSizeChanged(object sender, SizeChangedEventArgs args)
    {
        if (!_imageLoaded)
        {
            return;
        }
        await RenderOverlayAsync();
    }

    private async void ImageBrush_OnImageOpened(object sender, RoutedEventArgs e)
    {
        _imageLoaded = true;
        await RenderOverlayAsync();
    }

    // this method must be called after the background image is opened, otherwise
    // the render target bitmap is empty
    private async Task RenderOverlayAsync()
    {
        // setup composition
        // (in line here for readability - will be member variables moving forwards)
        var compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
        var canvasDevice = new CanvasDevice();
        var compositionDevice = CanvasComposition.CreateCompositionGraphicsDevice(compositor, canvasDevice);

        // determine what region of the background we need to "cut out" for the overlay
        GeneralTransform gt = Posters.TransformToVisual(LayoutRoot);
        Point elementPosition = gt.TransformPoint(new Point(0, 0));

        // our overlay height is as wide as our poster control and is 30 px high
        var overlayHeight = 30;
        var areaToRender = new Rect(elementPosition.X, elementPosition.Y, Posters.ActualWidth, overlayHeight);

        // Capture the image from our background.
        //
        // Note: this is just the <Image/> element, not the Grid. If we took the <Grid/>, 
        // we would also have all of the child elements, such as the <GridView/> rendered as well -
        // which defeats the purpose!
        // 
        // Note 2: this method must be called after the background image is opened, otherwise
        // the render target bitmap is empty
        var bitmap = new RenderTargetBitmap();
        await bitmap.RenderAsync(BackgroundImage);
        var pixels = await bitmap.GetPixelsAsync();

        // we need the display DPI so we know how to handle the bitmap correctly when we render it
        var dpi = DisplayInformation.GetForCurrentView().LogicalDpi;

        // load the pixels from RenderTargetBitmap onto a CompositionDrawingSurface
        CompositionDrawingSurface uiElementBitmapSurface;
        using (
            // this is the entire background image
            // Note we are using the display DPI here.
            var canvasBitmap = CanvasBitmap.CreateFromBytes(
                canvasDevice, pixels.ToArray(),
                bitmap.PixelWidth,
                bitmap.PixelHeight,
                DirectXPixelFormat.B8G8R8A8UIntNormalized,
                dpi)
        )
        {
            // we create a surface we can draw on in memory.
            // note we are using the desired size of our overlay
            uiElementBitmapSurface =
                compositionDevice.CreateDrawingSurface(
                    new Size(areaToRender.Width, areaToRender.Height),
                    DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied);
            using (var session = CanvasComposition.CreateDrawingSession(uiElementBitmapSurface))
            {
                // here we draw just the part of the background image we wish to use to overlay
                session.DrawImage(canvasBitmap, 0, 0, areaToRender);
            }
        }

        // assign CompositionDrawingSurface to the CompositionSurfacebrush with which I want to paint the relevant SpriteVisual
        var backgroundImageBrush = _compositor.CreateSurfaceBrush(uiElementBitmapSurface);

        // load in our opacity mask image.
        // this is created in a graphic tool such as paint.net
        var opacityMaskSurface = await SurfaceLoader.LoadFromUri(new Uri("ms-appx:///Assets/OpacityMask.Png"));

        // create surfacebrush with ICompositionSurface that contains the background image to be masked
        backgroundImageBrush.Stretch = CompositionStretch.UniformToFill;

        // create surfacebrush with ICompositionSurface that contains the gradient opacity mask asset
        CompositionSurfaceBrush opacityBrush = _compositor.CreateSurfaceBrush(opacityMaskSurface);
        opacityBrush.Stretch = CompositionStretch.UniformToFill;

        // create maskbrush
        CompositionMaskBrush maskbrush = _compositor.CreateMaskBrush();
        maskbrush.Mask = opacityBrush; // surfacebrush with gradient opacity mask asset
        maskbrush.Source = backgroundImageBrush; // surfacebrush with background image that is to be masked

        // create spritevisual of the approproate size, offset, etc.
        SpriteVisual maskSprite = _compositor.CreateSpriteVisual();
        maskSprite.Size = new Vector2((float)Posters.ActualWidth, overlayHeight);
        maskSprite.Brush = maskbrush; // paint it with the maskbrush

        // set the sprite visual as a child of the XAML element it needs to be drawn on top of
        ElementCompositionPreview.SetElementChildVisual(Posters, maskSprite);
    }

答案 1 :(得分:0)

    <Grid Height="30"
          VerticalAlignment="Top">
        <Grid.Background>
            <LinearGradientBrush EndPoint="0.5,1"
                                 StartPoint="0.5,0">
                <GradientStop Color="White"
                              Offset="0" />
                <GradientStop Color="Transparent"
                              Offset="1" />
            </LinearGradientBrush>
        </Grid.Background>
    </Grid>

上面的代码创建了一个30px渐变,该渐变从总白度上升到总透明度。尝试将它放在列表视图上,看看它是否顺利。

答案 2 :(得分:0)

  

正如我之前试图解释的那样 - 背景不是一致的纯色 - 它是一个变化的图像。

我认为我们应该知道的一件事是,默认情况下ListView控件的背景是透明的。因此,如果ListView的父控件设置为背景图像,为了实现所需的布局,我们需要为ListView设置另一个背景,同时此背景无法填充整个ListView

所以,这是一个方法:

<Grid>
    <Grid.Background>
        <ImageBrush ImageSource="Assets/background.png" />
    </Grid.Background>
    <Grid Margin="0,100">
        <Grid.RowDefinitions>
            <RowDefinition Height="30" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">
            <Grid.Background>
                <LinearGradientBrush EndPoint="0.5,1"
                             StartPoint="0.5,0">
                    <GradientStop Color="Transparent"
                          Offset="0" />
                    <GradientStop Color="Wheat"
                          Offset="1" />
                </LinearGradientBrush>
            </Grid.Background>
        </Grid>
        <Grid Grid.Row="1" Background="Wheat" />
        <ListView ItemsSource="{x:Bind listCollection}" Grid.RowSpan="2">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding testText}" FontSize="20" />
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Grid>

正如您在此代码中看到的,我将图像设置为rootGrid的背景,并将另一个Grid放入其中以实现所需的布局。在这个网格中,ListView当然应占用所有空间,但我们可以将此Grid分为两部分,一部分用于LinearGradientBrush,另一部分用于背景ListView。以下是此布局的渲染图像: enter image description here

如果你想将另一张图片设置为ListView的背景,我想我们只能获得此图片的平均颜色,并将GradientStop的{​​{1}}绑定到此颜色。

<强>更新

对于Offset = 1的前景,我认为你是对的,我们需要覆盖它上面的面具。这是一种方法:

ListView

此处存在问题,默认情况下可以看到<Grid> <Grid.Background> <ImageBrush ImageSource="Assets/background.png" /> </Grid.Background> <Grid Margin="0,100"> <Grid.RowDefinitions> <RowDefinition Height="30" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Grid Grid.Row="0"> <Grid.Background> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="Transparent" Offset="0" /> <GradientStop Color="Wheat" Offset="1" /> </LinearGradientBrush> </Grid.Background> </Grid> <Grid Grid.Row="1" Background="Wheat" /> <ListView ItemsSource="{x:Bind listCollection}" Grid.RowSpan="2"> <ListView.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding testText}" FontSize="20" /> </DataTemplate> </ListView.ItemTemplate> </ListView> <Grid Grid.Row="0"> <Grid.Background> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="Transparent" Offset="0" /> <GradientStop Color="Wheat" Offset="1" /> </LinearGradientBrush> </Grid.Background> </Grid> </Grid> </Grid> 的滚动条,当在其上使用遮罩时,滚动条也会被覆盖。为了实现更好的布局,最好将ListView设置为ScrollViewer.VerticalScrollBarVisibility="Hidden"