胸怀万里世界, 放眼无限未来。这篇文章主要讲述ACE框架特性调研——手势事件流程分析相关的知识,希望能为你提供帮助。
作者:王清
前言:
ACE全称是Ability Cross-platform Environment (元能力跨平台执行环境),是应用在OpenHarmony上的UI框架。作为一个UI框架,需要提供视图布局,UI组件,事件响应机制等的支持,并且当前主流的应用终端都为触摸屏,UI的操作大都通过手势完成,我们这里就对ACE框架的手势事件流程做一个简单的分析。
事件手势分类:
从鸿蒙开发者网站提供的API上我们可以看到,在基于TS扩展的开发范式说明里,单独对手势做了一个类别,而在基于JS扩展的开发范式说明里,则是归类到通用事件里,名称也略有不同:
文章图片
代码结构与简单类图:
代码结构:
./ace/ace_engine/core
|-- event#event主文件夹,定义了Event的Trigger,Maker,Handler,convertor等处理类
||-- include
||-- src
|-- gesture#gesture主文件夹,定义了各种手势的具体实现类与对应的Recognizer
||-- include
||-- src
|-- components#组件文件夹
||-- gesture_listenser#对触摸目标对象做一些处理,注册Recognizer等。
gesture文件夹下的类图:这里仅对gesture文件下的文件做一个类图梳理,可以很清晰的看到Gesture类和Recognizer类的对应关系,其中Gesture类会创建一个对应的Recognizer,并set对应相关的OnActionID,priority和Mask,而Recognizer类则是处理手势相关的事件逻辑等。
文章图片
手势事件流程:
我们仅观测手势事件在ACE框架中的流程,ACE框架中,AceAbility类是运行起始点,一切都从这里开始:
手势事件时序图:手势事件在ACE框架中的入口是OnPointerEvent,AceAbility里面获取到Container容器和FlutterAceView后就开始对这个pointer事件做一系列的处理:
文章图片
手势事件示例代码流程:我们以Pinch为例,对整个手势事件的流程的关键代码做一个分析。
当应用调用相关接口后,JS的Binding操作创建Gesture,然后Gesture里面会创建对应的Recognizer:
JSClass<
JSPinchGesture>
::Declare("PinchGesture");
JSClass<
JSPinchGesture>
::StaticMethod("create", &
JSPinchGesture::Create, opt);
JSClass<
JSPinchGesture>
::StaticMethod("pop", &
JSGesture::Pop);
JSClass<
JSPinchGesture>
::StaticMethod("onActionStart", &
JSGesture::JsHandlerOnActionStart);
JSClass<
JSPinchGesture>
::StaticMethod("onActionUpdate", &
JSGesture::JsHandlerOnActionUpdate);
JSClass<
JSPinchGesture>
::StaticMethod("onActionEnd", &
JSGesture::JsHandlerOnActionEnd);
JSClass<
JSPinchGesture>
::StaticMethod("onActionCancel", &
JSGesture::JsHandlerOnActionCancel);
JSClass<
JSPinchGesture>
::Bind(globalObj);
Create函数里面会创建一个Gesture实例,并将其push到gestureComponent中:
void JSPinchGesture::Create(const JSCallbackInfo&
args)int32_t fingersNum = DEFAULT_PINCH_FINGER;
double distanceNum = DEFAULT_PINCH_DISTANCE;
if (args.Length() >
0 &
&
args[0]->
IsObject())
JSRef<
JSObject>
obj = JSRef<
JSObject>
::Cast(args[0]);
JSRef<
JSVal>
fingers = obj->
GetProperty(GESTURE_FINGERS);
JSRef<
JSVal>
distance = obj->
GetProperty(GESTURE_DISTANCE);
if (fingers->
IsNumber())
int32_t fingersNumber = fingers->
ToNumber<
int32_t>
();
fingersNum = fingersNumber <
= DEFAULT_PINCH_FINGER ? DEFAULT_PINCH_FINGER : fingersNumber;
if (distance->
IsNumber())
double distanceNumber = distance->
ToNumber<
double>
();
distanceNum = LessNotEqual(distanceNumber, 0.0) ? DEFAULT_PINCH_DISTANCE : distanceNumber;
auto gestureComponent = ViewStackProcessor::GetInstance()->
GetGestureComponent();
auto gesture = AceType::MakeRefPtr<
OHOS::Ace::PinchGesture>
(fingersNum, distanceNum);
gestureComponent->
PushGesture(gesture);
Gesutre创建对应的Reconizer,来添加对应的事件回调以及设置priority和gestureMask
GestureMask枚举说明:
名称 | 描述 |
---|---|
Normal | 不屏蔽子组件的手势,按照默认手势识别顺序进行识别。 |
IgnoreInternal | 屏蔽子组件的手势,仅当前容器的手势进行识别。子组件上系统内置的手势不会被屏蔽,如子组件为List组件时,内置的滑动手势仍然会触发。 |
RefPtr<
GestureRecognizer>
PinchGesture::CreateRecognizer(WeakPtr<
PipelineContext>
context)auto newContext = context.Upgrade();
if (!newContext)
LOGE("fail to create pinch recognizer due to context is nullptr");
return nullptr;
double distance = newContext->
NormalizeToPx(Dimension(distance_, DimensionUnit::VP));
auto pinchRecognizer = AceType::MakeRefPtr<
OHOS::Ace::PinchRecognizer>
(fingers_, distance);
//JS支持什么回调事件pinchRecognizer就需要添加对应的事件
if (onActionStartId_)
pinchRecognizer->
SetOnActionStart(*onActionStartId_);
if (onActionUpdateId_)
pinchRecognizer->
SetOnActionUpdate(*onActionUpdateId_);
if (onActionEndId_)
pinchRecognizer->
SetOnActionEnd(*onActionEndId_);
if (onActionCancelId_)
pinchRecognizer->
SetOnActionCancel(*onActionCancelId_);
pinchRecognizer->
SetPriority(priority_);
//设置优先级
pinchRecognizer->
SetPriorityMask(gestureMask_);
//设置GestureMaskreturn pinchRecognizer;
事件流程中,Ace起始触发:
void AceAbility::OnPointerEvent(std::shared_ptr<
MMI::PointerEvent>
&
pointerEvent)auto container = Platform::AceContainer::GetContainer(abilityId_);
//获取Container
if (!container)
return;
auto flutterAceView = static_cast<
Platform::FlutterAceView*>
(container->
GetView());
//获取View
if (!flutterAceView)
return;
flutterAceView->
DispatchTouchEvent(flutterAceView, pointerEvent);
//分发事件
Container里面初始化CallBack,context进行PostTask操作,aceView注册回调:
auto&
&
touchEventCallback = [context = pipelineContext_, id = instanceId_](const TouchEvent&
event)
ContainerScope scope(id);
context->
GetTaskExecutor()->
PostTask(
[context, event]()
context->
OnTouchEvent(event);
context->
NotifyDispatchTouchEventDismiss(event);
,
TaskExecutor::TaskType::UI);
;
aceView_->
RegisterTouchEventCallback(touchEventCallback);
PipelineContext的OnTouchEvent进行处理,里面EventManager的TouchTest进行触摸测试获取触摸事件目标列表touchTestResults_,具体实现在RenderNode函数TouchTest里,TouchTest类似前端的事件冒泡机制,它先从顶部节点开始对所有RenderNode进行深度遍历,然后从最底层的节点开始向上将每个节点收集到一个为TouchTestResult类型的result变量中,最后根据该result进行事件分发。
bool RenderNode::TouchTest(const Point&
globalPoint, const Point&
parentLocalPoint, const TouchRestrict&
touchRestrict,
TouchTestResult&
result)if (disableTouchEvent_ || disabled_)
return false;
Point transformPoint = GetTransformPoint(parentLocalPoint);
//判断触摸是否在组件区域
if (!InTouchRectList(transformPoint, GetTouchRectList()))
return false;
const auto localPoint = transformPoint - GetPaintRect().GetOffset();
bool dispatchSuccess = false;
const auto&
sortedChildren = SortChildrenByZIndex(GetChildren());
//进行深度遍历
if (IsChildrenTouchEnable())
for (auto iter = sortedChildren.rbegin();
iter != sortedChildren.rend();
++iter)
auto&
child = *iter;
if (!child->
GetVisible() || child->
disabled_ || child->
disableTouchEvent_)
continue;
if (child->
TouchTest(globalPoint, localPoint, touchRestrict, result))
dispatchSuccess = true;
break;
if (child->
IsTouchable() &
&
(child->
InterceptTouchEvent() || IsExclusiveEventForChild()))
auto localTransformPoint = child->
GetTransformPoint(localPoint);
for (auto&
rect : child->
GetTouchRectList())
if (rect.IsInRegion(localTransformPoint))
dispatchSuccess = true;
break;
auto beforeSize = result.size();
for (auto&
rect : GetTouchRectList())
if (touchable_ &
&
rect.IsInRegion(transformPoint))
// Calculates the coordinate offset in this node.
globalPoint_ = globalPoint;
const auto coordinateOffset = globalPoint - localPoint;
coordinatePoint_ = Point(coordinateOffset.GetX(), coordinateOffset.GetY());
OnTouchTestHit(coordinateOffset, touchRestrict, result);
break;
auto endSize = result.size();
return (dispatchSuccess || beforeSize != endSize) &
&
IsNotSiblingAddRecognizerToResult();
事件的分发,touchTestResults_是上面代码TouchTest里面获取:
bool EventManager::DispatchTouchEvent(const TouchEvent&
point)ACE_FUNCTION_TRACE();
const auto iter = touchTestResults_.find(point.id);
if (iter != touchTestResults_.end())
bool dispatchSuccess = true;
for (auto entry = iter->
second.rbegin();
entry != iter->
second.rend();
++entry)
if (!(*entry)->
DispatchEvent(point)) //Recognizer处理
dispatchSuccess = false;
break;
//如果有一个手势识别器已经获胜,其他手势识别器仍然会受到事件影响,每个识别器需要自己过滤额外的事件。
if (dispatchSuccess)
for (const auto&
entry : iter->
second)
if (!entry->
HandleEvent(point)) ///Recognizer处理
break;
//如果事件类型为UP(结束)或者CANCEL(被打断),则移除该事件
if (point.type == TouchType::UP || point.type == TouchType::CANCEL)
GestureReferee::GetInstance().CleanGestureScope(point.id);
touchTestResults_.erase(point.id);
return true;
return false;
RenderGestureListener继承RenderProxy类,RenderProxy继承自RenderNode类,RenderGestureListener重新实现了OnTouchTestHit函数,以返回用于接收触摸事件的触摸目标对象,coordinateOffset作为recognizer来计算触摸点的局部位置。这里面注册pinchRecognizer,这样在接收到pinch事件时即可触发创建pinchRecognizer时添加的事件回调:
void RenderGestureListener::OnTouchTestHit(const Offset&
coordinateOffset, const TouchRestrict&
touchRestrict, TouchTestResult&
result)/***省略一些的Recognizer注册代码**/
if (clickRecognizer_)
clickRecognizer_->
SetCoordinateOffset(coordinateOffset);
result.emplace_back(clickRecognizer_);
if (pinchRecognizer_)
pinchRecognizer_->
SetCoordinateOffset(coordinateOffset);
result.emplace_back(pinchRecognizer_);
此外,每个Recognizer类都重新实现ReconcileFrom函数将给定recognizer的状态转换为this。 实现必须检查给定的recognizer类型是否与当前的类型匹配。 如果匹配失败,返回值应该为false如果成功则为true
bool PinchRecognizer::ReconcileFrom(const RefPtr<
GestureRecognizer>
&
recognizer)RefPtr<
PinchRecognizer>
curr = AceType::DynamicCast<
PinchRecognizer>
(recognizer);
if (!curr)
Reset();
return false;
if (curr->
fingers_ != fingers_ || curr->
distance_ != distance_ || curr->
priorityMask_ != priorityMask_)
Reset();
return false;
onActionStart_ = std::move(curr->
onActionStart_);
onActionUpdate_ = std::move(curr->
onActionUpdate_);
onActionEnd_ = std::move(curr->
onActionEnd_);
onActionCancel_ = std::move(curr->
onActionCancel_);
return true;
最后每个Recognizer类里都有相应事件的具体处理逻辑函数HandleXXXEVENT对事件做处理,调用SendCallbackMsg函数将信息传递到GestureEvent里:
void HandleTouchDownEvent(const TouchEvent&
event) override;
void HandleTouchUpEvent(const TouchEvent&
event) override;
void HandleTouchMoveEvent(const TouchEvent&
event) override;
void HandleTouchCancelEvent(const TouchEvent&
event) override;
void PinchRecognizer::SendCallbackMsg(const std::unique_ptr<
GestureEventFunc>
&
callback)if (callback &
&
*callback)
GestureEvent info;
info.SetTimeStamp(time_);
info.SetScale(scale_);
info.SetPinchCenter(pinchCenter_);
info.SetDeviceId(deviceId_);
info.SetSourceDevice(deviceType_);
(*callback)(info);
GestureEvent给上层JS调用,如:JsGestureFunction::Execute(const GestureEvent& info)
手势事件代码流程总结:我们再用文字流程的方式对时序图和代码流程做一个总结:
文章图片
总结:
在ACE框架中,手势和事件都是相对复杂的板块,一篇文章无法涵盖全部信息,本文仅对手势事件的流程做一个简单阐述,难免有些疏漏,欢迎指正补充,有兴趣的读者也可以考虑对其进一步研究。
引用:
通用事件与通用手势说明:
https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-gesture-settings.md
更多原创内容请关注:深开鸿技术团队入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。
想了解更多关于开源的内容,请访问:
51CTO 开源基础软件社区
【ACE框架特性调研——手势事件流程分析】https://ost.51cto.com/#bkwz
推荐阅读
- 图文并茂!深入了解RocketMQ的过期删除机制
- 深度详解JVM类字节码
- JUC必知必会:线程和进程
- Cilium native-routing 跨节点通信
- 夜莺5.5--实现impala自动关闭waiting to be closed会话
- 时间服务器 ntp和chrony
- 网络安全学习-WEB安全常见漏洞
- Gitlab的基本使用
- kubeadm init初始化失败运行reset后需要执行的clean up 命令