wenmo8 发布的文章

Mono 官网主页
[1]和 Mono
GitHub 页面今日发布公告
[2],微软宣布将 Mono 项目移交给 WineHQ 组织,也就是 Linux 兼容 Windows 应用框架 Wine 的开发团队。

image

微软从 2014 年开始向.NET 开源发展,
当时微软宣布.NET Core即将开源
[3]。然而,在此之前,.NET 就已经有了一个名为 Mono 的开源计划。 Mono 项目始于 2001 年,最初它主要致力于为 Linux 桌面应用程序建立一个.NET 开发平台。第一次正式版本是在 2003 年发布的,此后该项目逐渐演进为在多个平台和操作系统上支持.NET。 这一事件标明微软将Mono 的现代化改造的工作已经完成,是一个里程碑事件。Wine 是一款免费和开源的兼容层,其中就包含 Mono 引擎。它使得在运行 Linux、FreeBSD 或其他一些操作系统的计算机上运行一些 Windows 应用程序成为可能。它也是 Valve 用来让 Steam Deck 用户在 Linux 驱动的手持设备上玩 Windows 游戏的 Proton 软件的基础。目前 Mono 项目已经移交到了 WineHQ 官网 Gitlab 页面:
https://gitlab.winehq.org/wine-mono/mono[4
]。

Mono 是基于.NET Framework 的,.NET Framework 是作为 Windows 一部分发布的.NET 大版本。经过多年的发展,Mono 和.NET 都可以应用在不同的环境中。在.NET 领域中,这最终导致了.NET Core 3+(这是所有未来工作和创新的基础)的发布,而.NET Framework 是一个长期维护的版本,它将继续被修复和调整。 但不会有任何重大的创新。

在 Mono 的世界里,运行时演进为支持我们所谓的“移动配置文件”,它是 API 的一个精选子集,适用于平衡用户需求与降低独立运行时部署的需求。这是 Xamarin .NET 努力的基础,也是最近 WebAssembly 工作的基础。

使用.NET 的不同场景以及使用它们的环境将开发人员必须使用的 API 集合分割开了。对于开发人员来说,没有一种简单的方法可以发布在所有平台上都可以工作的库二进制文件。

创建能够适用于所有不同环境的二进制库的愿望是创建.NET 标准的推动力——一个 API 的通用界面,可以在所有不同版本的.NET 上工作,无论是小型设备、移动系统,还是大型服务器。我们在这里所采取的方法是提出一套在所有平台上都可以平等使用的 API。但这仍然意味着我们要维护库的不同实现。

2020年发布的.NET 5完成了.NET Framework、Mono和.NET Core的统一工作,整个生态的统一工作现在还在继续,这一事件标明在运行时层面已经完成了CoreCLR 和 Mono 的统一,上层的库使用同一套API。统一了类库的实现,同时为用户提供了两种运行时选项。在较高的层次上,在 CoreCLR 中具有一个高吞吐量、高性能的运行时,还具有一个轻量级(但速度没有那么快)的 Mono 运行时。每个运行时都已针对它们最常使用的工作负载进行了调整:CoreCLR 用于服务器和桌面应用程序;Mono 用于移动和轻量级应用程序,例如 WebAssembly。可以在我们支持的所有平台上执行 C#或 F#代码。在某些平台上,用户将能够选择他们想要使用的运行时,而在其他平台上,将只有一个运行时可以使用。例如,对于 Windows 上的桌面应用程序,只有 CoreCLR 运行时适用,而对于 iOS,只有 Mono 运行时可用。

下个月.NET 9将发布RC版本,它将于年底在 .NET Conf 2024 上发布。我们最关注的领域是云原生和智能应用程序开发。现在 .NET 是可以并行安装了,并且可以按照用户需要的频率进行升级,我们有了千载难逢的机会来共同演进语言和运行时,而且你已经可以看到其中的一些东西了。


相关链接:

嵌入式STM32单片机开发环境配置教学Win/Mac

· 本教程支持Windows和Mac

· Windows可选的开发软件为Keil、Clion、STM32CubeMX,可自由选择开发方式

· Mac的开发环境为(Clion+OpenOCD+STM32CubeMX),仅支持HAL库

Windows配置教程

在Windows上面开发Stm32有多种工具组合,可以单纯使用Keil进行库函数、Hal库以及寄存器的开发,也可以通过Stm32CubeMX配合Keil开发STM32的Hal库,也可以使用CLion+OpenOCD+STM32CubeMX的组合来更现代化的开发Hal库,每种方式都有其优点,读者可以任意选择一个更加适合自己的开发方式,笔者使用的是第三种也就是CLion+OpenOCD+STM32CubeMX的组合,本章节中除了会讲述软件和环境的安装配置还会对每种开发方式中相关软件工程文件的使用配置进行基本的概述。

Keil5配置教程

需要的安装包下载地址:Keil5配置包.zip
​        链接:
https://pan.baidu.com/s/1joWgDoV7qujdAT3Jack0_w?pwd=nbvw
​        提取码:nbvw

一、软件介绍

Microcontroller Development Kit(简称 MDK),是 ARM 旗下公司 Keil 为 ARM 系列单片机 开发的一款便捷的开发工具,支持市面上绝大部分的单片机系列。

二、环境配置注意事项!!!

1.
安装的所有路径都不用能有中文出现!!

2. 在配置环境前请将电脑上的所有!
注意是所有!
的杀毒软件全部关掉包括Windows自带的安全中心也全部关掉!!!

Windows安全中心关闭方法:

· 点击"设置">"隐私与安全性">"Windows安全中心">"打开Windows安全中心"

· 依次点击"病毒和威胁防护"、"防火墙和网络保护",关闭里面的保护内容即可

3. 下载解压后文件夹里应该有以下几个文件及文件夹,请先行检查是否存在.

· MDK531.exe

· Keil.STM32F4xx_DFP.2.13.0.pack

