开发工具: 华为云码道

本文配套仓库: 上游 hizzd/json_viewer;OHOS 适配内容位于本地仓库未提交工作区(ohos/example/ohos/docs/ohos-necessity-evaluation.mddocs/ohos-evidence/lib/json_viewer.dart 空安全化、example/lib/main.dart 与测试重写等)。
鸿蒙适配后仓库https://atomgit.com/oh-flutter/json_viewer

本文配套仓库:https://atomgit.com/oh-flutter/json_viewer(TAG:0.0.1-ohos-1.0.0-beta.1,分支:master),文中示例代码位于仓库 example/ 目录。

在这里插入图片描述

什么是 JSON 树形浏览器? 在调试接口返回值、查看设备配置、排查数据结构时,原始 JSON 字符串是一行行密集文本,难以辨认层级关系。JSON 树形浏览器将扁平的 JSON 字符串解析为可折叠的树形视图:Map 对象按键名展开子节点,List 对象按索引展开元素,标量值(字符串、数字、布尔、null)直接显示。每一层可点击展开或收起,还能指定自动展开深度——开发者一目了然地看到数据全貌与层级嵌套关系。

把一段 JSON 数据以可折叠树形展示出来,是接口调试、配置查看、数据排查场景下的高频需求:展开看嵌套结构、收起折叠不关心的分支、按层级行数控制展开深度。鸿蒙应用同样需要这个能力。本文介绍如何使用鸿蒙化适配后的 Flutter 三方库 json_viewer,用一个 JsonViewerRoot 组件在鸿蒙 App 内渲染任意 JSON 对象的树形视图,支持自动展开层级与自定义节点构建,并附上 OpenHarmony-6.1.1.120 真机的完整实测记录。

一、最终运行效果

应用启动后,页面顶部有一个滑块控制自动展开层级(0-5),下方以树形展示示例 JSON 数据。拖动滑块到 5 时,所有深层嵌套节点自动展开;拖动到 0 时,所有节点折叠。点击任意 Map 或 List 节点的行可手动切换展开/收起。AppBar 右上角切换按钮可轮换两组示例数据。

验证点结果
应用启动,Flutter 页面正常渲染 JSON 树形视图通过
默认展开层级为 2,前两层节点自动展开通过
拖动滑块至 5,深层嵌套节点(如 author → meta → location → coords)自动展开通过
拖动滑块至 0,所有节点折叠为根行通过
点击 Map / List 节点行,手动展开/收起切换正常通过
点击 AppBar 切换按钮,两组示例数据轮换显示通过
null 值显示红色、bool 值显示青色、int 值显示浅绿色通过
全程无需申请任何敏感权限通过

获取复制后显示剪贴板内容 获取复制后显示剪贴板内容 获取复制后显示剪贴板内容 获取复制后显示剪贴板内容 获取复制后显示剪贴板内容

鸿蒙技术点:FlutterPage 与 XComponent 渲染管线
鸿蒙侧的 Flutter 渲染入口是 FlutterPage 组件,它在 Index.ets 中被 @Entry 组件的 build() 方法直接使用。FlutterPage 内部封装了 XComponent——OpenHarmony 提供的底层渲染画布组件。XComponent 通过 NAPI 桥接 C++ 引擎层,将 Flutter 的 Skia 渲染管线挂载到鸿蒙的渲染树中,使 Dart 层的 Widget 树(包括 JsonViewerRoot 及其递归构建的全部子节点)在鸿蒙设备上完整渲染。FlutterAbility 作为容器 Ability,管理 FlutterEngine 的生命周期,在 configureFlutterEngine 中注册所有平台插件。

二、json_viewer 是什么

json_viewer 原库(pub.dev 0.0.1,作者 hizzd)是一个 Flutter JSON 树形浏览器组件。它以可折叠树形展示任意 JSON 对象(Map、List 及其嵌套结构),支持指定自动展开层级与自定义节点构建回调。鸿蒙适配版在其基础上新增了 OpenHarmony / HarmonyOS 平台支持:Dart 层零改动(仅修复 Dart 3 空安全兼容性),新增 OHOS 平台插件骨架声明,使库可以插件方式在鸿蒙工程中完整消费。

