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
+ }
+}
+```
+
+该界面最终效果如下图所示:
+
+
+
+
+
+### 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`组件等,更多的用法期待你来探索~
+