- 有状态和无状态的控件
// StatefulWidget 有状态(更新)的控件
class MyText extends StatefulWidget {}
// 由两个类组成如:Test类和_Test类
// StatelessWidget 无状态(更新)的控件
class MyText extends StatelessWidget {}
// 有状态控件写法如下
class Test extends StatefulWidget {
const Test({super.key, required this.params});
final Map? params;
@override // 复写,拿父类有的方法来重写类似render
State<Test> createState() => _Test(); // 创建一个_Test类
}
class _Test extends State<Test> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return 控件;
}
}
2.颜色码的使用注意事项:
// 不能使用三位缩写颜色码,如#333必须改为#333333;
3.交互控件
GestureDetector
// 常用回调
// onTap 点击
// onTapDown 按下
// onTapUp 抬起
// onTapCancel 点击取消事件,比如点击某个按钮后未抬起手指状态下移出按钮
// onDoubleTap 双击
// onDoubleTapDown 双击按下
// onDoubleTapUp 双击抬起
// onDoubleTapCancel 双击取消
// onLongPress 长按
4.超出屏幕解决方法
SingleChildScrollView
// 注意事项:只适合稍微超出的内容,长列表不适合,长列表使用listview控件
// 其他注意事项:横向溢出一般用Expanded
5.Row控件
行内子控件宽度自适应:mainAxisSize: MainAxisSize.min
排列方式:mainAxisAlignment: MainAxisAlignment.spaceBetween;crossAxisAlignment: CrossAxisAlignment.start
6.外边框、圆角写法
decoration: BoxDecoration(
borderRadius:
const BorderRadius.all(Radius.circular(12)),
color: HexColor('#FFFFFF')
) // 12圆角白色底的矩形框
shape: BoxShape.circle, // 圆
// 圆角图片(背景)
Container(
width: 100,
height: 100,
decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(12.w),
shape: BoxShape.circle,
image: DecorationImage(
image: NetworkImage('xxxxxxxxx'),
fit: BoxFit.cover)
)
),
)
// border属性: strokeAlign: BorderSide.strokeAlignCenter,确定边框是在哪个位置,内边框/外边框/居中边框;
7.日历控件的使用
SfDateRangePicker
// 注意事项:日历本身属性更改才会刷新
// 解决方法:headerHeight: headerHeight,设置该属性,赋值为headerHeight+/-0.01,
// onViewChange方法内的数据更改需要在Future内,不然页面会报错,如下
void _onViewChange(dateRangePickerViewChangedArgs) {
Future.microtask(() {
setState(() {
xx = xxx;
});
});
}
8.数组map遍历方法
// 遍历值
list.map((item) => {
return item;
}).toList();
// 遍历索引
list.asMap().keys.map((index) => {
return list[index]['xxx'];
}).toList();
9.window问题,不能通过window获取屏幕相关属性,获取页面宽度用以下代替
MediaQuery.of(context).size.width
10.字符串的一些方法
判断是否包含某字符串:str.contains('xx');
截取字符串(从右往左截取两个字符):str.substring(0, str.length - 2);
......
11.滑块的使用及注意事项
Container(
margin:xxx,
width: xxx,
height: 20, // 这里是可点击拖拽滑动的区域,不能跟轨道高度一样小,不然会导致滑动非常不灵敏
child: SliderTheme(
data: SliderThemeData(
trackHeight: 6, // 轨道高度
xxxxxx
),
child: Slider(
value: xxx,
onChanged: (value) {
setState(() {
xxx = xxxx;
});
},
onChangeEnd: (value) {
setState(() {
xxx = xxxx;
});
},
// 轨道值范围
min: 0, // 开始值
max: 5, // 结束值
// divisions: 5, // 轨道要分几段
activeColor: HexColor('xxx'), // 滑动过的颜色
inactiveColor: HexColor('xxx'), // 未滑动过的颜色
),
),
)
12.拉起相机拍照或者获取相册获取照片
需要用到插件:image_picker
测试:web端、Android端
import 'dart:io'; // 文件操作用到
import 'package:image_picker/image_picker.dart';
var imgPath;
// 调取相机或者相册
void _takePhoto() async {
var img = await ImagePicker().pickImage(source: ImageSource.camera); // 拉起相册:ImageSource.gallery
setState(() {
imgPath = File(img!.path);
});
}
// 将图片展示
Image.network( // android端不支持
imgPath.path,
),
Image.file( // web端不支持
imgPath,
),
// 保存图片到本地用到的插件
import 'package:gallery_saver/gallery_saver.dart';
import "package:universal_html/html.dart" as html;
// 保存图片到本地
void _savePhoto() async {
// 测试gif动图下载后为png格式
if (kIsWeb) { // web端,下载完之后需要用户保存到手机
// 方法一,file文件
final a = html.AnchorElement(href: imgPath.path);
a.download = '图片.png';
a.click();
a.remove();
// 方法二,base64
Uint8List bytes = base64.decode(imgBase64);
final blob = html.Blob([bytes], 'image/jpeg');
final url = html.Url.createObjectUrlFromBlob(blob);
final a = html.AnchorElement(href: url);
a.download = '图片.png';
a.click();
a.remove();
html.Url.revokeObjectUrl(url);
Fluttertoast.showToast(
msg: '保存成功!',
);
} else {
GallerySaver.saveImage(File(imgPath!.path).path).then((value) {
Fluttertoast.showToast(
msg: '保存成功!',
);
});
}
}
13.使用动画的一些注意事项
late final AnimationController _repeatController;
// 线性动画
late final Animation<double> _animation;
@override
void initState() {
super.initState();
_repeatController = AnimationController(
duration: const Duration(microseconds: 2000), vsync: this)
..addListener(() {});
// 动画持续时间是 3秒,此处的this指 TickerProviderStateMixin或SingleTickerProviderStateMixin
_repeatController = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
)..repeat(); // 设置动画重复播放
// 创建一个从0到360弧度的补间动画 v * 2 * π
_animation = Tween<double>(begin: 0, end: 1).animate(_repeatController);
}
class _xxxState extends State<xxx>
with SingleTickerProviderStateMixin {
...
RotationTransition(
turns: _animation,
child: xxx,
)
}
14.上下固定,中间滚动的布局
Scaffold(
backgroundColor: HexColor('#F7F7F7'),
body: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Column(children: []),
)),
XXX(children: []) // 底部固定
]));
15.路由跳转及获取路由传参
// 跳转,带参
Navigator.push(
context,
MaterialPageRoute(
maintainState: true,
builder: (_) {
return getRouter("路由")({
"xxx": xxxxx, // 参数
});
}));
// 获取参数
@override
void initState() {
super.initState();
print('当前路由参数是:${widget.params}');
}
// 返回上一级路由
Navigator.of(context).pop();
Navigator.of(context).pop({"xx":"xxxx"}); // 返回前可以传参数给上一级
16.子控件撑满父控件的高度
IntrinsicHeight(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [Expanded(child:
Stack(children:[
子控件1,// 有高度,撑开父控件
子控件2,// 与父控件高度一致
]))]
)
)
17.图片的一些使用
// base64图片
Image.memory(
base64Decode(base64imgStr),
);
// file图片
Image.network( // android端不支持
imgPath.path,
);
Image.file( // web端不支持
imgPath,
);
// 工程内静态图片
Image.asset(
'resource/images/...',
width: 100,
fit: BoxFit.fitWidth, // 图片根据宽度高度自适应
);
18.渐变字
ShaderMask(
shaderCallback: (bounds) => LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [HexColor(渐变色码1), HexColor(渐变色码2)])
.createShader(
Rect.fromLTWH(0, 0, bounds.width, bounds.height),
),
child: Text(
'渐变字',
style: TextStyle(
color: Colors.white,
fontSize: 18,
),
),
);
19.弹窗
// 其中一种写法,AlertDialog实现
void showModalFun(BuildContext context) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
// 去掉默认样式,背景和padding
backgroundColor: const Color.fromRGBO(0, 0, 0, 0),
contentPadding: EdgeInsets.zero,
content: Container() // 弹窗
);
});
}
// 打开弹窗
showModalFun(context);
// 关闭弹窗
Navigator.of(context).pop();
// 另一种方法,Dialog实现,宽高不受限的写法
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(builder: (context, setState) {
return UnconstrainedBox(
// 抵消弹窗原有的约束,再自己设置宽度
constrainedAxis: Axis.vertical,
child: SizedBox(
width: MediaQuery.of(context).size.width,
child: Dialog(
// 去掉默认样式
backgroundColor: const Color.fromRGBO(0, 0, 0, 0),
insetPadding: EdgeInsets.zero,
child: xxxx))); // 真正的弹窗
});
});
20.路由相关
// 回到第一个路由(打开的首页)
Navigator.of(context).popUntil((route) => route.isFirst);
// 带参数路由
Navigator.push(
context,
MaterialPageRoute(
maintainState: true,
builder: (_) {
return getRouter("/xxx/xxx")(
{"a": xxx, "b": xxx});
}));
// 带回调的路由
() async {
await Navigator.push(
context,
MaterialPageRoute(
maintainState: true,
builder: (_) {
return getRouter("/xxx/xxx")(
{"a": xxx, "b": xxx});
}));
// 路由跳转结束回到页面后的操作
// ...
}
// 回到上一路由,也可用于关闭弹窗
Navigator.of(context).pop();
21.以页面高度自适应,页面高度不够展示给一个最小高度页面可滚动
SingleChildScrollView(
child: Stack(
alignment: Alignment.topCenter,
children: [
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height, // 拿页面高度
constraints: BoxConstraints(minHeight: 600.w), // 页面高度低于600赋值600, 不够展示了就只能滚动展示
),
Positioned(top: 100.w, bottom: 60.w, child: content()) // 需要自适应高度的控件
],
));
未完待续...