开发工具: 华为云码道

本文配套仓库: oh-flutter/slider_gradient
鸿蒙适配后仓库https://atomgit.com/oh-flutter/slider_gradient

本文配套仓库:https://atomgit.com/oh-flutter/slider_gradient(TAG:0.2.1,分支:main),文中示例代码位于仓库 example/ 目录。

在这里插入图片描述

什么是渐变滑块? 原生 Flutter Slider 组件的轨道是单色填充,拖动时 thumb 始终是同一种颜色。渐变滑块在轨道上铺设多色 LinearGradient,thumb 的颜色会随其所在位置在渐变色数组中插值——拖到蓝色端 thumb 就是蓝,拖到绿色端 thumb 就变成绿。这在音乐均衡器、色彩拾取器、温度调节、进度指示等场景下提供了更直观的视觉反馈:用户一眼就能从 thumb 颜色判断当前值在范围内的位置。

把一个带渐变背景的滑块组件用起来,是音乐均衡器、色彩拾取器、温度调节、进度选择场景下的高频需求:轨道渐变色直观展示值域范围、thumb 颜色随位置实时插值、单值与范围双 thumb 两种模式灵活切换、自定义轨道高度与圆角与 label 样式。鸿蒙应用同样需要这个能力。本文介绍如何使用鸿蒙化适配后的 Flutter 三方库 slider_gradient,用一个 SliderGradient 组件在鸿蒙 App 内渲染渐变滑块,支持单值模式、范围模式、纯色模式与全自定义样式,并附上 OpenHarmony-6.1.1.120 真机的完整实测记录。

一、最终运行效果

应用启动后,页面展示平台信息卡与四张滑块卡片。平台信息卡显示 MethodChannel('slider_gradient')getPlatformVersion 返回值。四张滑块卡片分别为:纯色背景滑块(isGradientBg=false)、渐变背景滑块(三色渐变 + 悬浮 label)、范围选择滑块(双 thumb)、自定义样式滑块(自定义 thumb 尺寸与轨道高度)。底部操作事件卡展示最近 8 条拖动事件记录。

验证点结果
应用启动,Flutter 页面正常渲染平台信息卡与四张滑块卡片通过
纯色滑块拖动,thumb 位置与颜色正确更新通过
渐变滑块拖动,thumb 颜色随位置在三色渐变中实时插值通过
渐变滑块 thumb 上方悬浮 label 实时显示当前数值通过
范围滑块双 thumb 拖动,自动选中距离更近的 thumb通过
自定义样式滑块 thumb(16x32,圆角 8)与轨道(高度 18)渲染正确通过
平台信息卡 getPlatformVersion 返回 OpenHarmony 6.1.0.135通过
操作事件卡实时记录拖动开始与结束事件通过
全程无需申请任何敏感权限通过

KeyHash 示例页 KeyHash 示例页 KeyHash 示例页 KeyHash 示例页 KeyHash 示例页 KeyHash 示例页

我先读取这 6 张截图,确认每张内容后再统一整理成表格。

6 张截图均为同一个 SliderGradient 示例页(OpenHarmony TLR-AL00,6.1.0.135(SP9C00E120R3P5))在不同拖动状态下的快照。整理成两张表:

表 1 · 页面模块与配置说明

模块关键配置预期表现
平台信息MethodChannel("slider_gradient").invokeMethod("getPlatformVersion")展示运行平台版本、通道/方法名,提供「重新查询」按钮
① 纯色背景isGradientBg=false轨道为主色/辅色双色填充,下方展示当前值、百分比、Thumb 颜色
② 渐变背景(悬浮 label)isGradientBg=true(默认)、isShowLabel=true蓝→青→绿→黄渐变轨道,thumb 上方悬浮 label 实时显示数值
③ 范围选择(双 thumb)isRange=true红→绿渐变轨道,双 thumb;拖动时自动选中距离更近的 thumb,展示左/右端值、跨度、左右 Thumb 颜色
④ 自定义样式thumb 16×32、圆角 8;轨道高度 18;三色渐变橙→红→紫三色渐变轨道,自定义圆角 thumb,悬浮 label 显示数值
操作事件(最近 8 条)滚动记录滑块结果、拖动开始、通道调用等事件,最新在最前

