Widgets

Button

介绍

Nylo Website 提供了一个 Button 类,包含八种开箱即用的按钮样式。每个按钮都内置支持:

  • 异步加载状态 -- 从 onPressed 返回一个 Future,按钮会自动显示加载指示器
  • 动画样式 -- 从 clickable、bounce、pulse、squeeze、jelly、shine、ripple、morph 和 shake 效果中选择
  • 水波纹样式 -- 添加 ripple、highlight、glow 或 ink 触摸反馈
  • 表单提交 -- 将按钮直接连接到 NyFormData 实例

您可以在 lib/resources/widgets/buttons/buttons.dart 中找到应用的按钮定义。该文件包含一个 Button 类,其中包含每种按钮类型的静态方法,方便您自定义项目的默认值。

基本用法

在您的 widget 中任意位置使用 Button 类。以下是页面中的简单示例:

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: SafeArea(
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          children: [
            Button.primary(
              text: "Sign Up",
              onPressed: () {
                routeTo(SignUpPage.path);
              },
            ),

            SizedBox(height: 12),

            Button.secondary(
              text: "Learn More",
              onPressed: () {
                routeTo(AboutPage.path);
              },
            ),

            SizedBox(height: 12),

            Button.outlined(
              text: "Cancel",
              onPressed: () {
                Navigator.pop(context);
              },
            ),
          ],
        ),
      ),
    ),
  );
}

每种按钮类型都遵循相同的模式 -- 传递一个 text 标签和一个 onPressed 回调。

可用按钮类型

所有按钮都通过 Button 类使用静态方法访问。

Primary

带有阴影的填充按钮,使用您主题的主色。最适合主要的行动号召元素。

Button.primary(
  text: "Sign Up",
  onPressed: () {
    // Handle press
  },
)

Secondary

带有较柔和表面颜色和微妙阴影的填充按钮。适合与主按钮并列的次要操作。

Button.secondary(
  text: "Learn More",
  onPressed: () {
    // Handle press
  },
)

Outlined

带有边框的透明按钮。适用于不太突出的操作或取消按钮。

Button.outlined(
  text: "Cancel",
  onPressed: () {
    // Handle press
  },
)

您可以自定义边框和文字颜色:

Button.outlined(
  text: "Custom Outline",
  borderColor: Colors.red,
  textColor: Colors.red,
  onPressed: () {},
)

Text Only

没有背景或边框的极简按钮。适合内联操作或链接。

Button.textOnly(
  text: "Skip",
  onPressed: () {
    // Handle press
  },
)

您可以自定义文字颜色:

Button.textOnly(
  text: "View Details",
  textColor: Colors.blue,
  onPressed: () {},
)

Icon

在文字旁边显示图标的填充按钮。图标默认显示在文字之前。

Button.icon(
  text: "Add to Cart",
  icon: Icon(Icons.shopping_cart),
  onPressed: () {
    // Handle press
  },
)

您可以自定义背景颜色:

Button.icon(
  text: "Download",
  icon: Icon(Icons.download),
  color: Colors.green,
  onPressed: () {},
)

Gradient

具有线性渐变背景的按钮。默认使用您主题的主色和第三色。

Button.gradient(
  text: "Get Started",
  onPressed: () {
    // Handle press
  },
)

您可以提供自定义渐变颜色:

Button.gradient(
  text: "Premium",
  gradientColors: [Colors.purple, Colors.pink],
  onPressed: () {},
)

Rounded

具有完全圆角的药丸形按钮。边框半径默认为按钮高度的一半。

Button.rounded(
  text: "Continue",
  onPressed: () {
    // Handle press
  },
)

您可以自定义背景颜色和边框半径:

Button.rounded(
  text: "Apply",
  backgroundColor: Colors.teal,
  borderRadius: BorderRadius.circular(20),
  onPressed: () {},
)

Transparency

具有背景模糊效果的磨砂玻璃风格按钮。放置在图片或彩色背景上方时效果很好。

Button.transparency(
  text: "Explore",
  onPressed: () {
    // Handle press
  },
)

您可以自定义文字颜色:

Button.transparency(
  text: "View More",
  color: Colors.white,
  onPressed: () {},
)

异步加载状态

Nylo Website 按钮最强大的功能之一是自动加载状态管理。当您的 onPressed 回调返回一个 Future 时,按钮将自动显示加载指示器并禁用交互,直到操作完成。

Button.primary(
  text: "Submit",
  onPressed: () async {
    await sleep(3); // Simulates a 3 second async task
  },
)

在异步操作运行期间,按钮将显示骨架加载效果(默认)。一旦 Future 完成,按钮将恢复到正常状态。

这适用于任何异步操作 -- API 调用、数据库写入、文件上传或任何返回 Future 的操作:

Button.primary(
  text: "Save Profile",
  onPressed: () async {
    await api<ApiService>((request) =>
      request.updateProfile(name: "John", email: "john@example.com")
    );
    showToastSuccess(description: "Profile saved!");
  },
)
Button.secondary(
  text: "Sync Data",
  onPressed: () async {
    await fetchAndStoreData();
    await clearOldCache();
  },
)

无需管理 isLoading 状态变量、调用 setState 或将任何内容包装在 StatefulWidget 中 -- Nylo Website 为您处理一切。

工作原理

当按钮检测到 onPressed 返回一个 Future 时,它使用 lockRelease 机制来:

  1. 显示加载指示器(由 LoadingStyle 控制)
  2. 禁用按钮以防止重复点击
  3. 等待 Future 完成
  4. 将按钮恢复到正常状态

动画样式

按钮通过 ButtonAnimationStyle 支持按压动画。这些动画在用户与按钮交互时提供视觉反馈。您可以在 lib/resources/widgets/buttons/buttons.dart 中自定义按钮时设置动画样式。

Clickable

Duolingo 风格的 3D 按压效果。按钮在按下时向下移动,释放时弹回。最适合主要操作和游戏风格的用户体验。

animationStyle: ButtonAnimationStyle.clickable()

微调效果:

ButtonAnimationStyle.clickable(
  translateY: 6.0,        // How far the button moves down (default: 4.0)
  shadowOffset: 6.0,      // Shadow depth (default: 4.0)
  duration: Duration(milliseconds: 100),
  enableHapticFeedback: true,
)

Bounce

按下时缩小按钮,释放时弹回。最适合添加到购物车、点赞和收藏按钮。

animationStyle: ButtonAnimationStyle.bounce()

微调效果:

ButtonAnimationStyle.bounce(
  scaleMin: 0.90,         // Minimum scale on press (default: 0.92)
  duration: Duration(milliseconds: 150),
  curve: Curves.easeOutBack,
  enableHapticFeedback: true,
)

Pulse

按住按钮时的微妙持续缩放脉冲。最适合长按操作或吸引注意力。

animationStyle: ButtonAnimationStyle.pulse()

微调效果:

ButtonAnimationStyle.pulse(
  pulseScale: 1.08,       // Max scale during pulse (default: 1.05)
  duration: Duration(milliseconds: 800),
  curve: Curves.easeInOut,
)

Squeeze

按下时水平压缩按钮并垂直扩展。最适合有趣和交互式的 UI。

animationStyle: ButtonAnimationStyle.squeeze()

微调效果:

ButtonAnimationStyle.squeeze(
  squeezeX: 0.93,         // Horizontal scale (default: 0.95)
  squeezeY: 1.07,         // Vertical scale (default: 1.05)
  duration: Duration(milliseconds: 120),
  enableHapticFeedback: true,
)

Jelly

摇晃的弹性变形效果。最适合有趣的、休闲的或娱乐应用。

animationStyle: ButtonAnimationStyle.jelly()

微调效果:

ButtonAnimationStyle.jelly(
  jellyStrength: 0.2,     // Wobble intensity (default: 0.15)
  duration: Duration(milliseconds: 300),
  curve: Curves.elasticOut,
  enableHapticFeedback: true,
)

Shine

按下时在按钮上扫过的光泽高光。最适合高级功能或您想要引起注意的 CTA。

animationStyle: ButtonAnimationStyle.shine()

微调效果:

ButtonAnimationStyle.shine(
  shineColor: Colors.white,  // Color of the shine streak (default: white)
  shineWidth: 0.4,           // Width of the shine band (default: 0.3)
  duration: Duration(milliseconds: 600),
)

Ripple

从触摸点扩展的增强涟漪效果。最适合 Material Design 强调。

animationStyle: ButtonAnimationStyle.ripple()

微调效果:

ButtonAnimationStyle.ripple(
  rippleScale: 2.5,       // How far the ripple expands (default: 2.0)
  duration: Duration(milliseconds: 400),
  curve: Curves.easeOut,
  enableHapticFeedback: true,
)

Morph

按下时按钮的边框半径增大,产生变形效果。最适合微妙、优雅的反馈。

animationStyle: ButtonAnimationStyle.morph()

微调效果:

ButtonAnimationStyle.morph(
  morphRadius: 30.0,      // Target border radius on press (default: 24.0)
  duration: Duration(milliseconds: 150),
  curve: Curves.easeInOut,
)

Shake

水平抖动动画。最适合错误状态或无效操作 -- 抖动按钮以表示出了问题。

animationStyle: ButtonAnimationStyle.shake()

微调效果:

ButtonAnimationStyle.shake(
  shakeOffset: 10.0,      // Horizontal displacement (default: 8.0)
  shakeCount: 4,          // Number of shakes (default: 3)
  duration: Duration(milliseconds: 400),
  enableHapticFeedback: true,
)

