* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef LIST_MAKER_H
#define LIST_MAKER_H
#include <functional>
#include <memory>
#include <arkui/native_node.h>
#include <arkui/native_type.h>
#include <hilog/log.h>
#include "ScrollableNode.h"
#include "ScrollableUtils.h"
#include "ScrollableEvent.h"
#include "ArkUINodeAdapter.h"
#ifndef LOG_TAG
#define LOG_TAG "ListMaker"
#endif
class ListMaker : public BaseNode {
public:
static ArkUI_NodeHandle CreateNativeNode();
static constexpr int32_t kNestedScrollParentFirst = 0;
static constexpr int32_t kNestedScrollChildFirst = 1;
static constexpr int32_t kNestedScrollSelfFirst = 2;
static constexpr int32_t kScrollIndexFirstDataIndex = 0;
static constexpr int32_t kScrollIndexLastDataIndex = 3;
static constexpr int32_t kVisibleChangeFirstChildDataIndex = 0;
static constexpr int32_t kVisibleChangeStartAreaDataIndex = 1;
static constexpr int32_t kVisibleChangeStartIndexDataIndex = 2;
static constexpr int32_t kVisibleChangeLastChildDataIndex = 3;
static constexpr int32_t kVisibleChangeEndAreaDataIndex = 4;
static constexpr int32_t kVisibleChangeEndIndexDataIndex = 5;
static constexpr int32_t kScrollFrameBeginDataIndex = 0;
static constexpr uint32_t kColorTransparent = 0x00000000U;
struct VisibleContentChange {
int32_t firstChildIndex = -1;
ArkUI_ListItemGroupArea startArea = ARKUI_LIST_ITEM_GROUP_AREA_OUTSIDE;
int32_t startItemIndex = -1;
int32_t lastChildIndex = -1;
ArkUI_ListItemGroupArea endArea = ARKUI_LIST_ITEM_GROUP_AREA_OUTSIDE;
int32_t endItemIndex = -1;
bool StartOnItem() const { return startArea == ARKUI_LIST_ITEM_SWIPE_AREA_ITEM && startItemIndex >= 0; }
bool EndOnItem() const { return endArea == ARKUI_LIST_ITEM_SWIPE_AREA_ITEM && endItemIndex >= 0; }
};
public:
ListMaker()
: BaseNode(NodeApiInstance::GetInstance()->GetNativeNodeAPI()->createNode(ARKUI_NODE_LIST)),
nodeApi_(NodeApiInstance::GetInstance()->GetNativeNodeAPI())
{
if (!IsNotNull(nodeApi_) || !IsNotNull(GetHandle())) {
return;
}
nodeApi_->addNodeEventReceiver(GetHandle(), &ListMaker::StaticEventReceiver);
const uint32_t scrollEventMask = SCROLL_EVT_FRAME_BEGIN | SCROLL_EVT_REACH_END;
scrollEventGuard_.Bind(nodeApi_, GetHandle(), this, scrollEventMask);
}
~ListMaker() override
{
scrollEventGuard_.Release();
if (!IsNotNull(nodeApi_)) {
return;
}
UnregisterSpecificEvents();
ResetListAdapter();
CleanupChildrenMainSizeOption();
}
void SetClipContent(bool clipEnabled)
{
SetAttributeInt32(nodeApi_, GetHandle(), NODE_SCROLL_CLIP_CONTENT, clipEnabled ? 1 : 0);
}
void SetEdgeEffectSpring(bool springEnabled)
{
int32_t effectValue = springEnabled ? ARKUI_EDGE_EFFECT_SPRING : ARKUI_EDGE_EFFECT_NONE;
SetAttributeInt32(nodeApi_, GetHandle(), NODE_SCROLL_EDGE_EFFECT, effectValue);
}
void SetScrollBarVisible(bool visible)
{
int32_t displayMode = visible ? ARKUI_SCROLL_BAR_DISPLAY_MODE_ON : ARKUI_SCROLL_BAR_DISPLAY_MODE_OFF;
SetAttributeInt32(nodeApi_, GetHandle(), NODE_SCROLL_BAR_DISPLAY_MODE, displayMode);
}
void SetItemSpacing(float spacing) { SetAttributeFloat32(nodeApi_, GetHandle(), NODE_LIST_SPACE, spacing); }
void SetScrollBarState(bool visible) { SetScrollBarVisible(visible); }
void SetSpace(float spacing) { SetItemSpacing(spacing); }
void SetNestedScrollMode(int32_t mode)
{
SetAttributeInt32(nodeApi_, GetHandle(), NODE_SCROLL_NESTED_SCROLL, mode);
}
void ScrollToIndex(int32_t index) { ScrollToIndex(index, false, ARKUI_SCROLL_ALIGNMENT_START); }
void ScrollToIndex(int32_t index, bool smooth, ArkUI_ScrollAlignment align)
{
ArkUI_NumberValue v[3];
v[0].i32 = index;
v[1].i32 = smooth ? 1 : 0;
v[2].i32 = static_cast<int32_t>(align);
ArkUI_AttributeItem it{v, 3};
nodeApi_->setAttribute(GetHandle(), NODE_LIST_SCROLL_TO_INDEX, &it);
}
void ScrollToIndexInGroup(int32_t groupIndex, int32_t itemIndex)
{
ArkUI_NumberValue values[] = {{.i32 = groupIndex}, {.i32 = itemIndex}};
ArkUI_AttributeItem item{values, 2};
nodeApi_->setAttribute(GetHandle(), NODE_LIST_SCROLL_TO_INDEX_IN_GROUP, &item);
}
void SetLazyAdapter(const std::shared_ptr<ArkUINodeAdapter> &adapter)
{
if (!IsNotNull(adapter)) {
nodeApi_->resetAttribute(GetHandle(), NODE_LIST_NODE_ADAPTER);
listAdapter_.reset();
return;
}
adapter->EnsurePlaceholderTypeOr(static_cast<int32_t>(ARKUI_NODE_LIST_ITEM));
ArkUI_AttributeItem item{nullptr, 0, nullptr, adapter->GetAdapter()};
nodeApi_->setAttribute(GetHandle(), NODE_LIST_NODE_ADAPTER, &item);
listAdapter_ = adapter;
}
void SetDirection(ArkUI_Axis axis)
{
ArkUI_NumberValue v0{};
v0.i32 = static_cast<int32_t>(axis);
ArkUI_AttributeItem it{&v0, 1};
nodeApi_->setAttribute(GetHandle(), NODE_LIST_DIRECTION, &it);
}
void SetSticky(ArkUI_StickyStyle sticky)
{
ArkUI_NumberValue v0{};
v0.i32 = static_cast<int32_t>(sticky);
ArkUI_AttributeItem it{&v0, 1};
nodeApi_->setAttribute(GetHandle(), NODE_LIST_STICKY, &it);
}
void SetCachedCount(int32_t count)
{
ArkUI_NumberValue v0{};
v0.i32 = count;
ArkUI_AttributeItem it{&v0, 1};
nodeApi_->setAttribute(GetHandle(), NODE_LIST_CACHED_COUNT, &it);
}
void SetInitialIndex(int32_t index)
{
ArkUI_NumberValue v0{};
v0.i32 = index;
ArkUI_AttributeItem it{&v0, 1};
nodeApi_->setAttribute(GetHandle(), NODE_LIST_INITIAL_INDEX, &it);
}
void SetDivider(float widthPx)
{
ArkUI_NumberValue v0{};
v0.f32 = widthPx;
ArkUI_AttributeItem it{&v0, 1};
nodeApi_->setAttribute(GetHandle(), NODE_LIST_DIVIDER, &it);
}
void SetAlignListItem(ArkUI_ListItemAlignment align)
{
ArkUI_NumberValue v0{};
v0.i32 = static_cast<int32_t>(align);
ArkUI_AttributeItem it{&v0, 1};
nodeApi_->setAttribute(GetHandle(), NODE_LIST_ALIGN_LIST_ITEM, &it);
}
void SetChildrenMainSizeOption(ArkUI_ListChildrenMainSize *opt)
{
ArkUI_AttributeItem it{nullptr, 0, nullptr, opt};
nodeApi_->setAttribute(GetHandle(), NODE_LIST_CHILDREN_MAIN_SIZE, &it);
childrenMainSizeOption_ = opt;
}
void SetFocusWrapMode(int mode)
{
ArkUI_NumberValue v0{};
v0.i32 = mode;
ArkUI_AttributeItem it{&v0, 1};
nodeApi_->setAttribute(GetHandle(), NODE_LIST_FOCUS_WRAP_MODE, &it);
}
void SetMaintainVisibleContentPosition(bool on)
{
ArkUI_NumberValue v0{};
v0.i32 = on ? 1 : 0;
ArkUI_AttributeItem it{&v0, 1};
nodeApi_->setAttribute(GetHandle(), NODE_LIST_MAINTAIN_VISIBLE_CONTENT_POSITION, &it);
}
void SetStackFromEnd(bool on)
{
ArkUI_NumberValue v0{};
v0.i32 = on ? 1 : 0;
ArkUI_AttributeItem it{&v0, 1};
nodeApi_->setAttribute(GetHandle(), NODE_LIST_STACK_FROM_END, &it);
}
void SetSyncLoad(bool on)
{
ArkUI_NumberValue v0{};
v0.i32 = on ? 1 : 0;
ArkUI_AttributeItem it{&v0, 1};
nodeApi_->setAttribute(GetHandle(), NODE_LIST_SYNC_LOAD, &it);
}
void SetScrollSnapAlign(int align )
{
ArkUI_NumberValue v0{};
v0.i32 = align;
ArkUI_AttributeItem it{&v0, 1};
nodeApi_->setAttribute(GetHandle(), NODE_LIST_SCROLL_SNAP_ALIGN, &it);
}
void SetLanes(int lanes)
{
ArkUI_NumberValue v0{};
v0.i32 = lanes;
ArkUI_AttributeItem it{&v0, 1};
nodeApi_->setAttribute(GetHandle(), NODE_LIST_LANES, &it);
}
void SetContentOffsets(float startPx, float endPx)
{
ArkUI_NumberValue v0{};
v0.f32 = startPx;
ArkUI_AttributeItem it0{&v0, 1};
nodeApi_->setAttribute(GetHandle(), NODE_SCROLL_CONTENT_START_OFFSET, &it0);
ArkUI_NumberValue v1{};
v1.f32 = endPx;
ArkUI_AttributeItem it1{&v1, 1};
nodeApi_->setAttribute(GetHandle(), NODE_SCROLL_CONTENT_END_OFFSET, &it1);
}
void RegisterOnScrollIndex(const std::function<void(int32_t, int32_t)> &callback)
{
onScrollIndexCallback_ = callback;
if (!isScrollIndexEventRegistered_) {
nodeApi_->registerNodeEvent(GetHandle(), NODE_LIST_ON_SCROLL_INDEX, 0, this);
isScrollIndexEventRegistered_ = true;
}
}
void RegisterOnVisibleContentChange(const std::function<void(const VisibleContentChange &)> &callback)
{
onVisibleChangeCallback_ = callback;
if (!isVisibleChangeEventRegistered_) {
nodeApi_->registerNodeEvent(GetHandle(), NODE_LIST_ON_SCROLL_VISIBLE_CONTENT_CHANGE, 0, this);
isVisibleChangeEventRegistered_ = true;
}
}
void RegisterOnWillScroll(const std::function<void()> &callback)
{
onWillScrollCallback_ = callback;
if (!isWillScrollEventRegistered_) {
nodeApi_->registerNodeEvent(GetHandle(), NODE_LIST_ON_WILL_SCROLL, 0, this);
isWillScrollEventRegistered_ = true;
}
}
void RegisterOnDidScroll(const std::function<void()> &callback)
{
onDidScrollCallback_ = callback;
if (!isDidScrollEventRegistered_) {
nodeApi_->registerNodeEvent(GetHandle(), NODE_LIST_ON_DID_SCROLL, 0, this);
isDidScrollEventRegistered_ = true;
}
}
void RegisterOnReachEnd(const std::function<void()> &callback) { onReachEndCallback_ = callback; }
void RegisterOnScrollFrameBegin(const std::function<void(float)> &callback)
{
onScrollFrameBeginCallback_ = callback;
}
protected:
void OnNodeEvent(ArkUI_NodeEvent *event) override
{
BaseNode::OnNodeEvent(event);
ArkUI_NodeComponentEvent *componentEvent = OH_ArkUI_NodeEvent_GetNodeComponentEvent(event);
if (!IsNotNull(componentEvent)) {
return;
}
int32_t eventType = OH_ArkUI_NodeEvent_GetEventType(event);
HandleSpecificListEvent(eventType, componentEvent);
}
private:
void UnregisterSpecificEvents()
{
if (isScrollIndexEventRegistered_) {
nodeApi_->unregisterNodeEvent(GetHandle(), NODE_LIST_ON_SCROLL_INDEX);
}
if (isVisibleChangeEventRegistered_) {
nodeApi_->unregisterNodeEvent(GetHandle(), NODE_LIST_ON_SCROLL_VISIBLE_CONTENT_CHANGE);
}
if (isWillScrollEventRegistered_) {
nodeApi_->unregisterNodeEvent(GetHandle(), NODE_LIST_ON_WILL_SCROLL);
}
if (isDidScrollEventRegistered_) {
nodeApi_->unregisterNodeEvent(GetHandle(), NODE_LIST_ON_DID_SCROLL);
}
}
void ResetListAdapter()
{
if (IsNotNull(listAdapter_)) {
nodeApi_->resetAttribute(GetHandle(), NODE_LIST_NODE_ADAPTER);
listAdapter_.reset();
}
}
void CleanupChildrenMainSizeOption()
{
if (IsNotNull(childrenMainSizeOption_)) {
nodeApi_->resetAttribute(GetHandle(), NODE_LIST_CHILDREN_MAIN_SIZE);
OH_ArkUI_ListChildrenMainSizeOption_Dispose(childrenMainSizeOption_);
childrenMainSizeOption_ = nullptr;
}
}
void OnScrollIndexEvt(const ArkUI_NodeComponentEvent *ev)
{
if (!onScrollIndexCallback_) {
return;
}
const int32_t firstIndex = ev->data[kScrollIndexFirstDataIndex].i32;
const int32_t lastIndex = ev->data[kScrollIndexLastDataIndex].i32;
onScrollIndexCallback_(firstIndex, lastIndex);
}
void OnVisibleChangeEvt(const ArkUI_NodeComponentEvent *ev)
{
if (!onVisibleChangeCallback_) {
return;
}
VisibleContentChange v{};
v.firstChildIndex = ev->data[kVisibleChangeFirstChildDataIndex].i32;
v.startArea = static_cast<ArkUI_ListItemGroupArea>(ev->data[kVisibleChangeStartAreaDataIndex].i32);
v.startItemIndex = ev->data[kVisibleChangeStartIndexDataIndex].i32;
v.lastChildIndex = ev->data[kVisibleChangeLastChildDataIndex].i32;
v.endArea = static_cast<ArkUI_ListItemGroupArea>(ev->data[kVisibleChangeEndAreaDataIndex].i32);
v.endItemIndex = ev->data[kVisibleChangeEndIndexDataIndex].i32;
onVisibleChangeCallback_(v);
}
void OnReachEndEvt()
{
if (onReachEndCallback_) {
onReachEndCallback_();
}
}
void OnScrollFrameBeginEvt(const ArkUI_NodeComponentEvent *ev)
{
if (onScrollFrameBeginCallback_) {
onScrollFrameBeginCallback_(ev->data[kScrollFrameBeginDataIndex].f32);
}
}
void OnWillScrollEvt()
{
if (onWillScrollCallback_) {
onWillScrollCallback_();
}
}
void OnDidScrollEvt()
{
if (onDidScrollCallback_) {
onDidScrollCallback_();
}
}
void HandleSpecificListEvent(int32_t eventType, ArkUI_NodeComponentEvent *ev)
{
switch (eventType) {
case NODE_LIST_ON_SCROLL_INDEX:
OnScrollIndexEvt(ev);
break;
case NODE_LIST_ON_SCROLL_VISIBLE_CONTENT_CHANGE:
OnVisibleChangeEvt(ev);
break;
case NODE_SCROLL_EVENT_ON_REACH_END:
OnReachEndEvt();
break;
case NODE_SCROLL_EVENT_ON_SCROLL_FRAME_BEGIN:
OnScrollFrameBeginEvt(ev);
break;
case NODE_LIST_ON_WILL_SCROLL:
OnWillScrollEvt();
break;
case NODE_LIST_ON_DID_SCROLL:
OnDidScrollEvt();
break;
default:
break;
}
}
ArkUI_ListChildrenMainSize *EnsureChildrenMainSizeOption()
{
if (!childrenMainSizeOption_) {
auto *opt = OH_ArkUI_ListChildrenMainSizeOption_Create();
SetChildrenMainSizeOption(opt);
}
return childrenMainSizeOption_;
}
void ChildrenMainSizeSetDefault(float mainSize)
{
auto *opt = EnsureChildrenMainSizeOption();
if (!opt) {
return;
}
OH_ArkUI_ListChildrenMainSizeOption_SetDefaultMainSize(opt, mainSize);
}
float ChildrenMainSizeGetDefault() const
{
if (!childrenMainSizeOption_) {
return 0.0f;
}
return OH_ArkUI_ListChildrenMainSizeOption_GetDefaultMainSize(childrenMainSizeOption_);
}
void ChildrenMainSizeResize(int32_t size)
{
auto *opt = EnsureChildrenMainSizeOption();
if (!opt) {
return;
}
OH_ArkUI_ListChildrenMainSizeOption_Resize(opt, size);
}
void ChildrenMainSizeSplice(int32_t index, int32_t deleteCount, int32_t addCount)
{
auto *opt = EnsureChildrenMainSizeOption();
if (!opt) {
return;
}
OH_ArkUI_ListChildrenMainSizeOption_Splice(opt, index, deleteCount, addCount);
}
void ChildrenMainSizeUpdate(int32_t index, float mainSize)
{
auto *opt = EnsureChildrenMainSizeOption();
if (!opt) {
return;
}
OH_ArkUI_ListChildrenMainSizeOption_UpdateSize(opt, index, mainSize);
}
float ChildrenMainSizeGet(int32_t index) const
{
if (!childrenMainSizeOption_) {
return 0.0f;
}
return OH_ArkUI_ListChildrenMainSizeOption_GetMainSize(childrenMainSizeOption_, index);
}
private:
ArkUI_NativeNodeAPI_1 *nodeApi_ = nullptr;
std::shared_ptr<ArkUINodeAdapter> listAdapter_;
ArkUI_ListChildrenMainSize *childrenMainSizeOption_ = nullptr;
std::function<void(int32_t, int32_t)> onScrollIndexCallback_;
std::function<void(const VisibleContentChange &)> onVisibleChangeCallback_;
std::function<void()> onReachEndCallback_;
std::function<void(float)> onScrollFrameBeginCallback_;
std::function<void()> onWillScrollCallback_;
std::function<void()> onDidScrollCallback_;
bool isScrollIndexEventRegistered_ = false;
bool isVisibleChangeEventRegistered_ = false;
bool isWillScrollEventRegistered_ = false;
bool isDidScrollEventRegistered_ = false;
ScrollEventGuard scrollEventGuard_;
};
#endif