16 Star 49 Fork 47

yanshuifeng / ArkUI_Docs

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

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

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

MyCircle

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

支持设备

手机/平板 智慧屏 智能穿戴
支持 支持 支持

子组件

接口说明

接口名称 参数 默认值 参数描述
MyCircle - - 展示一个圆,支持设置半径、边缘宽度和边缘颜色,可以通过点击事件获得当前圆的半径和边缘宽度。

属性方法

名称 属性类型 默认值 描述
circleRadius Length 20 默认半径。
circleEdge {edgeColor?: Color, edgeWidth?: Length} {edgeColor: Red, edgeWidth: 1} 默认边缘颜色和宽度。

事件方法

名称 参数类型 描述
onCircleClick (event: {radius: number, edgeWidth: number}) => void 点击MyCircle组件时触发该回调,返回当前circle的半径和边缘宽度,单位是px。

示例

@Entry
@Component
struct my_test_page {
    @State radiusOfMyCircle: number = -1
    @State edgeWidthOfMyCircle: number = -1

   build() {
        Column() {
            Text("MyCircle的半径为:" + this.radiusOfMyCircle)
              .fontSize(20)
            Text("MyCircle的边缘宽度为:" + this.edgeWidthOfMyCircle)
              .fontSize(20)
            MyCircle()
              .circleRadius('40px')
              .circleEdge({ edgeColor: 'rgb(255, 0, 0)', edgeWidth: 2})
              .onCircleClick((event: {radius: number, edgeWidth: number}) => {
                this.radiusOfMyCircle = event.radius
                this.edgeWidthOfMyCircle = event.edgeWidth
            })
        }.alignItems(HorizontalAlign.Center)
      .width(500)
      .height(2000)
    }
}

该界面最终效果如下图所示:

1. ets的属性解析

1.1 ets-loader中增加新组件的属性定义

1.1.1 在component_map.ts中增加MyCircle定义

文件路径为:REPO_ROOT\foundation\ace\huawei_proprietary\tools\ets-loader\compiler\src\component_map.ts

const COMPONENT_MAP: any = {
  /* .............................. */
  /* definition of other components */
  /* .............................. */

  // add definition of MyCircle
  MyCircle: {
    atomic: true,
    attrs: [
      'circleRadius', 'circleEdge', 'onCircleClick'
    ]
  }
};
1.1.2 新增**mycircle.d.ts**文件

文件路径为:REPO_ROOT\foundation\ace\huawei_proprietary\tools\ets-loader\compiler\declarations\mycircle.d.ts

interface MyCircle extends CommonMethod<MyCircle> {
  (): MyCircle;
  circleRadius(value: string | number): MyCircle;
  circleEdge(value: {edgeColor?:  Color | string | number, edgeWidth?: string | number}): MyCircle;
  onCircleClick(callback: (event: {radius: number, edgeWidth: number}) => void): MyCircle;
}

declare const MyCircle: MyCircle;

circleRadius(value: string | number)

因为circleRadius的属性类型为Length,所以这个属性要支持纯数字(.circleRadius(40))以及带单位的字符串(.circleRadius('40px'))写法。

circleEdge(value: {edgeColor?: Color | string | number, edgeWidth?: string | number})

其中,edgeColor后的问号表示该属性并未必填项,与属性方法中的定义一致。

只有在ets-loader中增加了新组件的属性定义,才能正常编译带有新组件界面的ets文件。

1.2 新增JSMyCircle

1.2.1 新增js_mycircle.h

文件路径:REPO_ROOT\foundation\ace\ace_engine\frameworks\bridge\declarative_frontend\jsview\js_mycircle.h

class JSMyCircle : public JSViewAbstract, public JSInteractableView {
public:
    static void Create();
    static void JSBind(BindingTarget globalObj);

    // parse and set attributes to MyCircleComponent
    static void SetCircleRadius(const JSCallbackInfo& info);
    static void SetCircleEdge(const JSCallbackInfo& info);