禁用动画

使用不带动画的按钮:

animationStyle: ButtonAnimationStyle.none()

更改默认动画

要更改按钮类型的默认动画,请修改您的 lib/resources/widgets/buttons/buttons.dart 文件:

class Button {
  static Widget primary({
    required String text,
    VoidCallback? onPressed,
    ...
  }) {
    return PrimaryButton(
      text: text,
      onPressed: onPressed,
      animationStyle: ButtonAnimationStyle.bounce(), // Change the default
    );
  }
}

水波纹样式

水波纹效果在按钮上提供视觉触摸反馈。通过 ButtonSplashStyle 进行配置。水波纹样式可以与动画样式组合以实现分层反馈。

可用水波纹样式

水波纹 工厂方法 描述
Ripple ButtonSplashStyle.ripple() 从触摸点开始的标准 Material 涟漪
Highlight ButtonSplashStyle.highlight() 没有涟漪动画的微妙高光
Glow ButtonSplashStyle.glow() 从触摸点辐射的柔和光晕
Ink ButtonSplashStyle.ink() 快速墨水飞溅,更快更灵敏
None ButtonSplashStyle.none() 无水波纹效果
Custom ButtonSplashStyle.custom() 完全控制水波纹工厂方法

示例

class Button {
  static Widget outlined({
    required String text,
    VoidCallback? onPressed,
    ...
  }) {
    return OutlinedButton(
      text: text,
      onPressed: onPressed,
      splashStyle: ButtonSplashStyle.ripple(),
      animationStyle: ButtonAnimationStyle.clickable(),
    );
  }
}

您可以自定义水波纹颜色和不透明度:

ButtonSplashStyle.ripple(
  splashColor: Colors.blue,
  highlightColor: Colors.blue,
  splashOpacity: 0.2,
  highlightOpacity: 0.1,
)

加载样式

异步操作期间显示的加载指示器由 LoadingStyle 控制。您可以在 buttons 文件中按按钮类型设置它。

Skeletonizer(默认)

在按钮上显示闪烁骨架效果:

loadingStyle: LoadingStyle.skeletonizer()

Normal

显示加载 widget(默认为应用加载器):

loadingStyle: LoadingStyle.normal(
  child: Text("Please wait..."),
)

None

保持按钮可见但在加载期间禁用交互:

loadingStyle: LoadingStyle.none()

表单提交

所有按钮都支持 submitForm 参数,它将按钮连接到 NyForm。点击时,按钮将验证表单并使用表单数据调用您的成功处理程序。

Button.primary(
  text: "Submit",
  submitForm: (LoginForm(), (data) {
    // data contains the validated form fields
    print(data);
  }),
  onFailure: (error) {
    // Handle validation errors
    print(error);
  },
)

submitForm 参数接受一个包含两个值的记录:

  1. 一个 NyFormData 实例(或作为 String 的表单名称)
  2. 一个接收验证数据的回调

默认情况下,showToastErrortrue,当表单验证失败时会显示 toast 通知。设置为 false 以静默处理错误:

Button.primary(
  text: "Login",
  submitForm: (LoginForm(), (data) async {
    await api<AuthApiService>((request) => request.login(data));
  }),
  showToastError: false,
  onFailure: (error) {
    // Custom error handling
  },
)

submitForm 回调返回一个 Future 时,按钮将自动显示加载状态,直到异步操作完成。

自定义按钮

所有按钮默认值定义在您项目的 lib/resources/widgets/buttons/buttons.dart 中。每种按钮类型在 lib/resources/widgets/buttons/partials/ 中都有对应的 widget 类。

更改默认样式

要修改按钮的默认外观,请编辑 Button 类:

class Button {
  static Widget primary({
    required String text,
    VoidCallback? onPressed,
    (dynamic, Function(dynamic data))? submitForm,
    Function(dynamic error)? onFailure,
    bool showToastError = true,
    double? width,
  }) {
    return PrimaryButton(
      text: text,
      onPressed: onPressed,
      submitForm: submitForm,
      onFailure: onFailure,
      showToastError: showToastError,
      loadingStyle: LoadingStyle.skeletonizer(),
      width: width,
      height: 52.0,
      animationStyle: ButtonAnimationStyle.bounce(),
      splashStyle: ButtonSplashStyle.glow(),
    );
  }
}

自定义按钮 Widget

要更改按钮类型的视觉外观,请编辑 lib/resources/widgets/buttons/partials/ 中对应的 widget。例如,要更改主按钮的边框半径或阴影:

// lib/resources/widgets/buttons/partials/primary_button_widget.dart

class PrimaryButton extends StatefulAppButton {
  ...