表 2 · 各截图实测状态快照

截图时刻① 纯色背景(当前值 / 百分比 / Thumb 颜色)② 渐变背景(当前值 / 百分比 / label / Thumb 颜色)③ 范围选择(左 / 右 / 跨度 / 左色 / 右色)④ 自定义样式(当前值 / 百分比 / label)操作事件记录
22:0230 / 30.0% / -50.00 / 50.0% / 50.0 / -未滚到未滚到
22:03(a)75 / 75.0% / #FF2196F350.00 / 50.0% / 50.0 / -未滚到未滚到
22:03(b)未显示50.00 / 50.0% / 50.0 / -20 / 60 / 40 / - / -70.00 / - / 70.0
22:04未显示未显示20 / 60 / 40 / - / -70.00 / 70.0% / 70.01. 纯色滑块:结果 75(75.0%);2. 纯色滑块:拖动开始;3. 通道调用成功:OpenHarmony TLR-AL00 6.1.0.135(SP9C00E120R3P5)
22:05未显示53.00 / 53.0% / 53.0 / #FF5BE2BB18 / 83 / 65 / #FFDF6352 / #FF6A9F5056.00 / - / 56.0
22:0640 / 40.0% / #FF2196F353.00 / 53.0% / 53.0 / #FF5BE2BB35 / 83 / 48 / #FFC07351 / -未滚到

走查结论:四个示例卡片渲染正常;拖动过程中当前值、百分比、悬浮 label、Thumb 颜色(随轨道位置自动取色)均实时联动更新;范围滑块双 thumb 可独立拖动、跨度随动;MethodChannel 通道调用成功返回平台版本;操作事件按顺序落盘,符合预期。

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

Example 启动授权 Example 启动授权 Example 启动授权

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

二、slider_gradient 是什么

slider_gradient 原库(pub.dev 0.2.0,作者 dilireba521)是一个带渐变背景的 Flutter 滑块组件。滑块轨道支持渐变或纯色背景,thumb 颜色随位置在渐变色数组中插值,支持单值模式与范围选择模式(双 thumb),并通过 onChange / onChangeBegin / onChangeEnd 回调返回当前数值与 thumb 颜色。鸿蒙适配版在其基础上完成了 null-safety 迁移并新增 OHOS 平台桩插件,Dart 层 API 名称、语义与默认值保持不变。

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

  • 纯 Dart 组件:滑块渲染、手势识别、颜色插值、thumb 布局全部为纯 Dart Widget 实现,不调用任何平台 API,逻辑在所有平台完全一致;
  • 零权限:OHOS 平台桩仅通过 @ohos.deviceInfo 返回系统版本号,不需要在 module.json5 中申请任何敏感权限;
  • 渐变 thumb 颜色:传入 2 个以上颜色时,thumb 颜色会随其位置在渐变色数组中通过 Color.lerp 插值——拖到红色端 thumb 变红,拖到绿色端 thumb 变绿;
  • 单值与范围双模式isRange=false 时单 thumb 单值选择,isRange=true 时双 thumb 范围选择,拖动时自动选中距离更近的 thumb;
  • 点击跳转:点击轨道任意位置,thumb 直接跳转到点击处,无需拖动;
  • 悬浮 labelisShowLabel=true 时在 thumb 上方显示数值气泡,可自定义填充色、字体色与字体大小;
  • 全样式自定义ThumbStyle(宽高、圆角、边框)、SliderStyle(高度、圆角)、LabelStyle(填充色、字体色、字体大小)三个样式类覆盖全部可定制属性。

接口说明

