鸿蒙原生系列之帧动画
〇、前言
之前所说的动画效果,到了这里,就只剩下帧动画这一种动画效果,还未系统学习,下面就开始就鸿蒙 NDK UI 中如何实现帧动画效果,进行详细学习。
一、动画分类
4、帧动画
4.1、ArkUI_AnimatorOption
实现帧动画的第一步,就是先创建一个动画参数,帧动画使用的动画参数对象,与属性动画所用的不是同一个,所以,千万别应用错了 ,虽然两者就差了几个字母,但是在讲究严谨的计算机世界中,失之毫厘,谬以千里。
首先,了解一下ArkUI_AnimatorOption对象相关的 API:

一般来说,这上面列出的API都需要调用一遍,因为只有这样,准备的动画参数才结构完整,才不会出现非预期的效果。
4.2、ArkUI_ExpectedFrameRateRange
实现帧动画的步骤中,除了需要设置常见的播放时长、播放延迟等动画参数外,还需要针对每一帧的预期帧率,准备一个 ArkUI_ExpectedFrameRateRange 对象并设置到动画参数中,该对象的结构如下:

而通过 API OH_ArkUI_AnimatorOption_SetExpectedFrameRateRange:

可以将预期帧率同动画参数结合起来,也就是将预期帧率追加到动画参数中。
4.3、ArkUI_CurveHandle
由于帧动画对应的动画参数设置动画曲线的方式,比较特殊,不是通过枚举值去设置,而是通过传入一个 ArkUI_CurveHandle 对象的形式进行设置,然而,ArkUI_CurveHandle 对象需要使用专门的动画曲线创建方法去创建,目前,支持的动画曲线类型有如下:

这些 API 都定义在 native_animate.h 文件中。
使用上述 API 创建出来的 ArkUI_CurveHandle 对象,同时可以用在 OH_ArkUI_AnimatorOption_SetKeyframeCurve 方法的参数中。
4.4、Callback
再次强调,动画是一种事件,应该为其设置所支持的回调 ,对于帧动画来说,支持设置的回调类型有如下四种,或者说支持进行回调处理的时机情况有如下四种 :
1)动画接收到帧时
2)动画完成时
3)动画被取消时
4)动画被重复时

4.5、动画控制
在之前的动画效果案例中,动画效果都是『直肠子』的,一旦触发,就不能中断,直到预设的整个动画效果播放完成,这一次,将结合动画控制去演示帧动画。
动画控制,主要有以下几种控制动作:
1)创建动画
2)播放动画
3)结束动画
4)重置动画
5)暂停动画
6)取消动画
7)反转动画
4.5.1、创建动画
创建动画,就是准备好帧动画的动画参数后,使用 ArkUI_NativeAnimateAPI_1 的特定 API:createAnimator 创建一个 animator 动画对象:

4.5.2、播放动画
播放动画的控制,不是利用 ArkUI_NativeAnimateAPI_1 的成员方法,而是利用OH_ArkUI_Animator_Play 方法:

该方法的参数对象,正是创建动画一步操作中获得的 ArkUI_AnimatorHandle 对象,也正是这个 ArkUI_AnimatorHandle 对象,是我们进行剩下几种动画控制操作所不能欠缺的存在。
4.5.3、结束动画
结束动画,使用 OH_ArkUI_Animator_Finish 方法:

4.5.4、重置动画
重置动画,其实就是创建一个新的帧动画参数,并针对性调整动画参数中的相关参数值,比如使用与之前所不同的动画曲线,然后再通过方法 OH_ArkUI_Animator_ResetAnimatorOption 去更新动画:

4.5.5、暂停动画
暂停动画,使用方法 OH_ArkUI_Animator_Pause() 实现:

4.5.6、取消动画
曲线动画,使用方法 OH_ArkUI_Animator_Cancel 实现功能:

4.5.7、反转动画
所谓反转动画 ,指的是以相反的顺序播放动画 ,比如预设的动画效果是先放大后旋转再平移,那么反转之后就变成先平移后旋转再放大,实现该功能需要使用方法 OH_ArkUI_Animator_Reverse:

二、动画实现
4、属性动画
4.1、页面结构

