ACE框架特性调研——手势事件流程分析 原创 精华

深开鸿
发布于 2022-4-28 14:42
浏览
1收藏

作者:王清

前言:

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

事件手势分类:

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

ACE框架特性调研——手势事件流程分析-鸿蒙开发者社区

代码结构与简单类图:

代码结构:

./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框架中的流程,ACE框架中,AceAbility类是运行起始点,一切都从这里开始:

手势事件时序图:

手势事件在ACE框架中的入口是OnPointerEvent,AceAbility里面获取到Container容器和FlutterAceView后就开始对这个pointer事件做一系列的处理:

ACE框架特性调研——手势事件流程分析-鸿蒙开发者社区

手势事件示例代码流程:

我们以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_);//设置GestureMask
    
    return 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框架特性调研——手势事件流程分析-鸿蒙开发者社区

总结:

在ACE框架中,手势和事件都是相对复杂的板块,一篇文章无法涵盖全部信息,本文仅对手势事件的流程做一个简单阐述,难免有些疏漏,欢迎指正补充,有兴趣的读者也可以考虑对其进一步研究。

引用:

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

https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-gesture-settings.md

https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-gesture-settings.md

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

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

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
1
收藏 1
回复
举报
1条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

感谢作者推开了手势事件的大门,赶快了解一下

回复
2022-4-28 16:07:41
回复
    相关推荐