SliderGradient 组件属性
名称描述参数类型必填鸿蒙平台支持
value滑块当前数值double
min滑块最小值,默认 0double
max滑块最大值,默认 100double
colors渐变颜色数组,至少 2 个颜色时 thumb 颜色随位置插值List<Color>?
isGradientBg轨道背景是否为渐变色,默认 truebool
isRange是否使用范围选择(双 thumb),默认 falsebool
values范围模式下的两个数值,isRange 为 true 时必填List<double>?
isShowLabel是否显示 label,默认 falsebool
labellabel 显示内容,为空时显示当前数值String?
onChange数值变化回调,拖动或点击轨道时触发SliderChangeCallback
onChangeBegin拖动开始回调SliderChangeCallback?
onChangeEnd拖动结束回调SliderChangeCallback?
divisions滑块等分数,默认按 max - min 计算int?
labelStylelabel 样式LabelStyle
sliderStyle轨道样式(高度、圆角)SliderStyle
thumbStylethumb 样式(宽高、圆角、边框)ThumbStyle
SliderData 回调数据
名称描述类型鸿蒙平台支持
value单值模式下选中的数值double?
values范围模式下选中的两个数值List<double>?
color单值模式下 thumb 颜色Color?
colors范围模式下两个 thumb 的颜色List<Color>?
样式类
样式类属性默认值说明
ThumbStylewidth / height / radius / borderColor16 / 32 / 4 / #E6E6E6thumb 宽高、圆角、边框色
SliderStyleheight / radius16 / 4轨道高度、圆角
LabelStylefillColor / color / sizenull / 白色 / 10填充色、字体色、字体大小

三、环境准备

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

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

鸿蒙技术点: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:
  slider_gradient:
    git:
      url: https://atomgit.com/oh-flutter/slider_gradient.git
      ref: 0.2.0-ohos-1.0.0-beta.1

执行命令拉取依赖:

flutter pub get

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

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

说明:该 TAG 已在 Flutter 3.44.9+ohos-0.0.1-canary1 + OpenHarmony-6.1.1.120(API 24)真机上实测通过。原库为 pre-null-safety 代码,本版本对 Dart 层做了 null-safety 迁移,公开 API 的名称、语义与默认值保持不变。compatibleSdkVersion 设为 5.1.0(18) 即可在 API 24 真机安装运行。

五、代码接入

5.1 导入库

import 'package:flutter/material.dart';
import 'package:slider_gradient/slider_gradient.dart';

导入后即可使用 SliderGradient 组件、SliderData 回调数据类、ThumbStyle / SliderStyle / LabelStyle 三个样式类,以及 SliderChangeCallback 回调类型定义。

5.2 基本用法:单值渐变滑块

double _value = 50;

SliderGradient(
  value: _value,
  min: 0,
  max: 100,
  colors: const [Color(0xFF4A90D9), Color(0xFF50E3C2)],
  isShowLabel: true,
  onChange: (SliderData data) {
    debugPrint('value: ${data.value}, color: ${data.color}');
  },
  onChangeEnd: (SliderData data) {
    setState(() {
      _value = data.value!;
    });
  },
)

value 为当前数值(必填),min / max 定义值域范围(默认 0-100)。colors 为渐变色数组,至少 2 个颜色时 thumb 颜色随位置插值。isShowLabel 为 true 时在 thumb 上方显示数值气泡。onChange 为必填回调,拖动或点击轨道时触发,回调参数 SliderData 包含当前数值 value 和 thumb 颜色 color

代码逐段分析:initData 初始化流程
initData 方法在 LayoutBuilderbuilder 回调中调用,获取父容器约束后初始化所有内部状态。首先根据 constraints.maxWidth 设置滑块总宽度 _sliderDefaultWidth。然后从 widget.colors 取首尾颜色作为 _beginColor / _endColor,若 colors 为 null 则取主题色与白色。计算滑块实际可用宽度 _defaultWidth(总宽减去两侧 padding 15)。通过 labelTextHeight 方法使用 TextPainter 测量 label 文本高度。最后调用 _location 方法根据初始 value 计算百分比 _percent,设置 AnimationController.value 和 thumb 颜色。

5.3 Thumb 颜色插值

/// 根据百分比在渐变色数组中插值出 thumb 颜色
Color _lerp(int len, double percent) {
  int _denominator = len - 1;
  int _num = 1;
  while (_num / _denominator < percent) {
    _num++;
  }
  return Color.lerp(widget.colors![_num - 1], widget.colors![_num],
      ((percent - (_num - 1) / _denominator) * _denominator).toDouble())!;
}

_lerp 方法接收颜色数组长度和当前位置百分比,在相邻两个颜色之间通过 Color.lerp 做线性插值。例如三色渐变 [蓝, 绿, 黄],thumb 在 50% 位置时,_denominator 为 2,_num 为 1,在蓝色和绿色之间插值 50%——thumb 显示蓝绿混合色。thumb 在 75% 位置时,_num 为 2,在绿色和黄色之间插值 50%——thumb 显示绿黄混合色。