一如既往,从简单到复杂,先将页面结构实现出来。如上图所示,整个页面的由 ArkTS 层和 C++ 层共同实现,其中红色方框圈选的部分为 C++ 实现,都是按钮,没有按钮标题的那一个就是演示动画效果的承载者,而剩下的都是为了控制动画所准备的。
C++ 相关的 UI 代码如下:
cpp
std::shared_ptr<ArkUIButtonNode> g_animator_button = nullptr;
ArkUI_AnimatorHandle animatorHandle = nullptr;
std::shared_ptr<ArkUIBaseNode> testFrameAnimate()
{
// 创建根节点 Column
auto column = std::make_shared<ArkUIColumnNode>();
column->SetPercentHeight(1);
column->SetPercentWidth(1);
column->SetItemAlign(ARKUI_HORIZONTAL_ALIGNMENT_CENTER);
column->SetJustifyContent(ARKUI_FLEX_ALIGNMENT_START);
// 创建button,后续创建的Animator动画作用在button组件上
auto button = std::make_shared<ArkUIButtonNode>();
// 设置button初始宽高
button->SetPercentWidth(0.2);
button->SetHeight(60);
button->SetBackgroundColor(0xFF000000);
// 存储button全局变量,在onTouch注册时需要使用
g_animator_button = button;
// 创建createButton,用于初始化Animator参数
auto createButton = std::make_shared<ArkUIButtonNode>();
createButton->SetPercentWidth(0.2);
createButton->SetHeight(40);
createButton->SetBackgroundColor(0xFF000000);
createButton->SetMargin(10, 0, 0, 0);
createButton->SetLabel("create");
// 设置Animator播放按钮
auto playButton = std::make_shared<ArkUIButtonNode>();
playButton->SetPercentWidth(0.2);
playButton->SetHeight(40);
playButton->SetBackgroundColor(0xFF000000);
playButton->SetMargin(10, 0, 0, 0);
playButton->SetLabel("play");
// 设置Animator结束按钮
auto finishButton = std::make_shared<ArkUIButtonNode>();
finishButton->SetPercentWidth(0.2);
finishButton->SetHeight(40);
finishButton->SetBackgroundColor(0xFF000000);
finishButton->SetMargin(10, 0, 0, 0);
finishButton->SetLabel("finish");
// 设置Animator更新按钮
auto resetButton = std::make_shared<ArkUIButtonNode>();
resetButton->SetPercentWidth(0.2);
resetButton->SetHeight(40);
resetButton->SetBackgroundColor(0xFF000000);
resetButton->SetMargin(10, 0, 0, 0);
resetButton->SetLabel("reset");
// 设置Animator暂停按钮
auto pauseButton = std::make_shared<ArkUIButtonNode>();
pauseButton->SetPercentWidth(0.2);
pauseButton->SetHeight(40);
pauseButton->SetBackgroundColor(0xFF000000);
pauseButton->SetMargin(10, 0, 0, 0);
pauseButton->SetLabel("pause");
// 设置Animator取消按钮
auto cancelButton = std::make_shared<ArkUIButtonNode>();
cancelButton->SetPercentWidth(0.2);
cancelButton->SetHeight(40);
cancelButton->SetBackgroundColor(0xFF000000);
cancelButton->SetMargin(10, 0, 0, 0);
cancelButton->SetLabel("cancel");
// 设置Animator以相反的顺序播放按钮
auto reverseButton = std::make_shared<ArkUIButtonNode>();
reverseButton->SetPercentWidth(0.2);
reverseButton->SetHeight(40);
reverseButton->SetBackgroundColor(0xFF000000);
reverseButton->SetMargin(10, 0, 0, 0);
reverseButton->SetLabel("reverse");
column->AddChild(button);
column->AddChild(createButton);
column->AddChild(playButton);
column->AddChild(finishButton);
column->AddChild(resetButton);
column->AddChild(pauseButton);
column->AddChild(cancelButton);
column->AddChild(reverseButton);
return column;
}
虽然代码量很多,但实际上都是相似度很高的代码,如果你为了追求代码简洁,你甚至可以将那些用来设置按钮的宽高、背景色以及标题的代码,抽取成一个公共方法。
4.2、创建动画
接下来,逐一地为页面上的几个动画控制按钮实现交互,首先,实现『create』按钮的点击事件处理功能:
cpp
// 注册点击事件到button上
createButton->RegisterNodeEvent(createButton->GetHandle(), NODE_ON_CLICK, 3, nullptr);
auto onTouch = [](ArkUI_NodeEvent *event)
{
// 点击button按钮时触发该逻辑
if (OH_ArkUI_NodeEvent_GetTargetId(event) == 3) {
showUITextCallback("帧动画", "创建");
// 获取context对象
static ArkUI_ContextHandle context = nullptr;
context = OH_ArkUI_GetContextByNode(g_animator_button->GetHandle());
// 获取ArkUI_NativeAnimateAPI接口
auto animateApi = NativeModuleInstance::GetInstance()->GetNativeAnimateAPI();
// 以下代码为创建Animator动画的关键流程,包括设置Animator动画参数、开启Animator动画
// 设置ArkUI_AnimatorOption参数,通过提供的C方法设置对应的参数
static ArkUI_AnimatorOption *option = OH_ArkUI_AnimatorOption_Create(0); // Animator动画状态数
OH_ArkUI_AnimatorOption_SetDuration(option, 2000); // 动画播放时长
OH_ArkUI_AnimatorOption_SetDelay(option, 10); // 动画延迟播放时长
OH_ArkUI_AnimatorOption_SetIterations(option, 3); // 动画重复次数
OH_ArkUI_AnimatorOption_SetFill(option, ARKUI_ANIMATION_FILL_MODE_NONE); // 是否恢复初始状态
OH_ArkUI_AnimatorOption_SetDirection(option, ARKUI_ANIMATION_DIRECTION_NORMAL); // 动画播放方向
ArkUI_CurveHandle curve =
OH_ArkUI_Curve_CreateCubicBezierCurve(0.5f, 4.0f, 1.2f, 0.0f); // 构造三阶贝塞尔曲线对象
OH_ArkUI_AnimatorOption_SetCurve(option, curve); // 动画插值曲线
OH_ArkUI_AnimatorOption_SetBegin(option, 100); // 动画插值起点
OH_ArkUI_AnimatorOption_SetEnd(option, 150); // 动画插值终点
ArkUI_ExpectedFrameRateRange *range = new ArkUI_ExpectedFrameRateRange;
range->max = 120;
range->expected = 60;
range->min = 30;
OH_ArkUI_AnimatorOption_SetExpectedFrameRateRange(option, range); // 设置Animator动画帧率
OH_ArkUI_AnimatorOption_SetKeyframe(option, 0.5, 120.5, 0); // 设置Animator动画关键帧参数
OH_ArkUI_AnimatorOption_SetKeyframeCurve(option, curve, 0); // 设置Animator动画关键帧曲线类型
OH_ArkUI_AnimatorOption_RegisterOnFrameCallback(
option, nullptr,
[](ArkUI_AnimatorOnFrameEvent *event)
{
showUITextCallback("帧动画事件", "接收到帧");
OH_ArkUI_AnimatorOnFrameEvent_GetUserData(event); // 获取动画事件对象中的用户自定义对象
auto value = OH_ArkUI_AnimatorOnFrameEvent_GetValue(event); // 获取动画事件对象中的当前进度
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Init",
"CXX OH_ArkUI_AnimatorOption_RegisterOnFrameCallback %{public}f", value);
g_animator_button->SetWidth(value);
});
OH_ArkUI_AnimatorOption_RegisterOnFinishCallback(option, reinterpret_cast<void *>(option),
[](ArkUI_AnimatorEvent *event)
{
showUITextCallback("帧动画事件", "动画结束");
OH_ArkUI_AnimatorEvent_GetUserData(
event); // 获取动画事件对象中的用户自定义对象
});
OH_ArkUI_AnimatorOption_RegisterOnCancelCallback(
option, nullptr, [](ArkUI_AnimatorEvent *event) { showUITextCallback("帧动画事件", "动画被取消"); });
OH_ArkUI_AnimatorOption_RegisterOnRepeatCallback(
option, nullptr, [](ArkUI_AnimatorEvent *event) { showUITextCallback("帧动画事件", "动画被重复"); });
// 执行对应的动画
animatorHandle = animateApi->createAnimator(context, option);
}
};
// 注册点击事件的回调函数
createButton->RegisterNodeEventReceiver(onTouch);
这里,涵盖了这一篇的关键代码,即帧动画参数的设置以及帧动画的创建。
4.3、动画控制
由于动画的播放、结束、暂停、取消和反转,都是调用一个 API 就能完成,所以,为了节省篇幅,相关实现代码直接集中在这一节里贴出:
cpp
playButton->RegisterNodeEvent(playButton->GetHandle(), NODE_ON_CLICK, 4, nullptr);
auto onTouchPlay = [](ArkUI_NodeEvent *event)
{
// 点击button按钮时触发该逻辑
if (OH_ArkUI_NodeEvent_GetTargetId(event) == 4) {
showUITextCallback("帧动画", "播放");
OH_ArkUI_Animator_Play(animatorHandle);
}
};
playButton->RegisterNodeEventReceiver(onTouchPlay);
finishButton->RegisterNodeEvent(finishButton->GetHandle(), NODE_ON_CLICK, 5, nullptr);
auto onTouchFinish = [](ArkUI_NodeEvent *event)
{
// 点击button按钮时触发该逻辑
if (OH_ArkUI_NodeEvent_GetTargetId(event) == 5) {
showUITextCallback("帧动画", "结束");
OH_ArkUI_Animator_Finish(animatorHandle);
}
};
finishButton->RegisterNodeEventReceiver(onTouchFinish);
pauseButton->RegisterNodeEvent(pauseButton->GetHandle(), NODE_ON_CLICK, 7, nullptr);
auto onTouchPause = [](ArkUI_NodeEvent *event)
{
// 点击button按钮时触发该逻辑
if (OH_ArkUI_NodeEvent_GetTargetId(event) == 7) {
showUITextCallback("帧动画", "暂停");
OH_ArkUI_Animator_Pause(animatorHandle);
}
};
pauseButton->RegisterNodeEventReceiver(onTouchPause);
cancelButton->RegisterNodeEvent(cancelButton->GetHandle(), NODE_ON_CLICK, 8, nullptr);
auto onTouchCancel = [](ArkUI_NodeEvent *event)
{
// 点击button按钮时触发该逻辑
if (OH_ArkUI_NodeEvent_GetTargetId(event) == 8) {
showUITextCallback("帧动画", "取消");
OH_ArkUI_Animator_Cancel(animatorHandle);
}
};
cancelButton->RegisterNodeEventReceiver(onTouchCancel);
reverseButton->RegisterNodeEvent(reverseButton->GetHandle(), NODE_ON_CLICK, 9, nullptr);
auto onTouchReverse = [](ArkUI_NodeEvent *event)
{
// 点击button按钮时触发该逻辑
if (OH_ArkUI_NodeEvent_GetTargetId(event) == 9) {
showUITextCallback("帧动画", "反转");
OH_ArkUI_Animator_Reverse(animatorHandle);
}
};
reverseButton->RegisterNodeEventReceiver(onTouchReverse);
4.4、重置动画
接下来,将剩余的 『reset』按钮的点击事件处理函数完成,便可以进行真机演示:
cpp
resetButton->RegisterNodeEvent(resetButton->GetHandle(), NODE_ON_CLICK, 6, nullptr);
auto onTouchReset = [](ArkUI_NodeEvent *event)
{
// 点击button按钮时触发该逻辑
if (OH_ArkUI_NodeEvent_GetTargetId(event) == 6) {
showUITextCallback("帧动画", "重置");
static ArkUI_AnimatorOption *option = OH_ArkUI_AnimatorOption_Create(0); // Animator动画状态数
OH_ArkUI_AnimatorOption_SetDuration(option, 1000);
OH_ArkUI_AnimatorOption_SetDelay(option, 0);
OH_ArkUI_AnimatorOption_SetIterations(option, 4);
// 根据自己得需要选择下述两种曲线适合得去设置OH_ArkUI_AnimatorOption_SetCurve
auto curve =
OH_ArkUI_Curve_CreateCurveByType(ARKUI_CURVE_EASE); // 动画以低速开始,然后加快,在结束前变慢
auto stepsCurve = OH_ArkUI_Curve_CreateStepsCurve(20, true); // 构造阶梯曲线对象
OH_ArkUI_AnimatorOption_SetCurve(option, stepsCurve);
OH_ArkUI_AnimatorOption_SetBegin(option, 200);
OH_ArkUI_AnimatorOption_SetEnd(option, 100);
OH_ArkUI_AnimatorOption_RegisterOnFrameCallback(
option, nullptr,
[](ArkUI_AnimatorOnFrameEvent *event)
{
showUITextCallback("帧动画事件", "接收到帧");
OH_ArkUI_AnimatorOnFrameEvent_GetUserData(event); // 获取动画事件对象中的用户自定义对象
auto value = OH_ArkUI_AnimatorOnFrameEvent_GetValue(event); // 获取动画事件对象中的当前进度
g_animator_button->SetWidth(value);
});
OH_ArkUI_Animator_ResetAnimatorOption(animatorHandle, option);
}
};
resetButton->RegisterNodeEventReceiver(onTouchReset);
所有按钮的单击处理函数中,代码量第二多的就是 『reset』按钮,因为需要重新准备帧动画参数。
4.5、真机演示
这里给个建议,进行代码的真机运行时,最好是使用鸿蒙6的设备进行,使用鸿蒙5的设备,可能会因为 API 未支持而看不到预期效果。