16 Star 51 Fork 48

yanshuifeng / ArkUI_Docs

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
Ace1.0前端新增组件开发指南:以新增MyCircle组件为例_ver1.0.md 32.85 KB
一键复制 编辑 原始数据 按行查看 历史
yanshuifeng 提交于 2022-01-20 10:59 . update design docs

Ace1.0前端新增组件开发指南:以新增MyCircle组件为例

本篇wiki将通过新增一个MyCircle组件为例,向大家展示新增一个Ace1.0组件的全流程。

完整的patch链接:http://mgit-tm.rnd.huawei.com/#/c/14477652/

mycircle

可点击的展示类组件,展示一个圆,支持设置半径、边缘宽度和边缘颜色,可以通过点击事件获得当前圆的半径和边缘宽度。

支持设备

手机 智慧屏 智能穿戴 轻量级智能穿戴 轻车机
支持 支持 支持 支持 支持

子组件

属性

名称 属性类型 默认值 必填 描述
circleradius 20vp 默认半径。

样式

名称 属性类型 默认值 必填 描述
circleedge 2vp red 默认边缘颜色和宽度。

事件

名称 参数类型 描述
circleclick {radius: circle radius, edgewidth: circle edge width} 点击MyCircle组件时触发该回调,返回当前circle的半径和边缘宽度,单位是px。

示例

<!-- xxx.hml -->
<div style="flex-direction: column;align-items: center;">
    <text>"MyCircle的半径为:{{radiusOfMyCircle}}"</text>
    <text>"MyCircle的边缘宽度为:{{edgeWidthOfMyCircle}}"</text>
    <mycircle  circleradius="40vp" style="circleedge: 2vp red;" @circleclick="onCircleClick"> </mycircle>
</div>
// 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的属性定义

文件路径为:REPO_ROOT\foundation\ace\ace_engine\frameworks\bridge\common\dom\dom_type.h

// 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的属性值

文件路径为:REPO_ROOT\foundation\ace\ace_engine\frameworks\bridge\common\dom\dom_type.cpp

// 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 新增MyCircleDeclaration

MyCircleDeclaration类负责保存组件属性样式和事件,js界面中解析出的属性、样式、方法会保存到MyCircleDeclaration中。

1.2.1 在DeclarationConstants中新增MyCircleDeclaration的属性、样式、方法定义

一、添加到REPO_ROOT\foundation\ace\ace_engine\frameworks\core\components\declaration\common\declaration_constants.h

class DeclarationConstants {
public:
    /* ............................................. */
    /* definition of declaration of other components */
    /* ............................................. */

    // default value of mycircle
    static const std::shared_ptr<MyCircleAttribute> DEFAULT_MYCIRCLE_ATTR;
    static const std::shared_ptr<MyCircleStyle> DEFAULT_MYCIRCLE_STYLE;
    static const std::shared_ptr<MyCircleEvent> DEFAULT_MYCIRCLE_EVENT;
};

二、添加到REPO_ROOT\foundation\ace\ace_engine\frameworks\core\components\declaration\common\declaration_constants.cpp

namespace OHOS::Ace {

/* ................................................. */
/* implementation of declaration of other components */
/* ................................................. */

// default value of mycircle
const std::shared_ptr<MyCircleAttribute> DeclarationConstants::DEFAULT_MYCIRCLE_ATTR =
    std::make_shared<MyCircleAttribute>();
const std::shared_ptr<MyCircleStyle> DeclarationConstants::DEFAULT_MYCIRCLE_STYLE = std::make_shared<MyCircleStyle>();
const std::shared_ptr<MyCircleEvent> DeclarationConstants::DEFAULT_MYCIRCLE_EVENT = std::make_shared<MyCircleEvent>();

} // namespace OHOS::Ace
1.2.2 新增mycircle_declaration.h

文件路径:REPO_ROOT\foundation\ace\ace_engine\frameworks\core\components\declaration\mycircle\mycircle_declaration.h