几个对使用者友好的特点:

  • 纯 Dart 实现:树形渲染、展开/收起、自动层级控制全部为纯 Dart Widget,不调用任何系统 API,逻辑在所有平台完全一致;
  • 零权限:OHOS 平台骨架仅通过 @kit.BasicServicesKitdeviceInfo 基础能力返回系统版本号,不需要在 module.json5 中申请任何敏感权限;
  • 任意 JSON 类型:支持 Map、List、String、int、double、bool、null 及任意层级嵌套,空 Map {} 和空 List [] 也能正常渲染;
  • 自动展开层级expandDeep 参数控制初始展开深度,运行时修改会自动重新应用展开状态;
  • 自定义节点构建onBuildNode 回调允许开发者完全接管每个节点的渲染 Widget,实现自定义样式与交互;
  • 颜色区分类型:null 值红色、bool 值青色、int 值浅绿色、其余黑色,Map 键名靛蓝色、List 键名深紫色,一目了然。

接口说明

JsonViewerRoot 组件
名称描述类型参数类型返回值必填鸿蒙平台支持
jsonObj要展示的 JSON 对象,支持 Map、List 与标量值属性dynamic
expandDeep自动展开层级,默认 2属性int
onBuildNode自定义节点构建回调,缺省时使用内置默认节点属性OnBuildNode
OnBuildNode 回调
名称描述类型参数类型返回值必填鸿蒙平台支持
onBuildNode(parent, nodeName, nodeValue)递归构建指定节点的展示 Widget,由库内部调用回调JsonNode? parent, String nodeName, dynamic nodeValueWidget

三、环境准备

本文所有实测均在以下环境完成:

版本说明
Flutter(ohos 版)0.0.1-ohos-1.0.0-beta.1主验证环境,真机实测
DevEco Studio26.0.0 (DS-261.23567.138.36.2600821)构建环境
编译 SDK5.1.0(18)(构建环境 26.0.0)宿主工程 compatibleSdkVersion 同值,保留带括号的旧格式
真机TLR-AL00(OpenHarmony-6.1.1.120)API 24

鸿蒙技术点:compatibleSdkVersion 与 API Level 的对应关系
compatibleSdkVersion 是鸿蒙工程 build-profile.json5 中的关键字段,声明应用的最低兼容 API 版本。鸿蒙的 API 版本与系统版本一一对应:5.1.0(18) 对应 API 18,6.1.0(23) 对应 API 23,6.1.1.120 对应 API 24。真机安装时,系统会校验应用的 compatibleSdkVersion 不高于设备实际 API 版本,否则报"此应用暂不支持在当前设备安装"。本文 example 工程设为 5.1.0(18),在 API 24 真机上可正常安装运行。

两点提醒:

  • 编译鸿蒙目标需要使用 ohos 版 Flutter SDK,普通 Flutter SDK 无法编译 ohos 产物;
  • 若在真机上安装应用报"此应用暂不支持在当前设备安装",是宿主工程的 compatibleSdkVersion 高于设备 API 导致的,与插件无关,处理方式见 FAQ Q3。

四、引入依赖

进入工程目录,在 pubspec.yaml 中添加 git 依赖:

dependencies:
  json_viewer:
    git:
      url: https://atomgit.com/oh-flutter/json_viewer.git
      # ref: 根据下方表格选择不同框架适配的 TAG 版本
      ref: 0.0.1-ohos-1.0.0-beta.1

执行命令拉取依赖:

flutter pub get

TAG 命名规则:原库版本-ohos-版本号-beta.x

Flutter 框架版本TAG 名称分支名
3.440.0.1-ohos-1.0.0-beta.1master

说明:该 TAG 已在 Flutter 3.44.9-ohos-0.0.1-canary1 + OpenHarmony-6.1.1.120(API 24)真机上实测通过。compatibleSdkVersion 设为 5.1.0(18) 即可在 API 24 真机安装运行。

五、代码接入

5.1 导入库

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:json_viewer/json_viewer.dart';

导入后即可使用 JsonViewerRoot 组件、OnBuildNode 回调类型,以及内部的 JsonViewerMapNodeJsonViewerListNodeJsonViewerNode 等节点类。

5.2 基本用法:渲染 JSON 树

final Map<String, dynamic> jsonObj =
    json.decode('{"menu": {"id": 123456, "success": false}}');

