在Flutter中页面UI通常都是由一些低级别组件组合而成,当我们需要封装一些通用组件时,应该首先考虑是否可以通过组合其它组件来实现,如果可以,则应优先使用组合,因为直接通过现有组件拼装会非常简单、灵活、高效。
image.png1. 实例:自定义渐变按钮
1. 实现GradientButton
Flutter Material组件库中的按钮默认不支持渐变背景,为了实现渐变背景按钮,我们自定义一个GradientButton组件,它需要支持一下功能:
- 背景支持渐变色
- 手指按下时有涟漪效果
- 可以支持圆角
DecoratedBox可以支持背景色渐变和圆角,InkWell在手指按下有涟漪效果,所以我们可以通过组合DecoratedBox和InkWell来实现GradientButton
class GradientButton extends StatelessWidget {
const GradientButton({
Key? key,
this.colors,
this.width,
this.height,
this.borderRadius,
required this.tapCallback,
required this.child,
this.disable = false,
}) : super(key: key);
final List<Color>? colors; // 渐变色数组
final double? width; // 按钮 宽
final double? height; // 按钮 高
final BorderRadius? borderRadius; // 按钮 圆角
final GestureTapCallback tapCallback;
final bool disable; // 禁用
final Widget child; // 子组件
@override
Widget build(BuildContext context) {
ThemeData theme = Theme.of(context);
List<Color> _colors = [];
if (disable) {
//确保colors数组不空
_colors = [Colors.grey.withAlpha(100), Colors.grey.withAlpha(100)];
} else {
//确保colors数组不空
_colors = colors ?? [theme.primaryColor, theme.primaryColorDark];
}
return DecoratedBox(
decoration: BoxDecoration(
borderRadius: borderRadius,
gradient: LinearGradient(colors: _colors),
),
child: Material(
type: MaterialType.transparency, // 透明材质 用于绘制墨水飞溅和高光
child: InkWell(
onTap: disable ? null : tapCallback,
splashColor: _colors.last,
borderRadius: borderRadius,
enableFeedback: !disable, // 是否给予操作反馈
highlightColor: Colors.transparent,
child: ConstrainedBox(
constraints: BoxConstraints.tightFor(width: width, height: height),
child: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: DefaultTextStyle(
style: TextStyle(fontWeight: FontWeight.bold),
child: child,
),
),
),
),
),
),
);
}
}
可以看到GradientButton是由DecoratedBox、Padding、Center、InkWell等组件组合而成。更多功能可以根据实际需要来完善。
2. 使用GradientButton
class MSGradientButtonDemo extends StatelessWidget {
const MSGradientButtonDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("GradientButton")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
GradientButton(
tapCallback: () => print("Button Clicked 1"),
child: Text("Submit 1"),
colors: [Colors.orange, Colors.cyan],
height: 50,
),
GradientButton(
tapCallback: () => print("Button Clicked 2"),
child: Text("Submit 2"),
colors: [Colors.cyan, Colors.green],
height: 50,
),
GradientButton(
tapCallback: () => print("Button Clicked 3"),
child: Text("Submit 3"),
colors: [Colors.yellow, Colors.red, Colors.orange],
height: 50,
),
GradientButton(
tapCallback: () => print("Button Clicked 4"),
child: Text("Submit 4"),
colors: [Colors.orange, Colors.cyan],
height: 50,
width: 200,
borderRadius: BorderRadius.circular(8),
),
GradientButton(
tapCallback: () => print("Button Clicked 5"),
child: Text("enable 5"),
width: 200,
height: 50,
borderRadius: BorderRadius.circular(8),
colors: [Colors.grey, Colors.red],
disable: false,
),
GradientButton(
tapCallback: () => print("Button Clicked 6"),
child: Text("Disable 6"),
width: 200,
height: 50,
borderRadius: BorderRadius.circular(8),
disable: true,
)
],
),
),
);
}
}
111.gif
2. 总结
通过组合的方式定义组件和我们之前写界面并无差异,不过在抽离出单独的组件时我们要考虑代码规范性,如必要参数要用required关键词标注,对于可选参数在特定场景需要判空或设置默认值等。这是由于使用者大多时候可能不了解组件的内部细节,所以为了保证代码健壮性,我们需要在用户错误地使用组件时能够兼容或报错提示(使用assert断言函数)。
网友评论