代码逐段分析:Color.lerp 颜色插值原理
Color.lerp(a, b, t) 是 Flutter 框架提供的颜色线性插值方法,在 ARGB 四个通道上分别做线性插值:result = a + (b - a) * t。例如 Color.lerp(Colors.blue, Colors.green, 0.5) 返回蓝绿各半的混合色。_lerp 方法首先确定当前位置落在哪两个颜色之间(通过 _num / _denominator < percent 循环递增),然后计算在这两个颜色之间的局部插值比例 t,最终调用 Color.lerp 得到 thumb 颜色。这一计算在 Dart 层完成,不依赖任何平台 API。

5.4 纯色背景模式

SliderGradient(
  value: _solidValue,
  min: 0,
  max: 100,
  isGradientBg: false,
  colors: const [Color(0xFF2196F3), Color(0xFFE3F2FD)],
  onChange: (d) => setState(() {
    _solidValue = d.value ?? _solidValue;
    _solidColor = d.color;
  }),
)

isGradientBg 为 false 时,轨道不使用 LinearGradient,而是纯色填充:colors[0] 为主色调(已选择部分),colors[1] 为辅助色(未选择部分)。thumb 颜色固定为主色调 _beginColor,不随位置插值。

代码逐段分析:backgroundWidget 纯色模式
backgroundWidget 方法根据 isGradientBg 选择渲染方式。渐变模式下,BoxDecoration.gradient 设为 LinearGradient(colors: widget.colors!),轨道整体渲染为渐变色。纯色模式下,gradient 为 null,color 设为 _endColor(辅助色),内部通过 Row 放置两个 sliderItem——左侧 sliderItem(_percent) 渲染主色调填充(宽度为 _defaultWidth * _percent),右侧 sliderItem(1 - _percentR) 渲染范围模式下右 thumb 到右端的辅助色填充。

5.5 范围选择模式(双 thumb)

List<double> _rangeValues = [20, 60];

SliderGradient(
  value: _rangeValues.first,
  min: 0,
  max: 100,
  isRange: true,
  values: _rangeValues,
  colors: const [Color(0xFFFF5252), Color(0xFF4CAF50)],
  onChange: (d) => setState(() {
    if (d.values != null && d.values!.length == 2) {
      _rangeValues = d.values!;
      _rangeColorL = d.colors?.first;
      _rangeColorR = d.colors?.last;
    }
  }),
)

isRange 为 true 时进入范围选择模式,必须传入长度为 2 且递增的 values。两个 thumb 分别代表范围左右端,拖动时通过 _rangeLocation 方法自动选择距离更近的 thumb。回调 SliderDatavalues 返回两个数值,colors 返回两个 thumb 的颜色。

代码逐段分析:_rangeLocation 自动 thumb 选择
_rangeLocation 方法决定拖动时移动哪个 thumb。计算拖动位置到左 thumb 的距离 _len 和到右 thumb 的距离 _lenR:若 _lenR > _len,说明离左 thumb 更近,返回 RangeType.left;若两者相等(_percentR == _percent),则看拖动位置在两个 thumb 中点的左侧还是右侧决定。这保证了用户在两个 thumb 重合或接近时仍能准确选择要移动的那一个。

鸿蒙技术点:SingleTickerProviderStateMixin 在鸿蒙上的行为
_SliderGradientState 混入了 SingleTickerProviderStateMixin,为 AnimationController 提供 vsync 信号。在鸿蒙平台上,Flutter Engine 的 C++ 层完整实现了 Ticker 机制——通过 XComponent 的帧回调驱动 Dart 层的 SchedulerBinding,在每次屏幕刷新时触发 AnimationControllertickAnimatedBuilder 监听 AnimationController 的值变化,自动重建子树。这意味着 AnimationController.animateTo(per) 的动画在鸿蒙上与 Android/iOS 行为完全一致——100ms 内从当前值平滑过渡到目标值,thumb 平滑滑动而非跳变。

5.6 手势处理

// 点击轨道:thumb 跳转到点击位置
void _tapDown(TapDownDetails details) {
  double _dx = details.localPosition.dx;
  _thumbLocation(_dx);
  widget.onChange(dataCallback());
}

