做那种的视频网站武汉网站排名提升
参考资料: 《Flutter实战·第二版》 10.2 组合现有组件
在Flutter中页面UI通常都是由一些低级别组件组合而成,当我们需要封装一些通用组件时,应该首先考虑是否可以通过组合其他组件来实现,如果可以,则应优先使用组合,因为直接通过现有组件拼装会非常简单、灵活、高效。
10.2.1 实例:自定义渐变按钮
Flutter中自带的按钮组件不支持渐变背景,这里自定义一个GradientButton
组件,具有下面的功能:
- 背景支持渐变色
- 手指按下时有涟漪效果
- 可以支持圆角
自带的按钮组件是下面的效果:
其实除了不是渐变色背景其它都满足了。在Flutter中,ElevatedButton
组件默认不具有圆角。然而,可以通过自定义其形状来为其添加圆角。可以在ElevatedButton
的style
属性中设置shape
属性来实现:
ElevatedButton( onPressed: () { print('Button pressed!'); }, child: Text('Press Me'), style: ElevatedButton.styleFrom( primary: Colors.blue, // 设置按钮的主色 onPrimary: Colors.white, // 设置按钮上文字的颜色 shape: RoundedRectangleBorder( // 设置按钮的形状为圆角矩形 borderRadius: BorderRadius.circular(20.0), // 设置圆角的大小 ), ), )
想有渐变背景的话,不能通过style
属性设置背景色,因为数据类型是有限制的。可以利用DecoratedBox
和InkWell
组合来实现,前者能够设置圆角和渐变背景色,后者可以提供涟漪效果。下面是实现代码,思路比较简单,主要是UI的绘制逻辑:
class GradientButton extends StatelessWidget {const GradientButton({Key? key, this.colors,this.width,this.height,this.onPressed,this.borderRadius,required this.child,}) : super(key: key);// 渐变色数组final List<Color>? colors;// 按钮宽高final double? width;final double? height;final BorderRadius? borderRadius;//点击回调final GestureTapCallback? onPressed;final Widget child;Widget build(BuildContext context) {ThemeData theme = Theme.of(context);//确保colors数组不空List<Color> _colors =colors ?? [theme.primaryColor, theme.primaryColorDark];return DecoratedBox(decoration: BoxDecoration(gradient: LinearGradient(colors: _colors),borderRadius: borderRadius,//border: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),),child: Material(type: MaterialType.transparency,child: InkWell(splashColor: _colors.last,highlightColor: Colors.transparent,borderRadius: borderRadius,onTap: onPressed,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,),),),),),),);}
}
需要注意组件的输入属性,按钮点击的回调为GestureTapCallback
类型,其属性除了child
之外,均为可选属性。其具有默认样式,如果没有传入color
属性,则默认为主题色主色调和暗色调。通过ConstrainedBox
可以定义按钮的大小,还可以设置默认的字体样式(这里可以按需求去掉)。
定义好之后就可以使用了,这里定义了三个不同的按钮,代码和效果如下:
import 'dart:ui';import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key});// This widget is the root of your application.Widget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),useMaterial3: true,),home: const MyHomePage(title: 'TEAL WORLD'),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key, required this.title});final String title;State<MyHomePage> createState() => _MyHomePageState();
}class _MyHomePageState extends State<MyHomePage> {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(backgroundColor: Theme.of(context).colorScheme.inversePrimary,title: Text(widget.title,style: TextStyle(color: Colors.teal.shade800, fontWeight: FontWeight.w900),),actions: [ElevatedButton(child: const Icon(Icons.refresh),onPressed: () {setState(() {});},)],),body: const ComponentTestRoute(),floatingActionButton: FloatingActionButton(onPressed: () {},tooltip: 'Increment',child: Icon(Icons.add_box,size: 30,color: Colors.teal[400],),), // This trailing comma makes auto-formatting nicer for build methods.);}
}class ComponentTestRoute extends StatefulWidget {const ComponentTestRoute({Key? key}) : super(key: key);ComponentTestRouteState createState() => ComponentTestRouteState();
}class ComponentTestRouteState extends State<ComponentTestRoute> {Widget build(BuildContext context) {return Row(children: [Expanded(child: Padding(padding: const EdgeInsets.only(top: 80),child: Column(children: [GradientButton(colors: const [Colors.purple, Colors.red],height: 50.0,child: const Text("Submit"),onPressed: () {},),GradientButton(height: 50.0,colors: [Colors.yellow, Colors.green.shade700],child: const Text("Submit"),onPressed: () {},),GradientButton(height: 50.0,//borderRadius: const BorderRadius.all(Radius.circular(5)),colors: const [Colors.cyanAccent, Colors.blueAccent],child: const Text("Submit"),onPressed: () {},),],),))],);}
}class GradientButton extends StatelessWidget {const GradientButton({Key? key,this.colors,this.width,this.height,this.onPressed,this.borderRadius,required this.child,}) : super(key: key);// 渐变色数组final List<Color>? colors;// 按钮宽高final double? width;final double? height;final BorderRadius? borderRadius;//点击回调final GestureTapCallback? onPressed;final Widget child;Widget build(BuildContext context) {ThemeData theme = Theme.of(context);//确保colors数组不空List<Color> _colors =colors ?? [theme.primaryColor, theme.primaryColorDark];return DecoratedBox(decoration: BoxDecoration(gradient: LinearGradient(colors: _colors),borderRadius: borderRadius,//border: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),),child: Material(type: MaterialType.transparency,child: InkWell(splashColor: _colors.last,highlightColor: Colors.transparent,borderRadius: borderRadius,onTap: onPressed,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,),),),),),),);}
}
按下之后涟漪的颜色为colors
中最后一个颜色:
虽然实现起来较为容易,但是在抽离出单独的组件时要考虑代码规范性,如必要参数要用required
关键词标注,对于可选参数在特定场景需要判空或设置默认值等。为了保证代码健壮性,我们需要在用户错误地使用组件时能够兼容或报错提示(使用assert
断言函数)。
在Dart语言中,
assert
是一种用于在开发过程中捕获错误的工具。assert
语句用于在调试模式下测试某个条件是否为真。如果条件为假,那么assert
语句将抛出一个AssertionError
异常,并停止程序的执行。这主要用于在开发过程中捕捉不应该发生的错误情况,从而帮助开发者定位问题。
assert
语句在发布模式(即生产环境)下会被忽略,因此不会影响最终用户的体验。这使得assert
成为开发过程中进行条件测试的理想工具,而无需担心在最终应用中引入额外的性能开销或错误处理逻辑。
下面是assert
在Dart中的基本用法:
void main() { int number = 5; // 使用 assert 语句检查 number 是否大于 0 assert(number > 0, 'number should be greater than 0'); // 如果条件为真,程序继续执行 print('Number is positive.'); // 如果条件为假,抛出 AssertionError,并显示提供的错误消息 // assert(number < 0, 'number should be less than 0'); // 这会抛出 AssertionError
}