    // parse and set click event to MyCircleComponent
    static void SetCircleClick(const JSCallbackInfo& info);
};

JSMyCircle多重继承自JSViewAbstractJSInteractableView,主要功能是解析并注册MyCircle组件的属性和事件。

JSViewAbstract:绘制相关属性解析和注册的基类。

JSInteractableView:交互相关事件解析和注册的基类。

// TODO:增加JSViewAbstract、JSInteractableView类说明

1.2.2 新增js_mycircle.cpp

文件路径:REPO_ROOT\foundation\ace\ace_engine\frameworks\bridge\declarative_frontend\jsview\js_mycircle.cpp

一、 在JSMyCircle::JSBind中绑定相关函数

void JSMyCircle::JSBind(BindingTarget globalObj)
{
    // Step1: indispensable step
    // Declare [MyCircle] and bind [Create] function to start parsing MyCircle component
    JSClass<JSMyCircle>::Declare("MyCircle");
    MethodOptions opt = MethodOptions::NONE;
    JSClass<JSMyCircle>::StaticMethod("create", &JSMyCircle::Create, opt);

    // Step2: bind funcitons that parse attributes and set to MyCircleComponent
    JSClass<JSMyCircle>::StaticMethod("circleRadius", &JSMyCircle::SetCircleRadius);
    JSClass<JSMyCircle>::StaticMethod("circleEdge", &JSMyCircle::SetCircleEdge);

    // Step3: bind functions that parse event and set to MyCircleComponent
    JSClass<JSMyCircle>::StaticMethod("onCircleClick", &JSMyCircle::SetCircleClick);

    // Step4: indispensable step
    // Declare that this is a subclass of JSViewAbstract
    JSClass<JSMyCircle>::Inherit<JSViewAbstract>();
    JSClass<JSMyCircle>::Bind<>(globalObj);
}

二、创建MyCircleComponent用以设置相关属性

void JSMyCircle::Create()
{
    RefPtr<Component> myCircleComponent = AceType::MakeRefPtr<OHOS::Ace::MyCircleComponent>();
    ViewStackProcessor::GetInstance()->Push(myCircleComponent);
}

三、解析属性样式

void JSMyCircle::SetCircleRadius(const JSCallbackInfo& info)
{
    if (info.Length() < 1) {
        LOGE("The arg is wrong, it is supposed to have at least 1 arguments");
        return;
    }

    if (!info[0]->IsNumber() && !info[0]->IsString()) {
        LOGE("Arg is not Number or String.");
        return;
    }
    auto myCircle = AceType::DynamicCast<MyCircleComponent>(ViewStackProcessor::GetInstance()->GetMainComponent());
    if (!myCircle) {
        LOGE("MyCircleComponent is null.");
        return;
    }
    if (info[0]->IsNumber()) {
        // when the [circleRadius] is set with a pure number, use [vp] unit as default
        myCircle->SetCircleRadius(Dimension(info[0]->ToNumber<double>(), DimensionUnit::VP));
    } else {
        myCircle->SetCircleRadius(StringUtils::StringToDimension(info[0]->ToString(), true));
    }
}

void JSMyCircle::SetCircleEdge(const JSCallbackInfo& info)
{
    if (info.Length() < 1) {
        LOGE("The arg is wrong, it is supposed to have atleast 1 argument.");
        return;
    }
    if (!info[0]->IsObject()) {
        LOGE("Arg is not a object.");
        return;
    }
    auto myCircle = AceType::DynamicCast<MyCircleComponent>(ViewStackProcessor::GetInstance()->GetMainComponent());
    if (!myCircle) {
        LOGE("MyCircleComponent is null.");
        return;
    }
    JSRef<JSObject> edgeSettingObj = JSRef<JSObject>::Cast(info[0]);

    // parse [edgeWidth] in [circleEdge]
    JSRef<JSVal> edgeWidth = edgeSettingObj->GetProperty("edgeWidth");
    if (edgeWidth->IsNumber()) {
        // when the [edgeWidth] is set with a pure number, use [vp] unit as default
        myCircle->SetEdgeWidth(Dimension(edgeWidth->ToNumber<double>(), DimensionUnit::VP));
    } else {
        myCircle->SetEdgeWidth(StringUtils::StringToDimension(edgeWidth->ToString(), true));
    }

    // parse [edgeColor] in [circleEdge]
    JSRef<JSVal> colorValue = edgeSettingObj->GetProperty("edgeColor");
    if (colorValue->IsString()) {
        myCircle->SetEdgeColor(Color::FromString(colorValue->ToString()));
    } else if (colorValue->IsNumber()) {
        myCircle->SetEdgeColor(Color(ColorAlphaAdapt(colorValue->ToNumber<uint32_t>())));
    }
}

