#夏日挑战赛#ACE框架特性调研——手势处理分析 原创 精华

深开鸿
发布于 2022-7-6 16:20
浏览
2收藏

本文正在参加星光计划3.0–夏日挑战赛
作者:巴延兴

前言:

ACE全称是Ability Cross-platform Environment (元能力跨平台执行环境),是应用在OpenHarmony上的UI框架。作为一个UI框架,需要提供视图布局,UI组件,事件响应机制等的支持,并且当前主流的应用终端都为触摸屏,UI的操作大都通过手势完成,我们这里就对ACE框架的手势处理做一个简单的分析。

手势分类:

从OpenHarmony开发者网站提供的API上我们可以看到,在基于TS扩展的开发范式说明里,单独对手势做了一个类别,而在基于JS扩展的开发范式说明里,则是归类到通用事件里,名称也略有不同:

#夏日挑战赛#ACE框架特性调研——手势处理分析-鸿蒙开发者社区

代码结构与简单类图:

代码结构:


./ace/ace\_engine/core

|-- gesture #gesture主文件夹,定义了各种手势的具体实现类与对应的Recongnizer

| |-- include

| |-- src

|-- components #组件文件夹

| |-- gesture\_listenser #对触摸目标对象做一些处理,注册Recongnizer等。

gesture文件夹下的类图:

这里仅对gesture文件下的文件做一个类图梳理,可以很清晰的看到Gesture类和Recongnizer类的对应关系,其中Gesture类会创建一个对应的Recongnizer,并set对应相关的OnActionID,priority和Mask,而Recongnizer类则是处理手势相关的事件逻辑等。

#夏日挑战赛#ACE框架特性调研——手势处理分析-鸿蒙开发者社区

手势简单代码流程:

我们以Pinch为例,当应用调用相关接口后,JS进行Binding操作:


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\_);//设置GestureMask

return pinchRecognizer;

}

手势事件的处理流程上,手势事件均归类于Touch事件(默认摸到屏幕才能进行操作,隔空手势暂不做讨论),RenderNode类有个函数TouchTest(EventManager中TouchTest调用RenderNode的TouchTest),TouchTest类似前端的事件冒泡机制,他的作用是触发Touch事件时收集对应的触摸事件目标列表,它先从顶部节点开始对所有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)) {

dispatchSuccess = false;

break;

}

}

//如果有一个手势识别器已经获胜,其他手势识别器仍然会受到事件影响,每个识别器需要自己过滤额外的事件。

if (dispatchSuccess) {

for (const auto& entry : iter-\>second) {

if (!entry-\>HandleEvent(point)) {

break;

}

}

}

//如果事件类型为UP(结束)或者CANCEL(被打断),则移除该事件

if (point.type == TouchType::UP || point.type == TouchType::CANCEL) {

GestureReferee::GetInstance().CleanGestureScope(point.id);

touchTestResults\_.erase(point.id);

}

return true;

}

LOGI("the %{public}d touch test result does not exist!", point.id);

return false;

}

RenderGestureListener继承RenderProxy类,RenderProxy继承自RenderNode类,RenderGestureListener重新实现了OnTouchTestHit函数,以返回用于接收触摸事件的触摸目标对象,coordinateOffset作为recognizer来计算触摸点的局部位置。这里面注册pinchRecognizer,这样在接收到pinch事件时即可触发创建pinchRecognizer时添加的事件回调:


void RenderGestureListener::OnTouchTestHit(const Offset& coordinateOffset, const TouchRestrict& touchRestrict, TouchTestResult& result)

{

/\*\*\*省略一些的Recongnizer注册代码\*\*/

if (clickRecognizer\_) {

clickRecognizer\_-\>SetCoordinateOffset(coordinateOffset);

result.emplace\_back(clickRecognizer\_);

}

if (pinchRecognizer\_) {

pinchRecognizer\_-\>SetCoordinateOffset(coordinateOffset);

result.emplace\_back(pinchRecognizer\_);

}

}

此外,每个Recongnizer类都重新实现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;

}

最后每个Recongnizer类里都有相应事件的具体处理逻辑函数HandleXXXEVENT对事件做处理:


void HandleTouchDownEvent(const TouchEvent& event) override;

void HandleTouchUpEvent(const TouchEvent& event) override;

void HandleTouchMoveEvent(const TouchEvent& event) override;

void HandleTouchCancelEvent(const TouchEvent& event) override;

总结:

在ACE框架中,当手指接触屏幕到对应组件收到事件响应有两个过程,触摸测试和事件分发,触摸测试用于收集那些可以收到事件的组件,事件分发用于执行对应组件的接收事件函数,这样组件就可以在接收到事件后处理对应的逻辑。此外TS开发范式通用手势API中,除了Gesture外也定义了priorityGesture(绑定优先识别手势。)和parallelGesture(绑定可与子组件手势同时触发的手势),手势事件为非冒泡事件,父组件设置parallelGesture时,父子组件相同的手势事件都可以触发,实现类似冒泡效果,这些原理和流程读者有兴趣的可以考虑进一步研究。

引用:

通用事件与通用手势说明:

https://developer.harmonyos.com/cn/docs/documentation/doc-references/js-components-common-events-0000001051151132

https://developer.harmonyos.com/cn/docs/documentation/doc-references/ts-gesture-settings-0000001113113018

更多原创内容请关注:深开鸿技术团队

入门到精通、技巧到案例,系统化分享OpenHarmony开发技术,欢迎投稿和订阅,让我们一起携手前行共建生态。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2022-11-17 15:06:05修改
4
收藏 2
回复
举报
1条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

感谢楼主对手势处理的讲解。

回复
2022-7-6 16:46:46
回复
    相关推荐