· keil.STM32F1xx_DFP.2.2.0.pack

· ARM.CMSIS.5.7.0.pack

· keygen_new2032.exe

· 串口调试助手(文件夹)

· ST-LINK驱动(文件夹)

· CH340驱动(USB串口驱动)_XP_WIN7共用 (文件夹)

如下图所示:

文件夹图片

4. 安装配置文件无病毒如果被安全软件误删,请动动小手关闭安全软件并从安全软件中恢复被“和谐”的文件。

在下图所示位置:选中发现的威胁将被隔离的文件选择"允许在设备上"然后点击"执行操作"即可
2

三、安装步骤

1.鼠标右键点击 "MDK531.exe",并选择以管理员身份启动进行安装

点击Next即可开始安装

安装keil

2.任意位置进行安装

任意位置均可安装,但建议将安装位置选择D盘防止后期C盘爆满(C盘清洁设置具体操作方法可以查看我的这篇Blogs:
Win11减少C盘占用及清爽系统配置教程
)

安装位置

安装过程中会要求填写姓名地址等联系方式,这部分无论填什么都不会影响后续的使用可以随意填写

安装填信息

填写完信息后耐心等待安装完成即可过程中可能会出现CMD窗口,完成安装后点击Finish便完成了软件本体的安装

安装完成

点击Finish后会出现一个Keil的界面,在这个界面里选择左上角Packs,把Check For Updates on launch的勾去掉后 关闭窗口,忽略提示即可。

关闭更新

3.安装芯片驱动包

安装完软件之后我们还需要安装STM32的软件驱动包,我们提供在学习和项目过程常用的F1和F4芯片的驱动包供大家使用,依次点击最开始文件夹中的"Keil.STM32F4xx_DFP.2.13.0.pack"和"keil.STM32F1xx_DFP.2.2.0.pack"安装即可,步骤可参考软件本体的安装步骤。

4.破解方法

安装之后我们还不能正常使用本软件还需对其进行破解,首先在桌面或者在开始菜单中找到安装完成的Keil5软件("keil uVision5"),右键以管理员身份打开,选择左上角"File"下的"License Management"并复制弹出窗口里面的CID.

破解

然后打开文件夹中的破解软件
"keygen_new2032.exe"
(
PS:如果没有在解压之前关闭安全中心,这个破解软件可能会被安全中心查杀
)将刚刚复制的内容粘贴到 CID 框,
Target
选择
ARM
,下面的下拉框选择
Professional
,然后点击Generate后会得到一个软件的激活码

破解软件1

然后将获得的激活码复制填入New License ID Code中,点击Add LIC,中间表格区域出现激活内容即算激活成功

破解2

至此,keil的安装完美结束啦!!!!!!!!!!!!!!!!

STM32CubeMX安装配置教程

一、软件介绍

STM32CubeMX 是 ST 意法半导体近几年来大力推荐的STM32 芯片图形化配置工具,目的就是为了方便开发者,允许用户使用图形化向导生成C初始化代码,可以大大减轻开发工作,时间和费用,提高开发效率。STM32CubeMX几乎覆盖了STM32 全系列芯片。在CubeMX 上,通过傻瓜化的操作便能实现相关配置,从MCU/MPU选型,引脚配置,系统时钟以及外设时钟设置,到外设参数配置,中间件参数配置,它给STM32开发者们提供了一种简单,方便,并且直观的方式来完成这些工作,所有的配置完成后它还可以根据所选的IDE生成对应的工程和初始化C代码。除此以外,STM32CubeMX还提供了功耗计算工具,可作为产品设计中功耗评估的参考。最终能够生成C语言代码,支持多种工具链,比如 MDK、IAR For ARM、TrueStudio等 省去了我们配置各种外设的时间,大大的节省了时间,并且随着ST停止了标准库的更新,ST官方大力推荐CubeMX与HAL库的使用,虽然现在还是 标准库的主场,但是CubeMX的使用率在逐年提高,所以学习下CubeMX的基本使用也是我们需要掌握的技能之一。

CubeMX/HAL 库使用学习链接:
http://t.csdnimg.cn/UH7Ah

Java 官网:
https://www.java.com/en/download/manual.jsp

CubeMX安装官网:
https://www.stmcu.com.cn/Designresource/detail/software/711298

二、安装软件

1. 进入上方给出的STM32CubeMX官网后点击下载(
在官网下载需要注册下ST官网账号,目前最新为6.10.0版本,软件本身免费,使用Clion 作为开发工具的不要下载最新版,建议下载6.4.0.
)拿到安装包后右键选择以管理员权限运行本安装包,并选择"Install For All Users"

stm

2. 随后在出现的界面一直点击Next即可


stm2

3. 选择安装路径的界面建议将软件安装至D盘,后期软件包大小有些许大安装在D盘可以防止C盘爆满影响系统运行速度

stm3

4. 之后一直选择Next并选择默认配置即可完成安装

stm4

三、安装芯片驱动包

打开安装好的 STM32CubeMX 软件 点上面的Help->Manageembeddedsoftware packages 会跳出来一个选择型号界面勾选上你要安装的HAL库,点击“InstallNow” 直到安装成功即可。

stm5

s

至此,STM32CubeMX的安装完美结束啦!!!!!!!!!!!!!!!!

OpenOCD安装配置方法

OpenOCD安装地址:
Download OpenOCD for Windows (gnutoolchains.com)

一、工具介绍

OpenOCD(Open On-Chip Debugger)是一款开源的开放式片上调试软件,需要在调 试适配器(如:JTAG、SWD等)的配合下可以对片上系统进行相应调试,以及在嵌入式设备 上测试系统内程序或边界接扫描测试。

二、安装方法

1. 在上述链接里选择下载OpenOCD并解压

ocd

2. 将解压后文件的bin目录添加到环境变量中后即可使用

首先打开系统设置,在搜索框搜索"环境"选择编辑系统环境变量,在弹出的窗口选择"环境变量"一项
ocd2