struct MyCircleAttribute : Attribute {
    Dimension circleRadius = 20.0_vp;
};

struct MyCircleStyle : Style {
    Dimension edgeWidth = 2.0_vp;
    Color edgeColor = Color::RED;
};

struct MyCircleEvent : Event {
    EventMarker circleClickEvent;
};

class MyCircleDeclaration : public Declaration {
    DECLARE_ACE_TYPE(MyCircleDeclaration, Declaration);

public:
    MyCircleDeclaration() = default;
    ~MyCircleDeclaration() override = default;

    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;

protected:
    void InitSpecialized() override;
    bool SetSpecializedAttr(const std::pair<std::string, std::string>& attr) override;
    bool SetSpecializedStyle(const std::pair<std::string, std::string>& style) override;
    bool SetSpecializedEvent(int32_t pageId, const std::string& eventId, const std::string& event) override;
};

}

其中, SetSpecializedAttr / SetSpecializedStyle / SetSpecializedEvent这三个重写的方法,分别是用于解析组件的属性、样式和事件。

1.2.3 新增mycircle_declaration.cpp

文件路径:REPO_ROOT\foundation\ace\ace_engine\frameworks\core\components\declaration\mycircle\mycircle_declaration.cpp

一、MyCircleDeclaration::InitSpecialized()

void MyCircleDeclaration::InitSpecialized()
{
    AddSpecializedAttribute(DeclarationConstants::DEFAULT_MYCIRCLE_ATTR);
    AddSpecializedStyle(DeclarationConstants::DEFAULT_MYCIRCLE_STYLE);
    AddSpecializedEvent(DeclarationConstants::DEFAULT_MYCIRCLE_EVENT);
}

注意,若是有自定义的AttributeStyle或者Event,必须重写InitSpecialized()函数,否则会导致crash。

二、组件属性的解析:SetSpecializedAttr

bool MyCircleDeclaration::SetSpecializedAttr(const std::pair<std::string, std::string>& attr)
{
    if (attr.first == DOM_MYCIRCLE_CIRCLE_RADIUS) { // "circleradius"
        auto& mycircleAttr = MaybeResetAttribute<MyCircleAttribute>(AttributeTag::SPECIALIZED_ATTR);
        if (mycircleAttr.IsValid()) {
            mycircleAttr.circleRadius = StringToDimension(attr.second);
        }
        return true;
    }
    return false;
}

这个方法由框架流程调用,我们只需要在这个方法里面实现对应属性的解析,并且保存到MyCircleDeclaration中。

如上代码中,入参attr的格式形如<"circleradius", "40vp">,则我们只需要判断attr.first"circleradius"时,将attr.second转换为Dimension格式,设置到MyCircleAttribute中即可。设置完成后,返回true

三、组件样式的解析:SetSpecializedStyle

bool MyCircleDeclaration::SetSpecializedStyle(const std::pair<std::string, std::string>& style)
{
    if (style.first == DOM_MYCIRCLE_CIRCLE_EDGE) { // "circleedge"
        auto& mycircleStyle = MaybeResetStyle<MyCircleStyle>(StyleTag::SPECIALIZED_STYLE);
        if (mycircleStyle.IsValid()) {
            std::vector<std::string> 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].
            mycircleStyle.edgeWidth = edgeWidth;
            mycircleStyle.edgeColor = edgeColor;
        }
        return true;
    }
    return false;
}

这个方法由框架流程调用,我们只需要在这个方法里面实现对应样式的解析,并且保存到MyCircleDeclaration中。

如上代码中,入参style的格式形如<"circleedge", "2vp red">,则我们只需要判断style.first"circleedge"时,将style.second进行解析,设置到MyCircleStyle中即可。设置完成后,返回true

四、组件事件的解析:SetSpecializedEvent

