鸿蒙原生系列之动画效果
- 〇、前言
- 一、动画分类
- 二、动画实现
-
- 1、属性动画
-
- [1.1、新增 TextInput 节点](#1.1、新增 TextInput 节点)
- 1.2、页面结构
- 1.3、ArkUI_NativeAnimateAPI_1
- 1.4、testPropertiesAnimate()
- 1.5、实现效果
〇、前言
开始之前,先说下题外话,就是我自己经过在华为官方鸿蒙开发者平台工单系统上的不懈追问,终于掌握了 NDK UI 中图片节点的 NODE_IMAGE_SRC 属性,如何使用包内图片作为图片资源进行显示:ArkUI_AttributeItem item = {.string = "resource:///logo.png"};;解决了包内图片的使用后,下一步就该解决如何使用网络图片了,所以,我又开始了新一轮的追问。
言归正传,动画效果在应用UI实现过程中会经常遇到,正确使用动画效果可以让 UI 具备灵动的特性,从而吸引用户,那么,在鸿蒙 NDK UI 中,如何实现动画效果呢?下面就让我由简入繁、带领大家学习如何用 C++ 代码实现动画效果。
一、动画分类
首先,了解一下 UI 中的动画效果分为几种?目前,UI动画效果主要有:
1)属性动画
2)组件出现/消失转场动画
3)关键帧动画
4)帧动画
1、属性动画
属性动画,在四种动画效果中,属于相对简单的那种;在 NDK UI 开发方式中,实现属性动画,需要借助 ArkUI_AnimateOption 去设置具体的动画参数。ArkUI_AnimateOption 位于 native_animate.h 中,因此,实现属性动画的时候,必须导入该头文件。
动画本身是一种事件,因此,属性动画的实现代码中,少不了回调函数的存在,而在鸿蒙 NDK UI 中,主要需要一个 ArkUI_ContextCallback 类型的 update 回到,和一个 ArkUI_AnimateCompleteCallback 类型的 complete 回调,分别对应动画的过程和结束即完成。
1.1、ArkUI_AnimateOption
ArkUI_AnimateOption 对象实例的获取,不是 new 出来的,而是通过专门的创建方法 OH_ArkUI_AnimateOption_Create 进行创建。获取了 ArkUI_AnimateOption 对象实例后,就可以使用相关方法对动画的持续时长、播放速度、动画曲线、动画延迟播放时间、动画播放次数、动画播放模式和动画的期望帧率等关键属性进行设置。
1.1.1、动画时长
动画时长,该属性的设置,使用 OH_ArkUI_AnimateOption_SetDuration(ArkUI_AnimateOption* option, int32_t value) 来完成,时间值的单位是毫秒。
1.1.2、播放速度
播放速度,用 OH_ArkUI_AnimateOption_SetTempo(ArkUI_AnimateOption* option, float value) 完成,速度值跟视频播放速度的概念一样,属于倍率体系。
1.1.3、动画曲线
动画曲线的设置方法有两个,分别是 OH_ArkUI_AnimateOption_SetCurve(ArkUI_AnimateOption* option, ArkUI_AnimationCurve value) 和 OH_ArkUI_AnimateOption_SetICurve(ArkUI_AnimateOption* option, ArkUI_CurveHandle value),后者由于前者生效。
第一个设置方法,具有默认值,默认使用 ARKUI_CURVE_LINEAR ,而合法的动画曲线候选值共有如下:

1.1.4、动画延迟播放时间
使用 OH_ArkUI_AnimateOption_SetDelay(ArkUI_AnimateOption* option, int32_t value) 进行设置,而时间值的单位同样是毫秒。
1.1.5、动画播放次数
OH_ArkUI_AnimateOption_SetIterations(ArkUI_AnimateOption* option, int32_t value)可以完成动画播放次数的设置。
1.1.6、动画播放模式
OH_ArkUI_AnimateOption_SetPlayMode(ArkUI_AnimateOption* option, ArkUI_AnimationPlayMode value),用于设置动画播放模式;现有的动画播放模式共有:

