@

.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. hdu:排列组合(指数型母函数)

    Problem Description有n种物品,并且知道每种物品的数量.要求从中选出m件物品的排列数.例如有两种物品A,B,并且数量都是1,从中选2件物品,则排列有"AB",&q ...

  2. turtle绘制风轮

    题目要求: 使用turtle库,绘制一个风轮效果,其中,每个风轮内角为45度,风轮边长150像素. 我的代码: import turtle turtle.setup(500,500,100,200) ...

  3. 【原创】GmSSL Android库编译

    相关内容: GmSSL Linux编译 环境搭建 重要 用编译方法2编译出的库,集成到工程之后,发现报 incompatible target错误,各种找不到定义.32位和64位都不行. 如果你也遇到 ...

  4. format UTF-8 BOM by AX

    #File CommaTextIo commaTextIo; FileIOPermission permission; CustTable custTable; str fileName = @&qu ...

  5. mysql数据迁移,通用windows->linux,linux->windows

    1. 将用到的数据库文件夹直接拷贝到目标文件夹,mysql5.7在linux中默认在var/lib/mysql,将data下ibdata1也要拷贝进去 2. linux下需要将所有者root改为mys ...

  6. 微信小程序-实现微信登录

    业务流程: 1:首先需要一个按钮触发事件 2:调用微信小程序的登录接口wx.login,拿到code 3:调用微信小程序的获取用户信息的接口wx.getUserProfile,拿到用户的个人信息 4: ...

  7. SpringBoot3.0 + SpringSecurity6.0+JWT

    JWT_SpringSecurity SpringBoot3.0 + SpringSecurity6.0+JWT Spring Security 是 Spring 家族中的一个安全管理框架. 一般We ...

  8. Spring-纯Java创建一个SSM【webapp】

    纯Java搭建webapp QuickStart 使用纯 Java 来搭建一个 SSM 环境,即在项目中,不存在任何 XML 配置,包括 web.xml 1创建一个Maven工程 引入依赖 <! ...

  9. 痞子衡嵌入式:分享一个i.MXRT系列配套DRAM压力测试上位机工具(i.MXRT DRAM Tester)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是恩智浦SE团队制作的i.MXRT配套DRAM压力测试上位机工具. 事情源于痞子衡的技术交流群里的提问,有群友在恩智浦官方技术公众号 [恩 ...

  10. Navicat基础教程

    1.安装 可以直接前往官网进行安装,网址如下: https://navicat.com.cn/download/navicat-premium 下载之后将下载后的压缩包解压到当前文件夹,然后根据里面自 ...