1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/openharmony-arkui_ace_engine

Клонировать/Скачать
如何新增一个组件.md 23 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 09.03.2025 14:45 e36eac3

Новый компонент MyCircle в фреймворке разработки интерфейсов JS UI: руководство по созданию

Эта статья wiki продемонстрирует полный процесс создания нового компонента JS UI с использованием примера нового компонента MyCircle.

Ссылка на полный патч: https://gitee.com/theretherehuh/ace_ace_engine/pulls/1/files

mycircle

Кликабельный компонент, представляющий собой круг, который позволяет устанавливать радиус, ширину границы и цвет границы. Текущий радиус и ширину границы можно получить через событие клика.

Поддерживаемые устройства

Мобильный телефон Умный экран Умные часы Легкие умные часы Легкий автомобильный компьютер
Да Да Да Да Да

Подкомпоненты

Отсутствуют

Атрибуты

Название Тип атрибута Значение по умолчанию Обязательный Описание
circleradius Длина 20vp Нет Значение по умолчанию радиуса.

Стили

Название Тип атрибута Значение по умолчанию Обязательный Описание
circleedge Длина Цвет 2vp red Нет Значение по умолчанию цвета и ширины границы.
Название Тип параметра Описание
circleclick {{радиус: радиус круга, ширина_края: ширина края круга}} При нажатии на компонент MyCircle вызывается этот обратный вызов, который возвращает текущий радиус и ширину границы круга, измеренные в px.

Пример

<!-- xxx.html -->
<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 Добавление определений свойств MyCircle в dom_type.h

Путь к файлу: frameworks\bridge\common\dom\dom_type.h

// Определение узловых тэгов
/* .................................... */
/* Определения узловых тэгов других компонентов */
/* .................................... */
ACE_EXPORT extern const char DOM_NODE_TAG_MYCIRCLE[];