接着在下方"系统变量"窗格双击"Path"即可进入系统环境变量的编辑窗口

ocd3

最后在弹出窗口点击"浏览"后选择OpenOCD的Bin文件夹点击确认即可

ocd4

至此,OpenOCD的安装完美结束啦!!!!!!!!!!!!!!!!

Clion的安装与嵌入式开发的配置

Clion安装地址:
CLion: A Cross-Platform IDE for C and C++ by JetBrains

一、软件介绍

Clion 是 Jetson 推出的一个C和C++集成开发环境,其可以支持嵌入式开发,虽然这个软件是收费的但如果你是学生或者教育工作者的话均可以申请免费使用但是速度通过速度较慢大致需要1Days的样子,还有一种方式在此不做过多的赘叙(为何你不看看万能的Tao宝呢???)

同时由于Clion自带MinGW,所以其实配置Clion的过程中不需要像其他网上的教程所说重新下载一遍MinGW!!!

二、安装教程

Clion的安装较为简单,在此就不详细展开唯一要注意的便是在安装的界面中记得勾选"添加Bin文件到PATH"免得后期自己添加过于麻烦,只不过安装完成之后需要重启一下更新系统环境变量

clion

三、嵌入式开发配置

安装完Clion、STM32CubeMX以及OpenOCD后便可以开始配置Clion的嵌入式开发环境,由于新版的Clion无法在启动界面打开软件设置(也有可能是我没找到入口?),读者可以先随便新建一个项目并打开,在打开的项目主页面点击"文件"->"构建、执行、部署"->"嵌入式开发",打开之后需要选择读者STM32CubeMX以及OpenOCD对应的bin文件的安装路径,一般来说添加了STM32CubeMX以及OpenOCD的环境变量后Clion会自动更新其路径,但如果没有自动更新出现路径也可以点击后面的三个点来手动选择软件的路径

s

在选择好软件的路径之后可以点击后面的测试按钮来测试STM32CubeMX以及OpenOCD在Clion上面是否成功配置,如果点击了测试后弹出的提示信息是绿色的即代表配置正确

w

至此,Clion的嵌入式配置安装完美结束啦!!!!!!!!!!!!!!!!

Mac配置教程

由于Mac系统的特殊性,Mac暂时还无法安装Keil,于是只能选择优雅的使用CLion+OpenOCD+STM32CubeMX这一套跨平台开源的的开发工具,这套工具以现代化的IDE工具Clion为核心,可以用以多个AI插件来复制开发如Github的Copliot,笔者的前开发工具便为这个,用起来那是朗朗上手!

由于Mac上的Stm32CubeMX以及Clion的配置方法与Win上的配置方法几乎一样,相关内容可以参考Windows的安装流程,本节仅介绍Mac上HomeBrew及OpenOCD的安装方法.

这里给出Mac上相关软件和工具的下载路径:

Clion:
https://www.jetbrains.com/clion/

STM32CubeMX:
https://www.st.com/en/development-tools/stm32cubemx.html#get-software

HomeBrew安装

一、工具介绍

Homebrew 是一个流行的包管理器,专为 MacOS(以及 Linux)操作系统设计。它允许用户通过命令行界面轻松安装、更新和管理软件包。Homebrew 的主要优势在于其易用性、灵活性和社区支持。用户可以通过简单的命令来安装所需的软件,而无需复杂的配置过程。 Homebrew 的工作原理是通过一个中央仓库,即 Homebrew Formulae,来管理软件包的配方(Formulae)。每个配方都是一个 Ruby 脚本,定义了如何下载、配置、编译和安装软件。Homebrew 社区不断更新这些配方,以确保软件包的最新性和兼容性。

home

二、安装方法

打开Mac的终端命令行

1. 检查系统中是否有HomeBrew

brew -v  #有输出的话便代表电脑中存在HomeBrew

2. 如果有HomeBrew,先进行卸载

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/uninstall.sh)"

类似于ROS,HomeBrew也有大神做了一个一键自动化安装脚本(但可能需要挂代理进行ke xue shang wang)

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" #根据提示一步步安装即可

出现一下图片中的内容即代表HomeBrew安装成功

ho

至此,Mac上的HomeBrew安装完美结束啦!!!!!!!!!!!!!!!!

OpenOCD安装

Mac由于是Unix的内核,因此在HomeBrew这种包管理工具的加持之下,Mac配置环境下载工具比Win会轻松很多,就如OpenOCD来说Mac上只需一行代码便可以安装配置完成

	brew install open-ocd  #下载OpenOCD

ARM toolchain安装

由于Mac是Arm系统于是需要安装Arm的开发工具链(类似与Win中的MinGW),同样也是两行命令就能很快解决进行下载

brew tap ArmMbed/homebrew-formulae
brew install arm-none-eabi-gcc

为了测试是否安装成功,可以在终端中输入一下命令进行检查,有输出便代表安装成功

arm-none-eabi-gcc -v 

接下来就只剩修改Clion中的STM32CubeMX以及OpenOCD路径了,配置方式和Win一样便不在赘叙

安装过程中的问题解决方法:

在安装过程中遇到的任何问题及解决办法或者文档中出现的错误欢迎大家联系我进行添加和删改!

先看一下效果吧:

如果不会写动画或者懒得写动画,就直接交给Blend来做吧;

其实Blend操作起来很简单,有点类似于在操作PS,我们只需要设置关键帧,鼠标点来点去就可以了,Blend会自动帮我们生成我们想要的动画效果.

第一步:要创建一个空的WPF项目

第二步:右键我们的项目,在最下方有一个,
在Blend中设计

如果没有这个,应该是你在安装vs的时候把它勾掉了,默认是勾选的,没有的话也可以重新安装回来

点一下上方导航条的
工具栏

安装一下就可以了

第三步:打开的Blend项目操作页面和WPF的页面几乎是一模一样的

我们在里面添加一个button按钮,设置长100,高30

