diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000000000000000000000000000000000000..2072f06d9e7273fe8733d996f289ebb0f7c9caeb
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "entry/src/main/cpp/libboundscheck"]
+ path = entry/src/main/cpp/libboundscheck
+ url = https://gitee.com/openeuler/libboundscheck.git
diff --git a/README.md b/README.md
index bc105f1d14184138e00440c30513b22fbb5240cd..c65c9c9242f91b5860f932cebff6ae1b60e3d527 100644
--- a/README.md
+++ b/README.md
@@ -1,65 +1,104 @@
-# 多API版本兼容示例
+# 实现多API版本兼容
-### 介绍
-通过@ohos.deviceInfo(设备信息)模块deviceInfo.sdkApiVersion和deviceInfo.distributionOSApiVersion获取系统SDK版本,以Text、Scroll和HdsActionBar组件在API 18和API 20的兼容为例,介绍多版本适配的实现方法。在Native侧,通过deviceinfo的OH_GetSdkApiVersion获取系统SDK版本,以Button组件为例介绍多版本适配的实现方法。
+## 项目介绍
+本项目演示在 HarmonyOS 多版本环境下,如何在同一套代码中实现 API 能力探测与优雅降级,进而实现优雅的 API 兼容策略,覆盖 ArkTS层与Native层两类典型场景:
-### 工程目录
+1. **ArkTS(UI 层)**:通过 `deviceInfo.sdkApiVersion` / `deviceInfo.distributionOSApiVersion` 判断系统版本,选择高版本新特性或低版本替代实现。
+2. **Native(NAPI/C++ 层)**:
+ - 通过 `dlopen + dlsym` 对动态库符号进行探测,判断某个系统 API 是否存在,从而避免低版本调用导致崩溃。
+ - 通过OH_GetDistributionOSApiVersion()获取ISV发行版系统api版本并判断系统版本,选择高版本新特性或低版本特性替代实现。
+
+## 效果图预览
+| 首页 | 滚动场景 | 音频音量场景 |
+| :------------------------------------------: | :------------------------------------------: | :------------------------------------------: |
+|
|
|
|
+
+## 使用说明
+
+1. 使用git clone下载该项目代码,并将整个应用示例工程导入DevEco Studio。
+
+2. 打开Terminal终端,使用下面的命令初始化并更新引用的所有子模块:
+
+ ```
+ git submodule update --init --recursive
+ ```
+
+3. 执行编译构建,安装运行后,即可在设备上查看应用示例运行效果,以及进行相关调试。
+
+## 工程目录
```
-|──entry/src/main/cpp
-| |──classdef
-| | |──include
-| | | |──ArkUIBaseNode.h // 组件树操作的基类
-| | | |──ArkUINode.h // 通用组件的封装
-| | | |──ArkUIButtonNode.h // 实现按钮组件的封装类
-| | | └──NativeModuleInstance.h // ArkUI在Native侧模块的封装接口
-| | └──src
-| | | |──ArkUIBaseNode.cpp // 组件树操作的基类
-| | | |──ArkUINode.cpp // 通用组件的封装
-| | | |──ArkUIButtonNode.cpp // 实现按钮组件的封装类
-| | | └──NativeModuleInstance.cpp // ArkUI在Native侧模块的封装接口
-| |──function
-| | |──include
-| | | |──IntegratingWithArkts.h // 接入ArkTS界面
-| | | └──NativeEntry.h // 管理Native组件生命周期
-| | └──src
-| | | └──IntegratingWithArkts.cpp // 接入ArkTS界面
-| └──types
-| | └──libentry
-| | | |──Index.d.ts // Native侧接口导出声明文件
-| | | └──oh-package.json5
-| |──CMakeLists.txt // cmake配置文件
-| └──napi_init.cpp // 接口映射、模块注册
+├──entry/src/main/cpp
+│ ├──classdef
+│ │ ├──include
+│ │ │ ├──ArkUIBaseNode.h // 组件树操作的基类
+│ │ │ ├──ArkUINode.h // 通用组件的封装
+│ │ │ ├──ArkUIButtonNode.h // 实现按钮组件的封装类
+│ │ │ └──NativeModuleInstance.h // ArkUI在Native侧模块的封装接口
+│ │ └──src
+│ │ ├──ArkUIBaseNode.cpp // 组件树操作的基类
+│ │ ├──ArkUINode.cpp // 通用组件的封装
+│ │ ├──ArkUIButtonNode.cpp // 实现按钮组件的封装类
+│ │ └──NativeModuleInstance.cpp // ArkUI在Native侧模块的封装接口
+│ ├──function
+│ │ ├──include
+│ │ │ ├──IntegratingWithArkts.h // 接入ArkTS界面
+│ │ │ └──NativeEntry.h // 管理Native组件生命周期
+│ │ └──src
+│ │ └──IntegratingWithArkts.cpp // 接入ArkTS界面
+│ ├──libboundscheck // libboundscheck三方库
+│ └──types
+│ │ └──libentry
+│ │ ├──Index.d.ts // Native侧接口导出声明文件
+│ │ └──oh-package.json5
+│ ├──CMakeLists.txt // cmake配置文件
+│ └──napi_init.cpp // 接口映射、模块注册
├──entry/src/main/ets // 代码区
-│ ├──components // 自定义组件
-│ │ ├──ActionBarAdapter.ets // HdsActionBar组件版本兼容示例
-│ │ ├──NativeButtonAdapter.ets // Button组件版本兼容示例
-│ │ ├──ScrollComponentAdapter.ets // Scroll组件版本兼容示例
-│ │ └──TextComponentAdapter.ets // Text组件版本兼容示例
+│ ├──contants
+│ │ └──CommonConstants.ets // 常量类
│ ├──entryability
│ │ └──EntryAbility.ets // 程序入口类
│ ├──entrybackupability
│ │ └──EntryBackupAbility.ets // 应用数据备份和恢复类
-│ └──pages
-│ └──Index.ets // 应用主界面
+│ ├──pages
+│ │ ├──ActionBarScene.ets // HdsActionBar组件版本兼容示例展示页
+│ │ ├──AudioVolumeScene.ets // 音频音量场景兼容示例展示页
+│ │ ├──ButtonDisplayScene.ets // Button组件版本兼容示例展示页
+│ │ ├──Index.ets // 首页
+│ │ ├──MarqueeDisplayScene.ets // 走马灯场景兼容性示例展示页
+│ │ └──ScrollScene.ets // 滚动场景兼容示例展示页
+│ └──utils
+│ ├──CommonUtils.ets // 通用工具类
+│ └──Logger.ets // 日志工具类
+└──entry/src/main/resources // 应用资源目录
```
-### 使用说明
-安装应用之后,进入首页。
+## 实现说明
+本项目采用三类兼容策略:**版本判断**、**能力探测**和**安全装配**,确保应用在不同HarmonyOS版本上优雅运行。
+
+### ArkTS层兼容
+
+- **API版本分支**:通过`deviceInfo.sdkApiVersion`或`deviceInfo.distributionOSApiVersion`判断,选择高版本API或降级实现
+ - *ActionBar示例*:API 6.0.0+(60000)使用HdsActionBar,低版本用基础组件模拟
+ - *Text/Marquee*:仅在API 18+启用跑马灯效果
+- **特性按需装配**:Scroll组件通过"特性规格表"动态注册API 12/18/20的不同能力,使用WeakSet避免重复绑定
-### 实现说明
-* 通过@ohos.deviceInfo(设备信息)模块deviceInfo.sdkApiVersion和deviceInfo.distributionOSApiVersion属性来获取当前设备SDK版本,然后和目标版本进行比对。
-* 以Text组件的marqueeOptions属性使用为例来展示API18的兼容,实现了跑马灯效果
-* 以Scroll组件的maxZoomScale、minZoomScale和enableBouncesZoom为例子来展示API20的兼容,实现了图片缩放效果。
-* 以HdsActionBar组件的使用例子来展示API20的兼容,实现可以展开和收起的ActionBar效果。
-* 以Native侧的Button组件为例,展示API20的兼容。
+### Native层兼容
-### 相关权限
+- **版本阈值判断**:使用`OH_GetDistributionOSApiVersion()`与预设阈值(如50101对应5.1.1)选择不同UI枚举值
+- **动态符号探测**:通过`dlopen/dlsym`检测系统库符号是否存在,避免低版本调用崩溃
+- **严格生命周期管理**:Native节点创建与销毁严格配对,防止资源泄漏
+
+## 相关权限
不涉及
-### 约束与限制
+## 模块依赖
+
+- 依赖[libboundscheck](https://gitee.com/openeuler/libboundscheck)三方库
+
+## 约束与限制
1. 本示例仅支持标准系统上运行,支持设备:华为手机。
-2. HarmonyOS系统:HarmonyOS 6.0.0 Beta2及以上。
-3. DevEco Studio版本:DevEco Studio 6.0.0 Beta2及以上。
-4. HarmonyOS SDK版本:HarmonyOS 6.0.0 Beta2 SDK及以上。
+2. HarmonyOS系统:HarmonyOS 6.0.0及以上。
+3. DevEco Studio版本:DevEco Studio 6.0.0及以上。
+4. HarmonyOS SDK版本:HarmonyOS 6.0.0 SDK及以上。
diff --git a/entry/src/main/cpp/CMakeLists.txt b/entry/src/main/cpp/CMakeLists.txt
index eccba73b47b604f39f0a2a63da2655f0459cb91d..6f805c547909c6d077b354d20cffabfb73090614 100644
--- a/entry/src/main/cpp/CMakeLists.txt
+++ b/entry/src/main/cpp/CMakeLists.txt
@@ -9,17 +9,26 @@ if(DEFINED PACKAGE_FIND_FILE)
include(${PACKAGE_FIND_FILE})
endif()
+# -----------------------
+# Third party library path
+# -----------------------
+set(BOUNDSCHECK_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libboundscheck")
+
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/classdef/include
- ${NATIVERENDER_ROOT_PATH}/function/include)
+ ${NATIVERENDER_ROOT_PATH}/function/include
+ ${BOUNDSCHECK_SRC_DIR}/include)
file(GLOB BASECLADD_SOURCES "./classdef/src/*.cpp")
file(GLOB FUNCTION_SOURCES "./function/src/*.cpp")
+file(GLOB_RECURSE SOURCE_FILES "${BOUNDSCHECK_SRC_DIR}/src/*.c")
+
add_library(entry SHARED
napi_init.cpp
${BASECLADD_SOURCES}
- ${FUNCTION_SOURCES})
+ ${FUNCTION_SOURCES}
+ ${SOURCE_FILES})
target_link_libraries(entry PUBLIC libace_napi.z.so libace_ndk.z.so libhilog_ndk.z.so libnative_drawing.so libuv.so libdeviceinfo_ndk.z.so)
\ No newline at end of file
diff --git a/entry/src/main/cpp/classdef/include/ArkUIBaseNode.h b/entry/src/main/cpp/classdef/include/ArkUIBaseNode.h
index 6e7549ea8a031bc30187a74ae03b2a92a6c920b3..841165dd8313e95d597952737dbb14513acbc125 100644
--- a/entry/src/main/cpp/classdef/include/ArkUIBaseNode.h
+++ b/entry/src/main/cpp/classdef/include/ArkUIBaseNode.h
@@ -24,11 +24,19 @@
namespace NativeModule {
class ArkUIBaseNode {
public:
- explicit ArkUIBaseNode(ArkUI_NodeHandle handle)
- : handle_(handle), nativeModule_(NativeModuleInstance::GetInstance()->GetNativeNodeAPI()) {}
+ explicit ArkUIBaseNode(ArkUI_NodeHandle handle) : handle_(handle)
+ {
+ auto instance = NativeModuleInstance::GetInstance();
+ if (instance != nullptr) {
+ nativeModule_ = instance->GetNativeNodeAPI();
+ }
+ }
virtual ~ArkUIBaseNode()
{
+ if (nativeModule_ == nullptr) {
+ return;
+ }
// Encapsulate destructor to implement the function of removing child nodes.
if (!children_.empty()) {
for (const auto &child : children_) {
@@ -42,8 +50,6 @@ public:
void AddChild(const std::shared_ptr &child);
- std::list> GetChildren();
-
ArkUI_NodeHandle GetHandle() const;
protected:
diff --git a/entry/src/main/cpp/classdef/include/ArkUIButtonNode.h b/entry/src/main/cpp/classdef/include/ArkUIButtonNode.h
index a1804d7c6eea8e5ecdd16b6bb9e2f89d6c599c2f..0532cad9d788cd1b45705f6b7b359d11379690c0 100644
--- a/entry/src/main/cpp/classdef/include/ArkUIButtonNode.h
+++ b/entry/src/main/cpp/classdef/include/ArkUIButtonNode.h
@@ -23,8 +23,7 @@ class ArkUIButtonNode : public ArkUINode {
public:
ArkUIButtonNode()
: ArkUINode((NativeModuleInstance::GetInstance()->GetNativeNodeAPI())->createNode(ARKUI_NODE_BUTTON)) {}
-
- // Text attribute NDK interface encapsulation.
+
void SetFontSize(float fontSize);
void SetFontColor(uint32_t color);
@@ -37,4 +36,4 @@ public:
};
} // namespace NativeModule
-#endif // NDKCREATEUI_ARKUITEXTNODE_H
\ No newline at end of file
+#endif // NDKCREATEUI_ARKUI_BUTTON_H
\ No newline at end of file
diff --git a/entry/src/main/cpp/classdef/include/NativeModuleInstance.h b/entry/src/main/cpp/classdef/include/NativeModuleInstance.h
index da2059e6bb4372144e6056ce4d8800bc6a6bfe5a..24b4650f97aa4565d25012774dc36913ac591155 100644
--- a/entry/src/main/cpp/classdef/include/NativeModuleInstance.h
+++ b/entry/src/main/cpp/classdef/include/NativeModuleInstance.h
@@ -29,13 +29,12 @@ public:
return &instance;
}
- NativeModuleInstance()
- {
+ NativeModuleInstance() {
// Retrieve the function pointer structure object of the NDK interface for subsequent operations.
OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, arkUINativeNodeApi_);
- if (!arkUINativeNodeApi_) {
- return;
- }
+ // Recovery mechanisms and error handling should be triggered here based on actual scenarios when initialization
+ // fails; this is for demonstration purposes only.
+ assert(arkUINativeNodeApi_);
}
// Expose to other modules for use.
ArkUI_NativeNodeAPI_1 *GetNativeNodeAPI();
diff --git a/entry/src/main/cpp/classdef/src/ArkUIBaseNode.cpp b/entry/src/main/cpp/classdef/src/ArkUIBaseNode.cpp
index dbe80dbaa620bcceb4889464087db24ec3420068..c90c566aa979cff46e84192ebe955e6b667d01f0 100644
--- a/entry/src/main/cpp/classdef/src/ArkUIBaseNode.cpp
+++ b/entry/src/main/cpp/classdef/src/ArkUIBaseNode.cpp
@@ -22,7 +22,5 @@ void ArkUIBaseNode::AddChild(const std::shared_ptr &child)
OnAddChild(child);
}
-std::list> ArkUIBaseNode::GetChildren() { return children_; }
-
ArkUI_NodeHandle ArkUIBaseNode::GetHandle() const { return handle_; }
} // namespace NativeModule
\ No newline at end of file
diff --git a/entry/src/main/cpp/classdef/src/ArkUIButtonNode.cpp b/entry/src/main/cpp/classdef/src/ArkUIButtonNode.cpp
index 758f4bfdc1608985acf72f80e0a09ea2d50e945d..33d9701f390070b98302f6e7c9f4994d551ce477 100644
--- a/entry/src/main/cpp/classdef/src/ArkUIButtonNode.cpp
+++ b/entry/src/main/cpp/classdef/src/ArkUIButtonNode.cpp
@@ -15,6 +15,7 @@
#include
#include "ArkUIButtonNode.h"
+#include "securec.h"
namespace NativeModule {
// Text attribute NDK interface encapsulation.
@@ -37,7 +38,9 @@ void ArkUIButtonNode::SetFontColor(uint32_t color)
void ArkUIButtonNode::SetTextContent(const std::string &content)
{
assert(handle_);
- ArkUI_AttributeItem item = {nullptr, 0, content.c_str()};
+ char* contentPtr = new char[content.length() + 1];
+ strcpy_s(contentPtr, content.length() + 1, content.c_str());
+ ArkUI_AttributeItem item = {nullptr, 0, contentPtr};
nativeModule_->setAttribute(handle_, NODE_BUTTON_LABEL, &item);
}
@@ -54,7 +57,7 @@ void ArkUIButtonNode::SetButtonType(int32_t buttonType)
assert(handle_);
ArkUI_NumberValue value[] = {{.i32 = buttonType}};
ArkUI_AttributeItem item = {value, 1};
- nativeModule_->setAttribute(handle_, NODE_BUTTON_TYPE , &item);
+ nativeModule_->setAttribute(handle_, NODE_BUTTON_TYPE, &item);
}
// [End button_api]
} // namespace NativeModule
\ No newline at end of file
diff --git a/entry/src/main/cpp/classdef/src/ArkUINode.cpp b/entry/src/main/cpp/classdef/src/ArkUINode.cpp
index 832b3d2979012a70a6e8cb757baf8aca333f308a..e8d480fc613c18020e8f40e0d7fb88022d66c8e2 100644
--- a/entry/src/main/cpp/classdef/src/ArkUINode.cpp
+++ b/entry/src/main/cpp/classdef/src/ArkUINode.cpp
@@ -18,7 +18,9 @@
namespace NativeModule {
void ArkUINode::SetWidth(float width)
{
- assert(handle_);
+ if (!handle_) {
+ throw std::runtime_error("handle_ is null!");
+ }
ArkUI_NumberValue value[] = {{.f32 = width}};
ArkUI_AttributeItem item = {value, 1};
nativeModule_->setAttribute(handle_, NODE_WIDTH, &item);
@@ -26,7 +28,9 @@ void ArkUINode::SetWidth(float width)
void ArkUINode::SetPercentWidth(float percent)
{
- assert(handle_);
+ if (!handle_) {
+ throw std::runtime_error("handle_ is null!");
+ }
ArkUI_NumberValue value[] = {{.f32 = percent}};
ArkUI_AttributeItem item = {value, 1};
nativeModule_->setAttribute(handle_, NODE_WIDTH_PERCENT, &item);
@@ -34,7 +38,9 @@ void ArkUINode::SetPercentWidth(float percent)
void ArkUINode::SetHeight(float height)
{
- assert(handle_);
+ if (!handle_) {
+ throw std::runtime_error("handle_ is null!");
+ }
ArkUI_NumberValue value[] = {{.f32 = height}};
ArkUI_AttributeItem item = {value, 1};
nativeModule_->setAttribute(handle_, NODE_HEIGHT, &item);
@@ -42,7 +48,9 @@ void ArkUINode::SetHeight(float height)
void ArkUINode::SetPercentHeight(float percent)
{
- assert(handle_);
+ if (!handle_) {
+ throw std::runtime_error("handle_ is null!");
+ }
ArkUI_NumberValue value[] = {{.f32 = percent}};
ArkUI_AttributeItem item = {value, 1};
nativeModule_->setAttribute(handle_, NODE_HEIGHT_PERCENT, &item);
@@ -50,7 +58,9 @@ void ArkUINode::SetPercentHeight(float percent)
void ArkUINode::SetBackgroundColor(uint32_t color)
{
- assert(handle_);
+ if (!handle_) {
+ throw std::runtime_error("handle_ is null!");
+ }
ArkUI_NumberValue value[] = {{.u32 = color}};
ArkUI_AttributeItem item = {value, 1};
nativeModule_->setAttribute(handle_, NODE_BACKGROUND_COLOR, &item);
@@ -59,9 +69,11 @@ void ArkUINode::SetBackgroundColor(uint32_t color)
// Handle general events.
void ArkUINode::RegisterOnClick(const std::function &onClick)
{
- assert(handle_);
+ if (!handle_) {
+ throw std::runtime_error("handle_ is null!");
+ }
onClick_ = onClick;
- // 注册点击事件。
+ // Register click event
nativeModule_->registerNodeEvent(handle_, NODE_ON_CLICK, 0, nullptr);
}
@@ -112,11 +124,14 @@ void ArkUINode::ProcessNodeEvent(ArkUI_NodeEvent *event)
case NODE_TOUCH_EVENT: {
if (onTouch_) {
auto *uiInputEvent = OH_ArkUI_NodeEvent_GetInputEvent(event);
- float x = OH_ArkUI_PointerEvent_GetX(uiInputEvent);
- float y = OH_ArkUI_PointerEvent_GetY(uiInputEvent);
- auto type = OH_ArkUI_UIInputEvent_GetAction(uiInputEvent);
- onTouch_(type, x, y);
+ if (uiInputEvent != nullptr) {
+ float x = OH_ArkUI_PointerEvent_GetX(uiInputEvent);
+ float y = OH_ArkUI_PointerEvent_GetY(uiInputEvent);
+ auto type = OH_ArkUI_UIInputEvent_GetAction(uiInputEvent);
+ onTouch_(type, x, y);
+ }
}
+ break;
}
case NODE_EVENT_ON_DISAPPEAR: {
if (onDisappear_) {
@@ -139,16 +154,22 @@ void ArkUINode::ProcessNodeEvent(ArkUI_NodeEvent *event)
void ArkUINode::OnAddChild(const std::shared_ptr &child)
{
- nativeModule_->addChild(handle_, child->GetHandle());
+ if (child) {
+ nativeModule_->addChild(handle_, child->GetHandle());
+ }
}
void ArkUINode::OnRemoveChild(const std::shared_ptr &child)
{
- nativeModule_->removeChild(handle_, child->GetHandle());
+ if (child) {
+ nativeModule_->removeChild(handle_, child->GetHandle());
+ }
}
void ArkUINode::OnInsertChild(const std::shared_ptr &child, int32_t index)
{
- nativeModule_->insertChildAt(handle_, child->GetHandle(), index);
+ if (child) {
+ nativeModule_->insertChildAt(handle_, child->GetHandle(), index);
+ }
}
} // namespace NativeModule
\ No newline at end of file
diff --git a/entry/src/main/cpp/function/include/IntegratingWithArkts.h b/entry/src/main/cpp/function/include/IntegratingWithArkts.h
index edcecdfc05d114870a175dc86cb4c965b87f3a48..a1e90258af82e0bb1a7e61a0a855edd7546ad8e5 100644
--- a/entry/src/main/cpp/function/include/IntegratingWithArkts.h
+++ b/entry/src/main/cpp/function/include/IntegratingWithArkts.h
@@ -21,14 +21,15 @@
#include "ArkUIBaseNode.h"
namespace NativeModule {
-constexpr int32_t List_NUM = 30;
-constexpr int32_t TEXT_FONTSIZE = 16;
-constexpr int32_t TEXT_HEIGHT = 100;
+constexpr int32_t TEXT_FONT_SIZE = 16;
+constexpr int32_t TEXT_HEIGHT = 40;
napi_value CreateButtonNativeRoot(napi_env env, napi_callback_info info);
napi_value DestroyButtonNativeRoot(napi_env env, napi_callback_info info);
+napi_value CheckAudioVolumeManagerExists(napi_env env, napi_callback_info info);
+
std::shared_ptr CreateButtonExample();
} // namespace NativeModule
diff --git a/entry/src/main/cpp/function/include/NativeEntry.h b/entry/src/main/cpp/function/include/NativeEntry.h
index c744e84d0aa5b423206a045cc79727e1b2320030..5236a7d4f1d8e565c6d4bac86c81f284b49726db 100644
--- a/entry/src/main/cpp/function/include/NativeEntry.h
+++ b/entry/src/main/cpp/function/include/NativeEntry.h
@@ -28,6 +28,9 @@ public:
void SetRootNode(const std::shared_ptr &baseNode)
{
+ if (!handle_) {
+ return;
+ }
root_ = baseNode;
// Add Native components to NodeContent for mounting display.
OH_ArkUI_NodeContent_AddNode(handle_, root_->GetHandle());
@@ -35,6 +38,9 @@ public:
void DisposeRootNode()
{
+ if (root_ == nullptr) {
+ return;
+ }
// Uninstall components from NodeContent and destroy native components.
OH_ArkUI_NodeContent_RemoveNode(handle_, root_->GetHandle());
root_.reset();
diff --git a/entry/src/main/cpp/function/src/IntegratingWithArkts.cpp b/entry/src/main/cpp/function/src/IntegratingWithArkts.cpp
index 37704cd6497504a37c32ea401985868dab229787..dd37bc85d3d6f1701b79fd1664f4fb6da74b2eb3 100644
--- a/entry/src/main/cpp/function/src/IntegratingWithArkts.cpp
+++ b/entry/src/main/cpp/function/src/IntegratingWithArkts.cpp
@@ -15,25 +15,44 @@
#include
#include
+#include
+#include
+#include
+#include
#include "ArkUIButtonNode.h"
#include "NativeEntry.h"
+#include "hilog/log.h"
+#include "napi/native_api.h"
#include "IntegratingWithArkts.h"
-#include
+
+#undef LOG_DOMAIN
+#undef LOG_TAG
+#define LOG_DOMAIN 0x3200
+#define LOG_TAG "APILevelAdapt"
namespace NativeModule {
NativeEntry nativeEntry;
+constexpr int MIN_API_VERSION_5_1_1 = 50101;
napi_value CreateButtonNativeRoot(napi_env env, napi_callback_info info)
{
size_t argc = 1;
napi_value args[1] = {nullptr};
-
- // Get parameters passed in from JS.
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
+
+ // Check if the number of arguments is 1
+ if (argc != 1) {
+ napi_throw_error(env, nullptr, "Expected 1 argument");
+ return nullptr;
+ }
// Get NodeContent.
ArkUI_NodeContentHandle contentHandle;
- OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &contentHandle);
+ int32_t resultCode = OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &contentHandle);
+ if (resultCode != ARKUI_ERROR_CODE_NO_ERROR) {
+ napi_throw_error(env, nullptr, "Failed to get node content from Napi value");
+ return nullptr;
+ }
nativeEntry.SetContentHandle(contentHandle);
// Create a text list.
@@ -49,15 +68,15 @@ std::shared_ptr CreateButtonExample()
auto textNode = std::make_shared();
textNode->SetTextContent(std::string("Hello World"));
// [StartExclude button_api]
- textNode->SetFontSize(TEXT_FONTSIZE);
+ textNode->SetFontSize(TEXT_FONT_SIZE);
textNode->SetPercentWidth(1);
- textNode->SetHeight(40);
+ textNode->SetHeight(TEXT_HEIGHT);
textNode->SetTextAlign(ARKUI_TEXT_ALIGNMENT_CENTER);
// [EndExclude button_api]
// Regarding the proprietary interfaces of HarmonyOS, specifically the interfaces marked as since M.F.S(N).
// Compatibility judgment, the value corresponding to version 5.1.1(19) is 50101,
// which is derived from the new interface's since field 5*10000 + 1*100 + 1.
- if (OH_GetDistributionOSApiVersion() >= 50101) {
+ if (OH_GetDistributionOSApiVersion() >= MIN_API_VERSION_5_1_1) {
textNode->SetButtonType(ARKUI_BUTTON_ROUNDED_RECTANGLE);
} else {
textNode->SetButtonType(ARKUI_BUTTON_TYPE_CAPSULE);
@@ -68,7 +87,63 @@ std::shared_ptr CreateButtonExample()
napi_value DestroyButtonNativeRoot(napi_env env, napi_callback_info info)
{
+ size_t argc = 0;
+ napi_value args[1] = {nullptr};
+ napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
+ if (argc != 0) {
+ napi_throw_error(env, nullptr, "Expected 0 argument");
+ return nullptr;
+ }
nativeEntry.DisposeRootNode();
return nullptr;
}
+
+/**
+ * Checks whether the OH_AudioManager_GetAudioVolumeManager function exists in the dynamic library.
+ * This function dynamically loads the location library and attempts to retrieve
+ * the specified function symbol. The result indicates the availability of the
+ * location API on the current system.
+ */
+napi_value CheckAudioVolumeManagerExists(napi_env env, napi_callback_info info)
+{
+ size_t argc = 0;
+ napi_value args[1] = {nullptr};
+ napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
+ if (argc != 0) {
+ napi_throw_error(env, nullptr, "Expected 0 argument");
+ return nullptr;
+ }
+ // Handle to the dynamically loaded library
+ void *handle = NULL;
+ // Function pointer declaration for the target API
+ OH_AudioCommon_Result (*OH_AudioManager_GetAudioVolumeManager_Test)(OH_AudioVolumeManager **);
+ // Initialize function pointer to NULL
+ OH_AudioManager_GetAudioVolumeManager_Test = NULL;
+ // Flag indicating whether the function exists
+ bool isExisted = false;
+
+ // Attempt to dynamically load the location library
+ handle = dlopen("libohaudio.so", RTLD_LAZY);
+ if (handle != NULL) {
+ // Retrieve the address of the OH_AudioManager_GetAudioVolumeManager function from the library
+ OH_AudioManager_GetAudioVolumeManager_Test =
+ (OH_AudioCommon_Result(*)(OH_AudioVolumeManager **))dlsym(handle, "OH_AudioManager_GetAudioVolumeManager");
+
+ // Set flag based on whether the function symbol was successfully found
+ isExisted = OH_AudioManager_GetAudioVolumeManager_Test != NULL;
+
+ // Close the library handle to release resources
+ dlclose(handle);
+ } else {
+ const char* error = dlerror();
+ if (error != NULL) {
+ OH_LOG_INFO(LOG_APP, "Failed to load library: %s", error);
+ } else {
+ OH_LOG_INFO(LOG_APP, "Failed to load library: unknown error");
+ }
+ }
+ napi_value result = NULL;
+ napi_get_boolean(env, isExisted, &result);
+ return result;
+}
} // namespace NativeModule
\ No newline at end of file
diff --git a/entry/src/main/cpp/libboundscheck b/entry/src/main/cpp/libboundscheck
new file mode 160000
index 0000000000000000000000000000000000000000..1ae16ab92de4884eacea211dcd1989af95dae79b
--- /dev/null
+++ b/entry/src/main/cpp/libboundscheck
@@ -0,0 +1 @@
+Subproject commit 1ae16ab92de4884eacea211dcd1989af95dae79b
diff --git a/entry/src/main/cpp/napi_init.cpp b/entry/src/main/cpp/napi_init.cpp
index 3dbe3f3497de8d125588977a17708c3d5218cf30..476ddcbc6970b23aa38b8f6304244ed93005c121 100644
--- a/entry/src/main/cpp/napi_init.cpp
+++ b/entry/src/main/cpp/napi_init.cpp
@@ -21,10 +21,12 @@ EXTERN_C_START
void RegisterListComponents(napi_env env, napi_value exports)
{
napi_property_descriptor desc[] = {
- {"CreateButtonNativeRoot", nullptr, NativeModule::CreateButtonNativeRoot, nullptr, nullptr, nullptr,
+ {"createButtonNativeRoot", nullptr, NativeModule::CreateButtonNativeRoot, nullptr, nullptr, nullptr,
napi_default, nullptr},
- {"DestroyButtonNativeRoot", nullptr, NativeModule::DestroyButtonNativeRoot, nullptr, nullptr, nullptr,
+ {"destroyButtonNativeRoot", nullptr, NativeModule::DestroyButtonNativeRoot, nullptr, nullptr, nullptr,
napi_default, nullptr},
+ {"checkAudioVolumeManagerExists", nullptr, NativeModule::CheckAudioVolumeManagerExists, nullptr, nullptr,
+ nullptr, napi_default, nullptr},
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
}
diff --git a/entry/src/main/cpp/types/libentry/Index.d.ts b/entry/src/main/cpp/types/libentry/Index.d.ts
index 213acecd0d498a8be395609608dff52345f77203..84af24cc516c54a8288fdf089062661e6b0987dd 100644
--- a/entry/src/main/cpp/types/libentry/Index.d.ts
+++ b/entry/src/main/cpp/types/libentry/Index.d.ts
@@ -15,6 +15,21 @@
import { NodeContent } from "@kit.ArkUI";
-export const CreateButtonNativeRoot: (content: NodeContent) => void;
+/**
+ * Creates a native root node for a button component in the ArkUI framework.
+ * @param content - Entity Encapsulation of Node Content
+ * @returns void
+ */
+export const createButtonNativeRoot: (content: NodeContent) => void;
+
+/**
+ * Destroys the native root node for a button component and releases associated resources.
+ * @returns void
+ */
+export const destroyButtonNativeRoot: () => void;
-export const DestroyButtonNativeRoot: () => void;
\ No newline at end of file
+/**
+ * Checks whether the OH_AudioManager_GetAudioVolumeManager function exists in the dynamic library.
+ * @returns void
+ */
+export const checkAudioVolumeManagerExists: () => boolean;
\ No newline at end of file
diff --git a/entry/src/main/ets/components/ActionBarAdapter.ets b/entry/src/main/ets/components/ActionBarAdapter.ets
deleted file mode 100644
index 35a310373fefadd8d2d6cd804aaa51bdc38530c8..0000000000000000000000000000000000000000
--- a/entry/src/main/ets/components/ActionBarAdapter.ets
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * 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.
- */
-
-import { HdsActionBar, ActionBarButton, ActionBarStyle } from '@kit.UIDesignKit';
-import { deviceInfo } from '@kit.BasicServicesKit';
-
-@Component
-export struct ActionBarAdapter {
- @State isExpand: boolean = true;
- @State isPrimaryIconChanged: boolean = false;
- // [Start action_bar_api]
- build() {
- Column() {
- // Regarding the proprietary interfaces of HarmonyOS, specifically the interfaces marked as since M.F.S(N).
- // Compatibility judgment, the value corresponding to version 6.0.0(20) is 60000,
- // which is derived from the new interface's since field 6*10000 + 0*100 + 0.
- if (deviceInfo.distributionOSApiVersion >= 60000) {
- // Component that calls the API of version 6.0.0(20)
- HdsActionBar({
- startButtons: [new ActionBarButton({
- baseIcon: $r('sys.symbol.stopwatch_fill')
- })],
- endButtons: [new ActionBarButton({
- baseIcon: $r('sys.symbol.mic_fill')
- })],
- // [StartExclude action_bar_api]
- primaryButton: new ActionBarButton({
- baseIcon: $r('sys.symbol.plus'),
- altIcon: $r('sys.symbol.play_fill'),
- onClick: () => {
- this.isExpand = !this.isExpand;
- this.isPrimaryIconChanged = !this.isPrimaryIconChanged;
- }
- }),
- actionBarStyle: new ActionBarStyle({
- isPrimaryIconChanged: this.isPrimaryIconChanged
- }),
- isExpand: this.isExpand!!
- // [EndExclude action_bar_api]
- })
- } else {
- // Downgrading plan
- Row({ space: 25 }) {
- // [StartExclude action_bar_api]
- if (this.isExpand) {
- Button({ type: ButtonType.Circle }) {
- SymbolGlyph($r('sys.symbol.stopwatch_fill'))
- .fontSize(24)
- .fontColor([$r('sys.color.font_secondary')])
- }
- .aspectRatio(1)
- .height(45)
- .backgroundColor($r('sys.color.background_secondary'))
- .margin({ left: 10 })
- }
-
- Button({ type: ButtonType.Circle }) {
- SymbolGlyph(this.isExpand ? $r('sys.symbol.plus') : $r('sys.symbol.play_fill'))
- .fontSize(24)
- .fontColor([$r('sys.color.white')])
- }
- .aspectRatio(1)
- .height(55)
- .backgroundColor($r('sys.color.brand'))
- .onClick(() => {
- this.isExpand = !this.isExpand;
- })
-
- if (this.isExpand) {
- Button({ type: ButtonType.Circle }) {
- SymbolGlyph($r('sys.symbol.mic_fill'))
- .fontSize(24)
- .fontColor([$r('sys.color.font_secondary')])
- }
- .aspectRatio(1)
- .height(45)
- .backgroundColor($r('sys.color.background_secondary'))
- .margin({ right: 10 })
- }
- // [EndExclude action_bar_api]
- }
- // [StartExclude action_bar_api]
- .backgroundColor($r('sys.color.background_primary'))
- .borderRadius(30)
- // [EndExclude action_bar_api]
- }
- }
- // [StartExclude action_bar_api]
- .width('100%')
- .justifyContent(FlexAlign.Center)
- .alignItems(HorizontalAlign.Center)
- // [EndExclude action_bar_api]
- }
- // [End action_bar_api]
-}
\ No newline at end of file
diff --git a/entry/src/main/ets/components/ScrollComponentAdapter.ets b/entry/src/main/ets/contants/CommonConstants.ets
similarity index 41%
rename from entry/src/main/ets/components/ScrollComponentAdapter.ets
rename to entry/src/main/ets/contants/CommonConstants.ets
index a33bad4a85e62612a730e25caf1a0a85511639cb..793c4fc13756d0f30912723d9baec0967625198d 100644
--- a/entry/src/main/ets/components/ScrollComponentAdapter.ets
+++ b/entry/src/main/ets/contants/CommonConstants.ets
@@ -13,34 +13,44 @@
* limitations under the License.
*/
-import { deviceInfo } from '@kit.BasicServicesKit';
-
-class MyModifier implements AttributeModifier {
- applyNormalAttribute(instance: ScrollAttribute): void {
- // Judgment is made by the api version information of deviceInfo
- if (deviceInfo.sdkApiVersion >= 20) {
- // To adapt to the Scroll scaling property, you can set the minimum and maximum scaling ratios
- instance.maxZoomScale(1.2)
- instance.minZoomScale(0.5)
- instance.enableBouncesZoom(true)
- }
- }
+/**
+ * Route type define
+ */
+export interface Route {
+ title: ResourceStr;
+ to: string;
}
+export const ACTION_BAR_SCENE_NAME: string = 'ActionBarScene';
+
+export const BUTTON_DISPLAY_SCENE_NAME: string = 'ButtonDisplayScene';
+export const SCROLL_SCENE_NAME: string = 'ScrollScene';
-@Component
-export struct ScrollComponentAdapter {
- adaptModifier: MyModifier = new MyModifier();
+export const MARQUEE_DISPLAY_SCENE_NAME: string = 'MarqueeDisplayScene';
- build() {
- Scroll() {
- // 'app.media.startIcon' is just an example, please replace the actual image.
- Image($r('app.media.startIcon'))
- .width('80%')
- }
- .scrollable(ScrollDirection.FREE)
- .width('80%')
- .attributeModifier(this.adaptModifier)
+export const AUDIO_VOLUME_SCENE_NAME: string = 'AudioVolumeScene';
+
+export const ROUTES: Route[] = [
+ {
+ title: $r('app.string.action_bar_scene'),
+ to: ACTION_BAR_SCENE_NAME
+ },
+ {
+ title: $r('app.string.button_display_scene'),
+ to: BUTTON_DISPLAY_SCENE_NAME
+ },
+ {
+ title: $r('app.string.scroll_scene'),
+ to: SCROLL_SCENE_NAME
+ },
+ {
+ title: $r('app.string.marquee_display_scene'),
+ to: MARQUEE_DISPLAY_SCENE_NAME
+ },
+ {
+ title: $r('app.string.audio_volume_scene'),
+ to: AUDIO_VOLUME_SCENE_NAME
}
-}
+]
+
diff --git a/entry/src/main/ets/entryability/EntryAbility.ets b/entry/src/main/ets/entryability/EntryAbility.ets
index d3ee54d2beea61d38866762868bd056d3e39e3c9..4c7be41246b50b10a8f8543f48833a6bc0079597 100644
--- a/entry/src/main/ets/entryability/EntryAbility.ets
+++ b/entry/src/main/ets/entryability/EntryAbility.ets
@@ -16,10 +16,13 @@
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
+import { Logger } from '../utils/Logger';
const DOMAIN = 0x0000;
export default class EntryAbility extends UIAbility {
+ private windowClass: window.Window | null = null;
+
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
@@ -38,6 +41,16 @@ export default class EntryAbility extends UIAbility {
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
+ windowStage.getMainWindow().then((windowObj) => {
+ this.windowClass = windowObj;
+ try {
+ AppStorage.setOrCreate('uiContext', this.windowClass.getUIContext())
+ } catch (error) {
+ Logger.error(`Failed to obtain a UIContext instance.`)
+ }
+ }).catch(() => {
+ Logger.error(`Failed to obtain the main window of this window stage.`)
+ })
hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
});
}
diff --git a/entry/src/main/ets/pages/ActionBarScene.ets b/entry/src/main/ets/pages/ActionBarScene.ets
new file mode 100644
index 0000000000000000000000000000000000000000..40a5d830bfc665069711d6b772a8233bb214a9aa
--- /dev/null
+++ b/entry/src/main/ets/pages/ActionBarScene.ets
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+import { ActionBarButton, ActionBarStyle, HdsActionBar } from '@kit.UIDesignKit';
+import { deviceInfo } from '@kit.BasicServicesKit';
+
+@Builder
+export function ActionBarSceneBuilder() {
+ ActionBarScene()
+}
+
+@Component
+struct ActionBarScene {
+ @State isExpand: boolean = true;
+ @State isPrimaryIconChanged: boolean = false;
+
+ aboutToAppear(): void {
+ }
+
+ build() {
+ // [Start action_bar_api]
+ NavDestination() {
+ Column() {
+ // Regarding the proprietary interfaces of HarmonyOS, specifically the interfaces marked as since M.F.S(N).
+ // Compatibility judgment, the value corresponding to version 6.0.0(20) is 60000,
+ // which is derived from the new interface's since field 6*10000 + 0*100 + 0.
+ if (deviceInfo.distributionOSApiVersion >= 60000) {
+ // Component that calls the API of version 6.0.0(20)
+ HdsActionBar({
+ startButtons: [new ActionBarButton({
+ baseIcon: $r('sys.symbol.stopwatch_fill')
+ })],
+ endButtons: [new ActionBarButton({
+ baseIcon: $r('sys.symbol.mic_fill')
+ })],
+ // [StartExclude action_bar_api]
+ primaryButton: new ActionBarButton({
+ baseIcon: $r('sys.symbol.plus'),
+ altIcon: $r('sys.symbol.play_fill'),
+ onClick: () => {
+ this.isExpand = !this.isExpand;
+ this.isPrimaryIconChanged = !this.isPrimaryIconChanged;
+ }
+ }),
+ actionBarStyle: new ActionBarStyle({
+ isPrimaryIconChanged: this.isPrimaryIconChanged
+ }),
+ isExpand: this.isExpand
+ // [EndExclude action_bar_api]
+ })
+ } else {
+ // Downgrading plan
+ Row({ space: 25 }) {
+ // [StartExclude action_bar_api]
+ if (this.isExpand) {
+ Button({ type: ButtonType.Circle }) {
+ SymbolGlyph($r('sys.symbol.stopwatch_fill'))
+ .fontSize(24)
+ .fontColor([$r('sys.color.font_secondary')])
+ }
+ .aspectRatio(1)
+ .height(45)
+ .backgroundColor($r('sys.color.background_secondary'))
+ .margin({ left: 10 })
+ }
+
+ Button({ type: ButtonType.Circle }) {
+ SymbolGlyph(this.isExpand ? $r('sys.symbol.plus') : $r('sys.symbol.play_fill'))
+ .fontSize(24)
+ .fontColor([$r('sys.color.white')])
+ }
+ .aspectRatio(1)
+ .height(55)
+ .backgroundColor($r('sys.color.brand'))
+ .onClick(() => {
+ this.isExpand = !this.isExpand;
+ })
+
+ if (this.isExpand) {
+ Button({ type: ButtonType.Circle }) {
+ SymbolGlyph($r('sys.symbol.mic_fill'))
+ .fontSize(24)
+ .fontColor([$r('sys.color.font_secondary')])
+ }
+ .aspectRatio(1)
+ .height(45)
+ .backgroundColor($r('sys.color.background_secondary'))
+ .margin({ right: 10 })
+ }
+ // [EndExclude action_bar_api]
+ }
+ // [StartExclude action_bar_api]
+ .backgroundColor($r('sys.color.background_primary'))
+ .borderRadius(30)
+ // [EndExclude action_bar_api]
+ }
+ }
+ // [StartExclude action_bar_api]
+ .width('100%')
+ .height('100%')
+ .justifyContent(FlexAlign.End)
+ .alignItems(HorizontalAlign.Center)
+ // [EndExclude action_bar_api]
+ }
+ .title($r('app.string.action_bar_scene'))
+ .backgroundColor($r('app.color.common_backgroundColor'))
+ // [End action_bar_api]
+ }
+}
\ No newline at end of file
diff --git a/entry/src/main/ets/pages/AudioVolumeScene.ets b/entry/src/main/ets/pages/AudioVolumeScene.ets
new file mode 100644
index 0000000000000000000000000000000000000000..def383de9356dba7e6f27b98eb89ff1669986bcb
--- /dev/null
+++ b/entry/src/main/ets/pages/AudioVolumeScene.ets
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+import testNapi from 'libentry.so';
+import CommonUtils from '../utils/CommonUtils';
+
+@Builder
+export function AudioVolumeSceneBuilder() {
+ AudioVolumeScene()
+}
+
+@Builder
+function AudioVolumeScene() {
+ NavDestination() {
+ Column() {
+ Row() {
+ Button($r('app.string.audio_volume_button_label'))
+ .width('100%')
+ .onClick(() => {
+ const isExisted = testNapi.checkAudioVolumeManagerExists();
+ const ctx = AppStorage.get('uiContext') as UIContext | undefined;
+ CommonUtils.showToast(ctx, isExisted ? $r('app.string.support_label') : $r('app.string.unsupport_label'));
+ })
+ }
+ .width('100%')
+ }
+ .justifyContent(FlexAlign.End)
+ .padding({
+ left: 16,
+ right: 16
+ })
+ .width('100%')
+ .height('100%')
+ }
+ .title($r('app.string.audio_volume_scene'))
+ .backgroundColor($r('app.color.common_backgroundColor'))
+}
\ No newline at end of file
diff --git a/entry/src/main/ets/components/NativeButtonAdapter.ets b/entry/src/main/ets/pages/ButtonDisplayScene.ets
similarity index 57%
rename from entry/src/main/ets/components/NativeButtonAdapter.ets
rename to entry/src/main/ets/pages/ButtonDisplayScene.ets
index fd99dd4386e36c9dc8be3642375c128318600904..03856b4d493d12a4f1aa3a7ae99e46aa65536127 100644
--- a/entry/src/main/ets/components/NativeButtonAdapter.ets
+++ b/entry/src/main/ets/pages/ButtonDisplayScene.ets
@@ -16,27 +16,40 @@
import { NodeContent } from '@kit.ArkUI';
import nativeNode from 'libentry.so';
+@Builder
+export function ButtonDisplaySceneBuilder() {
+ ButtonDisplayScene()
+}
@Component
-export struct NativeButtonAdapter {
+struct ButtonDisplayScene {
private rootSlot = new NodeContent();
aboutToAppear(): void {
- nativeNode.CreateButtonNativeRoot(this.rootSlot);
+ nativeNode.createButtonNativeRoot(this.rootSlot);
}
aboutToDisappear(): void {
- nativeNode.DestroyButtonNativeRoot();
+ nativeNode.destroyButtonNativeRoot();
}
build() {
- Column() {
- Row() {
- // Bind NodeContent and ContentSlot placeholder components.
- ContentSlot(this.rootSlot)
+ NavDestination() {
+ Column() {
+ Row() {
+ // Bind NodeContent and ContentSlot placeholder components.
+ ContentSlot(this.rootSlot)
+ }
}
+ .justifyContent(FlexAlign.End)
+ .padding({
+ left: 16,
+ right: 16
+ })
+ .width('100%')
+ .height('100%')
}
- .width('100%')
- .height('30%')
+ .title($r('app.string.button_display_scene'))
+ .backgroundColor($r('app.color.common_backgroundColor'))
}
-}
+}
\ No newline at end of file
diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets
index 90d1e6d188728732bf8b0c2d1f13ee700ea168fc..2754c69050b29ff604e106199e575f66a5a22cfb 100644
--- a/entry/src/main/ets/pages/Index.ets
+++ b/entry/src/main/ets/pages/Index.ets
@@ -13,22 +13,55 @@
* limitations under the License.
*/
-import { ScrollComponentAdapter } from '../components/ScrollComponentAdapter';
-import { TextComponentAdapter } from '../components/TextComponentAdapter';
-import { ActionBarAdapter } from '../components/ActionBarAdapter';
-import { NativeButtonAdapter } from '../components/NativeButtonAdapter';
+import { Route, ROUTES } from '../contants/CommonConstants';
@Entry
@Component
struct Index {
+ private routes: Route[] = ROUTES;
+ private pageInfos: NavPathStack = new NavPathStack();
build() {
- Column() {
- ScrollComponentAdapter()
- TextComponentAdapter()
- ActionBarAdapter()
- NativeButtonAdapter()
+ Navigation(this.pageInfos) {
+ List() {
+ ForEach(this.routes, (item: Route, index: number) => {
+ ListItem() {
+ Column() {
+ Row() {
+ Text(item.title)
+ .width('91.1%')
+ .height(48)
+ .fontWeight(FontWeight.Medium)
+ .fontSize('16fp')
+ SymbolGlyph($r('sys.symbol.chevron_forward'))
+ .fontSize($r('sys.float.padding_level12'))
+ .fontColor([$r('sys.color.font_tertiary')])
+ }
+ .padding({ left: 12 })
+ .onClick(() => {
+ this.pageInfos.pushPath({
+ name: item.to
+ })
+ })
+ if (this.routes.length - 1 !== index) {
+ Divider()
+ .strokeWidth(0.5)
+ .color('#0D000000')
+ .width('93%')
+ }
+ }
+ }
+ .height(48)
+ }, (item: Route, index: number) => JSON.stringify(item) + index)
+ }
+ .borderRadius(15)
+ .width('91.1%')
+ .height(48 * this.routes.length)
+ .backgroundColor(Color.White)
}
+ .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
.height('100%')
.width('100%')
+ .title($r('app.string.index_label'))
+ .backgroundColor($r('app.color.common_backgroundColor'))
}
}
diff --git a/entry/src/main/ets/components/TextComponentAdapter.ets b/entry/src/main/ets/pages/MarqueeDisplayScene.ets
similarity index 64%
rename from entry/src/main/ets/components/TextComponentAdapter.ets
rename to entry/src/main/ets/pages/MarqueeDisplayScene.ets
index b28c8efbdb66dcdf568921d8cb05e8ebd2b95473..6ee165e19b2536cd6d38414a1c5e68fc16877ff3 100644
--- a/entry/src/main/ets/components/TextComponentAdapter.ets
+++ b/entry/src/main/ets/pages/MarqueeDisplayScene.ets
@@ -13,7 +13,7 @@
* limitations under the License.
*/
-import { deviceInfo } from "@kit.BasicServicesKit";
+import { deviceInfo } from '@kit.BasicServicesKit';
class MyModifier implements AttributeModifier {
applyNormalAttribute(instance: TextAttribute): void {
@@ -26,16 +26,26 @@ class MyModifier implements AttributeModifier {
}
}
-@Component
-export struct TextComponentAdapter {
- private message: string = 'This a a simple Code of Text Marquee';
- private adaptModifier: MyModifier = new MyModifier();
+@Builder
+export function MarqueeDisplaySceneBuilder() {
+ MarqueeDisplayScene()
+}
- build() {
- Text(this.message)
- .fontSize(24)
- .height(200)
- .width('100%')
- .attributeModifier(this.adaptModifier)
+@Builder
+function MarqueeDisplayScene() {
+ NavDestination() {
+ Column() {
+ Row() {
+ Text('This is a simple Code of Text Marquee.')
+ .fontSize(24)
+ .height(200)
+ .width('100%')
+ .attributeModifier(new MyModifier())
+ }
+ }
+ .width('100%')
+ .height('100%')
}
-}
+ .title($r('app.string.marquee_display_scene'))
+ .backgroundColor($r('app.color.common_backgroundColor'))
+}
\ No newline at end of file
diff --git a/entry/src/main/ets/pages/ScrollScene.ets b/entry/src/main/ets/pages/ScrollScene.ets
new file mode 100644
index 0000000000000000000000000000000000000000..5f7d4bef722132b8376a3a8c216d92719b709b0d
--- /dev/null
+++ b/entry/src/main/ets/pages/ScrollScene.ets
@@ -0,0 +1,189 @@
+/*
+ * 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.
+ */
+
+import { deviceInfo } from '@kit.BasicServicesKit';
+import CommonUtils from '../utils/CommonUtils';
+import { Logger } from '../utils/Logger';
+
+// Type definition for feature installer function that applies specific features to a ScrollAttribute instance
+type FeatureInstaller = (instance: ScrollAttribute) => void;
+
+// Interface defining a feature specification with minimum API version requirement and installer function
+interface FeatureSpec {
+ minVersion: number;
+ installer: FeatureInstaller;
+}
+
+const ctx = AppStorage.get('uiContext') as UIContext | undefined;
+// Registry to track whether onDidScroll feature has been installed for a ScrollAttribute instance
+// Using WeakSet to avoid memory leaks and allow garbage collection of unused instances
+const didScrollRegistry = new WeakSet();
+// Registry to track whether onScrollEdge feature has been installed for a ScrollAttribute instance
+const scrollEdgeRegistry = new WeakSet();
+// Registry to track whether zoom features have been installed for a ScrollAttribute instance
+const zoomRegistry = new WeakSet();
+// Registry to ensure the toast is shown only once per fling cycle
+const flingToastRegistry = new WeakMap();
+
+// Array of feature specifications organized by minimum API version requirements
+const FEATURE_SPECS: ReadonlyArray = [
+ {
+ minVersion: 12,
+ installer: (instance: ScrollAttribute) => {
+ // Skip installation if this feature is already registered for the instance
+ if (didScrollRegistry.has(instance)) {
+ return;
+ }
+ // API 12+ supports `onDidScroll` callback for tracking scroll events
+ instance.onDidScroll((_xOffset: number, _yOffset: number, scrollState: ScrollState) => {
+ const hasToastShown = flingToastRegistry.get(instance) ?? false;
+ if (scrollState === ScrollState.Fling) {
+ if (!hasToastShown) {
+ CommonUtils.showToast(ctx, $r('app.string.fling_prompt_dialog_tip'));
+ flingToastRegistry.set(instance, true);
+ }
+ } else if (hasToastShown) {
+ // Reset the flag when leaving the fling state so future fling actions can trigger the toast again
+ flingToastRegistry.set(instance, false);
+ }
+ });
+ // Mark this instance as having the feature installed to prevent duplicate registration
+ didScrollRegistry.add(instance);
+ }
+ },
+ {
+ minVersion: 18,
+ installer: (instance: ScrollAttribute) => {
+ // Skip installation if this feature is already registered for the instance
+ if (scrollEdgeRegistry.has(instance)) {
+ return;
+ }
+ // API 18+ enhances scroll edge detection capabilities
+ instance.onScrollEdge((side: Edge) => {
+ if (side === Edge.Top) {
+ CommonUtils.showToast(ctx, $r('app.string.scroll_to_top_tip'));
+ } else if (side === Edge.Bottom) {
+ CommonUtils.showToast(ctx, $r('app.string.scroll_to_bottom_tip'));
+ }
+ });
+ // Mark this instance as having the feature installed to prevent duplicate registration
+ scrollEdgeRegistry.add(instance);
+ }
+ },
+ {
+ minVersion: 20,
+ installer: (instance: ScrollAttribute) => {
+ // Skip installation if this feature is already registered for the instance
+ if (zoomRegistry.has(instance)) {
+ return;
+ }
+ // API 20+ introduces zoom capabilities for scrollable content
+ instance.maxZoomScale(2);
+ instance.minZoomScale(1);
+ instance.enableBouncesZoom(true);
+
+ // Register zoom event callbacks
+ instance.onDidZoom((scale: number) => {
+ Logger.info(`onDidZoom:${scale}`);
+ });
+ instance.onZoomStart(() => {
+ CommonUtils.showToast(ctx, $r('app.string.zoom_start_trigger_tip'));
+ });
+ instance.onZoomStop(() => {
+ CommonUtils.showToast(ctx, $r('app.string.zoom_stop_trigger_tip'));
+ });
+
+ // Mark this instance as having the feature installed to prevent duplicate registration
+ zoomRegistry.add(instance);
+ }
+ }
+];
+
+// Custom attribute modifier that dynamically applies features based on device API version
+class MyModifier implements AttributeModifier {
+ applyNormalAttribute(instance: ScrollAttribute): void {
+ // Get current device SDK API version
+ const sdkVersion: number = deviceInfo.sdkApiVersion ?? 0;
+ // Apply all compatible features based on device API version
+ FEATURE_SPECS.forEach(feature => {
+ if (sdkVersion >= feature.minVersion) {
+ feature.installer(instance);
+ }
+ });
+ }
+}
+
+@Builder
+export function ScrollSceneBuilder() {
+ ScrollScene()
+}
+
+@Component
+struct ScrollScene {
+ // Instance of our custom modifier to apply version-adaptive features
+ private adaptModifier: MyModifier = new MyModifier();
+ // Determine scroll direction based on API version:
+ // Use FREE direction on API 20+ (supports zoom), otherwise use VERTICAL
+ private readonly scrollDirection: ScrollDirection =
+ (deviceInfo.sdkApiVersion ?? 0) >= 20 ? ScrollDirection.FREE : ScrollDirection.Vertical;
+ // Sample data array for rendering scrollable content
+ private arr: number[] = [];
+
+ aboutToAppear(): void {
+ for (let i = 1; i <= 12; i++) {
+ this.arr.push(i);
+ }
+ }
+
+ build() {
+ NavDestination() {
+ Column() {
+ Scroll() {
+ Column() {
+ ForEach(this.arr, (item: number, index: number) => {
+ Row() {
+ if (index % 2 === 0) {
+ Text(item.toString())
+ .fontSize(16)
+ .textAlign(TextAlign.Center)
+ } else {
+ Image($r('app.media.daily_video'))
+ .objectFit(ImageFit.Cover)
+ }
+ }
+ .width('90%')
+ .height(150)
+ .backgroundColor(0xFFFFFF)
+ .borderRadius(15)
+ .clip(true)
+ .justifyContent(FlexAlign.Center)
+ .margin({ top: 10 })
+ }, (item: number, index: number) => `${index}_${item}`)
+ }
+ .width('100%')
+ }
+ .scrollable(this.scrollDirection)
+ .scrollBar(BarState.Off)
+ .width('100%')
+ .attributeModifier(this.adaptModifier)
+ .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
+ }
+ .width('100%')
+ .height('100%')
+ }
+ .title($r('app.string.scroll_scene'))
+ .backgroundColor($r('app.color.common_backgroundColor'))
+ }
+}
\ No newline at end of file
diff --git a/entry/src/main/ets/utils/CommonUtils.ets b/entry/src/main/ets/utils/CommonUtils.ets
new file mode 100644
index 0000000000000000000000000000000000000000..8fab8e0b10c3a448dff5d6fa12713d8000d1b2c1
--- /dev/null
+++ b/entry/src/main/ets/utils/CommonUtils.ets
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+import { Logger } from './Logger';
+
+/**
+ * Common utility helpers shared across the project.
+ */
+export default class CommonUtils {
+ public static showToast(ctx: UIContext | undefined, curMsg: ResourceStr) {
+ const uiContext = ctx ?? AppStorage.get('uiContext') as UIContext | undefined;
+ if (!uiContext) {
+ Logger.warn('UIContext is not available');
+ return;
+ }
+ try {
+ uiContext?.getPromptAction().showToast({
+ message: curMsg,
+ duration: 1500
+ });
+ } catch (error) {
+ Logger.error(`Failed to open toast dialog, error info: ${JSON.stringify(error)}`);
+ }
+ }
+}
\ No newline at end of file
diff --git a/entry/src/main/ets/utils/Logger.ets b/entry/src/main/ets/utils/Logger.ets
new file mode 100644
index 0000000000000000000000000000000000000000..5b50f8509ed45ac59d2c95b318af8d0508f99328
--- /dev/null
+++ b/entry/src/main/ets/utils/Logger.ets
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+import { hilog } from '@kit.PerformanceAnalysisKit';
+
+/**
+ * Logger utility to centralize log formatting and output.
+ */
+export class Logger {
+ // Log domain to distinguish module source.
+ private static readonly domain: number = 0xFF00;
+ // Log prefix for quick issue localization.
+ private static readonly prefix: string = 'APILevelAdapt';
+ // Log format template.
+ private static format: string = '%{public}s';
+
+ /**
+ * Print a debug level log.
+ * @param args Debug message content.
+ */
+ static debug(...args: string[]): void {
+ hilog.debug(Logger.domain, Logger.prefix, Logger.format, ...args);
+ }
+
+ /**
+ * Print an info level log.
+ * @param args Information content.
+ */
+ static info(...args: string[]): void {
+ hilog.info(Logger.domain, Logger.prefix, Logger.format, ...args);
+ }
+
+ /**
+ * Print a warning level log.
+ * @param args Warning content.
+ */
+ static warn(...args: string[]): void {
+ hilog.warn(Logger.domain, Logger.prefix, Logger.format, ...args);
+ }
+
+ /**
+ * Print an error level log.
+ * @param args Error content.
+ */
+ static error(...args: string[]): void {
+ hilog.error(Logger.domain, Logger.prefix, Logger.format, ...args);
+ }
+}
\ No newline at end of file
diff --git a/entry/src/main/module.json5 b/entry/src/main/module.json5
index 53024e8f020cc0824592631ba6924108c8028f0f..5a4061626e928c13c5c8a51f20235e1d47549a40 100644
--- a/entry/src/main/module.json5
+++ b/entry/src/main/module.json5
@@ -10,6 +10,7 @@
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
+ "routerMap": "$profile:router_map",
"abilities": [
{
"name": "EntryAbility",
diff --git a/entry/src/main/resources/base/element/color.json b/entry/src/main/resources/base/element/color.json
index 3c712962da3c2751c2b9ddb53559afcbd2b54a02..886bf7b59aa42889d6182fcb662536cdb1135d41 100644
--- a/entry/src/main/resources/base/element/color.json
+++ b/entry/src/main/resources/base/element/color.json
@@ -3,6 +3,10 @@
{
"name": "start_window_background",
"value": "#FFFFFF"
+ },
+ {
+ "name": "common_backgroundColor",
+ "value": "#F1F3F5"
}
]
}
\ No newline at end of file
diff --git a/entry/src/main/resources/base/element/string.json b/entry/src/main/resources/base/element/string.json
index 720747a71938e8c0e7c70cc781227de15f759c29..a8d5e7d0e12fe8a2941d4c6311570d8342def594 100644
--- a/entry/src/main/resources/base/element/string.json
+++ b/entry/src/main/resources/base/element/string.json
@@ -11,6 +11,62 @@
{
"name": "EntryAbility_label",
"value": "APILevelAdapt"
+ },
+ {
+ "name": "action_bar_scene",
+ "value": "Action Bar Scene"
+ },
+ {
+ "name": "button_display_scene",
+ "value": "Button Display Scene"
+ },
+ {
+ "name": "scroll_scene",
+ "value": "Scroll Scene"
+ },
+ {
+ "name": "marquee_display_scene",
+ "value": "Marquee Display Scene"
+ },
+ {
+ "name": "audio_volume_scene",
+ "value": "Audio Volume Scene"
+ },
+ {
+ "name": "index_label",
+ "value": "Multi-API Version Compatibility Sample"
+ },
+ {
+ "name": "audio_volume_button_label",
+ "value": "Check volume manager support"
+ },
+ {
+ "name": "support_label",
+ "value": "Supported"
+ },
+ {
+ "name": "unsupport_label",
+ "value": "Not Supported"
+ },
+ {
+ "name": "fling_prompt_dialog_tip",
+ "value": "Inertial scrolling..."
+ },
+ {
+ "name": "scroll_to_top_tip",
+ "value": "Scrolled to the top of the list!"
+ },
+ {
+ "name": "scroll_to_bottom_tip",
+ "value": "Scrolled to the bottom of the list!"
+ },
+ {
+ "name": "zoom_start_trigger_tip",
+ "value": "Zoom gesture started!"
+ },
+ {
+ "name": "zoom_stop_trigger_tip",
+ "value": "Zoom gesture ended!"
}
]
}
\ No newline at end of file
diff --git a/entry/src/main/resources/base/media/daily_video.png b/entry/src/main/resources/base/media/daily_video.png
new file mode 100644
index 0000000000000000000000000000000000000000..50d1bc6164ec948f602520920684b00ea21e01c8
Binary files /dev/null and b/entry/src/main/resources/base/media/daily_video.png differ
diff --git a/entry/src/main/resources/base/profile/router_map.json b/entry/src/main/resources/base/profile/router_map.json
new file mode 100644
index 0000000000000000000000000000000000000000..8c58a71f8fb73e1d52ddc2a358050427f9d8871d
--- /dev/null
+++ b/entry/src/main/resources/base/profile/router_map.json
@@ -0,0 +1,44 @@
+{
+ "routerMap": [
+ {
+ "name": "ActionBarScene",
+ "pageSourceFile": "src/main/ets/pages/ActionBarScene.ets",
+ "buildFunction": "ActionBarSceneBuilder",
+ "data": {
+ "description": "this is ActionBarScene page."
+ }
+ },
+ {
+ "name": "ButtonDisplayScene",
+ "pageSourceFile": "src/main/ets/pages/ButtonDisplayScene.ets",
+ "buildFunction": "ButtonDisplaySceneBuilder",
+ "data": {
+ "description": "this is ButtonDisplayScene page."
+ }
+ },
+ {
+ "name": "ScrollScene",
+ "pageSourceFile": "src/main/ets/pages/ScrollScene.ets",
+ "buildFunction": "ScrollSceneBuilder",
+ "data": {
+ "description": "this is ScrollScene page."
+ }
+ },
+ {
+ "name": "MarqueeDisplayScene",
+ "pageSourceFile": "src/main/ets/pages/MarqueeDisplayScene.ets",
+ "buildFunction": "MarqueeDisplaySceneBuilder",
+ "data": {
+ "description": "this is MarqueeDisplayScene page."
+ }
+ },
+ {
+ "name": "AudioVolumeScene",
+ "pageSourceFile": "src/main/ets/pages/AudioVolumeScene.ets",
+ "buildFunction": "AudioVolumeSceneBuilder",
+ "data": {
+ "description": "this is AudioVolumeScene page."
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/entry/src/main/resources/en_US/element/string.json b/entry/src/main/resources/en_US/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..a8d5e7d0e12fe8a2941d4c6311570d8342def594
--- /dev/null
+++ b/entry/src/main/resources/en_US/element/string.json
@@ -0,0 +1,72 @@
+{
+ "string": [
+ {
+ "name": "module_desc",
+ "value": "module description"
+ },
+ {
+ "name": "EntryAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "EntryAbility_label",
+ "value": "APILevelAdapt"
+ },
+ {
+ "name": "action_bar_scene",
+ "value": "Action Bar Scene"
+ },
+ {
+ "name": "button_display_scene",
+ "value": "Button Display Scene"
+ },
+ {
+ "name": "scroll_scene",
+ "value": "Scroll Scene"
+ },
+ {
+ "name": "marquee_display_scene",
+ "value": "Marquee Display Scene"
+ },
+ {
+ "name": "audio_volume_scene",
+ "value": "Audio Volume Scene"
+ },
+ {
+ "name": "index_label",
+ "value": "Multi-API Version Compatibility Sample"
+ },
+ {
+ "name": "audio_volume_button_label",
+ "value": "Check volume manager support"
+ },
+ {
+ "name": "support_label",
+ "value": "Supported"
+ },
+ {
+ "name": "unsupport_label",
+ "value": "Not Supported"
+ },
+ {
+ "name": "fling_prompt_dialog_tip",
+ "value": "Inertial scrolling..."
+ },
+ {
+ "name": "scroll_to_top_tip",
+ "value": "Scrolled to the top of the list!"
+ },
+ {
+ "name": "scroll_to_bottom_tip",
+ "value": "Scrolled to the bottom of the list!"
+ },
+ {
+ "name": "zoom_start_trigger_tip",
+ "value": "Zoom gesture started!"
+ },
+ {
+ "name": "zoom_stop_trigger_tip",
+ "value": "Zoom gesture ended!"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/entry/src/main/resources/zh_CN/element/string.json b/entry/src/main/resources/zh_CN/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..01703d42e0c3a20143de818e85ce2e2cd42aac21
--- /dev/null
+++ b/entry/src/main/resources/zh_CN/element/string.json
@@ -0,0 +1,72 @@
+{
+ "string": [
+ {
+ "name": "module_desc",
+ "value": "module description"
+ },
+ {
+ "name": "EntryAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "EntryAbility_label",
+ "value": "APILevelAdapt"
+ },
+ {
+ "name": "action_bar_scene",
+ "value": "动作条场景"
+ },
+ {
+ "name": "button_display_scene",
+ "value": "按钮展示场景"
+ },
+ {
+ "name": "scroll_scene",
+ "value": "滚动场景"
+ },
+ {
+ "name": "marquee_display_scene",
+ "value": "走马灯场景"
+ },
+ {
+ "name": "audio_volume_scene",
+ "value": "音频音量场景"
+ },
+ {
+ "name": "index_label",
+ "value": "多API版本兼容示例"
+ },
+ {
+ "name": "audio_volume_button_label",
+ "value": "检测是否支持音量管理器相关功能"
+ },
+ {
+ "name": "support_label",
+ "value": "支持"
+ },
+ {
+ "name": "unsupport_label",
+ "value": "不支持"
+ },
+ {
+ "name": "fling_prompt_dialog_tip",
+ "value": "惯性滚动中..."
+ },
+ {
+ "name": "scroll_to_top_tip",
+ "value": "滚动到了顶部!"
+ },
+ {
+ "name": "scroll_to_bottom_tip",
+ "value": "滚动到了底部!"
+ },
+ {
+ "name": "zoom_start_trigger_tip",
+ "value": "手势缩放开始!"
+ },
+ {
+ "name": "zoom_stop_trigger_tip",
+ "value": "手势缩放停止!"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/screenshots/pic1.png b/screenshots/pic1.png
new file mode 100644
index 0000000000000000000000000000000000000000..d2d3ff9dd1859c9e647e9301caa954edb64935c1
Binary files /dev/null and b/screenshots/pic1.png differ
diff --git a/screenshots/pic2.png b/screenshots/pic2.png
new file mode 100644
index 0000000000000000000000000000000000000000..fa64dd0d4a337de3284591f638863f4d131760bc
Binary files /dev/null and b/screenshots/pic2.png differ
diff --git a/screenshots/pic3.png b/screenshots/pic3.png
new file mode 100644
index 0000000000000000000000000000000000000000..d56c7c979a2e267bd3ef395dbcfdc7ef51a10b09
Binary files /dev/null and b/screenshots/pic3.png differ