* 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.
*/
#include <algorithm>
#include <string>
#include <vector>
#include <arkui/native_type.h>
#include "ScrollableNode.h"
#include "ScrollableUtils.h"
#include "ArkUINodeAdapter.h"
#include "WaterFlowMaker.h"
#include "WaterFlowSection.h"
namespace {
constexpr char K_ITEM_TITLE_PREFIX[] = "FlowItem ";
constexpr float K_FONT_SIZE = 16.0f;
constexpr float K_MAIN_A = 120.0f;
constexpr float K_MAIN_B = 160.0f;
constexpr float K_MAIN_C = 100.0f;
constexpr float K_WATER_FLOW_W = 400.0f;
constexpr float K_WATER_FLOW_H = 600.0f;
constexpr int K_INIT_RESERVE = 200;
constexpr int K_INIT_SEED = 100;
constexpr int32_t K_CROSS_COUNT = 2;
constexpr float K_COLUMN_GAP = 10.0f;
constexpr float K_ROW_GAP = 10.0f;
constexpr int32_t K_MARGIN_TOP = 12, K_MARGIN_RIGHT = 12, K_MARGIN_BOTTOM = 12, K_MARGIN_LEFT = 12;
constexpr float K_WIDTH_PERCENT_FULL = 1.0f;
constexpr bool K_SYNC_LOAD = true;
constexpr int32_t K_CACHED_ITEM_COUNT = 24;
constexpr float K_ITEM_MAIN_MIN = 80.0f;
constexpr float K_ITEM_MAIN_MAX = 220.0f;
constexpr int32_t K_LAYOUT_DIRECTION_RTL = 1;
constexpr int32_t K_LAYOUT_MODE_WATER_FLOW = 1;
constexpr char K_COLUMN_TEMPLATE[] = "1fr 1fr";
constexpr char K_ROW_TEMPLATE[] = "auto";
constexpr int32_t K_SCROLL_TO_INDEX = 5;
constexpr int32_t K_SCROLL_ALIGN_CENTER = static_cast<int32_t>(ARKUI_SCROLL_ALIGNMENT_CENTER);
constexpr int K_AUTO_THRESHOLD = 20;
constexpr int K_AUTO_BATCH = 100;
constexpr int K_AUTO_MAX_ITEMS = 100000;
constexpr float K_MAIN_SEQ[] = {K_MAIN_A, K_MAIN_B, K_MAIN_C};
constexpr size_t K_MAIN_SEQ_COUNT = sizeof(K_MAIN_SEQ) / sizeof(K_MAIN_SEQ[0]);
constexpr uint32_t K_PALETTE[] = {0xFF6A5ACD, 0xFF00FFFF, 0xFF00FF7F, 0xFFDA70D6, 0xFFFFC0CB};
constexpr size_t K_PALETTE_COUNT = sizeof(K_PALETTE) / sizeof(K_PALETTE[0]);
}
static std::shared_ptr<WaterFlowMaker> gNode;
static std::shared_ptr<ArkUINodeAdapter> gAdapter;
static std::shared_ptr<WaterFlowSection> gSection;
static std::vector<std::string> gItems;
struct AutoAppendConfig {
bool enabled = true;
int threshold = K_AUTO_THRESHOLD;
int batch = K_AUTO_BATCH;
int maxItems = K_AUTO_MAX_ITEMS;
bool appending = false;
int lastSizeTriggered = -1;
};
static AutoAppendConfig g_auto;
* 根据索引获取主轴尺寸
* @param idx 索引值
* @return 主轴尺寸
*/
static inline float MainSizeByIndex(int32_t idx)
{
if (K_MAIN_SEQ_COUNT == 0U) {
return K_MAIN_C;
}
const size_t i = static_cast<size_t>(idx) % K_MAIN_SEQ_COUNT;
return K_MAIN_SEQ[i];
}
* 根据索引获取颜色
* @param index 索引值
* @return 颜色值
*/
static inline uint32_t ColorByIndex(int index)
{
if (K_PALETTE_COUNT == 0U) {
return 0xFFFFFFFFU;
}
const size_t i = static_cast<size_t>(index) % K_PALETTE_COUNT;
return K_PALETTE[i];
}
static inline void BindText(ArkUI_NativeNodeAPI_1 *api, ArkUI_NodeHandle flowItem, int32_t index)
{
ArkUI_NodeHandle text = api->getFirstChild(flowItem);
if (!text) {
return;
}
SetTextContent(api, text, gItems[static_cast<size_t>(index)].c_str());
SetAttributeFloat32(api, text, NODE_FONT_SIZE, K_FONT_SIZE);
}
* 方法描述
* @param total 参数描述
* @return 返回值描述
*/
static inline bool ReachedBoundary(int total)
{
return (total >= g_auto.maxItems) || (total == g_auto.lastSizeTriggered);
}
static inline bool ShouldAppend(int32_t index, int total)
{
return g_auto.enabled && !g_auto.appending && !ReachedBoundary(total) && index >= (total - g_auto.threshold);
}
static void AppendBatchInternal(int addCount)
{
if (!gAdapter || !gSection || !gNode || addCount <= 0) {
return;
}
const int32_t start = static_cast<int32_t>(gItems.size());
gItems.reserve(gItems.size() + static_cast<size_t>(addCount));
for (int i = 0; i < addCount; ++i) {
gItems.emplace_back(std::string(K_ITEM_TITLE_PREFIX) + std::to_string(start + i));
}
gAdapter->InsertRange(start, addCount);
const int32_t newCount = static_cast<int32_t>(gItems.size());
OH_ArkUI_WaterFlowSectionOption_SetItemCount(gSection->GetSectionOptions(), 0, newCount);
gNode->SetSection(gSection);
g_auto.lastSizeTriggered = static_cast<int>(gItems.size());
}
static void MaybeAppendOnTail(int32_t index)
{
const int total = static_cast<int>(gItems.size());
if (!ShouldAppend(index, total)) {
return;
}
g_auto.appending = true;
const int remain = g_auto.maxItems - total;
const int toAdd = remain > 0 ? std::min(g_auto.batch, remain) : 0;
AppendBatchInternal(toAdd);
g_auto.appending = false;
}
static int32_t AdapterGetTotalCount() { return static_cast<int32_t>(gItems.size()); }
static uint64_t AdapterGetStableId(int32_t i)
{
const std::string &key = gItems[static_cast<size_t>(i)];
return static_cast<uint64_t>(std::hash<std::string>{}(key));
}
static ArkUI_NodeHandle AdapterOnCreate(ArkUI_NativeNodeAPI_1 *api, int32_t )
{
if (!api) {
return nullptr;
}
ArkUI_NodeHandle text = api->createNode(ARKUI_NODE_TEXT);
ArkUI_NodeHandle item = api->createNode(ARKUI_NODE_FLOW_ITEM);
api->addChild(item, text);
return item;
}
static void AdapterOnBind(ArkUI_NativeNodeAPI_1 *api, ArkUI_NodeHandle item, int32_t index)
{
SetAttributeFloat32(api, item, NODE_HEIGHT, MainSizeByIndex(index));
SetAttributeFloat32(api, item, NODE_WIDTH_PERCENT, K_WIDTH_PERCENT_FULL);
SetAttributeUInt32(api, item, NODE_BACKGROUND_COLOR, ColorByIndex(index));
BindText(api, item, index);
MaybeAppendOnTail(index);
}
static ArkUINodeAdapter::Callbacks MakeCallbacks()
{
ArkUINodeAdapter::Callbacks cb{};
cb.getTotalCount = &AdapterGetTotalCount;
cb.getStableId = &AdapterGetStableId;
cb.onCreate = &AdapterOnCreate;
cb.onBind = &AdapterOnBind;
cb.onRecycle = nullptr;
return cb;
}
static ArkUI_NodeHandle CreateFooter()
{
ArkUI_NativeNodeAPI_1 *api = nullptr;
OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, api);
if (!api) {
return nullptr;
}
ArkUI_NodeHandle text = api->createNode(ARKUI_NODE_TEXT);
SetTextContent(api, text, "到底啦…");
SetAttributeFloat32(api, text, NODE_FONT_SIZE, 14.0f);
ArkUI_NodeHandle footer = api->createNode(ARKUI_NODE_FLOW_ITEM);
SetAttributeFloat32(api, footer, NODE_WIDTH_PERCENT, 1.0f);
SetAttributeFloat32(api, footer, NODE_HEIGHT, 48.0f);
SetAttributeUInt32(api, footer, NODE_BACKGROUND_COLOR, 0x11000000U);
api->addChild(footer, text);
return footer;
}
static void SetupSection()
{
ArkUI_Margin margin{K_MARGIN_TOP, K_MARGIN_RIGHT, K_MARGIN_BOTTOM, K_MARGIN_LEFT};
SingleSectionParams params{};
params.itemCount = static_cast<int32_t>(gItems.size());
params.crossCount = K_CROSS_COUNT;
params.colGap = K_COLUMN_GAP;
params.rowGap = K_ROW_GAP;
params.margin = margin;
params.getMainSizeByIndex = &MainSizeByIndex;
params.userData = nullptr;
params.getMainSizeByIndexWithUserData = nullptr;
gNode->SetSingleSection(params);
gSection = gNode->GetWaterFlowSection();
}
static void SetupNodeAndAdapter()
{
gNode = std::make_shared<WaterFlowMaker>();
gNode->SetHeight(K_WATER_FLOW_H);
gNode->SetWidth(K_WATER_FLOW_W);
gNode->SetScrollCommon();
gNode->SetLayoutDirection(K_LAYOUT_DIRECTION_RTL);
gNode->SetColumnTemplate(K_COLUMN_TEMPLATE);
gNode->SetRowTemplate(K_ROW_TEMPLATE);
gNode->SetGaps(K_COLUMN_GAP, K_ROW_GAP);
gNode->SetCachedCount(K_CACHED_ITEM_COUNT);
gNode->SetItemConstraintSize(K_ITEM_MAIN_MIN, K_ITEM_MAIN_MAX);
gNode->SetLayoutMode(ARKUI_WATER_FLOW_LAYOUT_MODE_SLIDING_WINDOW);
gNode->SetSyncLoad(K_SYNC_LOAD);
SetupSection();
if (ArkUI_NodeHandle footer = CreateFooter()) {
gNode->SetFooter(footer);
}
gAdapter = std::make_shared<ArkUINodeAdapter>();
gAdapter->SetCallbacks(MakeCallbacks());
gNode->SetLazyAdapter(gAdapter);
gAdapter->ReloadAllItems();
}
static void InitData()
{
gItems.clear();
gItems.reserve(static_cast<size_t>(K_INIT_RESERVE));
for (int i = 0; i < K_INIT_SEED; ++i) {
gItems.emplace_back(std::string(K_ITEM_TITLE_PREFIX) + std::to_string(i));
}
}
static ArkUI_NodeHandle BuildWaterFlow()
{
InitData();
SetupNodeAndAdapter();
return gNode->GetWaterFlow();
}
ArkUI_NodeHandle WaterFlowMaker::CreateNativeNode()
{
ArkUI_NativeNodeAPI_1 *api = nullptr;
OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, api);
if (!api) {
return nullptr;
}
ArkUI_NodeHandle page = api->createNode(ARKUI_NODE_COLUMN);
if (!page) {
return nullptr;
}
SetAttributeFloat32(api, page, NODE_WIDTH_PERCENT, 1.0f);
SetAttributeFloat32(api, page, NODE_HEIGHT_PERCENT, 1.0f);
ArkUI_NodeHandle waterflow = BuildWaterFlow();
if (waterflow) {
SetAttributeFloat32(api, waterflow, NODE_WIDTH_PERCENT, 1.0f);
SetAttributeFloat32(api, waterflow, NODE_LAYOUT_WEIGHT, 1.0f);
api->addChild(page, waterflow);
}
return page;
}