- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
@ 。
2007年9月5日iPod classic/nano3/touch在同一场发布会上发布,苹果首次向我们展示了Cover Flow 。
在iOS7之前的“音乐”App中,旋转设备90度,或在iTunes中的“查看”下,选择“Cover Flow”都可以进入到Cover Flow视图.
Cover Flow的交互设计非常优秀:通过指尖滑动从堆叠的专辑库中翻动和挑选一张专辑的交互方式不仅有趣,而且在有限的屏幕空间内,展现了更多的专辑封面.
但由于流媒体时代弱化了专辑的概念,拟物化设计退潮以及设备性能/续航等方面的考虑,苹果逐步放弃了Cover Flow.
在2012年新发布的iTunes 11,2013年新发布的iOS 7,以及2018年发布的MacOS Mojave中删除了Cover Flow界面,Gallery View取而代之 。
那个是乔布斯时代的苹果——使事情变得简单和有趣。最近我很怀念这个功能,但由于我手头上已经没有任何一台设备能访问这个功能了。于是在 .NET MAUI 中复刻了Cover Flow.
使用 .NET MAUI 实现跨平台支持,本项目可运行于Android、iOS平台.
实际上,Cover flow的原理非常简单,核心算法是对专辑图片进行3D变换(3DTransform).
.NET MAUI 并没有直接提供3D变换,但我们可以通过 SkiaSharp 来实现.
PS: Skia 本身是一个开源图形库,它提供适用于各种语言和硬件平台的通用 API,(如 C++/Qt、Chrome、Android、iOS等 ),根据本博文提到的算法,你可以用Skia尝试在你擅长的平台上实现相同的效果.
视图元素的3D变换(3DTransform)中,有一类是以视图元素的Y或X轴作为旋转中心做旋转,称之为3D旋转,除了专业的程序设计领域外,经常使用图形处理工具,甚至是ppt的同学可能都熟悉这个概念。在ppt中插入图形,设置形状格式,可以看到“三维旋转”的选项,如下图:
这里涉及到一个透视的概念,透视是指在视觉上,远处的物体比近处的物体小,来思考一下,在现实世界中要看到同样大小的物体,可以离得很近,视野变大,物体的畸变会变大。也可以离得很远,用一个望远镜去看,视野变小,物体的畸变也会变小。透视参数就是在屏幕中模拟了现实世界中近大远小透视效果,我简单用ptt做一个演示:
三个图形沿Y轴方向旋转, 从左到右透视距离依次减小,透视角度依次增大,换句话说是离得更近,视野变大,物体的畸变变大.
在大多数支持3D旋转的图形系统中都会包含透视这个参数变量,如css中的perspective亦或是ppt中的“透视”格式.
在Skia中,3D变换是通过矩阵乘法实现的,这里需要大致了解数字图像处理的基本知识,可以参考 这里 .
矩阵乘法就是把原始图像矩阵的横排和变换矩阵的竖排相应位相乘,将结果相加.
在二维空间,原始图像中的每个像素点 (x,y) 所代表的单列矩阵,通过变换矩阵相乘,得到新的像素点 (x',y')。 例如缩小图像:
因为要考虑平移等非线性计算,常用 3*3 的矩阵来表示变换 在三维空间,用一个 4*4 的矩阵来表示变换,例如围绕Y轴旋转的变换矩阵如下:
| cos(α) 0 –sin(α) 0 |
| 0 1 0 0 |
| sin(α) 0 cos(α) 0 |
| 0 0 0 1 |
另外涉及到的图像处理是平行变换(Skew),每一个平台上的值可能不同,但是原理都是通过增加或减少X轴或Y轴的值来实现平行变换.
在Skia中,根据参数值转换 x' 后的值随着 y 增加而增加。 这就是导致倾斜的原因.
如有一个200*100的图形,其左上角位于 (0、0) 的点上,并且呈现 xSkew 值为 1.5,则以下并行影像结果如下:
底部边缘 y 的坐标值为 100,因此将 150 像素移向右侧.
接下来我们用代码实现3D变换 。
我们还是以分治的思路实现,图片变换由控件内部实现,平移及动画由控件外部实现.
新建.NET MAUI项目,命名 Coverflow 。将界面图片资源文件拷贝到项目\Resources\Raw中并将他们包含在MauiImage资源清单中.
<ItemGroup>
<MauiImage Include="Resources\Raw\*.jpg" />
</ItemGroup>
在项目中添加SkiaSharp绘制功能的引用 Microsoft.Maui.Graphics.Skia 以及 SkiaSharp.Views.Maui.Controls .
<ItemGroup>
<PackageReference Include="Microsoft.Maui.Graphics.Skia" Version="7.0.59" />
<PackageReference Include="SkiaSharp.Views.Maui.Controls" Version="2.88.3" />
</ItemGroup>
创建3D变换的图片控件RotationImage.xaml,代码如下:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:forms="clr-namespace:SkiaSharp.Views.Maui.Controls;assembly=SkiaSharp.Views.Maui.Controls"
x:Class="Coverflow.RotationImage">
<forms:SKCanvasView x:Name="canvasView"
Grid.Row="8"
PaintSurface="OnCanvasViewPaintSurface" />
</ContentView>
在RotationImage.xaml.cs中添加代码:
SKBitmap对象 。
public SKBitmap bitmap { get; private set; }
初始化方法,以及在图形大小变化时应用初始化 。
private async void RotationImage_SizeChanged(object sender, EventArgs e)
{
await InitBitmap();
}
private async Task InitBitmap()
{
using (Stream stream = await FileSystem.OpenAppPackageFileAsync("./15.jpg"))
{
if (stream!=null)
{
var mainDisplayInfo = DeviceDisplay.Current.MainDisplayInfo;
var pixcelHeight = mainDisplayInfo.Density*200;
var pixcelWidth = mainDisplayInfo.Density*200;
var bitmap = SKBitmap.Decode(stream);
bitmap= bitmap.Resize(new SKImageInfo((int)pixcelHeight,
(int)pixcelWidth),
SKFilterQuality.Medium);
this.bitmap=bitmap;
}
}
}
初始化时将读取图片资源文件,然后将图片缩放到 200*200 的大小.
注意此处使用 mainDisplayInfo.Density 将MAUI各平台的逻辑分辨率转为图片的真实分辨率 。
此时在画布中绘制了一个简单的 200*200 专辑封面图片 。
在Skia用SKMatrix44类来描述 4*4 的变换矩阵,同时提供了 CreateRotation 和 CreateRotationDegrees 方法,可用于指定旋转围绕的轴 。
RotationImage_SizeChanged中,添加代码如下:
SKMatrix matrix = SKMatrix.CreateTranslation(-xCenter, -yCenter);
SKMatrix44 matrix44 = SKMatrix44.CreateIdentity();
matrix44.PostConcat(SKMatrix44.CreateRotationDegrees(1, 0, 0, (float)0));
matrix44.PostConcat(SKMatrix44.CreateRotationDegrees(0, 1, 0, (float)25));
matrix44.PostConcat(SKMatrix44.CreateRotationDegrees(0, 0, 1, (float)0));
SKMatrix44 perspectiveMatrix = SKMatrix44.CreateIdentity();
perspectiveMatrix[3, 2] = -1 / 800;
matrix44.PostConcat(perspectiveMatrix);
matrix= matrix.PostConcat(matrix44.Matrix);
matrix= matrix.PostConcat(
SKMatrix.CreateTranslation(xCenter, yCenter));
将变换矩阵应用到画布中 。
canvas.SetMatrix(matrix);
此时在画布中专辑封面图片以800的透视距离,绕Y轴旋转25度 。
首先计算倾斜角度,如有一个200*100的图形,其左上角位于 (0、0) 的点上,图中的角度α:
150 像素到 100 像素垂直方向的比率是该角度的正切值,即 56.3 度.
RotationImage_SizeChanged中,对matrix对象应用平行变换 。
matrix.SkewY = (float)Math.Tan(Math.PI * (float)15 / 180);
此时在画布中专辑封面图片以15度平行变换 。
在cover flow中,封面图片包含倒影效果.
之前的绘制的封面图片,在控件中央(也是画布中央)的位置。为了放置倒影后仍然处于控件中心,画布应该一分为二:上半部分绘制封面图片,下半部分绘制倒影.
更改代码:
//float yBitmap = yCenter - bitmap.Height / 2;
float yBitmap = yCenter-bitmap.Height;
绘制倒影封面图片:
using (SKPaint paint = new SKPaint())
{
paint.Color = SKColors.Black.WithAlpha((byte)(255 * 0.8));
canvas.Scale(1, -1, 0, yCenter);
canvas.DrawBitmap(bitmap, xBitmap, yBitmap, paint);
SKRect rect = SKRect.Create(xBitmap, yBitmap, bitmap.Width, bitmap.Height);
canvas.DrawRect(rect, paint);
}
倒影用一个黑色半透明的矩形覆盖在原始封面图片上,并且将画布沿Y轴翻转,使得倒影图片在封面图片的下方.
将图片源,旋转角度,平行角度等作为绑定属性,以便在XAML中绑定。代码忽略.
创建MainPageViewModel.cs,用于界面绑定数据源.
AlbumInfo描述专辑信息 。
public class AlbumInfo
{
public AlbumInfo() { }
public string AlbumName { get; set; }
public string AlbumArtSource { get; set; }
}
在MainPageViewModel构造函数中,初始化AlbumInfo列表,在控件中绑定此列表作为数据源 。
在MainPage.xaml中,创建一个Grid作为专辑封面容器,我们将使用绑定集合的方式,将专辑封面添加到这个容器中.
代码如下:
<Grid Grid.Row="1"
x:Name="BoxLayout"
Background="black"
BindableLayout.ItemsSource="{Binding AlbumInfos}">
它的DataTemplate代表一个专辑信息,使用Grid布局,专辑封面图片与专辑名称分别位于Grid的第一行和第二行.
<BindableLayout.ItemTemplate>
<DataTemplate>
<Grid Style="{StaticResource BoxFrameStyle}"
Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<controls:RotationImage WidthRequest="200"
HeightRequest="500"
ImageWidth="200"
ImageHeight="200"
Source="{Binding AlbumArtSource}"></controls:RotationImage>
<Label Margin="0,30,0,0"
Text="{Binding AlbumName}"
HorizontalTextAlignment="Center"
VerticalOptions="Center"></Label>
</Grid>
</DataTemplate>
</BindableLayout.ItemTemplate>
对专辑封面Grid的样式进行定义:
<ContentPage.Resources>
<Style TargetType="Grid"
x:Key="BoxFrameStyle">
<Setter Property="HeightRequest"
Value="100"></Setter>
<Setter Property="WidthRequest"
Value="100"></Setter>
<Setter Property="HorizontalOptions"
Value="Center"></Setter>
<Setter Property="VerticalOptions"
Value="Center"></Setter>
</Style>
</ContentPage.Resources>
效果如下:
Cover Flow的滑动交互由两种方式实现:1. 左右轻扫屏幕,切换到上一张或下一张专辑封面;2. 拨动底部Slider控件,切换到指定的专辑封面.
两种方式都会改变当前位置,我们将当前位置定义为一个整数,表示当前专辑在容器中的索引.
private int currentPos;
当手势触发时,根据手势方向,改变当前位置:
this.currentPos=e.Direction==SwipeDirection.Right
? Math.Max(0, this.currentPos-1)
: Math.Min(this.BoxLayout.Children.Count-1, this.currentPos+1);
当Slider控件的值发生变化时,根据Slider的值,计算当前位置:
var currentPos = (int)Math.Floor(e.NewValue* (this.BoxLayout.Children.Count-1));
if (this.currentPos!=currentPos)
{
this.currentPos = currentPos;
}
当前位置索引的值始终在0到专辑封面数量减1之间.
当前封面是从专辑堆叠中挑选出来的,它的位置是固定的,左右两边的封面相对于当前封面,有一个固定的距离,step为当前封面和左右第一张封面之间的距离,slidePadding为其它封面和当前封面之间的距离.
其它封面的位置,分为两种情况:1. 在当前封面的左边;2. 在当前封面的右边.
封面叠层的顺序是当前封面最靠上,左右两边的封面随着距离由近及远,依次向下叠放.
创建RenderTransform方法,作为刷新的入口,当当前位置发生变化时,调用此方法,重新计算每个专辑封面的位置和叠放顺序.
private void RenderTransform(int currentPos)
{
var step=40.0;
var currentSlidePadding=100.0;
foreach (var bitmapLayout in this.BoxLayout.Children)
{
var pos = this.BoxLayout.Children.IndexOf(bitmapLayout);
double xBitmap;
int zIndex;
if (pos < currentPos)
{
zIndex=pos;
xBitmap = (double)(-(currentPos * step) + (pos * step) - currentSlidePadding);
}
else if (pos > currentPos)
{
zIndex=this.BoxLayout.Children.Count-pos;
xBitmap = (double)(((pos - currentPos) * step) + currentSlidePadding);
}
else
{
xBitmap = 0;
zIndex=this.BoxLayout.Children.Count;
}
(bitmapLayout as VisualElement).ZIndex = zIndex;
(bitmapObj as RotationImage).TranslationX=xBitmap;
}
}
创建后,运行效果如下 。
我们对当前封面的左边的封面,以及当前封面的右边的封面,分别计算旋转角度,以实现3D效果.
var rotateY = 65;
foreach (var bitmapLayout in this.BoxLayout.Children)
{
double targetRotateY;
if (pos < currentPos)
{
targetRotateY=rotateY;
}
else if (pos > currentPos)
{
targetTransY=transY;
}
else
{
targetTransY=0;
}
(bitmapObj as RotationImage).RotateY=targetRotateY;
}
再对3D旋转的封面进行平行变换调整,并对封面位置作微调 。
var rotateY = 65;
var skewY = 0;
var transY = 0;
foreach (var bitmapLayout in this.BoxLayout.Children)
{
double targetRotateY;
double targetSkewY;
double targetTransY;
if (pos < currentPos)
{
targetRotateY=rotateY;
targetSkewY=skewY;
targetTransY=-transY;
}
else if (pos > currentPos)
{
targetRotateY=-rotateY;
targetSkewY=-skewY;
targetTransY=transY;
}
else
{
targetRotateY=0;
targetSkewY=0;
targetTransY=0;
}
(bitmapObj as RotationImage).RotateY=targetRotateY;
(bitmapObj as RotationImage).TranslationX=xBitmap;
(bitmapObj as RotationImage).SkewY=targetSkewY;
(bitmapObj as RotationImage).TransY=targetTransY;
}
最后配置封面图片的缩放,以及封面标题显示、隐藏.
效果如下:
至此我们完成了静态的工作内容,下一步要让界面的过渡动画更加流畅,我们将使用MAUI的动画框架,实现平滑的过渡动画.
我们通过创建Animation对象,添加子动画来实现。详情请参考 Animation子动画 .
RotateY、SkewY、TranslationX、Scale直接赋值的方式将由动画代替。动画是一种缓动机制,通过属性的缓慢改变实现平滑的过渡动画.
在渲染中我们为每一个封面创建一个Animation对象,然后添加子动画,最后调用Animation对象的Commit方法, 。
在400ms内将各属性缓慢应用到界面上。各属性步调一致,所以动画的过程是平滑的.
foreach (var bitmapLayout in this.BoxLayout.Children)
{
uint duration = 400;
...
Animation albumAnimation = new Animation();
var originTranslationX = (bitmapLayout as VisualElement).TranslationX;
var originScale = (bitmapLayout as VisualElement).Scale;
var animation1 = new Animation(v => (bitmapLayout as VisualElement).TranslationX = v, originTranslationX, xBitmap, Easing.CubicInOut);
var animation2 = new Animation(v => (bitmapLayout as VisualElement).Scale = v, originScale, targetScale, Easing.CubicInOut);
if (targetSkewY!=(bitmapObj as RotationImage).SkewY)
{
var animation4 = new Animation(v => (bitmapObj as RotationImage).SkewY = v, (bitmapObj as RotationImage).SkewY, targetSkewY, Easing.CubicInOut);
albumAnimation.Add(0, 1, animation4);
}
if (targetRotateY!=(bitmapObj as RotationImage).RotateY)
{
var animation3 = new Animation(v => (bitmapObj as RotationImage).RotateY = v, (bitmapObj as RotationImage).RotateY, targetRotateY, Easing.CubicInOut);
albumAnimation.Add(0, 1, animation3);
}
if (targetTransY!=(bitmapObj as RotationImage).TransY)
{
var animation5 = new Animation(v => (bitmapObj as RotationImage).TransY = v, (bitmapObj as RotationImage).TransY, targetTransY, Easing.CubicInOut);
albumAnimation.Add(0, 1, animation5);
}
albumAnimation.Add(0, 1, animation1);
albumAnimation.Add(0, 1, animation2);
albumAnimation.Commit((bitmapLayout as VisualElement), "AlbumArtImageAnimation", 16, duration);
}
效果如下:
在页面大小变化时,重新渲染变换.
private void MainPage_SizeChanged(object sender, EventArgs e)
{
RenderTransform(currentPos);
}
step和currentSlidePadding值将由屏幕宽度计算得出,使得在不同屏幕大小设备,或者横竖屏切换时,效果保持一致.
var xCenter = this.BoxLayout.Width / 2;
var step = xCenter*0.12;
var currentSlidePadding = this.BoxLayout.Width * 0.15;
Github:maui-samples 。
关注我,学习更多.NET MAUI开发知识! 。
最后此篇关于[MAUI]在.NETMAUI中复刻苹果CoverFlow的文章就讲到这里了,如果你想了解更多关于[MAUI]在.NETMAUI中复刻苹果CoverFlow的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我尝试在我自己的 android projet 中添加 android coverflow 当 myt Activity 启动时,我收到以下错误: 06-18 15:32:35.690: E/Andr
我正在尝试使用 AsyncImageView ( https://github.com/nicklockwood/AsyncImageView ) 作为封面来实现 Coverflow ( iCarou
我从最近两个小时开始搜寻。并发现了CoverFlow的许多链接。但大多数情况下无法在iOS 5上使用,并且会出错。使用很多代码,我可以更改编译器设置,但不适用于我。 请提供与iOS 5或任何开放源代码
有人可以向我推荐任何适用于 Android 的 CoverFlow 示例或库,它们可以达到与 IOS CoverFlow 相同的效果吗?对选择具有封面翻转效果。我浏览了几个 android 库和示例,
已关闭。此问题旨在寻求有关书籍、工具、软件库等的建议。不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以
我已经实现了 Android Coverflow 示例。单击图像时,我可以检索它的位置并在 ImageView 中显示图像。我的另一个要求是在我传递其 Id 时将图像聚焦在 coverFlow 中。我
我正在将这个伟大的项目用于 iPhone 的 Cover Flow (OpenFlow)。 https://github.com/thefaj/OpenFlow/ 但是,这仅支持图像。例如,当我修改代
在我的一个应用程序中,我需要从数据库中读取文本并将其显示给用户。为此,我想到了使用 Coverflow。那么这里的任何人都可以让我知道,是否可以使用 CoverFlow 而不是图像来显示文本?我正在尝
我想用自定义适配器创建一个封面流程( ImageView 作为缩略图, TextView 作为标题,进度条指示一些进展......)。 Cover flow widget 的所有示例或实现均基于 Ga
按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the
我想制作 UIScrollView 或像这样的封面流 我尝试了一些像 icaraousal 等的封面流,但那些没有产生像这样的感觉所以如果有人知道这种类型的封面那么请给我建议. 和iCarousal创
我用 coverflow 创建了一个应用程序。我到处都看过,但似乎无法弄清楚如何在单击封面流中的每个单独图像时使用 onclicklistener 来进行简单的 Activity 。下面是我的代码,我
我正在使用https://github.com/Polidea/android-coverflow我的应用程序中的 coverflow 小部件。我遇到的问题是我无法控制小部件的起始位置。我很惊讶没有人
我想为非触摸应用程序实现 CoverFlow。就像是: https://www.youtube.com/watch?v=0MT58xIzp5c 我已经掌握了基础知识,但有两个问题: 与轻弹相比,使用鼠
我正在使用这个 CoverFlow:http://www.inter-fuser.com/2010/02/android-coverflow-widget-v2.html . 我用这个 coverfl
我想在我的 Wordpress 主题内的 Swiper 实例上使用 coverflow 效果。我注意到在我将开发控制台附加到页面下并更改断点之前,效果不会被触发。我需要修复,这是代码。是否可以使用 P
这是我最喜欢的封面: http://paulbakaus.com/lab/js/coverflow/ 有什么办法可以下载吗?或者有实现指南吗? 我看到的只是一些博客文章,没有关于如何实现的内容。 谢谢
我有这个页面,其中应该附有一个粘性导航。在页面上,有一个我从 dynamicdrive.com 获得的 coverflow 插件。 coverflow 插件就像一个图像 slider 。它看起来像这样
我正在尝试使用这个插件: http://www.jacksasylum.eu/ContentFlow/ 2分: 我想知道如何将它放入我的 div 并留在 div 的体积内?就像现在一样,我的 div
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。 关闭 18 天前
我是一名优秀的程序员,十分优秀!