// 水平拖动:thumb 跟随手指移动
void _horizontalDragUpdate(DragUpdateDetails details) {
  double _dx = details.localPosition.dx;
  _thumbLocation(_dx);
  controller.value += details.primaryDelta! / _defaultWidth;
  widget.onChange(dataCallback());
}

GestureDetector 绑定了 onTapDown(点击跳转)、onTapUp(点击结束回调)、onHorizontalDragStart(拖动开始回调)、onHorizontalDragUpdate(拖动更新)、onHorizontalDragEnd(拖动结束回调)五种手势。_thumbLocation 方法将触摸坐标转换为百分比并更新 thumb 位置和颜色。

代码逐段分析:_thumbLocation 位置计算
_thumbLocation 方法将触摸坐标转换为 thumb 百分比位置。若坐标 <= _sliderDefaultPadding(左侧 padding),百分比设为 0;若 >= _sliderDefaultPadding + _defaultWidth(右端),百分比设为 1;否则计算 (dx - padding) / _defaultWidth 并保留两位小数。在范围模式下,通过 _rangeLocation 判断移动左 thumb 还是右 thumb,分别更新 _percent_percentR。最后调用 controller.animateTo(per) 让 thumb 平滑过渡到目标位置,并通过 _getThumbColor 更新 thumb 颜色。

5.7 自定义样式

SliderGradient(
  value: _customValue,
  min: 0,
  max: 100,
  isShowLabel: true,
  thumbStyle: const ThumbStyle(
    width: 16,
    height: 32,
    radius: 8,
    borderColor: Colors.black12,
  ),
  sliderStyle: const SliderStyle(height: 18, radius: 9),
  colors: const [
    Color(0xFFFF9800),
    Color(0xFFE91E63),
    Color(0xFF9C27B0),
  ],
  onChange: (d) => setState(() {
    _customValue = d.value ?? _customValue;
    _customColor = d.color;
  }),
)

ThumbStyle 控制 thumb 的宽(16)、高(32)、圆角(8)、边框色。SliderStyle 控制轨道的高度(18)和圆角(9)。LabelStyle 控制 label 的填充色、字体色和字体大小。三个样式类覆盖了滑块的全部可定制属性,满足 UI 设计师的精细化需求。

鸿蒙技术点:CustomSingleChildLayout 自定义布局
thumb 的定位使用了 CustomSingleChildLayout + _ModalSliderLayout 委托。_ModalSliderLayout 继承 SingleChildLayoutDelegate,在 getPositionForChild 方法中根据 progress(百分比)和 thumbWidth 计算 thumb 的水平偏移:Offset(size.width * progress - thumbWidth / 2, 0)——百分比乘以轨道宽度再减去 thumb 半宽,使 thumb 中心对齐百分比位置。shouldRelayout 方法在 progress 变化时返回 true,触发重新布局。这一自定义布局逻辑在 Dart 层完成,鸿蒙平台的 Flutter Engine 完整支持 SingleChildLayoutDelegate 的布局协议。

5.8 实战:色彩温度调节器

实际业务中常见的场景是设置页面放一个色彩温度调节器,thumb 颜色随温度从蓝到红渐变。下面是一个可直接使用的组件:

class TemperatureSlider extends StatefulWidget {
  const TemperatureSlider({super.key});

  
  State<TemperatureSlider> createState() => _TemperatureSliderState();
}

class _TemperatureSliderState extends State<TemperatureSlider> {
  double _value = 22.0;

  String get _tempLabel {
    if (_value < 18) return '冷';
    if (_value < 24) return '舒适';
    if (_value < 30) return '温';
    return '热';
  }

  
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            const Text('温度', style: TextStyle(fontSize: 16)),
            Text('${_value.toStringAsFixed(1)}°C  $_tempLabel',
                style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
          ],
        ),
        const SizedBox(height: 8),
        SliderGradient(
          value: _value,
          min: 10,
          max: 35,
          isShowLabel: true,
          colors: const [
            Color(0xFF2196F3),  // 蓝(冷)
            Color(0xFF4CAF50),  // 绿(舒适)
            Color(0xFFFF9800),  // 橙(温)
            Color(0xFFF44336),  // 红(热)
          ],
          onChange: (d) => setState(() {
            _value = d.value ?? _value;
          }),
          onChangeEnd: (d) => setState(() {
            _value = d.value ?? _value;
          }),
        ),
      ],
    );
  }
}