然后在设计器中右键我们的Button,找到编辑模板,编辑副本, 点击确定, 然后vs会给我们生成一大段代码

他的ControlTemplate里面的代码是这样的

<Border x:Name="border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="true">
     <ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>

我们改成下面这样一段代码

<Grid>
     <Border x:Name="border" Background="{TemplateBinding Background}" BorderBrush="#FFDACFCF" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="true" CornerRadius="10"/>
<Border x:Name="border1" Background="#7FFFFFFF" CornerRadius="10">
<Border.Clip>
<RectangleGeometry>
<RectangleGeometry.Rect>
<Rect Width="0" Height="30" X="50" Y="0"/>
</RectangleGeometry.Rect>
</RectangleGeometry>
</Border.Clip>
</Border>
<ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> </Grid>

我们把原本的ContentPresenter从Border内部拿出来,然后在他们中间夹一层Border,再设置一下这个Border的背景色为White,接着在属性面板里面把他的透明度调整成百分之50

(也可以自己根据喜好调整颜色和透明度,只要能看到Button的文本就行了)

再给这个border添加一个Clip(Clip是什么可以把鼠标放在Clip上,按F1,查看文档说明)

设置里面的Rect的坐标和宽高(坐标点是基于空间的左上角,左上角就是(0,0),右下角就是(宽,高))

因为我们设置的从中间像两边扩散的效果,所以我们把这个矩形放到中间位置,也就是(50,0)的位置

第四步:现在来利用Blend来写一段动画

Blend得左边有一个
文档大纲

打开它就能看见
对象和时间轴

点一下那个绿色得
+
号,Blend会创建一个空得动画

接着整个界面就会被红色的线圈住

这个时候我们就可以添加动画了

我们在0秒和1秒的位置,分别点一下那个记录关键帧的按钮

然后我们再点一下1秒关键帧的位置

最后在代码里面选中我们的Border的Rect

修改这里的值,就表示,在1秒以后,这个border的clip会变成什么值

初始值是50,0,0,30

我们改成0,0,100,30,按回车,Blend就自动把动画给生成了

第五步:把这个动画运用到Button的Triggers下面的IsMouseOver为true的下面

<Trigger Property="IsMouseOver" Value="true">
    <Trigger.EnterActions>
        <BeginStoryboard x:Name="bs1" Storyboard="{StaticResource Storyboard1}"/>
    </Trigger.EnterActions>
    <Trigger.ExitActions>
         <StopStoryboard BeginStoryboardName="bs1"/>
    </Trigger.ExitActions>
</Trigger>

这样在IsMouseOver这个属性变成True的时候,就会执行这个动画了;

或者我们也可以用EventTrigger来实现这个效果

<EventTrigger RoutedEvent="{x:Static UIElement.MouseEnterEvent}">
     <BeginStoryboard Storyboard="{StaticResource Storyboard1}" x:Name="bs1"/>
</EventTrigger>
<EventTrigger RoutedEvent="{x:Static UIElement.MouseLeaveEvent}">
     <StopStoryboard BeginStoryboardName="bs1"/>
</EventTrigger>

区别就在于一个是在属性变更的时候触发动画,一个是在路由事件中执行动画,二者任选其一即可

项目github地址:
bearhanQ/WPFFramework: Share some experience (github.com)

QQ技术交流群:332035933;

HarmonyOS SDK实况窗服务
(Live View Kit)作为一个实时呈现应用服务信息变化的小窗口,遍布于设备的各个使用界面,它的魅力在于将复杂的应用场景信息简洁提炼并实时刷新,在不影响当前其他应用操作的情况下,时刻向用户展示最新的信息动态,用户也可以点击实况窗卡片或胶囊进入应用落地页查看详细信息,享受来自应用的高效信息同步服务。

image

实况窗服务为不同场景定制了多样化的卡片模板,包括进度可视化模板、强调文本模板、左右文本模板、赛事比分模板、导航模板,除了这5种卡片形态的模板外,实况窗还有实况胶囊和实况计时器两种形态。下面,本文将详细展示这些模板,介绍其适用的场景,并讲解模板的具体实现步骤。

开发准备

在创建本地实况窗之前,需要先完成基本的准备工作,并开通实况窗服务权益。开通实况窗权益大致分为5个步骤,详细的申请步骤可参考实况窗服务的
开发指南

开发步骤

下面将以在本地创建、更新和结束实况窗为例,展示具体的开发步骤。

1.导入liveViewManager。

在创建本地实况窗前,需要在项目中导入liveViewManager,并新建实况窗控制类,构造isLiveViewEnabled()方法,用于校验实况窗开关是否打开。打开实况窗开关是创建实况窗的前提条件。示例代码如下:

import { liveViewManager } from '@kit.LiveViewKit';

export class LiveViewController {
private static async isLiveViewEnabled(): Promise<boolean> {
return await liveViewManager.isLiveViewEnabled();
  }
}

2.创建实况窗。

实况窗根据扩展区不同共有5种样式模板:进度可视化模板、强调文本模板、左右文本模板、赛事比分模板和导航模板。

进度可视化模板

进度可视化模板可适用于打车、外卖等需要呈现完整进程及当前节点的场景,通过进度可视化模板的实况窗,用户可一眼查看应用的服务进程和实时变化。这里以即时配送场景为例,展示具体的示例代码。

image

在构建LiveViewController后,需要在代码中初始化LiveViewController并调用liveViewManager.startLiveView()方法创建实况窗。其中event的取值为DELIVERY则代表即时配送场景,若取值为TAXI则表示出行打车场景。

import { liveViewManager } from '@kit.LiveViewKit';
import { Want, wantAgent } from '@kit.AbilityKit';

export class LiveViewController {
  public async startLiveView(): Promise<liveViewManager.LiveViewResult> {
    // 校验实况窗开关是否打开
    if (!LiveViewController.isLiveViewEnabled()) {
      throw new Error("Live view is disabled.");
    }
    // 创建实况窗
    const defaultView = await LiveViewController.buildDefaultView();
    return await liveViewManager.startLiveView(defaultView);
  }