四、解析组件事件

JSRef<JSVal> MyCircleClickEventToJSValue(const MyCircleClickEvent& eventInfo)
{
    JSRef<JSObject> obj = JSRef<JSObject>::New();
    obj->SetProperty("radius", eventInfo.GetRadius());
    obj->SetProperty("edgeWidth", eventInfo.GetEdgeWidth());
    return JSRef<JSVal>::Cast(obj);
}

void JSMyCircle::SetCircleClick(const JSCallbackInfo& info)
{
    if (!info[0]->IsFunction()) {
        LOGE("The arg is not a function.");
        return;
    }
    auto circleClickFunc = AceType::MakeRefPtr<JsEventFunction<MyCircleClickEvent, 1>>(
        JSRef<JSFunc>::Cast(info[0]), MyCircleClickEventToJSValue);
    auto myCircle = AceType::DynamicCast<MyCircleComponent>(ViewStackProcessor::GetInstance()->GetMainComponent());
    if (!myCircle) {
        LOGE("myCircle component is null");
        return;
    }
    myCircle->SetCircleClickEvent(EventMarker([execCtx = info.GetExecutionContext(), func = std::move(circleClickFunc)]
        (const BaseEventInfo* info) {
        JAVASCRIPT_EXECUTION_SCOPE(execCtx);
        auto eventInfo = TypeInfoHelper::DynamicCast<MyCircleClickEvent>(info);
        func->Execute(*eventInfo);
    }));
}

1.3 添加JSMyCircle::JSBind()JsRegisterViews

一、添加到REPO_ROOT\foundation\ace\ace_engine\frameworks\bridge\declarative_frontend\engine\quickjs\qjs_view_register.cpp

void JsRegisterViews(BindingTarget global)
{
    JSContext* ctx = QJSContext::Current();

    QJSUtils::DefineGlobalFunction(ctx, JsLoadDocument, "loadDocument", 1);
    QJSUtils::DefineGlobalFunction(ctx, JsDumpMemoryStats, "dumpMemoryStats", 1);

  /* ......................... */
  /* add JSXXX::JSBind(global) */
  /* ......................... */

  // add JSMyCircle::JSBind(global)
    JSMyCircle::JSBind(global);

  /* ......................... */
  /* add JSXXX::JSBind(global) */
  /* ......................... */

    LOGD("View classes and jsCreateDocuemnt, registerObservableObject functions registered.");
}

二、添加到REPO_ROOT\foundation\ace\ace_engine\frameworks\bridge\declarative_frontend\engine\v8\v8_view_register.cpp

void JsRegisterViews(BindingTarget global)
{
    JSContext* ctx = QJSContext::Current();

    QJSUtils::DefineGlobalFunction(ctx, JsLoadDocument, "loadDocument", 1);
    QJSUtils::DefineGlobalFunction(ctx, JsDumpMemoryStats, "dumpMemoryStats", 1);

  /* ............................ */
  /* add JSXXX::JSBind(globalObj) */
  /* ............................ */

  // add JSMyCircle::JSBind(global)
    JSMyCircle::JSBind(globalObj);

  /* ............................ */
  /* add JSXXX::JSBind(globalObj) */
  /* ............................ */

    LOGD("View classes and jsCreateDocuemnt, registerObservableObject functions registered.");
}

