[MAUI]模仿微信“按住-说话”的交互实现
今天使用这个控件,做一个模仿微信“按住-说话”的小功能,最终效果如下:
使用.NET MAUI实现跨平台支持,本项目可运行于Android、iOS平台。
创建页面布局
新建.NET MAUI项目,命名HoldAndSpeak
MainPage.xaml中创建一个PitContentLayout
Grid容器,并对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) : 像素重映射 与 图像扭曲
文章目录 一、重映射简介二、图像扭曲 一、重映射简介 重映射,就是把一幅图像中某位置的像素放置到另一图像指定位置的过程。即: 在重映射过程中,图像的大小也可以同时发生改变。此时像素与像素之间的关系就不是一一对应关系,因…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...

BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...

算法岗面试经验分享-大模型篇
文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer (1)资源 论文&a…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...