bool MyCircleDeclaration::SetSpecializedEvent(int32_t pageId, const std::string& eventId, const std::string& event)
{
    if (event == DOM_MYCIRCLE_CIRCLE_CLICK) { // "circleclick"
        auto& mycircleEvent = MaybeResetEvent<MyCircleEvent>(EventTag::SPECIALIZED_EVENT);
        if (mycircleEvent.IsValid()) {
            mycircleEvent.circleClickEvent = EventMarker(eventId, event, pageId);
        }
        return true;
    }
    return false;
}

这个方法由框架流程调用,我们只需要在这个方法里面实现对应事件的解析,并且保存到MyCircleDeclaration中。

如上代码中,只要判断入参event的值为"circleclick",则我们只需要使用eventIdpageId构造一个EventMarker,并设置到MyCircleEvent中即可。设置完成后,返回true

五、MyCircleAttributeSetGet

void MyCircleDeclaration::SetCircleRadius(const Dimension& circleRadius)
{
    auto& attribute = MaybeResetAttribute<MyCircleAttribute>(AttributeTag::SPECIALIZED_ATTR);
    attribute.circleRadius = circleRadius;
}

const Dimension& MyCircleDeclaration::GetCircleRadius() const
{
    auto& attribute = static_cast<MyCircleAttribute&>(GetAttribute(AttributeTag::SPECIALIZED_ATTR));
    return attribute.circleRadius;
}

六、MyCircleStyleSetGet

void MyCircleDeclaration::SetEdgeWidth(const Dimension& edgeWidth)
{
    auto& style = MaybeResetStyle<MyCircleStyle>(StyleTag::SPECIALIZED_STYLE);
    style.edgeWidth = edgeWidth;
}

void MyCircleDeclaration::SetEdgeColor(const Color& edgeColor)
{
    auto& style = MaybeResetStyle<MyCircleStyle>(StyleTag::SPECIALIZED_STYLE);
    style.edgeColor = edgeColor;
}

const Dimension& MyCircleDeclaration::GetEdgeWidth() const
{
    auto& style = static_cast<MyCircleStyle&>(GetStyle(StyleTag::SPECIALIZED_STYLE));
    return style.edgeWidth;
}

const Color& MyCircleDeclaration::GetEdgeColor() const
{
    auto& style = static_cast<MyCircleStyle&>(GetStyle(StyleTag::SPECIALIZED_STYLE));
    return style.edgeColor;
}

七、MyCircleEventSetGet

void MyCircleDeclaration::SetCircleClickEvent(const EventMarker& circleClickEvent)
{
    auto& event = MaybeResetEvent<MyCircleEvent>(EventTag::SPECIALIZED_EVENT);
    event.circleClickEvent = circleClickEvent;
}

const EventMarker& MyCircleDeclaration::GetCircleClickEvent() const
{
    auto& event = static_cast<MyCircleEvent&>(GetEvent(EventTag::SPECIALIZED_EVENT));
    return event.circleClickEvent;
}

八、添加到declaration_creator_manager.cpp

文件路径:REPO_ROOT:\foundation\ace\ace_engine\frameworks\core\components\declaration\common\declaration_creator_manager.cpp

const RefPtr<Declaration> DeclarationCreatorManager::CreateDeclaration(const std::string& tag)
{
    static const LinearMapNode<std::function<RefPtr<Declaration>(const std::string&)>> declarationCreators[] = {
        /* ..................................................... */
    	/* declarationCreator of declaration of other components */
    	/* ..................................................... */
        { DOM_NODE_TAG_LINE, DeclarationCreator<SvgLineDeclaration> },
        { DOM_NODE_TAG_MYCIRCLE, DeclarationCreator<MyCircleDeclaration> },
        { DOM_NODE_TAG_PATH, DeclarationCreator<SvgPathDeclaration> },
        /* ..................................................... */
    	/* declarationCreator of declaration of other components */
    	/* ..................................................... */
    };
    // code block
    return declaration;
}