  private static async buildDefaultView(): Promise<liveViewManager.LiveView> {
    return {
      // 构造实况窗请求体
      id: 0, // 实况窗ID,开发者生成。
      event: "DELIVERY", // 实况窗的应用场景。DELIVERY:即时配送(外卖、生鲜)
      liveViewData: {
        primary: {
          title: "骑手已接单",
          content: [
            { text: "距商家 " },
            { text: "300 ", textColor: "#FF007DFF" },
            { text: "米 | " },
            { text: "3 ", textColor: "#FF007DFF" },
            { text: "分钟到店" }
          ], // 所有文本仅能设置为一种颜色,不设置textColor时,默认展示#FF000000
          keepTime: 15,
          clickAction: await LiveViewController.buildWantAgent(),
          layoutData: {
            layoutType: liveViewManager.LayoutType.LAYOUT_TYPE_PROGRESS,
            progress: 40,
            color: "#FF317AF7",
            backgroundColor: "#f7819ae0",
            indicatorType: liveViewManager.IndicatorType.INDICATOR_TYPE_UP,
            indicatorIcon: "indicator.png", // 进度条指示器图标,取值为
"/resources/rawfile"路径下的文件名
            lineType: liveViewManager.LineType.LINE_TYPE_DOTTED_LINE,
            nodeIcons: ["icon_1.png", "icon_2.png", "icon_3.png"] // 进度条每个节点图标,
取值为"/resources/rawfile"路径下的文件名
          }
        }
      }
    };
  }

  private static async isLiveViewEnabled(): Promise<boolean> {
    return await liveViewManager.isLiveViewEnabled();
  }

  private static async buildWantAgent(): Promise<Want> {
    const wantAgentInfo: wantAgent.WantAgentInfo = {
      wants: [
        {
          bundleName: 'xxx.xxx.xxx', // 应用实际bundleName
          abilityName: 'EntryAbility'
        } as Want
      ],
      operationType: wantAgent.OperationType.START_ABILITIES,
      requestCode: 0,
      wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
    };
    const agent = await wantAgent.getWantAgent(wantAgentInfo);
    return agent;
  }
}

强调文本模板

强调文本模板适用于取餐、排队等需要强调部分文本信息的场景。通过强调文本模板实况窗,用户可以快速获取取餐码、排号情况等重要信息,这里以取餐场景为例,展示具体的示例代码。

image

在强调文本模板中,event取值为PICK_UP则代表取餐场景,若取值为QUEUE则代表排队场景。

import { liveViewManager } from '@kit.LiveViewKit';
import { Want, wantAgent } from '@kit.AbilityKit';

export class LiveViewController {
  public async startLiveView(): Promise<liveViewManager.LiveViewResult> {
    // 校验实况窗开关是否打开
    if (!LiveViewController.isLiveViewEnabled()) {
      throw new Error("Live view is disabled.");
    }
    // 创建实况窗
    const defaultView = await LiveViewController.buildDefaultView();
    return await liveViewManager.startLiveView(defaultView);
  }

  private static async buildDefaultView(): Promise<liveViewManager.LiveView> {
    return {
      // 构造实况窗请求体
      id: 0, // 实况窗ID,开发者生成。
      event: "PICK_UP", // 实况窗的应用场景。PICK_UP:取餐。
      liveViewData: {
        primary: {
          title: "餐品已备好",
          content: [
            { text: "请前往", textColor: "#FF000000" },
            { text: "XXX店取餐", textColor: "#FF000000" }
          ],
          keepTime: 15,
          clickAction: await LiveViewController.buildWantAgent(),
          layoutData: {
            layoutType: liveViewManager.LayoutType.LAYOUT_TYPE_PICKUP,
            title: "取餐码",
            content: "72988",
            underlineColor: "#FF0A59F7",
            descPic: "coffee.png"
          }
        }
      }
    };
  }
  ... ...
}

左右文本模板

左右文本模板适用于高铁、航班等左右信息对称的场景,通过该模板,用户可以快速获取始发地、目的地、开始和结束时间等出行信息。这里以高铁列车票场景为例,展示具体的示例代码。

image

在左右文本模板中,event取值为TRAIN则代表高铁/火车场景,若取值为FLIGHT则代表航班场景。

import { liveViewManager } from '@kit.LiveViewKit';
import { Want, wantAgent } from '@kit.AbilityKit';

export class LiveViewController {
  public async startLiveView(): Promise<liveViewManager.LiveViewResult> {
    // 校验实况窗开关是否打开
    if (!LiveViewController.isLiveViewEnabled()) {
      throw new Error("Live view is disabled.");
    }
    // 创建实况窗
    const defaultView = await LiveViewController.buildDefaultView();
    return await liveViewManager.startLiveView(defaultView);
  }

  private static async buildDefaultView(): Promise<liveViewManager.LiveView> {
    return {
      // 构造实况窗请求体
      id: 0, // 实况窗ID,开发者生成。
      event: "TRAIN", // 实况窗的应用场景。TRAIN:高铁/火车。
      liveViewData: {
        primary: {
          title: "列车检票提醒",
          content: [
            { text: "检票口 " },
            { text: "6B ", textColor: "#FF007DFF" },
            { text: "| 座位 " },
            { text: "03车 12F", textColor: "#FF007DFF" }
          ],// 所有文本仅能设置为一种颜色,不设置textColor时,默认展示#FF000000
          keepTime: 15,
          clickAction: await LiveViewController.buildWantAgent(), // 点击实况窗默认动作。
          layoutData: {
            layoutType: liveViewManager.LayoutType.LAYOUT_TYPE_FLIGHT,
            firstTitle: "09:00",
            firstContent: "上海虹桥",
            lastTitle: "14:20",
            lastContent: "汉口",
            spaceIcon: "icon.png",
            isHorizontalLineDisplayed: true,
            additionalText: "以上信息仅供参考" // 扩展区底部内容,仅可用于左右文本模板。
          }
        }
      }
    };
  }
  ... ...
}