  @override
  Widget buildButton(BuildContext context) {
    final theme = Theme.of(context);
    final bgColor = backgroundColor ?? theme.colorScheme.primary;
    final fgColor = contentColor ?? theme.colorScheme.onPrimary;

    return Container(
      width: width ?? double.infinity,
      height: height,
      decoration: BoxDecoration(
        color: bgColor,
        borderRadius: BorderRadius.circular(8), // Change the radius
      ),
      child: Center(
        child: Text(
          text,
          style: TextStyle(
            color: fgColor,
            fontSize: 16,
            fontWeight: FontWeight.w600,
          ),
        ),
      ),
    );
  }
}

创建新的按钮类型

要添加新的按钮类型:

  1. lib/resources/widgets/buttons/partials/ 中创建一个继承 StatefulAppButton 的新 widget 文件。
  2. 实现 buildButton 方法。
  3. Button 类中添加一个静态方法。
// lib/resources/widgets/buttons/partials/danger_button_widget.dart

class DangerButton extends StatefulAppButton {
  DangerButton({
    required super.text,
    super.onPressed,
    super.submitForm,
    super.onFailure,
    super.showToastError,
    super.loadingStyle,
    super.width,
    super.height,
    super.animationStyle,
    super.splashStyle,
  });

  @override
  Widget buildButton(BuildContext context) {
    return Container(
      width: width ?? double.infinity,
      height: height,
      decoration: BoxDecoration(
        color: Colors.red,
        borderRadius: BorderRadius.circular(14),
      ),
      child: Center(
        child: Text(
          text,
          style: TextStyle(
            color: Colors.white,
            fontSize: 16,
            fontWeight: FontWeight.w600,
          ),
        ),
      ),
    );
  }
}

然后在 Button 类中注册它:

class Button {
  ...

  static Widget danger({
    required String text,
    VoidCallback? onPressed,
    (dynamic, Function(dynamic data))? submitForm,
    Function(dynamic error)? onFailure,
    bool showToastError = true,
    double? width,
  }) {
    return DangerButton(
      text: text,
      onPressed: onPressed,
      submitForm: submitForm,
      onFailure: onFailure,
      showToastError: showToastError,
      loadingStyle: LoadingStyle.skeletonizer(),
      width: width,
      height: 52.0,
      animationStyle: ButtonAnimationStyle.shake(),
    );
  }
}

参数参考

通用参数(所有按钮类型)

参数 类型 默认值 描述
text String 必填 按钮标签文字
onPressed VoidCallback? null 点击按钮时的回调。返回 Future 以启用自动加载状态
submitForm (dynamic, Function(dynamic))? null 表单提交记录(表单实例,成功回调)
onFailure Function(dynamic)? null 表单验证失败时调用
showToastError bool true 表单验证错误时显示 toast 通知
width double? null 按钮宽度(默认全宽)

类型特定参数

Button.outlined

参数 类型 默认值 描述
borderColor Color? 主题轮廓颜色 边框颜色
textColor Color? 主题主色 文字颜色

Button.textOnly

参数 类型 默认值 描述
textColor Color? 主题主色 文字颜色

Button.icon

参数 类型 默认值 描述
icon Widget 必填 要显示的图标 widget
color Color? 主题主色 背景颜色

Button.gradient

参数 类型 默认值 描述
gradientColors List<Color>? 主色和第三色 渐变色节点

Button.rounded

参数 类型 默认值 描述
backgroundColor Color? 主题 primary container 颜色 背景颜色
borderRadius BorderRadius? 药丸形状(高度 / 2) 圆角半径

Button.transparency

参数 类型 默认值 描述
color Color? 主题自适应 文字颜色

ButtonAnimationStyle 参数

参数 类型 默认值 描述
duration Duration 因类型而异 动画持续时间
curve Curve 因类型而异 动画曲线
enableHapticFeedback bool 因类型而异 按下时触发触觉反馈
translateY double 4.0 Clickable:垂直按压距离
shadowOffset double 4.0 Clickable:阴影深度
scaleMin double 0.92 Bounce:按下时的最小缩放
pulseScale double 1.05 Pulse:脉冲期间的最大缩放
squeezeX double 0.95 Squeeze:水平压缩
squeezeY double 1.05 Squeeze:垂直扩展
jellyStrength double 0.15 Jelly:摇晃强度
shineColor Color Colors.white Shine:高光颜色
shineWidth double 0.3 Shine:光泽带宽度
rippleScale double 2.0 Ripple:扩展比例
morphRadius double 24.0 Morph:目标边框半径
shakeOffset double 8.0 Shake:水平位移
shakeCount int 3 Shake:振荡次数

ButtonSplashStyle 参数

参数 类型 默认值 描述
splashColor Color? 主题 surface 颜色 水波纹效果颜色
highlightColor Color? 主题 surface 颜色 高光效果颜色
splashOpacity double 0.12 水波纹不透明度
highlightOpacity double 0.06 高光不透明度
borderRadius BorderRadius? null 水波纹裁剪半径