Flutter与OpenHarmony打卡滑动开关组件

前言

滑动开关是移动应用中常见的二态选择组件,用于开启或关闭某项功能。在打卡工具类应用中,滑动开关可以用于控制提醒开关、深色模式、声音反馈等设置项。本文将详细介绍如何在Flutter和OpenHarmony平台上实现美观且交互流畅的滑动开关组件。

滑动开关的设计需要考虑视觉状态、动画效果和触摸反馈。一个优秀的开关组件应该有清晰的开关状态区分,平滑的切换动画,以及良好的触摸响应。我们将实现一个支持自定义样式的滑动开关组件。

Flutter滑动开关实现

首先创建自定义滑动开关:

dart 复制代码
class CustomSwitch extends StatefulWidget {
  final bool value;
  final ValueChanged<bool> onChanged;
  final Color? activeColor;
  final Color? inactiveColor;
  final double width;
  final double height;

  const CustomSwitch({
    Key? key,
    required this.value,
    required this.onChanged,
    this.activeColor,
    this.inactiveColor,
    this.width = 50,
    this.height = 28,
  }) : super(key: key);

  @override
  State<CustomSwitch> createState() => _CustomSwitchState();
}

CustomSwitch提供了丰富的自定义选项。value是当前开关状态,onChanged是状态变化回调。activeColor和inactiveColor分别设置开启和关闭状态的颜色。width和height控制开关的尺寸,默认值符合常见的开关比例。

实现开关动画和交互:

dart 复制代码
class _CustomSwitchState extends State<CustomSwitch>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: 200),
      vsync: this,
    );
    _animation = CurvedAnimation(parent: _controller, curve: Curves.easeInOut);
    if (widget.value) _controller.value = 1.0;
  }

  @override
  void didUpdateWidget(CustomSwitch oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.value != oldWidget.value) {
      widget.value ? _controller.forward() : _controller.reverse();
    }
  }

  void _handleTap() {
    widget.onChanged(!widget.value);
  }

  @override
  Widget build(BuildContext context) {
    final activeColor = widget.activeColor ?? Colors.green;
    final inactiveColor = widget.inactiveColor ?? Colors.grey.shade300;
    final thumbSize = widget.height - 4;

    return GestureDetector(
      onTap: _handleTap,
      child: AnimatedBuilder(
        animation: _animation,
        builder: (context, child) {
          return Container(
            width: widget.width,
            height: widget.height,
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(widget.height / 2),
              color: Color.lerp(inactiveColor, activeColor, _animation.value),
            ),
            child: Stack(
              children: [
                Positioned(
                  left: 2 + (widget.width - thumbSize - 4) * _animation.value,
                  top: 2,
                  child: Container(
                    width: thumbSize,
                    height: thumbSize,
                    decoration: BoxDecoration(
                      shape: BoxShape.circle,
                      color: Colors.white,
                      boxShadow: [
                        BoxShadow(
                          color: Colors.black.withOpacity(0.2),
                          blurRadius: 4,
                          offset: const Offset(0, 2),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          );
        },
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

开关使用AnimationController控制滑块位置和背景颜色的动画。Color.lerp在两种颜色之间进行插值,实现平滑的颜色过渡。滑块位置通过动画值计算,从左侧移动到右侧。白色圆形滑块带有阴影效果,增加立体感。didUpdateWidget确保外部状态变化时也能触发动画。

OpenHarmony滑动开关实现

在鸿蒙系统中创建滑动开关:

typescript 复制代码
@Component
struct CustomSwitch {
  @Prop isOn: boolean = false
  @Prop activeColor: string = '#4CAF50'
  @Prop inactiveColor: string = '#E0E0E0'
  @Prop width: number = 50
  @Prop height: number = 28
  private onChange: (value: boolean) => void = () => {}
  @State thumbPosition: number = 0

  aboutToAppear() {
    this.thumbPosition = this.isOn ? this.width - this.height + 2 : 2
  }

  handleTap() {
    const newValue = !this.isOn
    animateTo({ duration: 200, curve: Curve.EaseInOut }, () => {
      this.thumbPosition = newValue ? this.width - this.height + 2 : 2
    })
    this.onChange(newValue)
  }

  build() {
    Stack() {
      // 背景轨道
      Column()
        .width(this.width)
        .height(this.height)
        .borderRadius(this.height / 2)
        .backgroundColor(this.isOn ? this.activeColor : this.inactiveColor)
      
      // 滑块
      Column()
        .width(this.height - 4)
        .height(this.height - 4)
        .borderRadius((this.height - 4) / 2)
        .backgroundColor(Color.White)
        .shadow({ radius: 4, color: 'rgba(0,0,0,0.2)', offsetY: 2 })
        .position({ x: this.thumbPosition, y: 2 })
    }
    .width(this.width)
    .height(this.height)
    .onClick(() => this.handleTap())
  }
}

鸿蒙的滑动开关使用Stack层叠背景轨道和滑块。animateTo函数为滑块位置变化添加动画效果。position属性精确控制滑块位置,通过thumbPosition状态驱动动画。backgroundColor根据isOn状态切换颜色,实现开关状态的视觉反馈。

带标签的开关

实现带文字标签的开关组件:

dart 复制代码
class LabeledSwitch extends StatelessWidget {
  final String label;
  final String? description;
  final bool value;
  final ValueChanged<bool> onChanged;
  final IconData? icon;

  const LabeledSwitch({
    Key? key,
    required this.label,
    this.description,
    required this.value,
    required this.onChanged,
    this.icon,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: () => onChanged(!value),
      child: Padding(
        padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
        child: Row(
          children: [
            if (icon != null) ...[
              Icon(icon, color: Colors.grey.shade600),
              const SizedBox(width: 16),
            ],
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(label, style: const TextStyle(fontSize: 16)),
                  if (description != null)
                    Text(
                      description!,
                      style: TextStyle(fontSize: 13, color: Colors.grey.shade600),
                    ),
                ],
              ),
            ),
            CustomSwitch(value: value, onChanged: onChanged),
          ],
        ),
      ),
    );
  }
}

LabeledSwitch将开关与标签组合,适用于设置页面的开关项。InkWell让整行都可点击,提升操作便捷性。可选的icon在左侧显示图标,description在标签下方显示说明文字。这种设计是设置页面的标准布局模式。

设置页面开关列表

实现打卡应用的设置开关列表:

dart 复制代码
class SettingsSwitchList extends StatelessWidget {
  final Map<String, bool> settings;
  final Function(String, bool) onSettingChanged;

  static const settingsConfig = [
    {'key': 'reminder', 'label': '打卡提醒', 'description': '每天定时提醒打卡', 'icon': Icons.notifications},
    {'key': 'sound', 'label': '声音反馈', 'description': '打卡成功时播放声音', 'icon': Icons.volume_up},
    {'key': 'vibration', 'label': '震动反馈', 'description': '打卡成功时震动', 'icon': Icons.vibration},
    {'key': 'darkMode', 'label': '深色模式', 'description': '使用深色主题', 'icon': Icons.dark_mode},
    {'key': 'autoSync', 'label': '自动同步', 'description': '自动同步打卡数据到云端', 'icon': Icons.sync},
  ];

  const SettingsSwitchList({
    Key? key,
    required this.settings,
    required this.onSettingChanged,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: settingsConfig.map((config) {
        final key = config['key'] as String;
        return LabeledSwitch(
          label: config['label'] as String,
          description: config['description'] as String,
          icon: config['icon'] as IconData,
          value: settings[key] ?? false,
          onChanged: (value) => onSettingChanged(key, value),
        );
      }).toList(),
    );
  }
}

SettingsSwitchList是打卡应用设置页面的开关列表组件。settingsConfig定义了所有设置项的配置,包括键名、标签、描述和图标。settings Map存储当前的设置状态,onSettingChanged回调处理设置变更。这种配置驱动的设计让添加新设置项变得简单。

OpenHarmony设置开关

鸿蒙中实现设置开关列表:

typescript 复制代码
@Component
struct SettingsSwitchList {
  @Link settings: Record<string, boolean>

  settingsConfig: Array<{key: string, label: string, description: string, icon: Resource}> = [
    { key: 'reminder', label: '打卡提醒', description: '每天定时提醒打卡', icon: $r('app.media.notification') },
    { key: 'sound', label: '声音反馈', description: '打卡成功时播放声音', icon: $r('app.media.volume') },
    { key: 'vibration', label: '震动反馈', description: '打卡成功时震动', icon: $r('app.media.vibration') },
  ]

  build() {
    Column() {
      ForEach(this.settingsConfig, (config) => {
        this.SettingItem(config)
      })
    }
  }

  @Builder
  SettingItem(config: {key: string, label: string, description: string, icon: Resource}) {
    Row() {
      Image(config.icon)
        .width(24)
        .height(24)
        .fillColor('#666666')
        .margin({ right: 16 })
      
      Column() {
        Text(config.label)
          .fontSize(16)
        Text(config.description)
          .fontSize(13)
          .fontColor('#999999')
          .margin({ top: 2 })
      }
      .layoutWeight(1)
      .alignItems(HorizontalAlign.Start)
      
      Toggle({ type: ToggleType.Switch, isOn: this.settings[config.key] ?? false })
        .selectedColor('#4CAF50')
        .onChange((isOn: boolean) => {
          this.settings[config.key] = isOn
        })
    }
    .width('100%')
    .padding({ left: 16, right: 16, top: 12, bottom: 12 })
    .onClick(() => {
      this.settings[config.key] = !(this.settings[config.key] ?? false)
    })
  }
}

鸿蒙使用Toggle组件实现开关功能,type设为Switch显示滑动开关样式。@Link装饰器实现双向绑定,开关状态变化会自动同步到父组件。整行可点击,提升操作便捷性。ForEach遍历配置数组生成设置项列表。

总结

本文详细介绍了在Flutter和OpenHarmony平台上实现滑动开关组件的完整方案。滑动开关通过平滑的动画效果和清晰的状态区分,为用户提供了直观的开关交互。带标签的开关组件适用于设置页面,配置驱动的设计让开关列表的管理更加便捷。两个平台的实现都注重动画流畅性和触摸响应,确保开关操作体验优秀。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
liulian09167 小时前
Flutter for OpenHarmony 跨平台开发:单位转换功能实战指南
flutter
千码君20168 小时前
Trae:一些关于flutter和 go前后端开发构建的分享
android·flutter·gradle·android-studio·trae·vibe code
maaath10 小时前
【maaath】Flutter for OpenHarmony 手表配饰应用实战开发
flutter·华为·harmonyos
maaath10 小时前
【maaath】Flutter for OpenHarmony 跨平台计算器应用开发实践
flutter·华为·harmonyos
maaath15 小时前
【maaath】Flutter for OpenHarmony 闹钟时钟应用开发实战
flutter·华为·harmonyos
maaath16 小时前
【maaath】Flutter for OpenHarmony 短信管理应用实战
flutter·华为·harmonyos
maaath17 小时前
【maaath】Flutter for OpenHarmony打造跨平台便签备忘录应用
flutter·华为·harmonyos
千码君201617 小时前
flutter:与Android Studio模拟器的调试分享
android·flutter
xmdy586617 小时前
Flutter+开源鸿蒙实战|智联邻里Day8 Lottie动画集成+url_launcher跳转拨号+个人中心完善+全局UI统一
flutter·开源·harmonyos
liulian09161 天前
Flutter for OpenHarmony 跨平台开发:颜色选择器功能实战指南
flutter