@

.NET MAUI跨平台框架包含了识别平移手势的功能,在之前的博文[MAUI 项目实战] 手势控制音乐播放器(二): 手势交互中利用此功能实现了pan-pit拖拽系统。

简单来说就是拖拽物(pan)体到坑(pit)中,手势容器控件PanContainer描述了pan运动和pit位置的关系,并在手势运动中产生一系列消息事件。

今天使用这个控件,做一个模仿微信“按住-说话”的小功能,最终效果如下:

使用.NET MAU实现跨平台支持,本项目可运行于Android、iOS平台。

创建页面布局

新建.NET MAUI项目,命名HoldAndSpeak

MainPage.xaml中创建一个PitContentLayoutGrid容器,并对Grid容器进行如下布局:

在手机屏幕的底部设置两行两列的布局:

第一行第一列,对应取消发送手势区域,

第一行第二列,对应语音转文字手势区域,

第二行独占两列,对应发送手势区域。

布局如下图所示

<Grid x:Name="PitContentLayout"
Opacity="1">
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
</Grid>

创建三个PitGrid控件,并对这三个功能区域的PitGrid控件命名,CancelPitTransliterationPit,分别对应了取消发送、语音转文字、发送。

为每个PitGrid控件添加内容:

发送区域是一个底部弧形区域,我们用一个巨大的圆形+Y轴方向的偏移,通过只保留屏幕底部往上的一部分圆形区域来实现底部弧形区域的效果,代码如下:

<BoxView TranslationY="450"
x:Name="SendBox"
HeightRequest="1000"
WidthRequest="1000"
CornerRadius="500">
</BoxView>

取消发送和语音转文字区域是一个圆形区域,我们用一个正常大小的圆形来实现。

PitContentLayout区域整体代码如下

<Grid x:Name="PitContentLayout"
Opacity="1">
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<controls1:PitGrid x:Name="CancelPit"
TranslationX="-40" PitName="CancelPit"> <BoxView x:Name="CancelBox"
HeightRequest="80"
WidthRequest="80"
CornerRadius="50"
Margin="7.5"
Color="{StaticResource PhoneContrastBackgroundBrush}"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"></BoxView>
<Label x:Name="CancelLabel"
TextColor="{StaticResource PhoneContrastForegroundBrush}"
FontFamily="FontAwesome"
FontSize="28"
Rotation="-10"
HorizontalOptions="CenterAndExpand"
Margin="0"></Label> </controls1:PitGrid>
<controls1:PitGrid x:Name="TransliterationPit"
PitName="TransliterationPit"
TranslationX="40"
Grid.Column="1"> <BoxView x:Name="TransliterationBox"
HeightRequest="80"
WidthRequest="80"
CornerRadius="50"
Margin="7.5"
Color="{StaticResource PhoneContrastBackgroundBrush}"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"></BoxView>
<Label x:Name="TransliterationLabel"
TextColor="{StaticResource PhoneContrastForegroundBrush}"
FontSize="28"
Text="文"
Rotation="10"
HorizontalOptions="CenterAndExpand"
Margin="0"></Label> </controls1:PitGrid>
<controls1:PitGrid x:Name="SendPit"
PitName="SendPit"
Grid.ColumnSpan="2"
Grid.Row="1"> <BoxView TranslationY="450"
x:Name="SendBox"
HeightRequest="1000"
WidthRequest="1000"
CornerRadius="500"
Margin="7.5"
Color="{StaticResource PhoneContrastBackgroundBrush}"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"></BoxView>
<Label x:Name="SendLabel"
TranslationY="30"
FontSize="28"
Rotation="45"
TextColor="{StaticResource PhoneContrastForegroundBrush}"
FontFamily="FontAwesome"
HorizontalOptions="CenterAndExpand"
Margin="0"></Label> </controls1:PitGrid> </Grid>

效果如下

创建手势控件

创建一个手势控件。他包裹的内容。是一个带有按住说话的按钮。

