From b745a252d4fc97a87342e2fb5d75900ee48698df Mon Sep 17 00:00:00 2001 From: theretherehuh <9214055+theretherehuh@user.noreply.gitee.com> Date: Fri, 25 Jun 2021 11:43:26 +0000 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=B0=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E6=8C=87=E5=AF=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...架新增组件开发指南:以新增MyCircle组件为例.md | 621 ++++++++++++++++++ 1 file changed, 621 insertions(+) create mode 100644 Ace开发框架新增组件开发指南:以新增MyCircle组件为例.md diff --git a/Ace开发框架新增组件开发指南:以新增MyCircle组件为例.md b/Ace开发框架新增组件开发指南:以新增MyCircle组件为例.md new file mode 100644 index 00000000..f76c350b --- /dev/null +++ b/Ace开发框架新增组件开发指南:以新增MyCircle组件为例.md @@ -0,0 +1,621 @@ +# Ace开发框架新增组件开发指南:以新增MyCircle组件为例 + +本篇wiki将通过新增一个MyCircle组件为例,向大家展示新增一个Ace组件的全流程。 + +完整的patch链接:https://gitee.com/theretherehuh/ace_ace_engine/pulls/1/files + + + +### mycircle + +可点击的展示类组件,展示一个圆,支持设置半径、边缘宽度和边缘颜色,可以通过点击事件获得当前圆的半径和边缘宽度。 + +#### 支持设备 + +| 手机 | 智慧屏 | 智能穿戴 | 轻量级智能穿戴 | 轻车机 | +| ---- | ------ | -------- | -------------- | ------ | +| 支持 | 支持 | 支持 | 支持 | 支持 | + +#### 子组件 + +无 + +#### 属性 + +| 名称 | 属性类型 | 默认值 | 必填 | 描述 | +| ------------ | -------- | ------ | ---- | ---------- | +| circleradius | length | 20vp | 否 | 默认半径。 | + +#### 样式 + +| 名称 | 属性类型 | 默认值 | 必填 | 描述 | +| ---------- | ------------ | ------- | ---- | -------------------- | +| circleedge | length color | 2vp red | 否 | 默认边缘颜色和宽度。 | + +#### 事件 + +| 名称 | 参数类型 | 描述 | +| ----------- | ----------------------------------------------------- | ------------------------------------------------------------ | +| circleclick | {radius: circle radius, edgewidth: circle edge width} | 点击MyCircle组件时触发该回调,返回当前circle的半径和边缘宽度,单位是px。 | + +#### 示例 + +```typescript + +
+ "MyCircle的半径为:{{radiusOfMyCircle}}" + "MyCircle的边缘宽度为:{{edgeWidthOfMyCircle}}" + +
+``` + +```js +// xxx.js +export default{ + data:{ + radiusOfMyCircle: -1, + edgeWidthOfMyCircle: -1, + }, + onCircleClick(event) { + this.radiusOfMyCircle = event.radius + this.edgeWidthOfMyCircle = event.edgewidth + } +} +``` + +该界面最终效果如下图所示: + +![](https://gitee.com/theretherehuh/ace_ace_engine/raw/resources/mycircle.gif) + + + +### 1. `js`的界面解析 + +#### 1.1 `dom_type`中增加新组件的属性定义 + +##### 1.1.1 在`dom_type.h`中增加`MyCircle`的属性定义 + +文件路径为:`frameworks\bridge\common\dom\dom_type.h` + +```c++ +// node tag defines +/* .................................... */ +/* node tag defines of other components */ +/* .................................... */ +ACE_EXPORT extern const char DOM_NODE_TAG_MYCIRCLE[]; + +/* ........................... */ +/* defines of other components */ +/* ........................... */ + +// mycircle defines +ACE_EXPORT extern const char DOM_MYCIRCLE_CIRCLE_EDGE[]; +ACE_EXPORT extern const char DOM_MYCIRCLE_CIRCLE_RADIUS[]; +ACE_EXPORT extern const char DOM_MYCIRCLE_CIRCLE_CLICK[]; +``` + +  + +##### 1.1.2 在`dom_type.cpp`中增加`MyCircle`的属性值 + +文件路径为:`frameworks\bridge\common\dom\dom_type.cpp` + +```c++ +// node tag defines +/* .................................... */ +/* node tag defines of other components */ +/* .................................... */ +const char DOM_NODE_TAG_MYCIRCLE[] = "mycircle"; + +/* ........................... */ +/* defines of other components */ +/* ........................... */ + +// mycircle defines +const char DOM_MYCIRCLE_CIRCLE_EDGE[] = "circleedge"; +const char DOM_MYCIRCLE_CIRCLE_RADIUS[] = "circleradius"; +const char DOM_MYCIRCLE_CIRCLE_CLICK[] = "circleclick"; +``` + +  + +#### 1.2 新增`DOMMyCircle`类 + +##### 1.2.1 新增`dom_mycircle.h` + +文件路径:`frameworks\bridge\common\dom\dom_mycircle.h` + +```c++ +class DOMMyCircle final : public DOMNode { + DECLARE_ACE_TYPE(DOMMyCircle, DOMNode); + +public: + DOMMyCircle(NodeId nodeId, const std::string& nodeName); + ~DOMMyCircle() override = default; + + RefPtr GetSpecializedComponent() override + { + return myCircleChild_; + } + +protected: + bool SetSpecializedAttr(const std::pair& attr) override; + bool SetSpecializedStyle(const std::pair& style) override; + bool AddSpecializedEvent(int32_t pageId, const std::string& event) override; + +private: + RefPtr myCircleChild_; +}; +``` + +`DOMMyCircle`继承自`DOMNode`,主要功能是解析界面并生成相应的`Component`节点。 + +  + +##### 1.2.2 新增`dom_mycircle.cpp` + +文件路径:`frameworks\bridge\common\dom\dom_mycircle.cpp` + +**一、组件属性的解析:`SetSpecializedAttr`** + +```c++ +bool DOMMyCircle::SetSpecializedAttr(const std::pair& attr) +{ + if (attr.first == DOM_MYCIRCLE_CIRCLE_RADIUS) { // "circleradius" + myCircleChild_->SetCircleRadius(StringToDimension(attr.second)); + return true; + } + return false; +} +``` + +这个方法由框架流程调用,我们只需要在这个方法里面实现对应属性的解析,并且设置到`MyCircleComponent`中。 + +如上代码中,入参`attr`的格式形如`<"circleradius", "40vp">`,则我们只需要判断`attr.first`为`"circleradius"`时,将`attr.second`转换为`Dimension`格式,设置到`MyCircleComponent`中即可。设置完成后,返回`true`。 + +  + +**二、组件样式的解析:`SetSpecializedStyle`** + +```c++ +bool DOMMyCircle::SetSpecializedStyle(const std::pair& style) +{ + if (style.first == DOM_MYCIRCLE_CIRCLE_EDGE) { // "circleedge" + std::vector edgeStyles; + // The value of [circleedge] is like "2vp red" or "2vp". To parse style value like this, we need 3 steps. + // Step1: Split the string value by ' ' to get vectors like ["2vp", "red"]. + StringUtils::StringSpliter(style.second, ' ', edgeStyles); + Dimension edgeWidth(1, DimensionUnit::VP); + Color edgeColor(Color::RED); + + // Step2: Parse edge color and edge width accordingly. + switch(edgeStyles.size()) { + case 0: // the value is empty + LOGW("Value for circle edge is empty, using default setting."); + break; + case 1: // case when only edge width is set + // It should be guaranteed by the tool chain when generating js-bundle that the only value is a + // number type for edge width rather than a color type for edge color. + edgeWidth = StringUtils::StringToDimension(edgeStyles[0]); + break; + case 2: // case when edge width and edge color are both set + edgeWidth = StringUtils::StringToDimension(edgeStyles[0]); + edgeColor = Color::FromString(edgeStyles[1]); + break; + default: + LOGW("There are more than 2 values for circle edge, please check. The value is %{private}s", + style.second.c_str()); + break; + } + + // Step3: Set edge color and edge width to [mycircleStyle]. + myCircleChild_->SetEdgeWidth(edgeWidth); + myCircleChild_->SetEdgeColor(edgeColor); + return true; + } + return false; +} +``` + +这个方法由框架流程调用,我们只需要在这个方法里面实现对应样式的解析,并且保存到`MyCircleComponent`中。 + +如上代码中,入参`style`的格式形如`<"circleedge", "2vp red">`,则我们只需要判断`style.first`为`"circleedge"`时,将`style.second`进行解析,设置到`MyCircleComponent`中即可。设置完成后,返回`true`。 + +  + +**四、组件事件的解析:`SetSpecializedEvent`** + +```c++ +bool DOMMyCircle::AddSpecializedEvent(int32_t pageId, const std::string& event) +{ + if (event == DOM_MYCIRCLE_CIRCLE_CLICK) { // "circleclick" + myCircleChild_->SetCircleClickEvent(EventMarker(GetNodeIdForEvent(), event, pageId)); + return true; + } + return false; +} +``` + +这个方法由框架流程调用,我们只需要在这个方法里面实现对应事件的解析,并且保存到`MyCircleComponent`中。 + +如上代码中,只要判断入参`event`的值为`"circleclick"`,则我们只需要使用`eventId`和`pageId`构造一个`EventMarker`,并设置到`MyCircleComponent`中即可。设置完成后,返回`true`。 + +  + +#### 1.3 在`dom_document.cpp`里增加`mycircle`组件 + +文件路径:`frameworks\bridge\common\dom\dom_document.cpp` + +``` +RefPtr DOMDocument::CreateNodeWithId(const std::string& tag, NodeId nodeId, int32_t itemIndex) +{ + // code block + static const LinearMapNode(*)(NodeId, const std::string&, int32_t)> domNodeCreators[] = { + // DomNodeCreators of other components + { DOM_NODE_TAG_MENU, &DOMNodeCreator }, + // "mycircle" must be inserted between "menu" and "navigation-bar" + { DOM_NODE_TAG_MYCIRCLE, &DOMNodeCreator }, + { DOM_NODE_TAG_NAVIGATION_BAR, &DOMNodeCreator }, + // DomNodeCreators of other components + }; + // code block + return domNode; +} +``` + +这里尤其要注意一点,`domNodeCreators[]`是一个线性表,添加`{ DOM_NODE_TAG_MYCIRCLE, &DOMNodeCreator }`的地方必须要符合字母序。 + +```c++ +DOM_NODE_TAG_MENU[] = "menu", +DOM_NODE_TAG_NAVIGATION_BAR[] = "navigation-bar", +DOM_NODE_TAG_MYCIRCLE[] = "mycircle" +``` + +所以`DOM_NODE_TAG_MYCIRCLE`的记录必须添加在`"menu"`之后,`"navigation-bar"`之前。 + +  + +### 2. 后端的布局和绘制 + + 组件在后端的布局和绘制,需要相应地新增以下几个类:`MyCircleComponent`、`MyCircleElement`、`RenderMyCircle`、`FlutterRenderMyCircle`。 + +在后端引擎中,`Component`树、`Element`树和`Render`树为后端引擎维持和更新UI最为核心的三棵树。 + +  + +#### 2.1 新增`MyCircleComponent`类 + +##### 2.1.1 新增`mycircle_component.h` + +文件路径:`frameworks\core\components\mycircle\mycircle_component.h` + +```c++ +class ACE_EXPORT MyCircleComponent : public RenderComponent { + DECLARE_ACE_TYPE(MyCircleComponent, RenderComponent); + +public: + MyCircleComponent() = default; + ~MyCircleComponent() override = default; + + RefPtr CreateRenderNode() override; + RefPtr CreateElement() override; + + void SetCircleRadius(const Dimension& circleRadius); + void SetEdgeWidth(const Dimension& edgeWidth); + void SetEdgeColor(const Color& edgeColor); + void SetCircleClickEvent(const EventMarker& circleClickEvent); + + const Dimension& GetCircleRadius() const; + const Dimension& GetEdgeWidth() const; + const Color& GetEdgeColor() const; + const EventMarker& GetCircleClickEvent() const; + +private: + Dimension circleRadius_ = 20.0_vp; + Dimension edgeWidth_ = 2.0_vp; + Color edgeColor_ = Color::RED; + EventMarker circleClickEvent_; +}; +``` + +  + +##### 2.1.2 新增`mycircle_component.cpp` + +文件路径:`frameworks\core\components\mycircle\mycircle_component.cpp` + +**一、提供`Set`接口** + +```c++ +void MyCircleComponent::SetCircleRadius(const Dimension& circleRadius) +{ + circleRadius_ = circleRadius; +} + +void MyCircleComponent::SetEdgeWidth(const Dimension& edgeWidth) +{ + edgeWidth_ = edgeWidth; +} + +void MyCircleComponent::SetEdgeColor(const Color& edgeColor) +{ + edgeColor_ = edgeColor; +} + +void MyCircleComponent::SetCircleClickEvent(const EventMarker& circleClickEvent) +{ + circleClickEvent_ = circleClickEvent; +} +``` + +  + +**二、提供`Get`接口** + +```c++ +const Dimension& MyCircleComponent::GetCircleRadius() const +{ + return circleRadius_; +} + +const Dimension& MyCircleComponent::GetEdgeWidth() const +{ + return edgeWidth_; +} + +const Color& MyCircleComponent::GetEdgeColor() const +{ + return edgeColor_; +} + +const EventMarker& MyCircleComponent::GetCircleClickEvent() const +{ + return circleClickEvent_; +} +``` + +  + +**三、实现`CreateRenderNode`和`CreateElement`函数** + +```c++ +RefPtr MyCircleComponent::CreateRenderNode() +{ + return RenderMyCircle::Create(); +} + +RefPtr MyCircleComponent::CreateElement() +{ + return AceType::MakeRefPtr(); +} +``` + +  + +**四、提供`declaration_`的`Set`和`Get`接口** + +```c++ +const RefPtr& MyCircleComponent::GetDeclaration() const +{ + return declaration_; +} + +void MyCircleComponent::SetDeclaration(const RefPtr& declaration) +{ + declaration_ = declaration; +} +``` + +  + +#### 2.2 新增`MyCircleElement`类 + +##### 2.2.1 新增`mycircle_element.h` + +文件路径:`frameworks\core\components\mycircle\mycircle_element.h` + +```c++ +class MyCircleElement : public RenderElement { + DECLARE_ACE_TYPE(MyCircleElement, RenderElement); + +public: + MyCircleElement() = default; + ~MyCircleElement() override = default; +}; +``` + +该组件在`element`层不涉及更多操作,只需要定义`MyCircleElement`类即可。 + +  + +#### 2.3 新增`RenderMyCircle`类 + +##### 2.3.1 新增`render_mycircle.h` + +文件路径:`frameworks\core\components\mycircle\render_mycircle.h` + +```c++ +using CallbackForJS = std::function; + +class RenderMyCircle : public RenderNode { + DECLARE_ACE_TYPE(RenderMyCircle, RenderNode); + +public: + static RefPtr Create(); + + void Update(const RefPtr& component) override; + void PerformLayout() override; + void HandleMyCircleClickEvent(const ClickInfo& info); + +protected: + RenderMyCircle(); + void OnTouchTestHit( + const Offset& coordinateOffset, const TouchRestrict& touchRestrict, TouchTestResult& result) override; + + Dimension circleRadius_; + Dimension edgeWidth_ = Dimension(1); + Color edgeColor_ = Color::RED; + CallbackForJS callbackForJS_; // callback for js frontend + RefPtr clickRecognizer_; +}; +``` + +  + +##### 2.3.2 新增`render_mycircle.cpp` + +文件路径:`frameworks\core\components\mycircle\render_mycircle.cpp` + +**一、处理点击事件** + +```c++ +RenderMyCircle::RenderMyCircle() +{ + clickRecognizer_ = AceType::MakeRefPtr(); + clickRecognizer_->SetOnClick([wp = WeakClaim(this)](const ClickInfo& info) { + auto myCircle = wp.Upgrade(); + if (!myCircle) { + LOGE("WeakPtr of RenderMyCircle fails to be upgraded, stop handling click event."); + return; + } + myCircle->HandleMyCircleClickEvent(info); + }); +} + +void RenderMyCircle::OnTouchTestHit( + const Offset& coordinateOffset, const TouchRestrict& touchRestrict, TouchTestResult& result) +{ + clickRecognizer_->SetCoordinateOffset(coordinateOffset); + result.emplace_back(clickRecognizer_); +} + +void RenderMyCircle::HandleMyCircleClickEvent(const ClickInfo& info) +{ + if (callbackForJS_) { + auto result = std::string("\"circleclick\",{\"radius\":") + .append(std::to_string(NormalizeToPx(circleRadius_))) + .append(",\"edgewidth\":") + .append(std::to_string(NormalizeToPx(edgeWidth_))) + .append("}"); + callbackForJS_(result); + } +} +``` + +1、创建一个`ClickRecognizer`; + +2、重写`OnTouchTestHit`函数,注册`RenderMyCircle`的`ClickRecognizer`,这样在接收到点击事件时即可触发创建`ClickRecognizer`时添加的事件回调; + +3、实现在接收到点击事件之后的处理逻辑`HandleMyCircleClickEvent` + +  + +**二、重写`Update`函数** + +```c++ +void RenderMyCircle::Update(const RefPtr& component) +{ + const auto& myCircleComponent = AceType::DynamicCast(component); + if (!myCircleComponent) { + LOGE("MyCircleComponent is null!"); + return; + } + circleRadius_ = myCircleComponent->GetCircleRadius(); + edgeWidth_ = myCircleComponent->GetEdgeWidth(); + edgeColor_ = myCircleComponent->GetEdgeColor(); + callbackForJS_ = + AceAsyncEvent::Create(myCircleComponent->GetCircleClickEvent(), context_); + + // call [MarkNeedLayout] to do [PerformLayout] with new params + MarkNeedLayout(); +} +``` + +`Update`函数负责从`MyCircleComponent`获取所有绘制、布局和事件相关的属性更新。 + +  + +**三、重写`PerformLayout`函数** + +```c++ +void RenderMyCircle::PerformLayout() +{ + double realSize = NormalizeToPx(edgeWidth_) + 2 * NormalizeToPx(circleRadius_); + Size layoutSizeAfterConstrain = GetLayoutParam().Constrain(Size(realSize, realSize)); + SetLayoutSize(layoutSizeAfterConstrain); +} +``` + +`PerformLayout`函数负责计算布局信息,并且调用`SetLayoutSize`函数设置自己所需要的布局大小。 + +  + +#### 2.4 新增`FlutterRenderMyCircle`类 + +##### 2.4.1 新增`flutter_render_mycircle.h` + +文件路径:`frameworks\core\components\mycircle\flutter_render_mycircle.h` + +```c++ +class FlutterRenderMyCircle final : public RenderMyCircle { + DECLARE_ACE_TYPE(FlutterRenderMyCircle, RenderMyCircle); + +public: + FlutterRenderMyCircle() = default; + ~FlutterRenderMyCircle() override = default; + + void Paint(RenderContext& context, const Offset& offset) override; +}; +``` + +  + +##### 2.4.2 新增`flutter_render_mycircle.cpp` + +文件路径:`frameworks\core\components\mycircle\flutter_render_mycircle.cpp` + +**一、实现`RenderMyCircle::Create()`函数** + +```c++ +RefPtr RenderMyCircle::Create() +{ + return AceType::MakeRefPtr(); +} +``` + +`RenderMyCircle::Create()`在基类`RenderMyCircle`中定义,因为我们当前使用的是`flutter`引擎,所以在`flutter_render_mycircle.cpp`里面实现,返回在`flutter`引擎上自渲染的`FlutterRenderMyCircle`类。 + +  + +**二、重写`Paint`函数** + +```c++ +void FlutterRenderMyCircle::Paint(RenderContext& context, const Offset& offset) +{ + auto canvas = ScopedCanvas::Create(context); + if (!canvas) { + LOGE("Paint canvas is null"); + return; + } + SkPaint skPaint; + skPaint.setAntiAlias(true); + skPaint.setStyle(SkPaint::Style::kStroke_Style); + skPaint.setColor(edgeColor_.GetValue()); + skPaint.setStrokeWidth(NormalizeToPx(edgeWidth_)); + + auto paintRadius = GetLayoutSize().Width() / 2.0; + canvas->canvas()->drawCircle(offset.GetX() + paintRadius, offset.GetY() + paintRadius, + NormalizeToPx(circleRadius_), skPaint); +} +``` + +`Paint`函数负责调用canvas相应接口去进行绘制,这一步可以认为是新增组件的最后一步,直接决定在屏幕上绘制什么样的UI界面。 + +  + +### 小结 + +到这里,新增一个`MyCircle`组件所需的所有步骤都已经完成,我们可以展示一个圆,支持设置半径、边缘宽度和边缘颜色,可以通过点击事件获得当前圆的半径和边缘宽度。 + +当然`MyCircle`组件是比较简单的示例组件,Ace开发框架支持更多更复杂的组件开发,比如提供单行文本输入组件`TextInput`、提供日历展示的`Calendar`组件等,更多的用法期待你来探索~ +