四色渐变 [蓝, 绿, 橙, 红] 直观展示温度从冷到热的过渡,thumb 颜色随位置在四个颜色之间插值。label 实时显示当前温度值,配合 _tempLabel 显示"冷/舒适/温/热"文字提示。该组件可直接嵌入应用设置页面。

六、运行与验证

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

设备项
机型OpenHarmony 真机
系统版本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.example.slider_gradient_example -a EntryAbility

应用启动后,initState 调用 _queryPlatformVersion() 通过 MethodChannel('slider_gradient').invokeMethod('getPlatformVersion') 获取平台版本。页面顶部平台信息卡显示 OpenHarmony 6.1.0.135,证明插件通道正常工作。

在这里插入图片描述

6.2 验证二:四场景滑块交互

# 模拟点击渐变滑块卡片中的 thumb 并拖动
hdc shell uitest uiInput click 600 800

# 模拟拖动到右侧
hdc shell uitest uiInput swipe 600 800 900 800 500

渐变滑块(卡片②)使用三色渐变 [蓝, 绿, 黄]isShowLabel=true。拖动时 thumb 颜色随位置在三色之间实时插值——从蓝色端拖到中间时 thumb 显示蓝绿混合色,拖到黄色端时 thumb 变为黄色。label 气泡实时显示当前数值。操作事件卡记录"渐变滑块:拖动开始"和"渐变滑块:结果 @FF2196F3"。

在这里插入图片描述

6.3 验证三:范围选择双 thumb

# 模拟点击范围滑块右 thumb 并拖动
hdc shell uitest uiInput swipe 700 1000 850 1000 500

范围滑块(卡片③)使用双 thumb,初始值 [20, 60],渐变色 [红, 绿]。拖动时自动选中距离更近的 thumb——点击中间偏右的位置,右 thumb 跟随移动。左 thumb 显示红色,右 thumb 显示绿色。操作事件卡记录"Range 滑块:结果 [20, 85],跨度 65"。

在这里插入图片描述

6.4 验证四:插件注册日志

通过 hdc hilog 抓取运行日志:

hdc shell hilog -r
hdc shell aa start -b com.example.slider_gradient_example -a EntryAbility
hdc shell hilog | grep SliderGradientPlugin

日志输出:

SliderGradientPlugin: onAttachedToEngine
SliderGradientPlugin: getPlatformVersion -> OpenHarmony 6.1.0.135

插件由 GeneratedPluginRegistrant 注册成功,MethodChannel('slider_gradient') 通道就绪,getPlatformVersion 方法返回 "OpenHarmony 6.1.0.135"

在这里插入图片描述

实测结论:

验证点结果
应用启动,Flutter 页面正常渲染平台信息卡与四张滑块卡片通过
纯色滑块拖动,thumb 位置与颜色正确更新通过
渐变滑块拖动,thumb 颜色随位置在三色渐变中实时插值通过
渐变滑块 thumb 上方悬浮 label 实时显示当前数值通过
范围滑块双 thumb 拖动,自动选中距离更近的 thumb通过
自定义样式滑块 thumb 与轨道渲染正确通过
平台信息卡 getPlatformVersion 返回 OpenHarmony 6.1.0.135通过
操作事件卡实时记录拖动事件通过
全程无需申请任何敏感权限通过

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

Example 启动授权 Example 启动授权 Example 启动授权


七、工作原理

整个调用链路如下:

Dart: SliderGradient(value, min, max, colors, onChange, ...)
  → _SliderGradientState (StatefulWidget + SingleTickerProviderStateMixin)
    → LayoutBuilder 获取父容器宽度
      → initData(constraints) 初始化颜色/百分比/label 尺寸
    → GestureDetector (onTapDown / onHorizontalDragUpdate / ...)
      → _thumbLocation(dx) 将触摸坐标转为百分比
        → _rangeLocation(per) 判断移动哪个 thumb(范围模式)
        → controller.animateTo(per)  // AnimationController 平滑过渡
        → _getThumbColor(type)  // Color.lerp 颜色插值
        → widget.onChange(dataCallback())  // 回调 SliderData
    → AnimatedBuilder(animation: controller)
      → Stack
        ├─ backgroundWidget()  // LinearGradient 或纯色填充
        ├─ thumbWidget(true)  // 左 thumb + label
        └─ Offstage(thumbWidget(false))  // 右 thumb(范围模式)