<controls1:PanContainer BackgroundColor="Transparent"
x:Name="DefaultPanContainer"
OnTapped="DefaultPanContainer_OnOnTapped"
AutoAdsorption="False"
OnfinishedChoise="DefaultPanContainer_OnOnfinishedChoise"> <Grid PropertyChanged="BindableObject_OnPropertyChanged"
VerticalOptions="Start"
HorizontalOptions="Start"> <BoxView HeightRequest="80"
WidthRequest="250"
Margin="7.5"
Color="{StaticResource PhoneContrastBackgroundBrush}"></BoxView>
<Label x:Name="PauseLabel"
HorizontalOptions="CenterAndExpand"
FontSize="28"
TextColor="{StaticResource PhoneForegroundBrush}"
Text="按住 说话"
Margin="0"></Label> </Grid> </controls1:PanContainer>

此时应该是可以拖动,并且在拖拽开始,进入pit,离开pit,释放时,分别触发Start,In,Out,Over四个状态。

但我们希望在拖拽时隐藏这个按钮,这将在创建动画章节将介绍。

创建TalkBox

创建一个圆角矩形,用来显示正在说话的动画。

<Grid Grid.Row="1"
Opacity="1"
x:Name="TalkBoxLayout">
<BoxView x:Name="TalkBox"
HeightRequest="80"
WidthRequest="200"
CornerRadius="20"
Margin="7.5"
Color="{StaticResource PhoneAccentBrush}"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"></BoxView> <controls:PlayingMotionView HorizontalOptions="CenterAndExpand"
x:Name="MotionView"
Margin="0"></controls:PlayingMotionView> </Grid>
</Grid>

效果如下

创建动画

拖拽物动画

在拖拽时我们希望可以隐藏拖拽物,设置 PanScalePanScaleAnimationLength属性为0,代码如下:

<controls1:PanContainer BackgroundColor="Transparent"
x:Name="DefaultPanContainer"
OnTapped="DefaultPanContainer_OnOnTapped"
AutoAdsorption="False"
PanScale="0.0"
PanScaleAnimationLength="0">

按钮激活动画

Codebeind代码中,配置Active和DeActive方法,用于激活和取消激活功能区域按钮的样式。

激活时,对应功能区域按钮背景颜色变为白色,字体颜色变为黑色,并且放大到1.2倍。

取消激活时,恢复到原来的样式。

代码如下



private void Active(BoxView currentContent, Label text, Color toColor, Color txtToColor, double scaleTo = 1.2)
{
currentContent.AbortAnimation("ActivateFunctionAnimations");
var parentAnimation = new Animation(); var txtFromColor = text.TextColor; var animation2 = new Animation(t => text.TextColor = GetColor(t, txtFromColor, txtToColor), 0, 1, Easing.SpringOut); var fromColor = currentContent.Color; var animation4 = new Animation(t => currentContent.Color = GetColor(t, fromColor, toColor), 0, 1, Easing.SpringOut);
var animation5 = new Animation(v => currentContent.Scale = v, currentContent.Scale, scaleTo); parentAnimation.Add(0, 1, animation2);
parentAnimation.Add(0, 1, animation4);
parentAnimation.Add(0, 1, animation5); parentAnimation.Commit(this, "ActivateFunctionAnimations", 16, 300);
} private void DeActive(BoxView currentContent, Label text)
{
currentContent.AbortAnimation("DeactivateFunctionAnimations");
var parentAnimation = new Animation(); var txtFromColor = text.TextColor;
var txtToColor = (Color)Application.Current.Resources["PhoneContrastForegroundBrush"]; var animation2 = new Animation(t => text.TextColor = GetColor(t, txtFromColor, txtToColor), 0, 1, Easing.SpringOut); var fromColor = currentContent.Color;
var toColor = (Color)Application.Current.Resources["PhoneContrastBackgroundBrush"]; var animation4 = new Animation(t => currentContent.Color = GetColor(t, fromColor, toColor), 0, 1, Easing.SpringOut);
var animation5 = new Animation(v => currentContent.Scale = v, currentContent.Scale, 1.0); parentAnimation.Add(0, 1, animation2);
parentAnimation.Add(0, 1, animation4);
parentAnimation.Add(0, 1, animation5); parentAnimation.Commit(this, "DeactivateFunctionAnimations", 16, 300);
}

在拖拽进入pit的事件中设置激活状态,在拖拽离开pit的事件中设置取消激活状态。