这里尤其要注意一点,declarationCreators[]是一个线性表,添加 { DOM_NODE_TAG_MYCIRCLE, DeclarationCreator<MyCircleDeclaration> }的地方必须要符合字母序。

DOM_NODE_TAG_LINE[] = "line"
DOM_NODE_TAG_MYCIRCLE[] = "mycircle"
DOM_NODE_TAG_PATH[] = "path"

所以DOM_NODE_TAG_MYCIRCLE的记录必须添加在"line"之后,"path"之前。

1.3 新增DOMMyCircle

1.3.1 新增dom_mycircle.h

文件路径:REPO_ROOT\foundation\ace\ace_engine\frameworks\bridge\common\dom\dom_mycircle.h

class DOMMyCircle final : public DOMNode {
    DECLARE_ACE_TYPE(DOMMyCircle, DOMNode);

public:
    DOMMyCircle(NodeId nodeId, const std::string& nodeName);
    ~DOMMyCircle() override = default;

    RefPtr<Component> GetSpecializedComponent() override
    {
        return myCircleChild_;
    }
    void ResetInitializedStyle() override;
    void InitializeStyle() override;

protected:
    void PrepareSpecializedComponent() override;

private:
    RefPtr<MyCircleComponent> myCircleChild_;
};

DOMMyCircle继承自DOMNode,主要功能是解析界面并生成相应的Component节点。

1.3.2 新增dom_mycircle.cpp

文件路径:REPO_ROOT\foundation\ace\ace_engine\frameworks\bridge\common\dom\dom_mycircle.cpp

一、 在构造的时候创建MyCircleComponent

DOMMyCircle::DOMMyCircle(NodeId nodeId, const std::string& nodeName) : DOMNode(nodeId, nodeName)
{
    myCircleChild_ = AceType::MakeRefPtr<MyCircleComponent>();
}

二、重写PrepareSpecializedComponent方法

void DOMMyCircle::PrepareSpecializedComponent()
{
    const auto& declaration = AceType::DynamicCast<MyCircleDeclaration>(declaration_);
    if (!declaration) {
        LOGE("mycircle declaration is null!");
        return;
    }
    myCircleChild_->SetDeclaration(declaration);
}

只要在该方法中将DOMMyCircledeclaration_成员变量设给myCircleChild_即可。

三、重写InitializeStyleResetInitializedStyle方法


void DOMMyCircle::InitializeStyle()
{
    auto declaration = AceType::DynamicCast<MyCircleDeclaration>(declaration_);
    if (!declaration) {
        LOGE("mycircle declaration is null!");
        return;
    }
    declaration->InitializeStyle();
}

void DOMMyCircle::ResetInitializedStyle()
{
    InitializeStyle();
}

1.4 在dom_document.cpp里增加mycircle组件

文件路径:REPO_ROOT\foundation\ace\ace_engine\frameworks\bridge\common\dom\dom_document.cpp

RefPtr<DOMNode> DOMDocument::CreateNodeWithId(const std::string& tag, NodeId nodeId, int32_t itemIndex)
{
    // code block
    static const LinearMapNode<RefPtr<DOMNode>(*)(NodeId, const std::string&, int32_t)> domNodeCreators[] = {
		// DomNodeCreators of other components
		{ DOM_NODE_TAG_MENU, &DOMNodeCreator<DOMMenu> },
		// "mycircle" must be inserted between "menu" and "navigation-bar"
        { DOM_NODE_TAG_MYCIRCLE, &DOMNodeCreator<DOMMyCircle> }, 
        { DOM_NODE_TAG_NAVIGATION_BAR, &DOMNodeCreator<DomNavigationBar> },
		// DomNodeCreators of other components
    };
	// code block
    return domNode;
}