赛事比分模板

赛事比分模板适用于竞技比赛的场景,通过该模板,用户可以快速获取比赛队伍、当前比分、场次等比赛信息。

image

在赛事比分模板中,SCORE代表赛事比分场景。

import { liveViewManager } from '@kit.LiveViewKit';
import { Want, wantAgent } from '@kit.AbilityKit';

export class LiveViewController {
  public async startLiveView(): Promise<liveViewManager.LiveViewResult> {
    // 校验实况窗开关是否打开
    if (!LiveViewController.isLiveViewEnabled()) {
      throw new Error("Live view is disabled.");
    }
    // 创建实况窗
    const defaultView = await LiveViewController.buildDefaultView();
    return await liveViewManager.startLiveView(defaultView);
  }

  private static async buildDefaultView(): Promise<liveViewManager.LiveView> {
    return {
      // 构造实况窗请求体
      id: 0, // 实况窗ID,开发者生成。
      event: "SCORE", // 实况窗的应用场景。SCORE:赛事比分。
      liveViewData: {
        primary: {
          title: "第四节比赛中",
          content: [
            { text: "XX VS XX" },
            { text: " | ", textColor: "#f7b7b1b3"},
            { text: "小组赛第五场"}
          ],
          keepTime: 1,
          clickAction: await LiveViewController.buildWantAgent(),
          layoutData: {
            layoutType: liveViewManager.LayoutType.LAYOUT_TYPE_SCORE,
            hostName: "队名A",
            hostIcon: "host.png",
            hostScore: "110",
            guestName: "队名B",
            guestIcon: "guest.png",
            guestScore: "102",
            competitionDesc: [
              { text: "●", textColor: "#FFFF0000" },
              { text: "Q4" }
            ],
            competitionTime: "02:16",
            isHorizontalLineDisplayed: true
          }
        }
      }
    };
  }
  ... ...
}

导航模板

导航模板适用于出行导航场景。通过该模板,用户可以快速获取所需导航的目的地大致方位信息。在导航模板中,event取值为NAVIGATION则代表导航场景。

image

import { liveViewManager } from '@kit.LiveViewKit';
import { Want, wantAgent } from '@kit.AbilityKit';

export class LiveViewController {
  public async startLiveView(): Promise<liveViewManager.LiveViewResult> {
    // 校验实况窗开关是否打开
    if (!LiveViewController.isLiveViewEnabled()) {
      throw new Error("Live view is disabled.");
    }
    // 创建实况窗
    const defaultView = await LiveViewController.buildDefaultView();
    return await liveViewManager.startLiveView(defaultView);
  }

  private static async buildDefaultView(): Promise<liveViewManager.LiveView> {
    return {
      // 构造实况窗请求体
      id: 0, // 实况窗ID,开发者生成。
      event: "NAVIGATION", // 实况窗的应用场景。NAVIGATION:导航。
      liveViewData: {
        primary: {
          title: "178米后左转",
          content: [
            { text: "去往", textColor: "#FF000000" },
            { text: " 南京东路", textColor: "#FF000000" }
          ],
          keepTime: 15,
          clickAction: await LiveViewController.buildWantAgent(),
          layoutData: {
            layoutType: liveViewManager.LayoutType.LAYOUT_TYPE_NAVIGATION,
            currentNavigationIcon: "navigation.png",
            navigationIcons: ["left.png","straight.png","straight.png","right.png"]
          }
        }
      }
    };
  }
  ... ...
}

实况胶囊

实况胶囊是在设备熄屏和状态栏中展示的区别于卡片态的另一种实况形态,胶囊内需显示最精简、最重要的内容,保证用户一瞥即得重要信息。并且,胶囊形态各模板参数固定,与创建实况窗时的模板类型无关。

image

在同步创建实况窗胶囊时,需要在liveViewManager.LiveView结构体中携带胶囊所需的参数capsule,不同胶囊类型携带不同的参数。可创建的胶囊类型有:文本胶囊、计时器胶囊和进度胶囊。这里以文本胶囊为例,展示具体的示例代码。

import { liveViewManager } from '@kit.LiveViewKit';
import { Want, wantAgent } from '@kit.AbilityKit';

export class LiveViewController {
  public async startLiveView(): Promise<liveViewManager.LiveViewResult> {
    // 校验实况窗开关是否打开
    if (!LiveViewController.isLiveViewEnabled()) {
      throw new Error("Live view is disabled.");
    }
    // 创建实况窗
    const defaultView = await LiveViewController.buildDefaultView();
    return await liveViewManager.startLiveView(defaultView);
  }

  private static async buildDefaultView(): Promise<liveViewManager.LiveView> {
    return {
      // 构造实况窗请求体
      id: 0, // 实况窗ID,开发者生成。
      event: "DELIVERY", // 实况窗的应用场景。DELIVERY:即时配送(外卖、生鲜)。
      liveViewData: {
        primary: {
          title: "餐品待支付",
          content: [
            { text: "咖啡 ", textColor: "#FF000000" },
            { text: "等2件商品", textColor: "#FF000000" }
          ],
          keepTime: 15,
          clickAction: await LiveViewController.buildWantAgent(),
          layoutData: {
            layoutType: liveViewManager.LayoutType.LAYOUT_TYPE_PICKUP,
            title: "待支付金额",
            content: "25.5元",
            underlineColor: "#FF0A59F7",
            descPic: "coffee.png"
          }
        },
        // 实况胶囊相关参数
        capsule: {
          type: liveViewManager.CapsuleType.CAPSULE_TYPE_TEXT,
          status: 1,
          icon: "capsule_store.png",
          backgroundColor: "#ff0676e7",
          title: "待支付"
        }
      }
    };
  }
  ... ...
}