case PanType.Out:
switch (args.CurrentPit?.PitName)
{
case "CancelPit":
DeActive(this.CancelBox, this.CancelLabel);
break; case "SendPit":
DeActive(this.SendBox, this.SendLabel);
break; case "TransliterationPit":
DeActive(this.TransliterationBox, this.TransliterationLabel);
break; default:
break;
}
break;
case PanType.In:
var parentAnimation = new Animation(); Color toColor = default;
double translationX = default;
double width = default;
switch (args.CurrentPit?.PitName)
{
case "CancelPit":
Active(this.CancelBox, this.CancelLabel, Colors.White, Colors.Black); this.TalkBox.AbortAnimation("TalkBoxAnimations"); break; case "SendPit":
Active(this.SendBox, this.SendLabel, Colors.Gray, Colors.Black, 1.0);
break; case "TransliterationPit":
Active(this.TransliterationBox, this.TransliterationLabel, Colors.White, Colors.Black);
break; default:
break;
}

TalkBox动画

创建GetColor方法,使用插值法用于获取渐变过程中获取当前进度的颜色

    private Color GetColor(double t, Color fromColor, Color toColor)
{
return Color.FromRgba(fromColor.Red + t * (toColor.Red - fromColor.Red),
fromColor.Green + t * (toColor.Green - fromColor.Green),
fromColor.Blue + t * (toColor.Blue - fromColor.Blue),
fromColor.Alpha + t * (toColor.Alpha - fromColor.Alpha));
}

在进入功能区域时,TalkBox的颜色,偏移量和宽度都会发生变化,创建一个复合动画TalkBoxAnimations,用于触发TalkBox的动画效果。

this.TalkBox.AbortAnimation("TalkBoxAnimations");

var fromColor = this.TalkBox.Color;

var animation2 = new Animation(t => this.TalkBox.Color = GetColor(t, fromColor, toColor), 0, 1, Easing.SpringOut);
var animation4 = new Animation(v => this.TalkBoxLayout.TranslationX = v, this.TalkBoxLayout.TranslationX, translationX);
var animation5 = new Animation(v => this.TalkBox.WidthRequest = v, this.TalkBox.Width, width); parentAnimation.Add(0, 1, animation2);
parentAnimation.Add(0, 1, animation4);
parentAnimation.Add(0, 1, animation5); parentAnimation.Commit(this, "TalkBoxAnimations", 16, 300);

最终效果如下:

Layout动画

创建一个用于显示功能区域和TalkBox的渐变动画,用于在拖拽开始和结束时,显示和隐藏这两个控件。

private void ShowLayout(double opacity = 1)
{
this.PitContentLayout.FadeTo(opacity);
this.TalkBoxLayout.FadeTo(opacity);
}
case PanType.Over:
ShowLayout(0);
break;
case PanType.Start:
ShowLayout();
break;

项目地址

Github:maui-samples

