Flutter 与布局相关的组件 :
widthFactor ( 宽度因子 ) 和 heightFactor ( 高度因子 ) 用于控制该组件的宽高 , 类型为 double 浮点型 ;
子组件高度 ;
代码示例 : 下面的代码中 , Center 没有设置宽高因子 , 默认为空 , 则该 Center 组件自动填充父容器 , 内部有一个 Widget 子组件 , 注意是单个子组件 ;
Center(
child: Wrap()
)
Column 组件是垂直方向的线性布局 , Row 组件是水平方向的线性布局 ,
Wrap 组件是在 Row 组件的基础上的水平线性布局 , 多了一个换行功能 , Wrap 组件可以有多行水平线性布局 ;
这是照片墙实现的主要组件 , Wrap 组件中由一组 Image 组件 List 集合作为子组件 ;
代码示例 :
// 可自动换行的水平线性布局
Wrap(
// 设置水平边距
spacing: 间距值 ( double 类型 ),
// 设置垂直间距
runSpacing: 间距值 ( double 类型 ),
children: <Widget>[
设置若干子组件
]
)
运行效果 : Center 组件填充整个屏幕 , Wrap 组件是 Center 的子组件 , 在中心显示 ;
参考博客 :
ClipRRect 组件是矩形切割组件 , 可以将组件切割成圆角矩形 ; borderRadius 属性用于设置圆角 , child 属性用于设置被切割的子组件 ;
代码示例 :
// 设置底部的大图片
ClipRRect(
// 设置圆角半径 5 像素
borderRadius: BorderRadius.circular(5),
// 设置图片
child: Image.file(file, width: 120, height: 90, fit: BoxFit.fill,),
),
运行效果 : 下图中的圆角矩形就是使用 ClipRRect 切割 Image 组件的效果 ;
参考博客 : 【Flutter】Flutter 布局组件 ( Opacity 组件 | ClipRRect 组件 | Padding 组件 ) 二、ClipRRect 组件
Stack 组件是帧布局组件 , 在其 children 字段设置一个 Widget 集合 ;
在 Stack 组件内部 , 可以使用 Positioned 组件指定某个子组件在 Stack 布局组件中的位置 ;
代码示例 :
// 帧布局
Stack(
children: <Widget>[
// 设置底部的大图片
ClipRRect(
// 设置圆角半径 5 像素
borderRadius: BorderRadius.circular(5),
// 设置图片
child: Image.file(file, width: 120, height: 90, fit: BoxFit.fill,),
),
// 使用 Positioned 组件在帧布局中定位子组件
// 设置右上角的关闭按钮
Positioned(
// 距离右侧 5
right: 5,
// 距离顶部 5
top: 5,
child: ,
),
]
)
效果展示 : 整体是 Stack 帧布局 , 使用 ClipRRect 组件将 Image 组件切割成了圆角矩形 , Stack 组件内使用 Positioned 组件将关闭按钮 , 放置在了右上角 ;
参考博客 : 【Flutter】Flutter 布局组件 ( FractionallySizedBox 组件 | Stack 布局组件 | Positioned 组件 ) 二、Stack 布局组件
关闭按钮首先由按键功能 , 在最外围使用 GestureDetector 组件 , 监听器 onTap 点击事件 , 点击时删除对应的图片文件 , 并更新整体布局 ;
GestureDetector 组件的 child 子组件就是我们看到的关闭按钮 , 先使用 ClipOval 圆形切割组件切割出一个黑色圆形 , 在中间使用 Center 组件放置一个 Icon 白色图标 , 就组成了圆形的关闭按钮 ;
关闭按钮代码示例 :
// 手势检测器组件
GestureDetector(
// 点击事件
onTap: (){
setState(() {
// 从图片集合中移除该图片
_images.remove(file);
});
},
// 右上角的删除按钮
child: ClipOval(
child: Container(
padding: EdgeInsets.all(3),
// 背景装饰
decoration: BoxDecoration(color: Colors.black),
// 图标, 20 像素 , 白色 , 关闭按钮
child: Icon(Icons.close, size: 20, color: Colors.white,),
),
),
),
运行效果 :
完整代码示例 :
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: '拍照示例'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
/// 需要导入 dart:io 库
/// import 'dart:io';
File _image;
/// 存放获取的图片集合, 初始化时为空
List<File> _images = [];
// 图片获取引擎
final picker = ImagePicker();
/// 获取摄像头图像的方法
Future getImageFromCamera() async {
/// 菜单按钮消失
Navigator.pop(context);
/// 需要导入 image_picker.dart 包
/// import 'package:image_picker/image_picker.dart';
final pickedFile =
await picker.getImage(source: ImageSource.camera);
setState(() {
if (pickedFile != null) {
//_image = File(pickedFile.path);
/// 添加到图片文件集合中
_images.add(File(pickedFile.path));
} else {
print('No image selected.');
}
});
}
/// 获取相册中的图像
Future getImageFromGallery() async {
/// 菜单按钮消失
Navigator.pop(context);
/// 需要导入 image_picker.dart 包
/// import 'package:image_picker/image_picker.dart';
final pickedFile =
await picker.getImage(source: ImageSource.gallery);
setState(() {
if (pickedFile != null) {
//_image = File(pickedFile.path);
/// 添加到图片文件集合中
_images.add(File(pickedFile.path));
} else {
print('No image selected.');
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Wrap(
spacing: 5,
runSpacing: 5,
children:
// 遍历 从相册选择的照片 或 相机拍摄的照片
_images.map((file){
// 每个照片都生成一个帧布局
// 照片填充整个布局, 右上角放置一个关闭按钮
return Stack(
children: <Widget>[
// 设置底部的大图片
ClipRRect(
// 设置圆角半径 5 像素
borderRadius: BorderRadius.circular(5),
// 设置图片
child: Image.file(file, width: 120, height: 90, fit: BoxFit.fill,),
),
// 设置右上角的关闭按钮
Positioned(
// 距离右侧 5
right: 5,
// 距离顶部 5
top: 5,
// 手势检测器组件
child: GestureDetector(
// 点击事件
onTap: (){
setState(() {
// 从图片集合中移除该图片
_images.remove(file);
});
},
// 右上角的删除按钮
child: ClipOval(
child: Container(
padding: EdgeInsets.all(3),
// 背景装饰
decoration: BoxDecoration(color: Colors.black),
// 图标, 20 像素 , 白色 , 关闭按钮
child: Icon(Icons.close, size: 20, color: Colors.white,),
),
),
),
)
],
);
/// 注意这里要转为 List 类型 , <Widget>[]
}).toList()
,
)
),
floatingActionButton: FloatingActionButton(
onPressed: () {
/// 浮动按钮点击事件
/// 点击浮动按钮 , 弹出一个菜单
/// 菜单有两个按钮 , 分别是 拍照 / 选择图片
showModalBottomSheet(
context: context,
builder: (context) {
return Container(
// 设置该弹出的组件高度 200 像素
height: 200,
child: Column(
children: <Widget>[
// 拍照按钮
GestureDetector(
child: ListTile(
// 相机图标
leading: Icon(Icons.camera_alt),
title: Text("拍照"),
/// 按钮点击事件
onTap: (){
// 调用 getImage 方法 , 调出相机拍照
getImageFromCamera();
},
),
),
// 相册按钮
GestureDetector(
child: ListTile(
// 相册图标
leading: Icon(Icons.photo_library_outlined),
title: Text("相册"),
/// 按钮点击事件
onTap: (){
// 调用 getImageFromGallery 方法 , 调出相册
getImageFromGallery();
},
),
),
],
),
);
});
},
tooltip: 'Pick Image',
child: Icon(Icons.add_a_photo),
),
);
}
_generateImageWidgets() {
}
}
运行效果 : 拍照获取第一个图片 , 从相册中选择第二章图片 , 然后删除第一张图片 ;
参考资料 :
博客源码下载 :