/* ........................... */
/* Определения других компонентов */
/* ........................... */
```// Определения MyCircle
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 Добавление значений свойств MyCircle в dom_type.cpp

Путь к файлу: frameworks\bridge\common\dom\dom_type.cpp

// Определение узловых тэгов
/* .................................... */
/* Определения узловых тэгов других компонентов */
/* .................................... */
const char DOM_NODE_TAG_MYCIRCLE[] = "mycircle";

/* ........................... */
/* Определения других компонентов */
/* ственно ........................... */

// Определения MyCircle
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

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_;
    }

protected:
    bool SetSpecializedAttr(const std::pair<std::string, std::string>& attr) override;
    bool SetSpecializedStyle(const std::pair<std::string, std::string>& style) override;
    bool AddSpecializedEvent(int32_t pageId, const std::string& event) override;

private:
    RefPtr<MyCircleComponent> myCircleChild_;
};

Класс DOMMyCircle наследуется от DOMNode, его основная задача — парсинг интерфейса и создание соответствующих узлов Component.

1.2.2 Добавлен dom_mycircle.cpp

Путь к файлу: frameworks/bridge/common/dom/dom_mycircle.cpp

I. Парсинг свойств компонента: SetSpecializedAttr```cpp bool DOMMyCircle::SetSpecializedAttr(const std::pair<std::string, std::string>& attr) { if (attr.first == DOM_MYCIRCLE_CIRCLE_RADIUS) { // "радиус окружности" myCircleChild_->SetCircleRadius(StringToDimension(attr.second)); return true; } return false; }


Этот метод вызывается процессом работы с фреймворком; нам нужно реализовать парсинг соответствующих свойств и установить их в `MyCircleComponent`.

Как показано в приведённом выше коде, входной параметр `attr` имеет вид, например, `<"радиус окружности", "40vp">`. В этом случае мы должны проверять значение `attr.first`, равное `"радиус окружности"`, преобразовать `attr.second` в формат `Dimension` и установить его в `MyCircleComponent`. После завершения установки возвращаем `true`.

**II. Парсинг стилей компонента: `SetSpecializedStyle`**```c++
bool DOMMyCircle::SetSpecializedStyle(const std::pair<std::string, std::string>& style)
{
    if (style.first == DOM_MYCIRCLE_CIRCLE_EDGE) { // "circleedge"
        std::vector<std::string> edgeStyles;
        // Значение `[circleedge]` может выглядеть как `"2vp red"` или `"2vp"`. Для парсинга такого значения стиля требуется три шага.
        // Шаг 1: Разделите строковое значение пробелами, чтобы получить вектор типа ["2vp", "red"].
        StringUtils::StringSplitter(style.second, ' ', edgeStyles);
        Dimension edgeWidth(1, DimensionUnit::VP);
        Color edgeColor(Color::RED);
``````markdown
// Шаг 2: Разбор цвета ребра и ширины ребра соответственно.
switch(edgeStyles.size()) {
    case 0: // значение пустое
        LOGW("Значение для границы круга пустое, используется стандартная настройка.");
        break;
    case 1: // случай, когда задана только ширина ребра
        // Должно быть гарантировано цепочкой инструментов при генерации js-пакета, что единственное значение является
        // типом числа для ширины ребра, а не типом цвета для цвета ребра.
        edgeWidth = StringUtils::StringToDimension(edgeStyles[0]);
        break;
    case 2: // случай, когда заданы ширина и цвет ребра
        edgeWidth = StringUtils::StringToDimension(edgeStyles[0]);
        edgeColor = Color::FromString(edgeStyles[1]);
        break;
    default:
        LOGW("Для границы круга задано более двух значений, проверьте. Значение равно %{private}s",
            style.second.c_str());
        break;
}
```Пожалуйста, обратите внимание, что в данном примере используются логические сообщения `LOGW`, которые остаются без изменения, так как они являются частью кода. Также остаются без изменений имена переменных, функций и методов.

```cpp
// Шаг 3: Установите цвет и ширину ребра в [mycircleStyle].
myCircleChild_->SetEdgeWidth(edgeWidth);
myCircleChild_->SetEdgeColor(edgeColor);
return true;
}
return false;
}

Этот метод вызывается процессом работы с фреймворком, поэтому нам нужно реализовать парсинг соответствующего стиля и сохранить его в MyCircleComponent.

Как показано в приведённом выше коде, входной параметр style имеет вид, например, <"circleedge", "2vp red">. Поэтому нам нужно проверять значение style.first, равное "circleedge", и затем анализировать style.second, чтобы установить стиль в MyCircleComponent. После завершения установки вернуть true.


Шаг 3: Парсинг специализированных событий — SetSpecializedEvent

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 Добавление компонента mycircle в dom_document.cpp

Файл: frameworks/bridge/common/dom/dom_document.cpp

RefPtr<DOMNode> DOMDocument::CreateNodeWithId(const std::string& tag, NodeId nodeId, int32_t itemIndex)
{
    // Блок кода
    static const LinearMapNode<RefPtr<DOMNode>(*)(NodeId, const std::string&, int32_t)> domNodeCreators[] = {
		// Создатели узлов других компонентов
		{ DOM_NODE_TAG_MENU, &DOMNodeCreator<DOMMenu> },
		// mycircle следует добавить между menu и navigation-bar
        { DOM_NODE_TAG_MYCIRCLE, &DOMNodeCreator<DOMMyCircle> }, 
        { DOM_NODE_TAG_NAVIGATION_BAR, &DOMNodeCreator<DomNavigationBar> },
		// Создатели узлов других компонентов
    };
	// Блок кода
    return domNode;
}

Здесь важно отметить, что domNodeCreators[] является линейным массивом, и место добавления { DOM_NODE_TAG_MYCIRCLE, &DOMNodeCreator<DOMMyCircle> } должно соответствовать алфавитному порядку:

const char* DOM_NODE_TAG_MENU = "menu";
const char* DOM_NODE_TAG_NAVIGATION_BAR = "navigation-bar";
const char* 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

class ACE_EXPORT MyCircleComponent : public RenderComponent {
    DECLARE_ACE_TYPE(MyCircleComponent, RenderComponent);
```### 3. Реализация методов `CreateRenderNode` и `CreateElement`

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

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

2.2 Добавление класса MyCircleElement

2.2.1 Добавление файла mycircle_element.h

Путь к файлу: 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`

Путь к файлу: `frameworks/core/components/mycircle/render_mycircle.h`

```cpp
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& coordinate_offset, const TouchRestrict& touch_restrict, TouchTestResult& result) override;

    Dimension circle_radius_ = Dimension(0);
    Dimension edge_width_ = Dimension(1);
    Color edge_color_ = Color::RED;
    CallbackForJS callback_for_js_;                   // callback for js frontend
    RefPtr<ClickRecognizer> click_recognizer_;
};

2.3.2 Добавление файла render_mycircle.cpp

Путь к файлу: frameworks/core/components/mycircle/render_mycircle.cpp

3. Обработка события нажатия