实况窗计时器

实况窗计时器适用于排队、抢票等场景。开发者若需要使用实况窗计时器,则需在liveViewManager.LiveView结构体中的配置timer字段,并在当前支持的字段中使用占位符:${placeholder.timer}。

image

具体的示例代码如下:

import { liveViewManager } from '@kit.LiveViewKit';
import { Want, wantAgent } from '@kit.AbilityKit';

export class LiveViewController {
  public async startLiveView(): Promise<liveViewManager.LiveViewResult> {
    // 校验实况窗开关是否打开
    if (!LiveViewController.isLiveViewEnabled()) {
      throw new Error("Live view is disabled.");
    }
    // 创建实况窗
    const defaultView = await LiveViewController.buildDefaultView();
    return await liveViewManager.startLiveView(defaultView);
  }

  private static async buildDefaultView(): Promise<liveViewManager.LiveView> {
    return {
      // 构造实况窗请求体
      id: 0, // 实况窗ID,开发者生成。
      event: "QUEUE", // 实况窗的应用场景。QUEUE:排队
      timer: {
        time: 620000,
        isCountdown: false,
        isPaused: false
      },
      liveViewData: {
        primary: {
          title: "大桌4人等位  32桌",
          content: [
            { text: "已等待 " }, 
            { text: "${placeholder.timer}", textColor:"#ff10c1f7" },
            { text: " | 预计还需>30分钟" }
          ], // 所有文本仅能设置为一种颜色,不设置textColor时,默认展示#FF000000
          keepTime: 15,
          clickAction: await LiveViewController.buildWantAgent(),
          layoutData: {
            layoutType: liveViewManager.LayoutType.LAYOUT_TYPE_PROGRESS,
            progress: 0,
            color: "#FFFF0000",
            backgroundColor: "#FF000000",
            indicatorType: liveViewManager.IndicatorType.INDICATOR_TYPE_OVERLAY,
            indicatorIcon: "indicator.png", // 进度条指示器图标,取值为
"/resources/rawfile"路径下的文件名
            lineType: liveViewManager.LineType.LINE_TYPE_DOTTED_LINE,
            nodeIcons: ["icon_1.png","icon_2.png"] // 进度条每个节点图标,取值为
"/resources/rawfile"路径下的文件名
          }
        }
      }
    };
  }
  ... ...
}

3.本地更新和结束实况窗。

在本地创建完实况窗后,若应用业务状态发生变化,则需要调用liveViewManager的updateLiveView()更新实况窗,更新时对请求体中需要修改的对应参数进行修改。在该应用的服务进程结束时,需要调用stopLiveView()来结束实况窗。这里以即时配送场景的进度可视化模板为例,来说明更新和结束实况窗及实况胶囊的方法,具体示例代码如下:

import { liveViewManager } from '@kit.LiveViewKit';
import { Want, wantAgent } from '@kit.AbilityKit';

export class LiveViewController {
  private static contentColor: string = '#FF000000';
  private static capsuleColor: string = '#FF308977';

  public async startLiveView(): Promise<liveViewManager.LiveViewResult> {
    // 校验实况窗开关是否打开
    if (!LiveViewController.isLiveViewEnabled()) {
      throw new Error("Live view is disabled.");
    }
    // 创建实况窗
    const defaultView = await LiveViewController.buildDefaultView();
    return await liveViewManager.startLiveView(defaultView);
  }

  public async updateLiveView(): Promise<liveViewManager.LiveViewResult> {
    // 校验实况窗开关是否打开
    if (!LiveViewController.isLiveViewEnabled()) {
      throw new Error("Live view is disabled.");
    }
    // 修改实况窗内容
    const defaultView = await LiveViewController.buildDefaultView();
    defaultView.liveViewData.primary.title = "预计23:49送达";
    defaultView.liveViewData.primary.content = [
      { text: "等待商家接单,",
        textColor: LiveViewController.contentColor },
      { text: "03:20未接单自动取消",
        textColor: LiveViewController.contentColor }
    ];
    defaultView.liveViewData.primary.layoutData = {
      layoutType: liveViewManager.LayoutType.LAYOUT_TYPE_PROGRESS,
      progress: 0,
      lineType: 0,
      nodeIcons: [ // 进度条每个节点图标,取值为"/resources/rawfile"路径下的文件名
        'icon_store_white.png',
        'icon_finish.png'
      ]
    };
    defaultView.liveViewData.capsule = {
      type: liveViewManager.CapsuleType.CAPSULE_TYPE_TEXT,
      status: 1,
      icon: 'capsule_store.png',
      backgroundColor: LiveViewController.capsuleColor,
      title: "待接单"
    };
    // 更新实况窗
    return await liveViewManager.updateLiveView(defaultView);
  }

  public async stopLiveView(): Promise<liveViewManager.LiveViewResult> {
    // 校验实况窗开关是否打开
    if (!LiveViewController.isLiveViewEnabled()) {
      throw new Error("Live view is disabled.");
    }
    // 修改实况窗内容
    const defaultView = await LiveViewController.buildDefaultView();
    defaultView.liveViewData.primary.title = '商品已送达';
    defaultView.liveViewData.primary.content = [
      { text: '感谢您的认可,',
        textColor: LiveViewController.contentColor },
      { text: '期待下一次光临',
        textColor: LiveViewController.contentColor }
    ];
    defaultView.liveViewData.primary.layoutData = {
      layoutType: liveViewManager.LayoutType.LAYOUT_TYPE_PROGRESS,
      progress: 100,
      lineType: 0,
      nodeIcons: [ // 进度条每个节点图标,取值为"/resources/rawfile"路径下的文件名
        'icon_order.png',
        'icon_finish.png'
      ]
    };
    defaultView.liveViewData.capsule = {
      type: liveViewManager.CapsuleType.CAPSULE_TYPE_TEXT,
      status: 1,
      icon: 'capsule_gps.png',
      backgroundColor: LiveViewController.capsuleColor,
      title: '已送达'
    };
    // 结束实况窗
    return await liveViewManager.stopLiveView(defaultView);
  }

