Flutter 中页面UI通常都是由一些简单组件组合而成,当需要封装一些通用组件时,应该首先考虑使用组合其他组件来实现,如果可以,则应优先使用组合,因为直接通过现有组件拼装会非常简单、灵活、高效。
实例:
- 实现GradientButton Flutter Material组件库中按钮默认不支持渐变背景,为了实现渐变背景按钮,定义一个GradientButton,支持功能:
背景支持渐变 手指按下有涟漪效果 可以支持圆角
DecoratedBox支持背景色渐变和圆角,InkWell在手指按下有涟漪效果,可以通过组合DecoratedBox和InWell实现GradientButton:
class SSLGradientButton extends StatelessWidget{
final List<Color>? colors;
final double? width;
final double? height;
final BorderRadius? borderRadius;
final GestureTapCallback? onPress;
final Widget child;
const SSLGradientButton({
Key? key,
required this.child,
this.width,
this.height,
this.borderRadius,
this.onPress,
this.colors,
}):super(key: key);
@override
Widget build(BuildContext context) {
// TODO: implement build
ThemeData theme = Theme.of(context);
List<Color> temColors = colors ?? [theme.primaryColor, theme.primaryColorDark];
return DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(colors: temColors),
borderRadius: borderRadius,
),
child: Material(
type: MaterialType.transparency,
child: InkWell(
splashColor: temColors.last,
highlightColor: Colors.transparent,
borderRadius: borderRadius,
//这里不能直接将onPress回调传递,传递后事件会无法响应,具体为啥暂时不清楚
onTap: tapAction,
child: ConstrainedBox(
constraints: BoxConstraints.tightFor(height: height, width: width),
child: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: DefaultTextStyle(
style: const TextStyle(fontWeight: FontWeight.bold),
child: child,
),
),
),
),
),
),
);
}
void tapAction(){
debugPrint("chick btn");
if (onPress != null){
onPress!();
}
}
}
组合中使用了DecoratedBox、Padding、InWell、Center等组件组合而成.
使用示例:
class SSLGradientButtonTest extends StatefulWidget{
const SSLGradientButtonTest({Key? key}):super(key: key);
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return SSLGradientButtonTestState();
}
}
class SSLGradientButtonTestState extends State<SSLGradientButtonTest>{
@override
Widget build(BuildContext context) {
// TODO: implement build
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SSLGradientButton(
colors: const [Colors.red, Colors.green],
height: 60,
onPress: (){
//同理。这里如果直接将回调抛给onTap回调,也是会有问题,会在执行时直接执行了回调
onTapAction("first btn");
},
child: const Text("first Button"),
),
SSLGradientButton(
colors: const [Colors.orange, Colors.grey],
height: 30,
onPress: (){
onTapAction("twice btn");
},
child: const Text("Twice button"),
),
SSLGradientButton(
colors: const [Colors.green, Colors.lightBlue],
height: 100,
onPress: (){
onTapAction("Three button");
},
child: const Text("Three button"),
),
],
);
}
//onTap(String btn){
//debugPrint("chick button $btn");
//}
void onTapAction(String btn){
debugPrint("chick button $btn");
}
}
总结
通过组合的方式定义组件和之前写的组件并无差异,不过在抽离出单独组件时,需要考虑代码的规范性,如必要参数需要用required关键词标注,并对于可选参数在特定的场景需要做判空或设置默认值处理。因为大多使用者不清楚组件内部的实现细节,为了保证代码的健壮性,需要在用户错误的使用组件时能够兼容或报错提醒使用者(assert断言函数)。