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

使用.NET MAUI实现跨平台支持,本项目可运行于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控件命名,CancelPit、TransliterationPit,分别对应了取消发送、语音转文字、发送。
为每个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>
效果如下

创建动画
拖拽物动画
在拖拽时我们希望可以隐藏拖拽物,设置 PanScale和PanScaleAnimationLength属性为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]模仿微信“按住-说话”的交互实现
今天使用这个控件,做一个模仿微信“按住-说话”的小功能,最终效果如下: 使用.NET MAUI实现跨平台支持,本项目可运行于Android、iOS平台。 创建页面布局 新建.NET MAUI项目,命名HoldAndSpeak MainPage.xaml中创建一个…...
windows开机运行jar
windows开机自启动jar包: 一、保存bat批处理文件 echo off %1 mshta vbscript:CreateObject("WScript.Shell").Run("%~s0 ::",0,FALSE)(window.close)&&exit java -jar E:\projects\ruoyi-admin.jar > E:\server.log 2>&1 &…...
使用DockerFile一键创建自定义linux开发环境
1,使用dcokerfile文件构建镜像,参考如下文件 # 使用ubuntu:20.04镜像作为基础 FROM ubuntu:20.04# 调整时区 ENV DEBIAN_FRONTENDnoninteractive TZAsia/Shanghai# build参数 ARG userxiang usergroupduo# 设置默认工作路径 WORKDIR /home/${user}# 拷贝…...
“深入探索JVM:解密Java虚拟机的工作原理“
标题:深入探索JVM:解密Java虚拟机的工作原理 摘要:本文将深入探索Java虚拟机(JVM)的工作原理,从字节码到实际执行过程,从内存管理到垃圾回收等方面进行解析,帮助读者更好地理解和优…...
【华为OD机试】数字最低位排序【2023 B卷|100分】
【华为OD机试】-真题 !!点这里!! 【华为OD机试】真题考点分类 !!点这里 !! 题目描述 给定一个非空数组(列表) 起元素数据类型为整型 请按照数组元素十进制最低位从小到大进行排序 十进制最低位相同的元素,相对位置保持不变 当数组元素为负值时,十进制最低为等同于去除符号…...
【Odoo16前端源码分析】xml模板的加载
一、模板内容的来源 情况A,组件类的template属性,比如ActionContainer.template /* odoo/addons/web/static/src/webclient/actions/action_container.js */export class ActionContainer extends Component {setup() {..} } .. ActionContainer.templ…...
Open3D (C++) 计算矩阵的广义逆
目录 一、算法原理1、广义逆2、计算过程二、代码实现三、结果展示四、参考链接本文由CSDN点云侠原创,原文链接。爬虫网站自重,把自己当个人,爬些不完整的误导别人有意思吗???? 一、算法原理 1、广义逆 非方阵不存在逆,但是存在广义逆(伪逆)。对于一个矩阵...
11.物联网操作系统内存管理
一。STM32编译过程及程序组成 STM32编译过程 程序的组成、存储与运行 MDK生成的主要文件分析 1.STM32编译过程 1.源文件(Source code)--》目标文件(Object code) .c(C语言)通过armcc生成.o,.s(汇编&…...
举办活动发布会,如何得到媒体支持?
传媒如春雨,润物细无声,大家好,我是51媒体网胡老师。 举办活动发布会并得到媒体报道的支持是一个关键的宣传和推广手段。以下是一些建议,帮助你增加吸引媒体关注和报道的机会: 1. 策划新闻价值:确保你的发…...
1139 First Contact(unique函数,string.substr()函数)
PTA | 程序设计类实验辅助教学平台 用map套个set来实现邻接表(排序都免了) #include<bits/stdc.h> using namespace std; int n,m,k; string a,b; map<string,set<string>>mp; int main() {cin.tie(0);cin >> n >> m;fo…...
Python元编程-装饰器介绍、使用
目录 一、Python元编程装饰器介绍 二、装饰器使用 1. 实现认证和授权功能 2.实现缓存功能 3.实现日志输出功能 三、附录 1. logging.basicConfig介绍 2. 精确到毫秒,打印时间 方法一:使用datetime 方法二:使用time 一、Python元编程…...
python进程池的使用
进程池的创建 apply() apply()方法用于向进程池提交一个任务,并等待任务完成并返回结果。 apply_async() apply_async()方法用于向进程池提交一个异步任务(即无需等待任务完成),将任务加入到进程池的队列里,并立即…...
Dockerfile构建lamp镜像
1、构建目录 [rootdocker ~]# mkdir compose_lamp [rootdocker ~]# cd compose_lamp/ 2、编写Docekerfile [rootdocker compose_lamp]# vim Dockerfile #基础镜像 FROM centos:7#维护该镜像的用户信息 MAINTAINER Crushlinux <crushlinux163.com>#安装httpd RUN yum -…...
LeetCode724. 寻找数组的中心下标
题干 给你一个整数数组 nums ,请计算数组的 中心下标 。 数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。 如果中心下标位于数组最左端,那么左侧数之和视为 0 ,因为在下标的左侧不存在元素。…...
【云计算 | Docker】Docker容器后台运行不了?entrypoint在作妖?
1. 问题 使用镜像alpine起个容器,使其保持后台运行,正常情况有如下的效果,可以发现容器保持运行状态。 [rootk8s-master helloWorld]# docker run -dit docker.io/alpine /bin/sh 8d39d7579d5e4f1a560aef16ba57ab5cae2506ea9105e21cbc0634…...
DAY02_Spring第三方资源配置管理Spring容器Spring注解开发Spring整合Mybatis和Junit
目录 一 第三方资源配置管理1 管理DataSource连接池对象问题导入1.1 管理Druid连接池1.2 管理c3p0连接池 2 加载properties属性文件问题导入2.1 基本用法2.2 配置不加载系统属性2.3 加载properties文件写法 二 Spring容器1 Spring核心容器介绍问题导入1.1 创建容器1.2 获取bean…...
烘焙小程序蛋糕店烘焙店源码点心店小程序源码
本系统开发使用JAVA技术栈开发 使用uniapp技术栈 支持微信小程序 ,对接打印机,对接第三方同城跑腿平台 用户端使用:uniapp 管理端使用:vueelementui 后台服务使用:springbootjpa...
HarmonyOS 开发基础(五)对用户名做点啥
一、实现用户名检验 条件渲染 、生命周期 1.规定用户名长度 2.限定使用的数字及字母(涉及正则表达) // 导出方式直接从文件夹 import MyInput from "../common/commons/myInput" Entry Component /* 组件可以基于struct实现,组件…...
【前端】搭建Vue3框架
目录 一、搭建准备二、node.js安装1、下载并安装2、配置默认安装目录和缓存日志目录①、创建默认安装目录和缓存日志目录(我的node.js目录在D盘,所以直接在node.js文件夹下创建)②、执行命令,配置默认安装目录和缓存日志目录到刚才…...
Opencv-C++笔记 (15) : 像素重映射 与 图像扭曲
文章目录 一、重映射简介二、图像扭曲 一、重映射简介 重映射,就是把一幅图像中某位置的像素放置到另一图像指定位置的过程。即: 在重映射过程中,图像的大小也可以同时发生改变。此时像素与像素之间的关系就不是一一对应关系,因…...
【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...
23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
【Linux】Linux 系统默认的目录及作用说明
博主介绍:✌全网粉丝23W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...