JsonViewerRoot(
  jsonObj: jsonObj,
  expandDeep: 2,
);

jsonObj 接收 json.decode 解析出的任意值——Map、List、字符串、数字、布尔、null 均可。expandDeep 指定自动展开层级(默认 2):深度小于 expandDeep 的节点自动展开,其余折叠。设为 0 时所有节点折叠为根行。

代码逐段分析:onBuildNodeDefault 递归构建
JsonViewerRoot 的构造器接收 jsonObj 后,在 build 方法中调用 onBuildNode(null, "[root]", widget.jsonObj) 启动递归。onBuildNodeDefault 是默认的节点构建回调:它根据 nodeValue 的运行时类型选择节点 Widget——Map 类型创建 JsonViewerMapNode(左偏移 10),List 类型创建 JsonViewerListNode(左偏移 10),其余(标量与 null)创建 JsonViewerNode(左偏移 0)。每创建一个子节点,expandDeep 减 1,当减至 0 时子节点不再自动展开。

5.3 展开/收起交互

Map 和 List 节点都是 StatefulWidget,点击行触发 setState 切换 isOpen

// JsonViewerMapNodeState.build 方法核心逻辑
Widget result = GestureDetector(
  onTap: () {
    setState(() {
      widget.isOpen = !widget.isOpen;
    });
  },
  child: Row(
    children: <Widget>[
      Icon(widget.isOpen ? Icons.arrow_drop_down : Icons.arrow_right),
      Text(widget.nodeName, style: TextStyle(color: Colors.indigo)),
    ],
  ),
);
if (widget.isOpen) {
  result = Column(
    children: <Widget>[
      result,
      Padding(
        padding: EdgeInsets.only(left: widget.leftOffset),
        child: Column(
          children: widget.buildChild(),
        ),
      ),
    ],
  );
}
return result;

点击展开时,箭头图标从 arrow_right 变为 arrow_drop_down,下方渲染子节点列表(由 buildChild() 递归调用 root.onBuildNode 生成)。点击收起时,子节点列表移除,仅保留节点行。List 节点还额外显示元素数量 [N]

代码逐段分析:buildChild 递归调用
JsonViewerMapNode.buildChild() 遍历 nodeValue 的所有键值对,对每个值调用 root.onBuildNode(this, key, value)——这会再次进入 onBuildNodeDefault,根据值的类型选择新的节点 Widget。如此递归,直到所有叶子节点(标量值或 null)都被构建为 JsonViewerNodeJsonViewerListNode.buildChild() 类似,但键名为 [0][1][2] 等索引格式。

5.4 自动展开层级控制

// initState 中根据 expandDeep 初始化展开状态

void initState() {
  super.initState();
  widget.isOpen = widget.expandDeep > 0;
}

// didUpdateWidget 中检测 expandDeep 变化,重新应用展开状态

void didUpdateWidget(JsonViewerMapNode oldWidget) {
  super.didUpdateWidget(oldWidget);
  if (widget.expandDeep != oldWidget.expandDeep) {
    widget.isOpen = widget.expandDeep > 0;
  }
}

initState 在节点首次创建时根据 expandDeep 决定是否展开。当外部修改 expandDeep(如拖动滑块)时,Flutter 复用既有 State,initState 不再执行——因此需要 didUpdateWidget:检测 expandDeep 变化后重新应用展开状态。注意副作用:此前手动展开/收起的节点会被重置为自动展开状态。

鸿蒙技术点:StatefulWidget 在鸿蒙上的生命周期一致性
StatefulWidgetinitStatedidUpdateWidgetbuilddispose 等生命周期回调由 Flutter Engine 的 C++ 层统一调度。在鸿蒙平台上,Flutter Engine 通过 XComponent 的渲染回调驱动 Dart 层的 Widget 构建、布局与绘制。setState 触发的重建流程在鸿蒙上与 Android/iOS 完全一致——标记 Element 为 dirty,下一帧调度 build 方法。这意味着 JsonViewerRoot 的展开/收起交互在鸿蒙上无需任何适配。

5.5 标量值颜色区分

// JsonViewerNode.build 方法中的颜色逻辑
var color = Colors.black;
if (this.nodeValue == null) {
  color = Colors.redAccent;
} else {
  switch (this.nodeValue.runtimeType) {
    case bool:
      color = Colors.teal;
      break;
    case int:
      color = Colors.lightGreen;
      break;
  }
}

