在Flutter中页面UI通常都是由一些低阶别的组件组合而成,当我们需要封装一些通用组件时,应该首先考虑是否可以通过组合其它组件来实现,如果可以,则应优先使用组合,因为直接通过现有组件拼装会非常简单、灵活、高效。

示例:自定义渐变按钮

Flutter Material组件库中的按钮默认不支持渐变背景,为了实现渐变背景按钮,我们自定义一个GradientButton组件,它需要支持一下功能:

  1. 背景支持渐变色
  2. 手指按下时有涟漪效果
  3. 可以支持圆角

我们先来看看最终要实现的效果:
组合现有组件 - 图1

我们DecoratedBox可以支持背景色渐变和圆角,InkWell在手指按下有涟漪效果,所以我们可以通过组合DecoratedBox和InkWell来实现GradientButton,代码如下:

  1. class GradientButton extends StatelessWidget {
  2. GradientButton({
  3. this.colors,
  4. this.width,
  5. this.height,
  6. this.onPressed,
  7. this.borderRadius,
  8. @required this.child,
  9. });
  10. // 渐变色数组
  11. final List<Color> colors;
  12. // 按钮宽高
  13. final double width;
  14. final double height;
  15. final Widget child;
  16. final BorderRadius borderRadius;
  17. //点击回调
  18. final GestureTapCallback onPressed;
  19. @override
  20. Widget build(BuildContext context) {
  21. ThemeData theme = Theme.of(context);
  22. //确保colors数组不空
  23. List<Color> _colors = colors ??
  24. [theme.primaryColor, theme.primaryColorDark ?? theme.primaryColor];
  25. BorderRadius borderRadius = this.borderRadius ?? BorderRadius.circular(8);
  26. return DecoratedBox(
  27. decoration: BoxDecoration(
  28. gradient: LinearGradient(colors: _colors),
  29. borderRadius: borderRadius,
  30. ),
  31. child: Material(
  32. type: MaterialType.transparency,
  33. child: InkWell(
  34. splashColor: _colors.last,
  35. highlightColor: Colors.transparent,
  36. borderRadius: borderRadius,
  37. onTap: onPressed,
  38. child: ConstrainedBox(
  39. constraints: BoxConstraints.tightFor(height: height, width: width),
  40. child: Center(
  41. child: Padding(
  42. padding: const EdgeInsets.all(8.0),
  43. child: DefaultTextStyle(
  44. style: TextStyle(fontWeight: FontWeight.bold),
  45. child: child,
  46. ),
  47. ),
  48. ),
  49. ),
  50. ),
  51. ),
  52. );
  53. }
  54. }

可以看到GradientButton是由DecoratedBox、Padding、Center、InkWell等组件组合而成。当然上面的代码只是一个示例,作为一个按钮它还并不完整,比如没有禁用状态,读者可以根据实际需要来完善。

使用GradientButton

  1. import 'package:flutter/material.dart';
  2. import '../widgets/index.dart';
  3. class GradientButtonRoute extends StatefulWidget {
  4. @override
  5. _GradientButtonRouteState createState() => _GradientButtonRouteState();
  6. }
  7. class _GradientButtonRouteState extends State<GradientButtonRoute> {
  8. @override
  9. Widget build(BuildContext context) {
  10. return Container(
  11. child: Column(
  12. children: <Widget>[
  13. GradientButton(
  14. colors: [Colors.orange, Colors.red],
  15. height: 50.0,
  16. child: Text("Submit"),
  17. onPressed: onTap,
  18. ),
  19. GradientButton(
  20. height: 50.0,
  21. colors: [Colors.lightGreen, Colors.green[700]],
  22. child: Text("Submit"),
  23. onPressed: onTap,
  24. ),
  25. GradientButton(
  26. height: 50.0,
  27. colors: [Colors.lightBlue[300], Colors.blueAccent],
  28. child: Text("Submit"),
  29. onPressed: onTap,
  30. ),
  31. ],
  32. ),
  33. );
  34. }
  35. onTap() {
  36. print("button click");
  37. }
  38. }

总结

通过组合的方式定义组件和我们之前写界面并无差异,不过在抽离出单独的组件时我们要考虑代码规范性,如必要参数要用@required 标注,对于可选参数在特定场景需要判空或设置默认值等。这是由于使用者大多时候可能不了解组件的内部细节,所以为了保证代码健壮性,我们需要在用户错误地使用组件时能够兼容或报错提示(使用assert断言函数)。