// Определение метода обработки клика
void RenderMyCircle::HandleMyCircleClickEvent(const ClickInfo& info)
{
    if (!click_recognizer_) {
        LOGW("No recognizer set");
        return;
    }
    auto context = GetContext();
    if (!context) {
        LOGW("No context available");
        return;
    }

    auto params = info.GetExtraData();
    if (!params) {
        LOGW("No extra data provided");
        return;
    }

    auto json_str = params->ToJsonString();
    if (!json_str.has_value()) {
        LOGW("Failed to convert parameters to JSON string");
        return;
    }
}
``````cpp
callbackForJS_(jsonStr.value());
RenderMyCircle::RenderMyCircle()
{
    clickRecognizer_ = AceType::MakeRefPtr<ClickRecognizer>();
    clickRecognizer_->SetOnClick([wp = WeakClaim(this)](const ClickInfo& info) {
        auto myCircle = wp.Upgrade();
        if (!myCircle) {
            LOGE("Неудачное обновление WeakPtr RenderMyCircle, прекращение обработки события нажатия.");
            return;
        }
        myCircle->HandleMyCircleClickEvent(info);
    });
}
## Описание методов
```1. Создание объекта `ClickRecognizer`;

2. Переопределение метода `OnTouchTestHit`, где регистрируется `ClickRecognizer` для `RenderMyCircle`. Это позволяет вызывать события обратного вызова, добавленные при создании `ClickRecognizer`, когда происходит событие нажатия.

3. Реализация логики обработки клика в методе `HandleMyCircleClickEvent`.

&nbsp;

### 2. Обновление метода `Update`

```cpp
void RenderMyCircle::Update(const RefPtr<Component>& component)
{
    const auto& myCircleComponent = AceType::DynamicCast<MyCircleComponent>(component);
    if (!myCircleComponent) {
        LOGW("MyCircleComponent is null!");
        return;
    }
    circleRadius_ = myCircleComponent->GetCircleRadius();
    edgeWidth_ = myCircleComponent->GetEdgeWidth();
    edgeColor_ = myCircleComponent->GetEdgeColor();
    callbackForJS_ =
        AceAsyncEvent<void(const std::string&)>::Create(myCircleComponent->GetCircleClickEvent(), context_);

    // Вызов [MarkNeedLayout], чтобы выполнить [PerformLayout] с новыми параметрами
    MarkNeedLayout();
}

Метод Update отвечает за получение всех атрибутов, связанных с рисованием, макетом и событиями, из компонента MyCircleComponent.

 

3. Переопределение метода PerformLayout

void RenderMyCircle::PerformLayout()
{
    double realSize = NormalizeToPx(edgeWidth_) + 2 * NormalizeToPx(circleRadius_);
    Size layoutSizeAfterConstrain = GetLayoutParam().Constrain(Size(realSize, realSize));
    SetLayoutSize(layoutSizeAfterConstrain);
}

Метод PerformLayout отвечает за вычисление информации о макете и установку требуемых размеров макета через вызов метода SetLayoutSize.

##### 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);
};
``````md
# Подведение итогов

## 2.4.2 Добавлен `flutter_render_mycircle.cpp`

Путь к файлу: `frameworks/core/components/mycircle/flutter_render_mycircle.cpp`

### 1. Реализация функции `RenderMyCircle::Create()`

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

Функция RenderMyCircle::Create() определена в базовом классе RenderMyCircle. Поскольку мы используем движок flutter, реализация этой функции находится в файле flutter_render_mycircle.cpp, который возвращает объект класса FlutterRenderMyCircle.

2. Переопределение метода 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 отвечает за вызов соответствующих интерфейсов канваса для выполнения рисования. Это можно считать последним шагом при создании нового компонента, так как он непосредственно определяет, какой пользовательский интерфейс будет отображаться на экране. ```На этом все необходимые шаги по добавлению компонента MyCircle завершены. Мы можем отобразить круг с возможностью установки радиуса, ширины границы и цвета границы. Также можно получить текущий радиус и ширину границы через событие клика.Конечно, компонент `MyCircle` является примером простого компонента. Фреймворк JS UI поддерживает разработку более сложных компонентов, таких как односимвольный текстовый ввод `TextInput` и компонент отображения календаря `Calendar`. Более подробные возможности ждут вас для исследования!

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/openharmony-arkui_ace_engine.git
git@api.gitlife.ru:oschina-mirror/openharmony-arkui_ace_engine.git
oschina-mirror
openharmony-arkui_ace_engine
openharmony-arkui_ace_engine
master