滑动控件:自定义的评价组件(星星)

  1. item.onTap
  2. for代码:i是固定值
  3. 效果
import 'package:ducafe_ui_core/ducafe_ui_core.dart';
import 'package:flutter/material.dart';

import '../index.dart';

/// 星级列表组件
class StarsListWidget extends StatelessWidget {
  /// 点击事件
  final Function(int value)? onTap;

  /// 图标 data
  final IconData? iconData;

  /// 星级数量
  final int starNum;

  /// 选中的星级
  final int value;

  /// 星级大小
  final double? size;

  // 颜色
  final Color? color;

  // 选中颜色
  final Color? selectedColor;

  // 元素间距
  final double? spacing;

  // 行间距
  final double? runSpacing;

  // 构造
  const StarsListWidget({
    super.key,
    this.onTap,
    this.size = 24,
    this.iconData,
    this.color,
    this.selectedColor,
    this.spacing = 5,
    this.runSpacing = 5,
    this.starNum = 5,
    this.value = 0,
  });

  @override
  Widget build(BuildContext context) {
    Color itemColor = color ?? context.colors.scheme.surfaceContainerHighest;
    Color itemSelectedColor = selectedColor ?? context.colors.scheme.primary;
    List<Widget> ws = [];
    for (var i = 1; i <= starNum; i++) {
      Widget item = IconWidget.icon(
        iconData ?? Icons.star,
        size: size ?? 12,
        color: i <= value ? itemSelectedColor : itemColor,
      );
      // 绑定点击事件
      item = item.onTap(() {
        // 如果当前值是1,并且点击的也是1,则变成0
        if (value == 1 && i == value) {
          onTap?.call(0);
        } else {
          onTap?.call(i);
        }
      });
      ws.add(item);
    }

    return ws.toWrap(
      spacing: spacing ?? AppSpace.listItem,
      runSpacing: runSpacing ?? AppSpace.listRow,
    );
  }
}

item.onTap

  // 绑定点击事件
  item = item.onTap(() {....});

上面其实是调用自己的扩展方法,创建点击事件

  /// 手势
  Widget onTap(
    GestureTapCallback? onTap, {
    Key? key,
    HitTestBehavior? behavior,
    bool excludeFromSemantics = false,
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
  }) =>
      GestureDetector(
        key: key,
        onTap: onTap, // 在这里绑定事件
        behavior: behavior ?? HitTestBehavior.opaque,
        excludeFromSemantics: excludeFromSemantics,
        dragStartBehavior: dragStartBehavior,
        child: this, // 把原来的widget放进去
      );

for代码:i是固定值

List<Widget> ws = [];
for (var i = 1; i <= starNum; i++) {
  Widget item = IconWidget.icon(
    iconData ?? Icons.star,
    size: size ?? 12,
    color: i <= value ? itemSelectedColor : itemColor,
  );
  // 绑定点击事件
  item = item.onTap(() {
    // 如果当前值是1,并且点击的也是1,则变成0
    if (value == 1 && i == value) {
      onTap?.call(0);
    } else {
      onTap?.call(i);
    }
  });
  ws.add(item);
}

上面代码相当于以下代码:

List<Widget> ws = [
  // 第 1 颗星
  IconWidget.icon(
    iconData ?? Icons.star,
    size: size ?? 12,
    color: 1 <= value ? itemSelectedColor : itemColor,
  ).onTap(() {
    if (value == 1 && 1 == value) {
      onTap?.call(0);
    } else {
      onTap?.call(1);
    }
  }),

  // 第 2 颗星
  IconWidget.icon(
    iconData ?? Icons.star,
    size: size ?? 12,
    color: 2 <= value ? itemSelectedColor : itemColor,
  ).onTap(() {
    if (value == 1 && 2 == value) {
      onTap?.call(0);
    } else {
      onTap?.call(2);
    }
  }),

  // 第 3 颗星
  IconWidget.icon(
    iconData ?? Icons.star,
    size: size ?? 12,
    color: 3 <= value ? itemSelectedColor : itemColor,
  ).onTap(() {
    if (value == 1 && 3 == value) {
      onTap?.call(0);
    } else {
      onTap?.call(3);
    }
  }),

  // 第 4 颗星
  IconWidget.icon(
    iconData ?? Icons.star,
    size: size ?? 12,
    color: 4 <= value ? itemSelectedColor : itemColor,
  ).onTap(() {
    if (value == 1 && 4 == value) {
      onTap?.call(0);
    } else {
      onTap?.call(4);
    }
  }),

  // 第 5 颗星
  IconWidget.icon(
    iconData ?? Icons.star,
    size: size ?? 12,
    color: 5 <= value ? itemSelectedColor : itemColor,
  ).onTap(() {
    if (value == 1 && 5 == value) {
      onTap?.call(0);
    } else {
      onTap?.call(5);
    }
  }),
];

实际上,生成了5个星星,每一个星星有一个绑定函数。

value == 1 并且 i == 1 时,说明 当前只点亮了第 1 颗星,而且又点了第 1 颗星

这种情况下,代码会执行 onTap?.call(0);,也就是把评分重置为 0 颗星

如果我初始化的value不等于1,评分就不会为0

效果

(value == 1 && i == 1)这个判断实现的效果是:第二次点击一颗星会取消所有星星。

下面是绑定的点击函数

// 星级选中
void onStarTap(int value) {
  starValue = value;
  update(["filter_stars"]);
}

以下是绑定逻辑的代码

if (value == 1 && i == 1) {
  onTap?.call(0);
} else {
  onTap?.call(i);
}

完整流程可以这样理解:

  1. 初始状态starValue = 0,界面上没有星星被点亮。
  2. 第一次点击第 1 颗星
    • 此时 value = 0i = 1,不满足 value == 1 && i == 1
    • 执行 onTap?.call(1)starValue 变为 1
    • GetBuilder 触发重建 → 显示 1 颗星
  3. 第二次再点击第 1 颗星
    • 此时 value = 1i = 1,满足 value == 1 && i == 1
    • 执行 onTap?.call(0)starValue 变为 0
    • GetBuilder 触发重建 → 所有星星熄灭

×

喜欢就点赞,疼爱就打赏