return Padding(
  padding: EdgeInsets.only(left: 24),
  child: Row(
    children: <Widget>[
      Text(this.nodeName, style: TextStyle(color: Colors.black54)),
      Text(" : "),
      Text(
        this.nodeValue == null ? "null" : this.nodeValue.toString(),
        style: TextStyle(color: color),
      ),
    ],
  ),
);

叶子节点(标量值)统一由 JsonViewerNode 渲染:null 显示红色 null,bool 显示青色,int 显示浅绿色,其余(String、double)显示黑色。键名统一灰色,键名与值之间用 : 分隔。这使得 JSON 数据中的类型分布一目了然。

5.6 实战:带滑块的 JSON 查看器

实际业务中常见的场景是接口调试页面展示返回的 JSON 数据,并允许调整展开深度。下面是 demo 工程的完整示例:

class _MyAppState extends State<MyApp> {
  final Map<String, dynamic> sampleA = json.decode('''
{
  "name": "json_viewer",
  "description": "flutter json tree viewer.",
  "version": "0.0.1",
  "published": 2019,
  "rating": 4.8,
  "active": true,
  "author": {
    "name": "hizzd",
    "email": null,
    "roles": ["maintainer", "reviewer"],
    "meta": {
      "since": 2018,
      "location": {
        "city": "Shenzhen",
        "country": "CN",
        "coords": [22.54, 114.06]
      }
    }
  },
  "tags": ["flutter", "dart", "json", "widget"],
  "issues": [
    {"id": 1001, "title": "support null safety", "state": "closed"},
    {"id": 1002, "title": "ohos platform support", "state": "open", "assignee": null}
  ],
  "scores": [9.1, 8.7, 9.9, null, 9.3],
  "empty": {},
  "flags": [true, false, true]
}
''');

