本文介绍如何使用 .NET MAUI 开发一个电子木鱼应用。以实际的小应用开发为例,通过这个开发过程,介绍了其涉及的 .NET MAUI、Blazor、前端等相关知识点。文章涉及的应用已开源在 Github,大家可前往下载体验: https://github.com/sangyuxiaowu/MuYu
文章目录
1. 背景
电子木鱼不知道从什么时候火了起来,成了年轻人的新时尚。年轻人没有选择经常去寺庙像和尚那样念经,而是下载了电子木鱼软件,进行线上敲木鱼。虽然搞不懂这种“敲电子木鱼,见机甲佛祖,积赛博功德,修图灵真经”的赛博玄学,但是这个软件它做起来容易,需求还简单。在这么焦虑的假期生活中,还是假期的最后一天,最适合玩一玩 .NET MAUI 了。
2. 相关知识点
这个项目及文章使用并介绍了一下相关知识点:
- Blazor 播放音频,主要为 JS 调用
- Blazor 调用 .NET MAUI 各平台原生对话框
- .NET MAUI 的 Preferences 数据存储
- Blazor 循环渲染中的一个常见 BUG
界面 UI 方面因为使用还是 Web 的技术,所以更多的是前端的知识,可自行探索。
3. 开发过程
3.1 需求分析
这个电子木鱼整体功能非常简单,主要就是点击一下屏幕播放一下木鱼敲击的音频,这个是核心功能。
再往下设计,就是一些动效的优化,比如:
- 木鱼敲击的动效,放大
- 木鱼敲击的震动反馈,当然这一条可能没必要
- 木鱼敲击后的文字浮动,一敲“+999功德”这种的
更进一层的设计:
- 这里需要引入设置功能了,加入设置菜单
- 文字浮动内容的自定义
- 自动敲击积累功德
- 自定义的敲击音和木鱼样式,这里就要开始费美工了
再接下来迭代,当然也是有很多参考的相关案例,比如:
- 排行榜功能,区域排行,日,周,月各种排行榜单
- 分享功能,满足人们的分享欲
- 添加诵经背景音,使氛围更融洽
当然,以上只是瞎扯,作为一个清心寡欲的木鱼,做好自己定位是第一步。“天之道,不争而善胜”,作为佛系木鱼,当然只是纯粹的敲击,满足最基本的需求就可以了。
3.2 开发第一步
这个开发第一步,沿用我之前讲的那个《MAUI 安卓 UI 资源设置》文章的内容,先把 UI 资源处理一下。这次被老婆拒绝了,我亲自拿钢笔工具把这个木鱼 Logo 给绘制出来了,作为一个合格的全栈,美工也是需要会的。
这个 Logo 画完,内部应用的主体界面木鱼自然也就设计好了。
3.3 核心功能
电子木鱼的核心功能,当然是点击屏幕播放一下敲击音效了。因为我们使用的是 .NET MAUI Blazor ,为了后续界面动效的设计,我们通过前端的方式来播放音乐。
为了图方便,直接在
wwwroot/index.html
HTML 入口文件中添加如下
audio
标签和 JS 方法:
<audioid="player"><sourcesrc="other/muyu.wav"/></audio><script>
window.PlayAudioFile=(tips)=>{var audio = document.getElementById('player');
audio.play();}</script>
在应用的首页添加图片点击的事件,来调用 JS,并预留了文字浮动内容的传递:
<imgid="img"src="/images/muyu.svg"style="height:100%;max-width:100%"@onclick="ClickHandler"/>
asyncTaskClickHandler(){await JS.InvokeAsync<string>("PlayAudioFile", tipsInfo);}
本小节相关知识点:《JavaScript 互操作》
3.4 动效设计
动效设计这里我们主要实现木鱼敲击放大动效和敲击后的文字浮动。这里主要是前端的技术,我们使用 CSS3 的 @keyframes 创建两个动画规则:
@keyframes showbig{0%{transform:scale(1);}50%{transform:scale(1.05);}}@keyframes showtips{0%{opacity: 1;transform:translateY(0);}75%{opacity: .9;transform:translateY(-1.5em);}100%{opacity: 0;transform:translateY(-2em);}}
这里两个动效都是默认是只循环一遍,时长 0.3s :
.showbig{animation-timing-function: ease-in-out;animation-duration: .3s;animation-name: showbig;}
通过 JS 方式控制 DOM 的 Class 添加删除来实现动画的播放:
functionShowBig(){var img = document.getElementById('img');
img.classList.add('showbig');setTimeout(()=>{
img.classList.remove('showbig');},300);}
对于浮动的文字来说,快速敲击时,为了可以显示多条,这边的浮动文字是动态创建的 P 标签。还记得前面预留的
tipsInfo
吧,是在这里使用的。
functionShowTips(text){var p = document.createElement("p");
p.classList.add('tips');
p.innerHTML = text;var tips = document.getElementById('tips');
tips.appendChild(p);setTimeout(()=>{
tips.removeChild(p)},300);}
3.5 菜单设计
有了设置当然就可以搞个菜单出来了,菜单这里的设计也非常简单,我们可以直接新建一个 Blazor 组件
Shared/SettingMenu.razor
,来实现传递菜单项来生成菜单组件,并在点击选项后返回点击的菜单序号。
<divclass="mask"style="display:@(Show?"flex":"none")"><divclass="setting_menu"><divclass="title">@MenuTitle</div><ulclass="list">
@for (int i=0;i<NavData.Count();i++){<li@onclick="e=>MenuClick(i)">@NavData[i]</li>
}
</ul></div></div>
/// <summary>/// 点击回调/// </summary>[Parameter]publicEventCallback<int> OnMenuClick {get;set;}/// <summary>/// 菜单标题/// </summary>[Parameter]publicstring MenuTitle {get;set;}="设置";/// <summary>/// 显示隐藏/// </summary>[Parameter]publicbool Show {get;set;}=false;/// <summary>/// 菜单信息/// </summary>[Parameter]publicstring[] NavData {get;set;}/// <summary>/// 通知点击事件/// </summary>/// <param name="inx">点击的index</param>/// <returns></returns>asyncTaskMenuClick(int inx){await OnMenuClick.InvokeAsync(inx);}
那,跑一下看看,似乎,大概,也许是非常完美:
3.6 对话框
在我们的悬浮文字设置选项中,需要提供输入功能。当然,直接前端的方案,设计一个模态对话框似乎是一个非常好的方案,也符合 UI 的一致性。但是考虑到能省则省,这边没有使用前端的框架,所以再手写一个会稍微费点事的。如果说省事,当然是直接前端 JS 的
prompt
,但是这样又显得格格不入:
.NET MAUI Blazor 这种混合开发的模式大多都一样,对话框也是一个小问题。不过这里可以非常方便的使用本机对话框,只要拿到
Page
即可,在
razor
文件的 code 代码块中,我们可以这样用:
string result =await Application.Current.MainPage.DisplayPromptAsync("悬浮文字设置","请设置敲击后的自定义悬浮文字","确定","取消","请输入悬浮文字",5,null, tipsInfo);
本小节相关知识点:《显示弹出窗口》
3.7 数据存储
搞定了设置文字的交互,接下来就是存储了,总体来说我们需要存储的木鱼的总敲击次数,今日敲击次数和刚刚的自定义悬浮文字。毕竟不是复杂的应用,就没必要上数据库了,对于这种简单的键值数据存储我们可以直接选用
Preferences
。
Preferences
通过调用
Preferences.Set
方法来设置,提供键和值,以下就是完整的设置悬浮文字和存储的方法:
/// <summary>/// 获取初始的悬浮文字/// </summary>privatestring tipsInfo = Preferences.Default.Get("tips","功德+1");asyncTaskSetTips(){string result =await Application.Current.MainPage.DisplayPromptAsync("悬浮文字设置","请设置敲击后的自定义悬浮文字","确定","取消","请输入悬浮文字",5,null, tipsInfo);if(string.IsNullOrWhiteSpace(result))return;
Preferences.Default.Set("tips", result);
tipsInfo = result;}
本小节相关知识点:《Preferences》
3.8 Blazor 循环小 BUG
做的差不多了,菜单也有了,可以简单跑一下看看了,但是似乎菜单出了些问题,设置菜单的按钮并不好用,每一个回调都是 4:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ywko3Od-1675503302385)(./img/2.png)]
啊对,前面的完美,我是说界面。BUG 总会有的,还总是发生在不经意之间。
元芳你怎么看?
元芳:问一问 ChatGPT 吧,他说他懂这个的。
ChatGPT:是的,我了解Blazor。Blazor是一种用于开发Web应用程序的框架,允许使用C#代码和.NET运行时在浏览器中运行Web应用程序。它提供了一种方便的方法来使用.NET技术来构建客户端Web应用程序,而无需学习JavaScript。
仔细检查我们会发现,在前面的
SettingMenu.razor
菜单组件中,我们有一个循环,通过枚举
NavData
数组中的每一项并创建一个包含该项的列表项。因为需要序号,所以这里用的 for 循环,问题在于,在回调函数
MenuClick(i)
中,变量
i
是局部的,在循环结束后其值就改变了。
为了解决这个问题,可以将当前项的索引存储在闭包中:
<ulclass="list">
@for (int i = 0; i < NavData.Count(); i++)
{
int index = i;
<li@onclick="e => MenuClick(index)">@NavData[i]</li>
}
</ul>
修改完代码,再次运行:
4. 最后
至此,这个简单的电子木鱼的基础功能已实现,应用已经完成了大半。对于未尽的事宜,比如敲击计数和其他设置的功能我下次再说,容我先去敲一会木鱼,静个心。
版权归原作者 桑榆肖物 所有, 如有侵权,请联系我们删除。