UWP Composition - apply opacity mask up to 30px ListView

How can I apply an effect to a ListView where a 30px graduate is completely transparent and completely opaque? The idea is that when you scroll down, the top items gradually disappear.

I am creating a UWP application where the project requires the top 30px ListView to start with opacity 0 and go to opacity 1. Conceptually, I imagine an opacity mask that will be applied to the top of SpriteVisual, but I can't figure out how to achieve this.

I am trying to use this in the anniversary release of Windows 10, Composition and Win2D.

Edit: An image can do 1000 words:

Attenuation example

If you look at this image, there are two content elements in the lower left and lower right. Although the background seems black, it is actually a gradient. If you examine the top of these two elements, they become more transparent towards the top, showing the background. This is the effect I'm trying to achieve.

Edit 2: In an attempt to show the result of the effect I'm looking for, here is a GIF that shows the effect if I use overlay bitmaps: Scrolling Images

Header Background Image: background header

The bottom 30px has an alpha gradient and appears above the grid, giving a visible effect when the grid elements disappear and slide under the background.

The XAML schema looks like this:

<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> 

+6
source share
3 answers

So using @sohcatt in the list of problems of Windows UI Dev Labs I created a working solution.

enter image description here

Here is the 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> 

Here is the code:

  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); } 
+2
source
  <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> 

The above code creates a 30px gradient that updates from total whiteness to full transparency. Try putting it on your list and see if it is good.

0
source

As I tried to explain earlier - the background is not a solid solid color - it is an image that changes.

I think we should know that by default the ListView background control is transparent. Therefore, if the ListView parent control is set as a background image, to achieve the desired layout, we need to set another background for the ListView , and at that time this background cannot fill the entire ListView .

So here is a way:

 <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> 

As you can see in this code, I set the image as the background for the rootGrid and put another Grid inside to achieve the desired layout. In this grid, of course, the ListView should occupy all the space, but we can divide this Grid into two parts, one part on the LinearGradientBrush and the other on the background of the ListView . Here is a rendering image of this layout: enter image description here

And if you want to set another image as the background for the ListView , I think that we can only get the average color of this image and bind the GradientStop from Offset = 1 to this color.

Update

In the foreground of the ListView , I think you're right, we need to cover the mask over it. Here is a way:

 <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> 

There is a problem here, by default you can see the scroll bar of the ListView , and when using a mask, the scroll bar will also be covered above it. To achieve a better location, it is best to set ScrollViewer.VerticalScrollBarVisibility="Hidden" on the ListView .

0
source

All Articles