2. 后端的布局和绘制

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

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

MyCircleDeclaration类负责保存组件属性样式和事件,JSMyCircleets中解析出来的内容会通过MyCircleComponent保存到MyCircleDeclaration中。

2.1 新增MyCircleDeclaration

2.1.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
2.1.2 新增mycircle_declaration.h

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

struct MyCircleAttribute : Attribute {
    Dimension circleRadius;
};

struct MyCircleStyle : Style {
    Dimension edgeWidth = Dimension(1);
    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;
};
2.1.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。

二、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;
}

2.2 新增MyCircleComponent

2.2.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;

private:
    RefPtr<MyCircleDeclaration> declaration_;
};
2.2.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>();
}

2.3 新增MyCircleElement

2.3.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.4 新增RenderMyCircle

2.4.1 新增render_mycircle.h

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

using CircleClickCallback = std::function<void(const std::shared_ptr<BaseEventInfo>&)>;

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;
    CircleClickCallback circleClickCallback_;
    RefPtr<ClickRecognizer> clickRecognizer_;
};
2.4.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)
{
    circleClickCallback_(std::make_shared<MyCircleClickEvent>(NormalizeToPx(circleRadius_), NormalizeToPx(edgeWidth_)));
}

1、创建一个ClickRecognizer

2、重写OnTouchTestHit函数,注册RenderMyCircleClickRecognizer,这样在接收到点击事件时即可触发创建ClickRecognizer时添加的事件回调;

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

二、重写Update函数

void RenderMyCircle::Update(const RefPtr<Component>& component)
{
    const auto& myCircleComponent = AceType::DynamicCast<MyCircleComponent>(component);
    if (!myCircleComponent) {
        LOGE("MyCircleComponent is null!");
        return;
    }
    circleRadius_ = myCircleComponent->GetCircleRadius();
    edgeWidth_ = myCircleComponent->GetEdgeWidth();
    edgeColor_ = myCircleComponent->GetEdgeColor();
    circleClickCallback_ = AceAsyncEvent<void(const std::shared_ptr<BaseEventInfo>&)>::Create(
            myCircleComponent->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.5 新增FlutterRenderMyCircle

2.5.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主要包含ets相关文件的修改;dll文件包含将代码进行打包后生成的文件。
  2. 步骤1中编出的sdkdll文件在IDE工具所在的目录下进行替换。
  3. 编写示例项中的代码,并在右侧工具栏选择Previewer选项打开预览器插件观察效果。(建议使用悬浮窗口便于观察,可以采用鼠标对上述组件进行点击。)
  4. 如果需要更改代码并观察效果,首先需要关闭Previewer插件以防止该插件因为占用而替换dll文件失败,然后重复完成上述步骤1步骤2即可。
  • 编译命令:

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

# 编译dll文件
./build.sh --export-para PYCACHE_ENABLE:true --product-name Hi3516DV300 --ccache

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

    out/sdk/ohos-sdk/windows/previewer目录下拷贝所有文件,包含多个exedll库文件。

    out/sdk/ohos-sdk/windows/ets/build-tools/ets-loader目录下,包含sdk相关文件。

  • 替换sdkdll文件:

​ 替换sdk文件:将out/sdk/ohos-sdk/windows/ets/build-tools/ets-loader下的文件替换至Openharmony SDK所在目录的路径为[Openharmony SDK]\[sdk对应版本号]的目录下,替换完成后进入build-tools\ets-loader\,执行npm install即可。

​ 替换dll文件:将out/sdk/ohos-sdk/windows/previewer/common/bin下的libace_engine_windows.dll文件替换至Openharmony SDK所在目录的路径为[Openharmony SDK]\[sdk对应版本号]\previewer\common\bin目录下对应的文件即可。

  • 准备应用程序:

    新建IDE工程,选择[Standard]Empty Ability,在index.dts文件中加入示例代码。

  • 运行:

    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

搜索帮助