这里尤其要注意一点,domNodeCreators[]是一个线性表,添加{ DOM_NODE_TAG_MYCIRCLE, &DOMNodeCreator<DOMMyCircle> }的地方必须要符合字母序。

DOM_NODE_TAG_MENUp[] = "menu"
DOM_NODE_TAG_NAVIGATION_BAR[] = "navigation-bar"
DOM_NODE_TAG_MYCIRCLE[] = "mycircle"

所以DOM_NODE_TAG_MYCIRCLE的记录必须添加在"menu"之后,"navigation-bar"之前。

2. 后端的布局和绘制

组件在后端的布局和绘制,需要相应地新增以下几个类:MyCircleComponentMyCircleElementRenderMyCircleFlutterRenderMyCircle

在后端引擎中,Component树、Element树和Render树为后端引擎维持和更新UI最为核心的三棵树。这三棵树之间的更新关系大致如下图所示:

2.1 新增MyCircleComponent

2.1.1 新增mycircle_component.h

文件路径:REPO_ROOT\foundation\ace\ace_engine\frameworks\core\components\mycircle\mycircle_component.h

class ACE_EXPORT MyCircleComponent : public RenderComponent {
    DECLARE_ACE_TYPE(MyCircleComponent, RenderComponent);

public:
    MyCircleComponent();
    ~MyCircleComponent() override = default;

    RefPtr<RenderNode> CreateRenderNode() override;
    RefPtr<Element> 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;

    void SetDeclaration(const RefPtr<MyCircleDeclaration>& declaration);
    const RefPtr<MyCircleDeclaration>& GetDeclaration() const;

private:
    RefPtr<MyCircleDeclaration> declaration_;
};
2.1.2 新增mycircle_component.cpp

文件路径:REPO_ROOT\foundation\ace\ace_engine\frameworks\core\components\mycircle\mycircle_component.cpp

一、构造时初始化declaration

MyCircleComponent::MyCircleComponent()
{
    // Init declaration while component is constructing
    if (!declaration_) {
        declaration_ = AceType::MakeRefPtr<MyCircleDeclaration>();
        declaration_->Init();
    }
}

二、将MyCircleComponentSetGet接口桥接到MyCircleDeclaration

void MyCircleComponent::SetCircleRadius(const Dimension& circleRadius)
{
    declaration_->SetCircleRadius(circleRadius);
}

void MyCircleComponent::SetEdgeWidth(const Dimension& edgeWidth)
{
    declaration_->SetEdgeWidth(edgeWidth);
}

void MyCircleComponent::SetEdgeColor(const Color& edgeColor)
{
    declaration_->SetEdgeColor(edgeColor);
}

void MyCircleComponent::SetCircleClickEvent(const EventMarker& circleClickEvent)
{
    declaration_->SetCircleClickEvent(circleClickEvent);
}

const Dimension& MyCircleComponent::GetCircleRadius() const
{
    return declaration_->GetCircleRadius();
}

const Dimension& MyCircleComponent::GetEdgeWidth() const
{
    return declaration_->GetEdgeWidth();
}

const Color& MyCircleComponent::GetEdgeColor() const
{
    return declaration_->GetEdgeColor();
}

const EventMarker& MyCircleComponent::GetCircleClickEvent() const
{
    return declaration_->GetCircleClickEvent();
}

三、实现CreateRenderNodeCreateElement函数

RefPtr<RenderNode> MyCircleComponent::CreateRenderNode()
{
    return RenderMyCircle::Create();
}

RefPtr<Element> MyCircleComponent::CreateElement()
{
    return AceType::MakeRefPtr<MyCircleElement>();
}

四、提供declaration_SetGet接口

const RefPtr<MyCircleDeclaration>& MyCircleComponent::GetDeclaration() const
{
    return declaration_;
}

void MyCircleComponent::SetDeclaration(const RefPtr<MyCircleDeclaration>& declaration)
{
    declaration_ = declaration;
}

2.2 新增MyCircleElement

