如何将效果应用于ListView,其中前30px从完全透明到完全不透明?这个想法是,当你向下滚动时,顶部的项目逐渐消失。
我正在构建一个UWP应用程序,其中设计要求ListView的前30px从不透明度0开始并转换为不透明度1.从概念上讲,我想象的是一个不透明蒙版,它将应用于SpriteVisual的顶部但是我无法弄清楚如何实现这一目标。
我正在尝试使用Windows 10,Composition和Win2D的周年纪念版。
编辑:一张图片可能会刷1000字:
如果查看此图片,左下角和右下角有两个内容元素。虽然背景看起来是黑色的,但它实际上是一个渐变。如果您检查两个元素的顶部,它们会变得更加透明,通过背景显示。这就是我想要达到的效果。
编辑2: 为了显示我正在寻找的效果的结果,这里有一个GIF,如果我使用重叠的位图显示效果:
较低的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>
答案 0 :(得分:4)
因此,在Windows UI开发实验室问题列表的@sohcatt的帮助下,我已经构建了一个可行的解决方案。
这是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
。以下是此布局的渲染图像:
如果你想将另一张图片设置为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"
。