动画播放模式无默认值。
1.1.7、动画播放期望帧率
用 OH_ArkUI_AnimateOption_SetExpectedFrameRateRange(ArkUI_AnimateOption* option, ArkUI_ExpectedFrameRateRange* value) 进行设置,而 ArkUI_ExpectedFrameRateRange 对象的结构如下:

1.2、ArkUI_AnimateCompleteCallback
ArkUI_AnimateCompleteCallback 对象的结构如下:

成员变量中的 ArkUI_FinishCallbackType ,具体枚举值有:

ArkUI_AnimateCompleteCallback 对象实例,使用 new 方式创建。
1.3、ArkUI_ContextCallback
ArkUI_ContextCallback 对象实例,同样使用new 方式 创建,它的对象结构与 ArkUI_AnimateCompleteCallback 类似:

二、动画实现
1、属性动画
1.1、新增 TextInput 节点
本着逐一掌握如何使用C++代码实现UI节点的原则,本文选择使用 TextInput 作为动画『演武场』;TextInput 节点的实现代码如下:
cpp
#ifndef NATIVEPC_ARKUITEXTINPUTNODE_H
#define NATIVEPC_ARKUITEXTINPUTNODE_H
#include "ArkUINode.h"
namespace NativeModule {
class ArkUITextInputNode : public ArkUINode {
public:
ArkUITextInputNode()
: ArkUINode(NativeModuleInstance::GetInstance()->GetNativeNodeAPI()->createNode(ARKUI_NODE_TEXT_INPUT)){}
};
}
#endif //NATIVEPC_ARKUITEXTINPUTNODE_H
由于不需要设置 TextInput 的私有属性,所以类定义代码就比较简洁。
1.2、页面结构

动画的播放由按钮进行控制,点击按钮就能触发下方的 textinput 开始遵循预定义的动画效果进行变化。
由于需要将按钮和文本输入框分隔开来,避免组件边界紧挨着,因此,需要一个新的组件共有属性设置方法 SetMargin:
cpp
void SetMargin(float top, float right, float bottom, float left){
assert(handle_);
ArkUI_NumberValue value[] = {{.f32 = top}, {.f32 = right}, {.f32 = bottom}, {.f32 = left}};
ArkUI_AttributeItem item = {value, 4};
nativeModule_->setAttribute(handle_, NODE_MARGIN, &item);
}
1.3、ArkUI_NativeAnimateAPI_1
播放动画,需要用到一个全新的 API------ArkUI_NativeAnimateAPI_1;说实在的,它长得跟之前认识的 ArkUI_NativeNodeAPI_1 很像,所以,ArkUI_NativeAnimateAPI_1 的获取,不妨也按照 ArkUI_NativeNodeAPI_1 的获取那样,在 NativeModule.h 文件中进行定义:
cpp
class NativeModuleInstance
{
public:
static NativeModuleInstance *GetInstance()
{
static NativeModuleInstance instance;
return &instance;
}
NativeModuleInstance()
{
// 获取NDK接口的函数指针结构体对象,用于后续操作。
OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, arkUINativeNodeApi_);
assert(arkUINativeNodeApi_);
OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_ANIMATE, ArkUI_NativeAnimateAPI_1, animateApi_);
assert(animateApi_);
}
// 暴露给其他模块使用。
ArkUI_NativeNodeAPI_1 *GetNativeNodeAPI() { return arkUINativeNodeApi_; }
ArkUI_NativeAnimateAPI_1 *GetNativeAnimateAPI() { return animateApi_; }
private:
ArkUI_NativeNodeAPI_1 *arkUINativeNodeApi_ = nullptr;
ArkUI_NativeAnimateAPI_1 *animateApi_ = nullptr;
};
ArkUI_NativeAnimateAPI_1,ArkUI提供的Native侧动画接口集合,拥有如下成员函数:

而 animateTo() 便是实现属性动画所必须的:

1.4、testPropertiesAnimate()
不妨新建一个 testAnimate.h 文件,用于专门存放动画效果相关的实现案例。
作为一名合格的程序员,尤其是从事开发岗位或自动化测试岗位的,必须具备一定的英语水平,因为这可以有效避免在为脚本、变量、方法或类进行取名的时候,找不到可以正确描述功能意义的单词;当然了,大学毕业后,谁也不可能还记得那么多单词,因此,是否有专业翻译的意识,便成了代码可读性的分水岭。
首先,给出整个案例的完整代码:
cpp
#ifndef NATIVEPC_TESTANIMATE_H
#define NATIVEPC_TESTANIMATE_H
#include "ArkUIBaseNode.h"
#include "ArkUIButtonNode.h"
#include "ArkUIColumnNode.h"
#include "ArkUITextInputNode.h"
#include "ArkUITextNode.h"
#include "NativeEntry.h"
namespace NativeModule
{
std::shared_ptr<ArkUIButtonNode> g_animator_button = nullptr;
std::shared_ptr<ArkUITextInputNode> g_animator_textInput = nullptr;
std::shared_ptr<ArkUIBaseNode> testPropertiesAnimate()
{
auto column = std::make_shared<ArkUIColumnNode>();
column->SetPadding(10, false);
column->SetPercentHeight(1.0);
column->SetPercentWidth(1.0);
// 创建一个按钮节点
auto button = std::make_shared<ArkUIButtonNode>();
button->SetEnable();
button->SetPercentWidth(0.4);
button->SetHeight(60);
button->SetBackgroundColor(0xFF000000);
button->SetLabel("开始动画");
g_animator_button = button;
column->AddChild(button);
auto textInput = std::make_shared<ArkUITextInputNode>();
textInput->SetBackgroundColor(0xff27253b);
textInput->SetHeight(120);
textInput->SetPercentWidth(0.4);
textInput->SetMargin(50.0, 0.0, 0.0, 0.0);
g_animator_textInput = textInput;
column->AddChild(textInput);
button->RegisterNodeEvent(button->GetHandle(), NODE_ON_CLICK, 4, nullptr);
auto onTouchPlay = [](ArkUI_NodeEvent *event)
{
// 准备动画参数
ArkUI_AnimateOption *option = OH_ArkUI_AnimateOption_Create();
OH_ArkUI_AnimateOption_SetDuration(option, 2000);
OH_ArkUI_AnimateOption_SetTempo(option, 1.1);
OH_ArkUI_AnimateOption_SetCurve(option, ARKUI_CURVE_EASE);
OH_ArkUI_AnimateOption_SetDelay(option, 20);
OH_ArkUI_AnimateOption_SetIterations(option, 1);
OH_ArkUI_AnimateOption_SetPlayMode(option, ARKUI_ANIMATION_PLAY_MODE_REVERSE);
ArkUI_ExpectedFrameRateRange *range = new ArkUI_ExpectedFrameRateRange;
range->min = 10;
range->max = 120;
range->expected = 60;
OH_ArkUI_AnimateOption_SetExpectedFrameRateRange(option, range);
// 用户自定义参数
struct UserData
{
int32_t data;
};
UserData *onFinishUser = new UserData;
onFinishUser->data = 101;
// 设置完成的回调
ArkUI_AnimateCompleteCallback *completeCallback = new ArkUI_AnimateCompleteCallback;
completeCallback->userData = onFinishUser;
completeCallback->type = ARKUI_FINISH_CALLBACK_REMOVED;
completeCallback->callback = [](void *userData)
{
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Manager",
"CreateNativeNode onFinishCallback %{public}d",
reinterpret_cast<UserData *>(userData)->data);
};
// 用户自定义参数
UserData *eventUser = new UserData;
eventUser->data = 201;
static bool isBack = true;
ArkUI_ContextCallback *update = new ArkUI_ContextCallback;
update->userData = eventUser;
update->callback = [](void *user)
{
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Manager", "CreateNativeNode animateTo %{public}d",
reinterpret_cast<UserData *>(user)->data);
// 对应的属性变化 width height
if (isBack) {
g_animator_textInput->SetWidth(200);
g_animator_textInput->SetHeight(80);
}
else {
g_animator_textInput->SetWidth(100);
g_animator_textInput->SetHeight(40);
}
isBack = !isBack;
};
// 获取ArkUI_NativeAnimateAPI接口
auto animateApi = NativeModuleInstance::GetInstance()->GetNativeAnimateAPI();
// 获取context对象
static ArkUI_ContextHandle context = nullptr;
context = OH_ArkUI_GetContextByNode(g_animator_button->GetHandle());
// 执行对应的动画
animateApi->animateTo(context, option, update, completeCallback);
};
button->RegisterNodeEventReceiver(onTouchPlay);
return column;
}
} // namespace NativeModule
#endif // NATIVEPC_TESTANIMATE_H
下面逐一讲解整个案例的实现思路:
1.4.1、必须的全局指针字段
虽然,全局变量存在如果没有控制好使用方式会导致程序内部异常 的风险,但在此次的案例中,是必须定义一个 g_animator_button 和 g_animator_textInput,否则,在相关动画回调函数中,就没办法对定义在回调函数外的UI节点进行操作,而 ArkUI_AnimateCompleteCallback 对象的 callback 方法不支持捕获外部数据:

1.4.2、完成基本骨架
在 UI 开发过程中,一定要有先后意识 ,这个 『先』就是先完成直接可见的东西,也就是页面的整体结构,把需要的页面元素先定义出来,而『后』对应的就是不可见的东西,通常就是用户交互的处理代码,因此,一开始可以先在 testPropertiesAnimate 方法中编写如下代码,完成页面的整体:

如果页面元素的尺寸不合适、或者元素间距需要调整,这些简单的代码便能助力快速定位到对应的行。
1.4.3、由外到内实现回调函数
为了避免定义太多全局字段,将逻辑相关的代码直接放到回调函数内,是高效编程所必须的操作,在这个案例中,由于动画效果的触发是通过按钮的单击事件来完成的,所以嘛,理所当然的就需要注册按钮的点击事件以及相应的事件处理回调,所以,先在 UI 元素定义代码之后,接着编写如下代码片段:
cpp
button->RegisterNodeEvent(button->GetHandle(), NODE_ON_CLICK, 4, nullptr);
auto onTouchPlay = [](ArkUI_NodeEvent *event)
{
};
button->RegisterNodeEventReceiver(onTouchPlay);
先让按钮的回调函数里面啥也没有,或者就是一行简单的log打印,然后真机跑一下,看看是否顺利运行。
在按钮点击事件处理回调函数成功执行后,再具体地根据前面学习的属性动画的实现原理,逐步骤地编码:先准备ArkUI_AnimateOption和设置动画属性,再准备ArkUI_AnimateCompleteCallback 和 ArkUI_ContextCallback,最后用 animateTo 播放动画,而 ArkUI_AnimateCompleteCallback 和 ArkUI_ContextCallback 身上的回调函数,同样的先置空:
cpp
auto onTouchPlay = [](ArkUI_NodeEvent *event)
{
// 准备动画参数
ArkUI_AnimateOption *option = OH_ArkUI_AnimateOption_Create();
OH_ArkUI_AnimateOption_SetDuration(option, 2000);
OH_ArkUI_AnimateOption_SetTempo(option, 1.1);
OH_ArkUI_AnimateOption_SetCurve(option, ARKUI_CURVE_EASE);
OH_ArkUI_AnimateOption_SetDelay(option, 20);
OH_ArkUI_AnimateOption_SetIterations(option, 1);
OH_ArkUI_AnimateOption_SetPlayMode(option, ARKUI_ANIMATION_PLAY_MODE_REVERSE);
ArkUI_ExpectedFrameRateRange *range = new ArkUI_ExpectedFrameRateRange;
range->min = 10;
range->max = 120;
range->expected = 60;
OH_ArkUI_AnimateOption_SetExpectedFrameRateRange(option, range);
// ....
// 设置完成的回调
ArkUI_AnimateCompleteCallback *completeCallback = new ArkUI_AnimateCompleteCallback;
completeCallback->userData = onFinishUser;
completeCallback->type = ARKUI_FINISH_CALLBACK_REMOVED;
completeCallback->callback = [](void *userData)
{
};
// ...
ArkUI_ContextCallback *update = new ArkUI_ContextCallback;
update->userData = eventUser;
update->callback = [](void *user)
{
};
// ...
// 执行对应的动画
animateApi->animateTo(context, option, update, completeCallback);
};
同样真机部署试着运行,无错误之后,再将剩下的两个内层回调函数的实现代码补全。
1.5、实现效果
最终实现效果如下所示:
属性动画演示效果