[MAUI]模仿网易云音乐黑胶唱片的交互实现
Posted 林晓lx
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[MAUI]模仿网易云音乐黑胶唱片的交互实现相关的知识,希望对你有一定的参考价值。
@
用过网易云音乐App的同学应该都比较熟悉它播放界面。
这是一个良好的交互设计,留声机的界面隐喻准确地向人们传达产品概念和使用方法:当手指左右滑动时,便模拟了更换唱盘从而导向切换歌曲的交互功能。
今天在 .NET MAUI 中我们来实现这个交互效果,先来看看效果:
使用.NET MAU实现跨平台支持,本项目可运行于Android、iOS平台。
创建页面布局
项目模拟了网易云音乐的播放主界面,可播放本地音乐文件。使用MatoMusic.Core作为播放内核,此项目对其将不再赘述。请阅读此博文[MAUI 项目实战] 音乐播放器(二):播放内核
新建.NET MAUI项目,命名CloudMusicGroove
,项目引用MatoMusic.Core。
将界面图片资源文件拷贝到项目\\Resources\\Images中,这些界面图片资源可通过解包官方apk的方式轻松获取。
将他们包含在MauiImage资源清单中。
<MauiImage Include="Resources\\Images\\*" />
创建页面的静态布局,布局如下图所示
其中唱盘元素是一个300 × 300的圆形,专辑封面为200 × 200的圆形,图片的圆形区域是通过裁剪实现的,代码如下:
<Grid
VerticalOptions="Start"
HorizontalOptions="Start">
<Image Source="ic_disc.png"
WidthRequest="300"
HeightRequest="300" />
<Image HeightRequest="200"
WidthRequest="200"
x:Name="AlbumArtImage"
Margin="0"
Source="Binding CurrentMusic.AlbumArt"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"
Aspect="AspectFill">
<Image.Clip>
<RoundRectangleGeometry CornerRadius="125"
Rect="0,0,200,200" />
</Image.Clip>
</Image>
</Grid>
设置留声机唱针元素,代码如下:
<Image WidthRequest="100"
HeightRequest="167"
HorizontalOptions="Center"
VerticalOptions="Start"
Margin="70,-50,0,0"
Source="ic_needle.png"
x:Name="AlbumNeedle" />
创建PitContentLayout区域,这个区域是一个3 × 2的网格布局,用来放置三个功能区域
在PitContentLayout中创建三个PitGrid控件,并对这三个功能区域的PitGrid控件命名,LeftPit
、MiddlePit
,RightPit
,代码如下:
<Grid x:Name="PitContentLayout"
Opacity="1"
BindingContext="Binding CurrentMusicRelatedViewModel">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"></ColumnDefinition>
<ColumnDefinition Width="2*"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<controls1:PitGrid x:Name="LeftPit"
Background="pink"
PitName="LeftPit">
</controls1:PitGrid>
<controls1:PitGrid Grid.Column="1"
x:Name="MiddlePit"
Background="azure"
PitName="MiddlePit">
</controls1:PitGrid>
<controls1:PitGrid Grid.Column="2"
x:Name="RightPit"
Background="lightyellow"
PitName="RightPit">
</controls1:PitGrid>
</Grid>
创建手势控件
手势控件,或称为手势容器控件,它来对拖拽物进行包装,以赋予拖拽物响应平移手势的能力。
创建一个容器控件HorizontalPanContainer,控件包含的PanGestureRecognizer提供了当手指在屏幕移动这一过程的描述
<?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"
x:Class="MauiSample.Controls.HorizontalPanContainer">
<ContentView.GestureRecognizers>
<PanGestureRecognizer PanUpdated="PanGestureRecognizer_OnPanUpdated"></PanGestureRecognizer>
<TapGestureRecognizer Tapped="TapGestureRecognizer_OnTapped"></TapGestureRecognizer>
</ContentView.GestureRecognizers>
</ContentView>
创建一个手势控件。他将留声机唱盘区域包裹起来。这样当手指在唱盘区域滑动时,就可以触发平移手势事件。
<controls:HorizontalPanContainer Background="Transparent"
x:Name="DefaultPanContainer"
OnTapped="DefaultPanContainer_OnOnTapped"
OnfinishedChoise="DefaultPanContainer_OnOnfinishedChoise">
<controls:HorizontalPanContainer.Content>
<Grid PropertyChanged="BindableObject_OnPropertyChanged"
VerticalOptions="Start"
HorizontalOptions="Start">
<Image Source="ic_disc.png"
WidthRequest="300"
HeightRequest="300" />
<Image HeightRequest="200"
WidthRequest="200"
x:Name="AlbumArtImage"
Margin="0"
Source="Binding CurrentMusic.AlbumArt"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"
Aspect="AspectFill">
<Image.Clip>
<RoundRectangleGeometry CornerRadius="125"
Rect="0,0,200,200" />
</Image.Clip>
</Image>
</Grid>
</controls:HorizontalPanContainer.Content>
</controls:HorizontalPanContainer>
创建影子控件
影子控件用于滑动唱盘时,显示上一曲、下一曲的专辑封面。
在左右滑动的全程中,唱盘的中心点与相邻唱盘的中心点距离,应为屏幕宽度。如下图所示
唱盘与唱盘的距离应是
创建影子控件,这个控件将随拖拽物的移动而跟随移动,当然我们只需要保持X方向的移动即可。
在NowPlayingPage中的HorizontalPanContainer相邻容器视图中创建影子控件,代码如下:
<Grid TranslationX="Binding Source=x:Reference DefaultPanContainer ,Path=Content.TranslationX">
<Image Source="ic_disc.png"
WidthRequest="300"
HeightRequest="300" />
<Image HeightRequest="200"
WidthRequest="200"
Margin="0"
Source="Binding PreviewMusic.AlbumArt"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"
Aspect="AspectFill">
<Image.Clip>
<RoundRectangleGeometry CornerRadius="125"
Rect="0,0,200,200" />
</Image.Clip>
</Image>
</Grid>
我们将这个影子控件的TranslationX属性将绑定到拖拽物的TranslationX属性上,初步效果如下
拖拽区域需要两个影子控件,分别显示上一曲和下一曲的专辑封面。
我们需要将影子控件的偏移量与屏幕宽度作匹配,我们用转换器来实现这个功能。
创建CalcValueConverter.cs文件,代码如下:
public class CalcValueConverter : IValueConverter
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
var d = (double)value;
double compensation;
if (double.Parse((string)parameter)>=0)
compensation=((App.Current as App).PanContainerWidth+300)/2;
else
compensation=-1.5*(App.Current as App).PanContainerWidth+300/2;
return d+compensation;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
throw new NotImplementedException();
将CalcValueConverter添加至资源字典中,
<converter:CalcValueConverter x:Key="CalcValueConverter"></converter:CalcValueConverter>
对影子控件的属性绑定设置转换器,并设置转换器参数,代码如下:
左影子控件(上一曲专辑唱盘)
TranslationX="Binding Source=x:Reference DefaultPanContainer ,Path=Content.TranslationX,Converter=StaticResource CalcValueConverter,ConverterParameter=-1"
右影子控件(下一曲专辑唱盘)
TranslationX="Binding Source=x:Reference DefaultPanContainer ,Path=Content.TranslationX,Converter=StaticResource CalcValueConverter,ConverterParameter=-1"
唱盘拨动交互
当然我们仅希望拖拽物仅在水平方向上响应手势
在HorizontalPanContainer中,注册PanGestureRecognizer的响应事件PanGestureRecognizer_OnPanUpdated,在GestureStatus.Running添加代码如下:
private async void PanGestureRecognizer_OnPanUpdated(object sender, PanUpdatedEventArgs e)
var isInPit = false;
switch (e.StatusType)
case GestureStatus.Running:
var translationX = PositionX + e.TotalX;
var translationY = PositionY;
...
结合上一小节写的三个PitGrid,此时拖拽唱盘,并且在拖拽开始,进入pit,离开pit,释放时,分别触发Start,In,Out,Over四个状态事件。
响应状态事件的有效区域如下
创建检测唱盘中心点是否在有效区域的方法,
当平移方向为向右时,唱盘中心点的X坐标应大于右pit区域的起始X坐标;
当平移方向为向左时,唱盘中心点的X坐标应小于左pit区域的结束X坐标。
在GestureStatus.Running添加代码如下:
foreach (var item in PitLayout)
var pitRegion = new Region(item.X, item.X + item.Width, item.Y, item.Y + item.Height, item.PitName);
var isXin = (e.TotalX>0 && translationX >= pitRegion.StartX - Content.Width / 2 && pitRegion.StartX>this.Width/2)||
(e.TotalX<0 && translationX <= pitRegion.EndX - Content.Width / 2&&pitRegion.EndX<this.Width/2);
if (isXin)
isInPit = true;
...
在不同的pit中,处理对应的状态事件。
若在手指离开时,唱盘的中心点还在MiddlePit区域范围内,则将唱盘回弹移动到MiddlePit中心点。
若在LeftPit或RightPit区域,则将唱盘移动到LeftPit或RightPit区域中心点。
此时已经实现了拖拽唱盘的基本功能,但是在释放唱盘时,影子唱盘并没有如预期那样移动到MiddlePit的中心点。
当命中LeftPit或RightPit区域时,我们希望影子控件移动到MiddlePit中心点。当影子控件移动到位时,替换掉当前的唱盘,成为新的拖拽物。由此可以无限的拨动唱盘实现连续切歌的效果。
当手指释放,唱盘准备向左或右移动时,迅速将影子控件的位置替换成当前唱盘的位置。用当前唱盘的“瞬移”,看起来像唱盘被影子唱盘替换掉了,但是在屏幕中心活动的拖拽物,一直是真正的那个控件。
在GestureStatus.Completed添加代码如下:
case GestureStatus.Completed:
double destinationX;
var view = this.CurrentView;
if (isInPitPre)
var pitRegion = new Region(view.X, view.X + view.Width, view.Y, view.Y + view.Height, view.PitName);
var prefix = pitRegion.StartX>this.Width/2 ? 1 : -1;
destinationX=PositionX+prefix*(App.Current as App).PanContainerWidth;
else
destinationX=PositionX;
这样看起来像可以无限地拨动唱盘了
唱盘和唱针动画
唱盘转动,音乐随之播放,通过将专辑封面图片以20秒每圈的速度旋转来实现唱盘旋转的效果。
在NowPlayingPage中创建一个Animation对象,用于控制唱盘旋转。
private Animation rotateAnimation;
编写启动旋转动画方法StartAlbumArtRotation以及停止动画方法StopAlbumArtRotation,代码如下:
private void StartAlbumArtRotation()
this.AlbumArtImage.AbortAnimation("AlbumArtImageAnimation");
rotateAnimation = new Animation(v => this.AlbumArtImage.Rotation = v, this.AlbumArtImage.Rotation, this.AlbumArtImage.Rotation+ 360);
rotateAnimation.Commit(this, "AlbumArtImageAnimation", 16, 20*1000, repeat: () => true);
private void StopAlbumArtRotation()
this.AlbumArtImage.CancelAnimations();
if (this.rotateAnimation!=null)
this.rotateAnimation.Dispose();
效果如下:
注意,当音乐暂停后,停止旋转动画,当音乐恢复播放时,转盘应从之前停止的角度开始启动旋转动画。
在拨动唱盘或切歌时,唱针将从唱盘上移开,通过旋转唱针图片30度来实现唱针移开的效果。
首先设置锚点,AnchorX=0.18,AnchorY=0.059,如下:
<Image WidthRequest="100"
HeightRequest="167"
HorizontalOptions="Center"
VerticalOptions="Start"
Margin="70,-50,0,0"
Source="ic_needle.png"
x:Name="AlbumNeedle"
AnchorX="0.18"
AnchorY="0.059" />
在音乐播放时
当手指开始滑动时,唱针从唱盘上移开,唱盘停止旋转;
当手指离开时,唱针回到唱盘上,唱盘继续旋转。
private async void PanActionHandler(object recipient, HorizontalPanActionArgs args)
switch (args.PanType)
case HorizontalPanType.Over:
if (MusicRelatedViewModel.IsPlaying)
await this.AlbumNeedle.RotateTo(0, 300);
this.StartAlbumArtRotation();
break;
case HorizontalPanType.Start:
if (MusicRelatedViewModel.IsPlaying)
await this.AlbumNeedle.RotateTo(-30, 300);
this.StopAlbumArtRotation();
break;
...
效果如下:
当暂停、恢复时,唱针的位置也应该随之改变。
private async void MusicRelatedViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
if (e.PropertyName==nameof(MusicRelatedViewModel.IsPlaying))
if (MusicRelatedViewModel.IsPlaying)
await this.AlbumNeedle.RotateTo(0, 300);
this.StartAlbumArtRotation();
else
await this.AlbumNeedle.RotateTo(-30, 300);
this.StopAlbumArtRotation();
效果如下:
最终效果如下:
项目地址
本文来自博客园,作者:林晓lx,转载请注明原文链接:https://www.cnblogs.com/jevonsflash/p/17351565.html
用RotateDrawable实现网易云音乐唱片机效果
有一段时间没有更新文章了,记得上一篇文章讲的是《用ClipDrawable实现音频录制麦克风讲话效果》,用户反响也都还不错,自己也是深受鼓励。事实上从那之后就一直想写一篇关于RotateDrawable的文章,原因非常easy。RotateDrawable事实上和上一篇文章中的ClipDrawable非常类似。正愁着不知道以什么样的方式向大家介绍,也正是这个原因吧,一直没有发表新的文章。赶巧了。在用朋友手机的时候发现了一款名叫‘网易云音乐’的APP,在主播放页面有一个唱片机的功能感觉不错诶,于是乎。把玩了一番,心想着。何不用RotateDrawable实现这样一个功能呢? 说干就干!。。
老规矩。使用之前我们还是先要来了解一下今天的主角RotateDrawable
RotateDrawable
事实上从名字中就不难理解。RotateDrawable一定是一个和旋转有关的Drawable。的确,RotateDrawable能够控制drawable的旋转,在XML文件里定义RotateDrawable对象使用的根元素是< rotate… />元素。该元素包括下面几个重要的属性:
- android:drawable:指定将要进行旋转操作的Drawable对象。
- android:visible:视图是否可见,注意默认是false。也就是不可见。
- android:pivotX:pivotX表示旋转轴心在x轴横坐标上的位置,用百分比表示,表示在当前drawable总宽度百分之几的位置。
- android:pivotY:同理,pivotY表示旋转轴心在y轴横坐标上的位置,用百分比表示。表示在当前drawable总高度百分之几的位置。
- android:fromDegrees:fromDegrees表示起始角度。值大于0。则表示顺时针旋转,值小于0。则表示逆时针旋转。
- android:toDegrees:fromDegrees表示终点角度,同理,值大于0,则表示顺时针旋转,值小于0,则表示逆时针旋转。
之所以说RotateDrawable和ClipDrawable类似。是由于它们两个都能够通过调用方法setLevel(int level)来控制drawable的状态,ClipDrawable能够通过调用方法setLevel(int level)来控制截取区间的大小。相同,RotateDrawable能够通过调用方法setLevel(int level)来控制旋转角度的大小,取值相同是在0~10000之间。能够理解为把起始角度和终点角度之间的角度均等分为10000份。当level等于0的时候处于起始位置,当level等于10000的时候处于终点位置。至于中间部分由level的取值大小来决定。
了解了RotateDrawable的使用原理,那我们就进入正题,怎样使用RotateDrawable实现唱片机的效果,首先呢。当然是要准备素材!
素材大家能够到Iconfont下载。有能力的也能够自己PS,事实上我们的今天要用到的几张素材非常easy,会简单的PhotoShop操作基本就都能够做出来:
注意、注意、一定要注意,重要的事情说三遍:在选择或者制作素材的时候一定要注意一点,由于RotateDrawable是用于drawable的旋转操作,所以关于drawable的中心点位置必须严格要求。否则制作出来的drawable在旋转的时候会十分别扭。
如上面两张截图显示的一样,我制作素材的图片的大小是240x240,唱片的中心点坐标是120x120,也就是pivotX = 50%、pivotY = 50% 。操纵杆的中心点坐标是192x24。 那么pivotX = 80%、pivotY = 10%。
那好,既然素材已经准备完毕。并且它们的中心点也都确认完毕。紧接着。我们就在XML中定义这两个RotateDrawable:
唱片rotate_cd.xml:
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:pivotX="50%"
android:pivotY="50%"
android:visible="true"
android:fromDegrees="0"
android:toDegrees="360"
android:drawable="@mipmap/cd" >
</rotate>
操纵杆rotate_hander.xml:
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:pivotY="10%"
android:pivotX="80%"
android:toDegrees="0"
android:visible="true"
android:fromDegrees="-30"
android:drawable="@mipmap/box_handbar" >
</rotate>
最后,仅仅要将这两个drawable引用到两个相互叠加的ImageView上,并结合线程和属性动画适当的调用ImageView.getDrawable().setLevel(int level)方法就能实现完美的效果啦 !!!
<RelativeLayout
android:layout_width="140dp"
android:layout_height="140dp"
android:background="@mipmap/box_background" >
<ImageView
android:src="@drawable/rotate_cd"
android:id="@android:id/progress"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@mipmap/box_background" />
<ImageView
android:id="@android:id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/rotate_hander" />
</RelativeLayout>
附上一张效果图,须要源代码的小伙伴也能够点击这里下载哦!!
!
假设文中有表述不当或阐述错误的地方。还望正在看文章的您能够帮忙指出。有疑惑呢,也能够在评论中提问或者私信。期待您的意见和建议。欢迎关注交流。
以上是关于[MAUI]模仿网易云音乐黑胶唱片的交互实现的主要内容,如果未能解决你的问题,请参考以下文章