2.2.1 新增mycircle_element.h

文件路径:REPO_ROOT\foundation\ace\ace_engine\frameworks\core\components\mycircle\mycircle_element.h

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

文件路径:REPO_ROOT\foundation\ace\ace_engine\frameworks\core\components\mycircle\render_mycircle.h

using CallbackForDeclarative = std::function<void(const std::shared_ptr<BaseEventInfo>&)>;
using CallbackForJS = std::function<void(const std::string&)>;

class RenderMyCircle : public RenderNode {
    DECLARE_ACE_TYPE(RenderMyCircle, RenderNode);

public:
    static RefPtr<RenderNode> Create();

    void Update(const RefPtr<Component>& 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;
    CallbackForDeclarative callbackForDeclarative_; // callback for declarative frontend
    CallbackForJS callbackForJS_;                   // callback for js frontend
    RefPtr<ClickRecognizer> clickRecognizer_;
    RefPtr<MyCircleDeclaration> declaration_;
};

注意,我们现在是js前端和ets前端共用同一个后端,所以后端要对js前端事件和ets前端事件都做响应。因为这两个前端的事件格式不同,所以这里定义了CallbackForDeclarativeCallbackForJS分别响应两种前端事件。

在开发js前端组件时,可以忽略ets前端事件。

2.3.2 新增render_mycircle.cpp

文件路径:REPO_ROOT\foundation\ace\ace_engine\frameworks\core\components\mycircle\render_mycircle.cpp

一、处理点击事件

RenderMyCircle::RenderMyCircle()
{
    clickRecognizer_ = AceType::MakeRefPtr<ClickRecognizer>();
    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)
{
    auto pipelineContext = context_.Upgrade();
    if (!pipelineContext) {
        LOGE("PipelineContext is null!");
        return;
    }
    if (pipelineContext->GetIsDeclarative() && callbackForDeclarative_) {
        callbackForDeclarative_(
            std::make_shared<MyCircleClickEvent>(NormalizeToPx(circleRadius_), NormalizeToPx(edgeWidth_)));
        return;
    }
    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函数,注册RenderMyCircleClickRecognizer,这样在接收到点击事件时即可触发创建ClickRecognizer时添加的事件回调;

3、实现在接收到点击事件之后的处理逻辑HandleMyCircleClickEvent

再次说明,示例代码是已经同时支持了js前端和ets前端,在单单开发js前端组件时,可以忽略ets前端事件。

二、重写Update函数

void RenderMyCircle::Update(const RefPtr<Component>& component)
{
    const auto& myCircleComponent = AceType::DynamicCast<MyCircleComponent>(component);
    if (!myCircleComponent) {
        LOGE("MyCircleComponent is null!");
        return;
    }
    auto pipelineContext = context_.Upgrade();
    if (!pipelineContext) {
        LOGE("PipelineContext is null!");
        return;
    }
    declaration_ = myCircleComponent->GetDeclaration();
    circleRadius_ = declaration_->GetCircleRadius();
    edgeWidth_ = declaration_->GetEdgeWidth();
    edgeColor_ = declaration_->GetEdgeColor();
    if (pipelineContext->GetIsDeclarative()) {
        callbackForDeclarative_ = AceAsyncEvent<void(const std::shared_ptr<BaseEventInfo>&)>::Create(
            declaration_->GetCircleClickEvent(), context_);
    } else {
        callbackForJS_ =
            AceAsyncEvent<void(const std::string&)>::Create(declaration_->GetCircleClickEvent(), context_);
    }

    // call [MarkNeedLayout] to do [PerformLayout] with new params
    MarkNeedLayout();
}

Update函数负责从MyCircleComponent获取所有绘制、布局和事件相关的属性更新。

三、重写PerformLayout函数

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

文件路径:REPO_ROOT\foundation\ace\ace_engine\frameworks\core\components\mycircle\flutter_render_mycircle.h

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.5.2 新增flutter_render_mycircle.cpp

文件路径:REPO_ROOT\foundation\ace\ace_engine\frameworks\core\components\mycircle\flutter_render_mycircle.cpp

一、实现RenderMyCircle::Create()函数

RefPtr<RenderNode> RenderMyCircle::Create()
{
    return AceType::MakeRefPtr<FlutterRenderMyCircle>();
}

RenderMyCircle::Create()在基类RenderMyCircle中定义,因为我们当前使用的是flutter引擎,所以在flutter_render_mycircle.cpp里面实现,返回在flutter引擎上自渲染的FlutterRenderMyCircle类。

二、重写Paint函数

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界面。

3.Windows本地编译与验证

到目前为止,上述的代码已经完成mycircle组件的功能的开发。接下来,我们来对mycircle组件进行进一步的验证。(以下验证工作都假设在Windows环境下的IDE预览器上进行。)

验证工作主要分为以下几个步骤:

1.编译IDE工具所需的最新sdk文件和预览器所需最新dll文件。(编译命令已放在步骤后)sdk主要包含js相关文件的修改,dll文件包含将代码进行打包后生成的文件。

2.将步骤1中编出的sdkdll文件在IDE工具所在的目录下进行替换。(具体路径放在步骤后)

3.参考示例编写代码,编写完成在右侧工具栏选择Previewer选项打开预览器插件观察效果。(建议使用悬浮窗口便于观察,可以采用鼠标对上述组件进行点击。)

4.如果需要更改新增的底层代码,首先需要关闭Previewer插件防止插件占用导致替换dll文件失败。重复步骤1步骤2即可。

  • 编译命令

    参考官方指导,下载OpenHarmony完整代码,需要在Linux的环境下执行以下命令进行交叉编译:

# 编译dll文件
./build.sh --product-name m40 --gn-args use_musl=false --gn-args use_custom_libcxx=true --gn-args use_custom_clang=true --build-target ace_for_windows

# 编译sdk命令
./build.sh --product-name ohos-sdk
  • 获取编译产物:

    out\ohos-arm-release\mingw_x86_64\common\common目录下拷贝所有文件,包含多个exedll库文件。

    out\release\packages\ohos-sdk\windows目录下,包含sdk相关文件。

  • 替换sdk与dll文件:

​ 替换sdk文件:将out\release\packages\ohos-sdk\windows下的文件js压缩包解压,将里面的build-toolsform文件夹替换至HarmonyOS SDK所在目录的路径为[Huawei Sdk]\js\[sdk对应版本号]的目录下,替换完成后进入build-tools\ace-loader\,执行npm install即可。

​ 替换dll文件:将out\ohos-arm-release\mingw_x86_64\common\common下的libace_engine_windows.dll文件替换至HarmonyOS SDK所在目录的路径为[Huawei Sdk]\previewer\[sdk对应版本号]\tv\bin目录下对应的文件即可。

  • 准备应用程序:

    新建IDE工程,选择Empty Ability,在文件中加入示例代码。

  • 运行:

    IDE工具右侧选择Previewer选项进行预览操作。如若需要修改代码查看效果,编写完代码重复编译命令、获取编译产物、替换sdkdll文件、运行步骤即可。

小结

到这里,新增一个mycircle组件所需的所有步骤都已经完成,我们可以展示一个圆,支持设置半径、边缘宽度和边缘颜色,可以通过点击事件获得当前圆的半径和边缘宽度。

当然mycircle组件是比较简单的示例组件,Ace开发框架支持更多更复杂的组件开发,比如提供单行文本输入组件TextInput、提供日历展示的Calendar组件等,更多的用法期待你来探索~

其他
1
https://gitee.com/yan-shuifeng/arkui_docs.git
git@gitee.com:yan-shuifeng/arkui_docs.git
yan-shuifeng
arkui_docs
ArkUI_Docs
master

搜索帮助