  int _expandDeep = 2;

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('json_viewer example')),
        body: Column(
          children: <Widget>[
            Padding(
              padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
              child: Row(
                children: <Widget>[
                  Text('自动展开层级: '),
                  Expanded(
                    child: Slider(
                      min: 0,
                      max: 5,
                      divisions: 5,
                      value: _expandDeep.toDouble(),
                      label: '$_expandDeep',
                      onChanged: (double v) {
                        setState(() {
                          _expandDeep = v.round();
                        });
                      },
                    ),
                  ),
                  Text('$_expandDeep'),
                ],
              ),
            ),
            Expanded(
              child: SingleChildScrollView(
                child: Padding(
                  padding: EdgeInsets.all(8),
                  child: JsonViewerRoot(
                    jsonObj: sampleA,
                    expandDeep: _expandDeep,
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

滑块拖动时,setState 修改 _expandDeepJsonViewerRoot 收到新属性后触发 didUpdateWidget,所有 Map/List 节点根据新的 expandDeep 重新应用展开状态。整个树形视图实时响应,无需重建页面。

5.7 自定义节点构建

JsonViewerRoot(
  jsonObj: jsonObj,
  expandDeep: 2,
  onBuildNode: (parent, nodeName, nodeValue) {
    // 自定义:给 bool 值加图标
    if (nodeValue is bool) {
      return Row(
        children: [
          Icon(nodeValue ? Icons.check_circle : Icons.cancel, size: 16),
          Text(' $nodeName : $nodeValue'),
        ],
      );
    }
    // 其余类型使用默认构建
    return JsonViewerRoot(jsonObj: {}).onBuildNodeDefault(parent, nodeName, nodeValue);
  },
);

onBuildNode 回调接收三个参数:parent(父节点,根节点为 null)、nodeName(键名或索引)、nodeValue(值)。返回值为该节点渲染的 Widget。回调对每个节点(包括递归子节点)都会被调用,因此可以实现完全自定义的节点样式与交互逻辑。

六、运行与验证

以下为 demo 工程的真机实测记录。仓库中 example 的 Bundle Name 为 com.hizzd.json_viewer_example

设备项
机型TLR-AL00
系统版本OpenHarmony-6.1.1.120
API 版本24
构建环境Flutter 3.44.9-ohos-0.0.1-canary1, DevEco Studio 26.0.0

6.1 验证一:树形渲染与自动展开

安装、启动 demo:

# 构建 hap 后安装
hdc install entry-default-signed.hap

# 启动 demo
hdc shell aa start -b com.hizzd.json_viewer_example -a EntryAbility

应用启动后,JSON 树形视图默认展开 2 层:根节点 [root] 展开,第一层键(name、description、version、published、rating、active、author、tags、issues、scores、empty、flags)全部可见,第二层中 Map 节点(author)也展开显示其子键(name、email、roles、meta)。第三层及更深(meta → location → coords)折叠,需手动点击展开。

在这里插入图片描述

6.2 验证二:滑块控制展开层级

拖动滑块至 5:

# 模拟拖动滑块至最右端
hdc shell uitest uiInput click 950 120

滑块值变为 5 后,所有深层嵌套节点自动展开:author → meta → location → city/country/coords 全部可见,coords 列表的元素 22.54114.06 也展示出来。拖回 0 后,所有节点折叠为单行 [root]

在这里插入图片描述

6.3 验证三:手动展开/收起

点击 author 节点行(在 expandDeep=0 状态下):

# 模拟点击 author 节点行
hdc shell uitest uiInput click 200 400

author 节点展开,显示子键列表;再次点击收起。List 节点(如 tags)展开后显示元素数量 [4] 和索引格式的子节点 [0][1] 等。

6.4 验证四:插件注册日志

通过 hdc hilog 抓取运行日志:

# 清空日志缓冲后启动应用,抓取插件注册行
hdc shell hilog -r
hdc shell aa start -b com.hizzd.json_viewer_example -a EntryAbility
hdc shell hilog | grep JsonViewerPlugin

日志输出:

FlutterEngineCxnRegistry --> Adding plugin: JsonViewerPlugin

插件由 GeneratedPluginRegistrant 注册成功,MethodChannel('json_viewer') 通道就绪,getPlatformVersion 方法返回 OpenHarmony-6.1.1.120

实测结论:

验证点结果
应用启动,Flutter 页面正常渲染 JSON 树形视图通过
默认展开层级为 2,前两层节点自动展开通过
拖动滑块至 5,深层嵌套节点自动展开通过
拖动滑块至 0,所有节点折叠为根行通过
点击 Map / List 节点行,手动展开/收起切换正常通过
null 红色、bool 青色、int 浅绿色,类型颜色区分正确通过
插件注册日志确认 JsonViewerPlugin 挂载成功通过
全程无需申请任何敏感权限通过

以下是操作的视屏,可以参考一下:

Example 启动授权 Example 启动授权


七、工作原理

整个调用链路如下:

Dart: JsonViewerRoot(jsonObj: data, expandDeep: 2)
  → State.build 调用 onBuildNode(null, "[root]", jsonObj)
    → onBuildNodeDefault 根据 nodeValue 类型选择节点 Widget
      ├─ Map → JsonViewerMapNode (StatefulWidget, 可展开/收起)
      │    └─ buildChild() 遍历 entries, 递归调用 root.onBuildNode
      ├─ List → JsonViewerListNode (StatefulWidget, 可展开/收起)
      │    └─ buildChild() 遍历 elements, 递归调用 root.onBuildNode
      └─ 标量/null → JsonViewerNode (StatelessWidget, 叶子节点)
           └─ 根据 runtimeType 选择颜色渲染

用户点击节点行
  → setState({ isOpen = !isOpen })
    → 若 isOpen=true, Column 中追加 buildChild() 生成的子节点列表
    → 若 isOpen=false, 仅保留节点行

用户拖动滑块修改 expandDeep
  → JsonViewerRoot 收到新 expandDeep 属性
    → didUpdateWidget 检测 expandDeep 变化
      → widget.isOpen = widget.expandDeep > 0
        → 所有节点按新层级重新应用展开状态

ArkTS: JsonViewerPlugin.onMethodCall("getPlatformVersion")
  → deviceInfo.osFullName
    → result.success("OpenHarmony-6.1.1.120")

Dart 侧的树形构建、展开/收起、自动层级控制全部为纯 Dart Widget 实现,不经过任何平台通道。平台侧(OHOS)仅保留 getPlatformVersion 桩实现,通过 MethodChannel('json_viewer') 暴露,返回 deviceInfo.osFullName

鸿蒙技术点:FlutterPlugin 与 MethodCallHandler 接口
鸿蒙侧的 JsonViewerPlugin 实现了 FlutterPluginMethodCallHandler 两个接口。FlutterPluginonAttachedToEngine 在插件挂载到引擎时被调用,创建 MethodChannel('json_viewer') 并设置自身为回调处理器;onDetachedFromEngine 在卸载时清理通道并置 null。MethodCallHandleronMethodCall 处理来自 Dart 层的方法调用——收到 getPlatformVersion 时通过 @kit.BasicServicesKitdeviceInfo.osFullName 读取系统版本号并回传,未实现的方法返回 notImplemented()。所有方法调用包裹在 try/catch 中,异常时返回错误码 getPlatformVersion_failed

鸿蒙侧插件实现(ArkTS)核心代码:

import { deviceInfo } from '@kit.BasicServicesKit';
import {
  FlutterPlugin, FlutterPluginBinding, MethodCall,
  MethodCallHandler, MethodChannel, MethodResult,
} from '@ohos/flutter_ohos';

export default class JsonViewerPlugin implements FlutterPlugin, MethodCallHandler {
  private channel: MethodChannel | null = null;

  getUniqueClassName(): string {
    return "JsonViewerPlugin"
  }

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    try {
      this.channel = new MethodChannel(binding.getBinaryMessenger(), "json_viewer");
      this.channel.setMethodCallHandler(this)
    } catch (e) {
      console.error(`[JsonViewerPlugin] onAttachedToEngine failed: ${JSON.stringify(e)}`)
    }
  }

  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    try {
      if (this.channel != null) {
        this.channel.setMethodCallHandler(null)
        this.channel = null
      }
    } catch (e) {
      console.error(`[JsonViewerPlugin] onDetachedFromEngine failed: ${JSON.stringify(e)}`)
    }
  }

  onMethodCall(call: MethodCall, result: MethodResult): void {
    if (call.method == "getPlatformVersion") {
      this.getPlatformVersion(result)
    } else {
      result.notImplemented()
    }
  }

  private getPlatformVersion(result: MethodResult): void {
    try {
      const osFullName: string = deviceInfo.osFullName;
      result.success(osFullName)
    } catch (e) {
      result.error("getPlatformVersion_failed", `get platform version error: ${JSON.stringify(e)}`, null)
    }
  }
}

代码逐段分析:deviceInfo 导入方式
本库使用了鸿蒙 Kit 模块导入方式 import { deviceInfo } from '@kit.BasicServicesKit',而非旧版 import deviceInfo from '@ohos.deviceInfo'。Kit 是鸿蒙 API 12+ 引入的模块化导入方式,将相关系统能力打包为 Kit 包:BasicServicesKit 包含设备信息、系统能力查询等基础服务。deviceInfo.osFullName 返回完整系统版本名(如 OpenHarmony-6.1.1.120),属于 SystemCapability.Startup.SystemInfo 基础能力,无需任何权限。两种导入方式在功能上等价,Kit 方式是鸿蒙官方推荐的新写法。

鸿蒙技术点:GeneratedPluginRegistrant 自动生成机制
GeneratedPluginRegistrant.ets 由 Flutter 工具链根据 pubspec.yaml 中的 ohos 平台配置自动生成。当 flutter pub get 解析到 json_viewer 依赖声明了 ohos: pluginClass: JsonViewerPlugin 时,工具会在宿主工程的 example/ohos/entry/src/main/ets/plugins/ 下生成注册代码,将 new JsonViewerPlugin() 添加到 FlutterEngine 的插件列表。开发者无需手动编写注册逻辑——EntryAbility.configureFlutterEngine 中一行 GeneratedPluginRegistrant.registerWith(flutterEngine) 即完成全部插件注册。这一机制是 Flutter 插件生态在鸿蒙上无缝衔接的关键。

八、常见问题

Q1:树形视图没有展开任何节点?

检查 expandDeep 的值。默认为 2,表示前两层自动展开。若设为 0,所有节点折叠为根行——这通常不是 Bug,而是 expandDeep=0 的预期行为。通过滑块或代码将其设为大于 0 的值即可看到展开效果。

Q2:拖动滑块后,之前手动展开的节点被收起了?

这是 didUpdateWidget 的设计行为。当 expandDeep 变化时,所有 Map/List 节点的 isOpen 被重置为 expandDeep > 0 的结果,此前手动展开/收起的节点状态会被覆盖。这是为保证层级一致性——如果允许部分节点保持手动状态,与新的 expandDeep 产生冲突时行为不可预测。若需要保留手动展开状态,不要在运行时修改 expandDeep

Q3:真机安装 demo 时提示"此应用暂不支持在当前设备安装"?

这是宿主工程的 compatibleSdkVersion 高于真机 API 版本导致的安装校验失败,与插件无关。将 build-profile.json5 中的 compatibleSdkVersion 调整为不高于真机 API 的版本(如 5.1.0(18),注意保留带括号的旧格式)即可。本文配套仓库的 example 已用此配置在 OpenHarmony-6.1.1.120(API 24)真机上安装实测通过。

Q4:空 Map {} 或空 List [] 怎么显示?

空 Map 显示键名和箭头图标,点击展开后下方无子节点(空 Column)。空 List 显示键名、箭头图标和元素数量 [0],点击展开后同样无子节点。这两种情况不会报错,树形视图正常渲染。

Q5:如何自定义节点的样式?

使用 onBuildNode 回调。回调接收 (JsonNode? parent, String nodeName, dynamic nodeValue),返回自定义 Widget。回调对每个节点都会被调用(包括递归子节点),因此可以实现完全自定义的渲染逻辑。若只想修改部分类型的样式,在回调中判断 nodeValue.runtimeType,对其余类型调用 JsonViewerRoot 的默认构建方法即可。

Q6:getPlatformVersion 返回什么?

返回 deviceInfo.osFullName,如 OpenHarmony-6.1.1.120。这与 Android 返回 "Android ${Build.VERSION.RELEASE}"、iOS 返回 "iOS ${UIDevice.current.systemVersion}" 的语义对齐——系统发行版本号。该值通过 MethodChannel('json_viewer')getPlatformVersion 方法获取,属于 SystemCapability.Startup.SystemInfo 基础能力,无需任何权限。

九、结语

回顾一下:在 pubspec.yaml 中以 git TAG 引入 json_viewer,将任意 JSON 对象传入 JsonViewerRoot(jsonObj: data, expandDeep: 2),即可在鸿蒙 App 内渲染可折叠的树形视图。Map 按键名展开子节点,List 按索引展开元素,标量值按类型颜色区分。expandDeep 控制自动展开层级,运行时修改实时生效;onBuildNode 回调支持完全自定义节点渲染。树形构建、展开/收起、自动层级控制全部为纯 Dart 实现,在 Android、iOS、鸿蒙各平台行为完全一致;OHOS 平台骨架仅提供 getPlatformVersion 版本标识,零权限、零侵入。已在 OpenHarmony-6.1.1.120(API 24)真机完整实测,树形渲染、滑块控制、手动展开/收起全部通过。

使用中发现任何问题,欢迎到配套仓库提 Issue,也欢迎发 PR 共建。

相关链接

欢迎加入 CPF-Flutter 鸿蒙社区,社区入口、环境搭建指南和本文相关链接统一放在这里:


附:json_viewer 核心能力对照表

能力维度实现方式鸿蒙表现跨平台一致性
树形渲染JsonViewerRoot(jsonObj, expandDeep) 递归构建纯 Dart,一致全平台完全一致
Map 节点JsonViewerMapNode (StatefulWidget)展开/收起正常全平台完全一致
List 节点JsonViewerListNode (StatefulWidget)显示元素数量 [N]全平台完全一致
标量叶子JsonViewerNode (StatelessWidget)颜色区分类型全平台完全一致
自动展开expandDeep 参数 + initState初始展开正常全平台完全一致
层级重应用didUpdateWidget 检测 expandDeep 变化滑块拖动生效全平台完全一致
手动展开/收起GestureDetector.onTap + setState点击切换正常全平台完全一致
自定义节点onBuildNode 回调回调正常触发全平台完全一致
类型颜色null 红 / bool 青 / int 浅绿颜色区分正确全平台完全一致
平台版本MethodChannel('json_viewer')deviceInfo.osFullName格式对齐各平台
权限要求无敏感权限全平台均无
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