  private static async buildDefaultView(): Promise<liveViewManager.LiveView> {
    return {
      // 构造实况窗请求体
      id: 0, // 实况窗ID,开发者生成。
      event: "DELIVERY", // 实况窗的应用场景。DELIVERY:即时配送(外卖、生鲜)
      liveViewData: {
        primary: {
          title: "餐品待支付",
          content: [
            { text: "咖啡 ", textColor: "#FF000000" },
            { text: "等2件商品", textColor: "#FF000000" }
          ],
          keepTime: 15,
          clickAction: await LiveViewController.buildWantAgent(),
          layoutData: {
            layoutType: liveViewManager.LayoutType.LAYOUT_TYPE_PICKUP,
            title: "待支付金额",
            content: "25.5元",
            underlineColor: "#FF0A59F7",
            descPic: "coffee.png"
          }
        },
        // 实况胶囊相关参数
        capsule: {
          type: liveViewManager.CapsuleType.CAPSULE_TYPE_TEXT,
          status: 1,
          icon: "capsule_store.png",
          backgroundColor: "#FF308977",
          title: "待支付",
          content: "..."
        }
      }
    };
  }
  ... ...
}

了解更多详情>>

获取
实况窗服务开发指导文档

科学计算当中会用到不少浮点数的操作,这些浮点数可能是16位,32位,64位,80位甚至是128位。开源项目SoftFloat提供了一个高效的浮点运算实现,可以在没有硬件支持的情况下,高效模拟浮点数的各种操作。

那么,浮点数之间的比较,基本运算这些究竟是怎么实现的呢,可以拿32位浮点数作为例子。

这是32位浮点数加法的实现,首先声明了一个结构体float32_t。

typedef struct { uint32_t v; } float32_t;

这提供了32位浮点数的底层位表示,同时还声明了一个union。

union ui32_f32 { uint32_t ui; float32_t f; };

一方面保存了浮点数的位表示,另一方面也可以转换为32位无符号整型直接进行比较,这在后面的算法当中会直接涉及。先看看加法。

float32_t f32_add( float32_t a, float32_t b )
{
    union ui32_f32 uA;
    uint_fast32_t uiA;
    union ui32_f32 uB;
    uint_fast32_t uiB;
#if ! defined INLINE_LEVEL || (INLINE_LEVEL < 1)
    float32_t (*magsFuncPtr)( uint_fast32_t, uint_fast32_t );
#endif

    uA.f = a;
    uiA = uA.ui;
    uB.f = b;
    uiB = uB.ui;
#if defined INLINE_LEVEL && (1 <= INLINE_LEVEL)
    if ( signF32UI( uiA ^ uiB ) ) {
        return softfloat_subMagsF32( uiA, uiB );
    } else {
        return softfloat_addMagsF32( uiA, uiB );
    }
#else
    magsFuncPtr =
        signF32UI( uiA ^ uiB ) ? softfloat_subMagsF32 : softfloat_addMagsF32;
    return (*magsFuncPtr)( uiA, uiB );
#endif

}

这里uiA和uiB是存储无符号整型的,signF32UI是提取符号位的。signF32UI(uiA ^ uiB)判断符号位是否相同,如果相同则调用加法,如果符号位不相同则调用减法,因为没有浮点数,所以只能通过整型去模拟,另外,union存储浮点和整型有一个名词,似乎叫类型双关技术?不过这里union存储的只是位表示,并不是真的浮点数。

float32_t f32_sub( float32_t a, float32_t b )
{
    union ui32_f32 uA;
    uint_fast32_t uiA;
    union ui32_f32 uB;
    uint_fast32_t uiB;
#if ! defined INLINE_LEVEL || (INLINE_LEVEL < 1)
    float32_t (*magsFuncPtr)( uint_fast32_t, uint_fast32_t );
#endif

    uA.f = a;
    uiA = uA.ui;
    uB.f = b;
    uiB = uB.ui;
#if defined INLINE_LEVEL && (1 <= INLINE_LEVEL)
    if ( signF32UI( uiA ^ uiB ) ) {
        return softfloat_addMagsF32( uiA, uiB );
    } else {
        return softfloat_subMagsF32( uiA, uiB );
    }
#else
    magsFuncPtr =
        signF32UI( uiA ^ uiB ) ? softfloat_addMagsF32 : softfloat_subMagsF32;
    return (*magsFuncPtr)( uiA, uiB );
#endif

}

减法则是在判断符号那里反过来,其它一样。这时候可以看看比较运算怎么做。

bool f32_le( float32_t a, float32_t b )
{
    union ui32_f32 uA;
    uint_fast32_t uiA;
    union ui32_f32 uB;
    uint_fast32_t uiB;
    bool signA, signB;

    uA.f = a;
    uiA = uA.ui;
    uB.f = b;
    uiB = uB.ui;
    if ( isNaNF32UI( uiA ) || isNaNF32UI( uiB ) ) {
        softfloat_raiseFlags( softfloat_flag_invalid );
        return false;
    }
    signA = signF32UI( uiA );
    signB = signF32UI( uiB );
    return
        (signA != signB) ? signA || ! (uint32_t) ((uiA | uiB)<<1)
            : (uiA == uiB) || (signA ^ (uiA < uiB));

}

最后的表达式有点绕,一步一步拆分。首先符号不相等(一正一负)的话,如果A的符号是1,也就是负数,肯定比B小,否则走 || 后的分支。把A和B的最高位(符号位)剔除,判断是否相同,也就是+0和-0的情况,这里记得别漏了前面的!符号,因为判断两者是否都为0;如果A和B同号的话,如果都是正数则直接比较,如果都是负数,则前面的signA会对结果取反。

结语

最近处于校招阶段,正在准备,有时间会分享自己的心得和体会,希望尽早上岸。