ArkTS: SliderGradientPlugin.onMethodCall("getPlatformVersion")
  → deviceInfo.displayVersion
    → result.success("OpenHarmony 6.1.0.135")

Dart 侧的滑块渲染、手势识别、颜色插值、thumb 布局全部为纯 Dart Widget 实现,不经过任何平台通道。平台侧(OHOS)仅提供 getPlatformVersion 桩实现,通过 MethodChannel('slider_gradient') 暴露。

鸿蒙技术点:GestureDetector 在鸿蒙上的手势识别
GestureDetector 是 Flutter 框架层提供的手势识别组件,通过 GestureArena(手势竞技场)机制处理多种手势的竞争。在鸿蒙平台上,Flutter Engine 通过 XComponent 的触摸事件回调将鸿蒙系统的 TouchEvent 转换为 Flutter 的 PointerEvent,注入 Dart 层的 GestureBindingTapGestureRecognizerHorizontalDragGestureRecognizer 在手势竞技场中竞争——点击时 Tap 胜出触发 _tapDown / _tapUp,拖动时 HorizontalDrag 胜出触发 _horizontalDragStart / _horizontalDragUpdate / _horizontalDragEnd。整个手势识别流程在鸿蒙上与 Android/iOS 行为完全一致。

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

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

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

export default class SliderGradientPlugin implements FlutterPlugin, MethodCallHandler {
  private static readonly TAG: string = 'SliderGradientPlugin';
  private static readonly DOMAIN: number = 0xFF00;
  private channel: MethodChannel | null = null;

  getUniqueClassName(): string {
    return 'SliderGradientPlugin';
  }

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    this.channel = new MethodChannel(binding.getBinaryMessenger(), 'slider_gradient');
    this.channel.setMethodCallHandler(this);
    hilog.info(SliderGradientPlugin.DOMAIN, SliderGradientPlugin.TAG, 'onAttachedToEngine');
  }

  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    if (this.channel != null) {
      this.channel.setMethodCallHandler(null);
    }
    this.channel = null;
  }

  onMethodCall(call: MethodCall, result: MethodResult): void {
    if (call.method == 'getPlatformVersion') {
      const version = `OpenHarmony ${deviceInfo.displayVersion}`;
      result.success(version);
    } else {
      result.notImplemented();
    }
  }
}

代码逐段分析:deviceInfo.displayVersion
deviceInfo.displayVersion 返回鸿蒙系统的发行版本号(如 6.1.0.135),属于 SystemCapability.Startup.SystemInfo 基础能力,无需任何权限。插件将其拼接为 "OpenHarmony 6.1.0.135" 格式返回,与 Android 返回 "Android 13.0"、iOS 返回 "iOS 17.0" 的语义对齐——系统发行版本号。hilog 是鸿蒙的日志系统,hilog.info(DOMAIN, TAG, message) 将日志写入系统日志缓冲区,可通过 hdc hilog 抓取。

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

鸿蒙技术点:TextPainter locale 适配
labelTextHeight 方法使用 TextPainter 测量 label 文本高度。代码中有一处关键注释:locale: Localizations.localeOf(context)——指定 locale 是因为华为设备如果不指定 locale,TextPainter 算出的文字高度比系统实际渲染偏小。这是鸿蒙平台 Flutter 引擎在文字测量上的一个细微差异:默认 locale 下 TextPainter.layout 可能使用不同的字体度量标准,导致测量高度与实际渲染高度不一致。通过显式指定 Localizations.localeOf(context) 作为 locale,确保测量结果与实际渲染一致。这一适配是 slider_gradient 鸿蒙版特有的修复。

八、常见问题

Q1:thumb 拖动时颜色不变化?

