天天看點

#夏日挑戰賽#ACE架構特性調研——手勢處理分析

本文正在參加星光計劃3.0–夏日挑戰賽

作者:巴延興

前言:

ACE全稱是Ability Cross-platform Environment (元能力跨平台執行環境),是應用在OpenHarmony上的UI架構。作為一個UI架構,需要提供視圖布局,UI元件,事件響應機制等的支援,并且目前主流的應用終端都為觸摸屏,UI的操作大都通過手勢完成,我們這裡就對ACE架構的手勢處理做一個簡單的分析。

手勢分類:

從鴻蒙開發者網站提供的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

更多原創内容請關注:深開鴻技術團隊

入門到精通、技巧到案例,系統化分享HarmonyOS開發技術,歡迎投稿和訂閱,讓我們一起攜手前行共建鴻蒙生态。

繼續閱讀