[MAUI]模仿微信“按住-说话”的交互实现的更多相关文章

  1. 一个模仿微信群聊的H5页面

    开始 上半年小米Max发布的时候,做了一个在朋友圈传播的模仿微信的群聊界面H5页面:一群公司的大咖在群里聊小米Max,用户可以向大咖们提问,以此了解产品. 页面的主体是群聊对话,同时在对话中包含了很多 ...

  2. 自定义控件(模仿微信ToggleButton控件)

    弄过android开发的都知道,系统有一个默认的ToggleButton,但很多人都觉得他很难看,当然也包括我.如果你感觉他不难看,那你就继续使用系统的吧,这篇文章对你来说是多余的了. 今天来写一个模 ...

  3. (转)ZXing生成二维码和带logo的二维码,模仿微信生成二维码效果

    场景:移动支付需要对二维码的生成与部署有所了解,掌握目前主流的二维码生成技术. 1 ZXing 生成二维码 首先说下,QRCode是日本人开发的,ZXing是google开发,barcode4j也是老 ...

  4. Android Studio精彩案例(三)《模仿微信ViewPage+Fragment实现方式二》

    转载本专栏文章,请注明出处,尊重原创 .文章博客地址:道龙的博客 写在前面的话:此专栏是博主在工作之余所写,每一篇文章尽可能写的思路清晰一些,属于博主的"精华"部分,不同于以往专栏 ...

  5. .Net Webapi SignalR与微信小程序的交互

    .Net Webapi SignalR与微信小程序的交互 一.SignalR与Webapi 1.SignalR的安装: Signalr与跨域仅需要安装两个开源库 Microsoft.Owin.Cors ...

  6. 微信网页悬浮窗交互效果的web实现

    一.微信的悬浮窗交互效果 微信更新后,发现多了个悬浮窗功能.公众号阅读,网页浏览回退后默认会出现.再点击,可以回到刚才阅读的地方.于是,再也不会遇到回复老婆的信息,再切换回来重新找刚才阅读东西的麻烦了 ...

  7. css模仿微信弹出菜单

      css模仿微信弹出菜单 效果图: html: <div class="action-sheet-backdrop"> <div class="act ...

  8. js模仿微信语音播放的小功能

    自己写的一个模仿微信语音播放的小功能,实现的主要功能是:点击播放,点击暂停,播放切换,,,  代码如下: <!DOCTYPE html> <html lang="en&qu ...

  9. Android 模仿微信启动动画(转)

    本文内容 环境 项目结构 演示微信启动动画 本文演示微信启动动画.请点击此处下载,自行调试. 顺便抱怨一下,实践性(与研究性质的相对)技术博的“七宗罪”: 第一宗罪,错字连篇,逻辑不清: 第二宗罪,文 ...

  10. wing带你玩转自定义view系列(3)模仿微信下拉眼睛

    发现了爱神的自定义view系列,我只想说一个字:凸(艹皿艹 ) !!相见恨晚啊,早看到就不会走这么多弯路了 另外相比之下我这完全是小儿科..所以不说了,这篇是本系列完结篇....我要从零开始跟随爱哥脚 ...

随机推荐

  1. Ubuntu系统设置普通用户最大文件打开数

    背景: Elasticsearch启动报错: ERROR:  bootstrap checks failedmax file descriptors [4096] for elasticsearch ...

  2. 彻底解决impdp还原数控时提示不让写日志的问题ORA-39064

    之前通过impdp导入时一直没问题,突然今天导入时出现了问题,如下: 加权限.换空间.用其他用户均失败.   后来在网上找的大部分都是说字符集什么的,如果是字符集的问题那我之前导入应该就有问题,不可能 ...

  3. 08.File类与IO流

    一.File 类 是文件和目录路径名的抽象表示,主要用于文件和目录的创建.查找和删除等操作. Java 把电脑中的文件和文件夹(目录)封装为了一个 File 类. File 类是与系统无关的类,任何操 ...

  4. FHAdmin实战获取shell

    又是一个愉快的摸鱼的一天,闲来无事去逛先知社区突然看到了一篇名为shrio权限实战绕过的文章(https://xz.aliyun.com/t/8311),这时不禁突然 回想起来之前看到过的一个微信公众 ...

  5. C# 图片 等 文件 读取操作 的一点提示

    源于:在读取图片时,总喜欢首先采用:Image img=Image.FromFile("");操作,这种方式由于 调用图片的程序与图片文件是通过 绝对地址关联的,会造成 当前进程或 ...

  6. COM调用 – VB、PB

    本文使用Delphi和C++创建CRC32的COM文件(Dll). VB: V9.0 PB: V8.0 Delphi创建的文件,提供给VB9调用:C++创建的文件,提供给PB8调用. 一.VB部分 C ...

  7. Sql Server代理作业、定时任务

    需求: 本次需求为每15分钟获取一次思路为,创建结果表,代理作业定时更新数据并存入结果表,后端只需要调用结果表数据数据,如果超期不同的天数则给出不同的提示信息,因为没有触发点,所以用到了本文内容. 右 ...

  8. 调用webservice校时

    先调用网络获取网络时间 namespace Utility{    /// <summary>     /// 网络时间     /// </summary>     publ ...

  9. 不使用setTimeout的延迟执行

    function sleep(ms){ var time = new Date(); time.setTime(time.getTime() + ms); while(new Date().getTi ...

  10. MAC范洪攻击-macof

    macof 目的:攻击交换机的路由表,实现网络信息嗅探 macof是dsniff中的一个小工具 概要:交换机中存在着一个记录着MAC地址的表,为了完成数据的快速转发,这个表有着自动学习机制,学习后可以 ...