检查 colors 参数。thumb 颜色插值需要 colors 数组至少包含 2 个颜色。若 colors 为 null,默认使用主题色与白色,thumb 固定为主色调不随位置插值。若 isGradientBg 为 false(纯色模式),thumb 颜色也固定为主色调 colors[0],不随位置插值——只有渐变模式下 thumb 颜色才随位置变化。

Q2:范围模式下两个 thumb 重合时拖不动?

_rangeLocation 方法在两个 thumb 重合(_percent == _percentR)时,通过判断拖动位置在重合点的左侧还是右侧来决定移动哪个 thumb——左侧拖动移动左 thumb,右侧拖动移动右 thumb。若仍有问题,检查 values 是否满足 values.first <= values.last 的约束(构造器中有 assert 校验)。

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

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

Q4:label 文字显示位置偏移?

labelTextHeight 方法在测量 label 文本高度时需要指定 locale 参数。鸿蒙设备上若不指定 locale,TextPainter 算出的文字高度比系统实际渲染偏小,导致 label 气泡定位偏移。本库已通过 locale: Localizations.localeOf(context) 修复此问题。若在自定义组件中遇到类似问题,可参考此方案。

Q5:AnimationController 动画卡顿?

AnimationControllervsync 来自 SingleTickerProviderStateMixin,在鸿蒙上通过 XComponent 的帧回调驱动。若动画卡顿,检查是否有耗时操作阻塞了 UI 线程——SliderGradient 的所有计算(颜色插值、位置计算、布局)都是轻量操作,不会造成卡顿。若仍有问题,检查设备的屏幕刷新率设置。

Q6:原库无法在 Flutter 3.x 编译?

原库(pub.dev 0.2.0)为 pre-null-safety 代码,在 Dart 3(Flutter 3.x)下无法编译。本鸿蒙适配版已完成 null-safety 迁移,公开 API 的名称、语义与默认值保持不变。Flutter 2.12 以下环境请继续使用原库 pub.dev 版本。

九、结语

回顾一下:在 pubspec.yaml 中以 git TAG 引入 slider_gradient,构造 SliderGradient(value, min, max, colors, onChange) 即可在鸿蒙 App 内渲染渐变滑块。渐变模式下 thumb 颜色随位置在 colors 数组中通过 Color.lerp 实时插值,纯色模式下 thumb 固定为主色调。isRange=true 启用双 thumb 范围选择,拖动时自动选中距离更近的 thumb。ThumbStyle / SliderStyle / LabelStyle 三个样式类覆盖全部可定制属性。组件全部为纯 Dart 实现——LayoutBuilder 获取宽度、GestureDetector 识别手势、AnimationController 驱动动画、Color.lerp 插值颜色、CustomSingleChildLayout 定位 thumb,在 Android、iOS、鸿蒙各平台行为完全一致。已在 OpenHarmony-6.1.1.120(API 24)真机完整实测,纯色滑块、渐变滑块、范围滑块、自定义样式滑块四种场景全部通过。

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

相关链接

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


附:slider_gradient 核心能力对照表

能力维度实现方式鸿蒙表现跨平台一致性
渐变轨道LinearGradient + BoxDecoration渐变渲染正常全平台完全一致
纯色轨道Row + ClipRRect 双色填充纯色渲染正常全平台完全一致
Thumb 颜色插值Color.lerp 在渐变色数组中插值插值结果正确全平台完全一致
单值模式单 thumb + _percent拖动/点击正常全平台完全一致
范围模式双 thumb + _rangeLocation 自动选择双 thumb 交互正常全平台完全一致
点击跳转GestureDetector.onTapDown点击跳转正常全平台完全一致
水平拖动GestureDetector.onHorizontalDragUpdate拖动跟随正常全平台完全一致
平滑动画AnimationController.animateTo100ms 平滑过渡全平台完全一致
悬浮 labelPositioned + Offstage + Transformlabel 显示正常全平台完全一致
自定义布局CustomSingleChildLayout + _ModalSliderLayoutthumb 定位正确全平台完全一致
文本测量TextPainter(指定 locale)测量结果正确鸿蒙需指定 locale
样式自定义ThumbStyle / SliderStyle / LabelStyle样式渲染正常全平台完全一致
平台版本MethodChannel('slider_gradient')deviceInfo.displayVersion格式对齐各平台
权限要求无敏感权限全平台均无
Logo

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

更多推荐