文章目录
- 一、Flutter 组件回顾
- 二、Center 组件
- 三、Wrap 组件
- 四、ClipRRect 组件
- 五、Stack 组件与 Positioned 组件
- 六、按钮组件组合
- 七、完整代码示例
- 八、相关资源
一、Flutter 组件回顾
Flutter 与布局相关的组件 :
- Container : 容器组件 ;
- RenderObjectWidget : 布局渲染相关组件 ;
- SingleChildRenderObjectWidget : 单节点布局组件 ;
- Opacity : 常用于修改组件透明度 ;
- ClipOval : 裁剪布局组件 , 可以将布局裁剪成圆形 ;
- ClipRRect : 裁剪布局组件 , 可以将布局裁剪成方形 ;
- PhysicalModel : 将布局显示成不同的形状 ;
- Align : 布局设置组件 , 一般设置布局居中操作 ;
- Padding : 设置内边距的组件 ;
- SizeBox : 用于约束布局大小的组件 ;
- FractionallySizedBox : 约束布局水平 / 垂直方向的平铺操作 ;
- MultiChildRenderObjectWidget : 多节点布局组件 ;
- Stack : 相当于帧布局 FrameLayout ;
- Flex :
- Column : 相当于线性布局 , 垂直方向布局 , 组件从上到下摆放 ;
- Row : 相当于线性布局 , 水平方向布局 , 组件从左到右 ;
- Wrap : 该组件与 Row 组件类似 , Wrap 组件可以换行 ;
- Flow : 不常用 ;
- SingleChildRenderObjectWidget : 单节点布局组件 ;
- ParentDataWidget :
- Positioned : 用于固定组件位置的组件 ;
- Flexible : 用于约束组件在父容器中展开大小的组件 ;
二、Center 组件
widthFactor ( 宽度因子 ) 和 heightFactor ( 高度因子 ) 用于控制该组件的宽高 , 类型为 double 浮点型 ;
- 参数为空 : 如果参数为空 , 则填充整个布局 , 相当于 match_parent ;
- 参数不为空 : 如果参数不为空 , 则对应的宽高是 宽度/高度因子
×
\times
代码示例 : 下面的代码中 , Center 没有设置宽高因子 , 默认为空 , 则该 Center 组件自动填充父容器 , 内部有一个 Widget 子组件 , 注意是单个子组件 ;
Center(
child: Wrap()
)
三、Wrap 组件
Column 组件是垂直方向的线性布局 , Row 组件是水平方向的线性布局 ,
Wrap 组件是在 Row 组件的基础上的水平线性布局 , 多了一个换行功能 , Wrap 组件可以有多行水平线性布局 ;
这是照片墙实现的主要组件 , Wrap 组件中由一组 Image 组件 List 集合作为子组件 ;
代码示例 :
// 可自动换行的水平线性布局
Wrap(
// 设置水平边距
spacing: 间距值 ( double 类型 ),
// 设置垂直间距
runSpacing: 间距值 ( double 类型 ),
children: <Widget>[
设置若干子组件
]
)
运行效果 : Center 组件填充整个屏幕 , Wrap 组件是 Center 的子组件 , 在中心显示 ;
参考博客 :
- 【Flutter】Flutter 布局组件 ( 布局组件简介 | Row 组件 | Column 组件 | SizedBox 组件 | ClipOval 组件 ) 二、Row 和 Column 组件
- 【Flutter】Flutter 布局组件 ( Wrap 组件 | Expanded 组件 ) 一、Wrap 组件
四、ClipRRect 组件
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 组件与 Positioned 组件
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() {
}
}
运行效果 : 拍照获取第一个图片 , 从相册中选择第二章图片 , 然后删除第一张图片 ;
八、相关资源
参考资料 :
- Flutter 官网 : https://flutter.dev/
- Flutter 插件下载地址 : https://pub.dev/packages
- Flutter 开发文档 : https://flutter.cn/docs ( 强烈推荐 )
- 官方 GitHub 地址 : https://github.com/flutter
- Flutter 中文社区 : https://flutter.cn/
- Flutter 实用教程 : https://flutter.cn/docs/cookbook
- Flutter CodeLab : https://codelabs.flutter-io.cn/
- Dart 中文文档 : https://dart.cn/
- Dart 开发者官网 : https://api.dart.dev/
- Flutter 中文网 ( 非官方 , 翻译的很好 ) : https://flutterchina.club/ , http://flutter.axuer.com/docs/
- Flutter 相关问题 : https://flutterchina.club/faq/ ( 入门阶段推荐看一遍 )
博客源码下载 :
-
GitHub 地址 : https://github.com/han1202012/flutter_photo ( 随博客进度一直更新 , 有可能没有本博客的源码 )
-
博客源码快照 : https://download.csdn.net/download/han1202012/15978310 ( 本篇博客的源码快照 , 可以找到本博客的源码 )