容器类组件介绍
环境介绍以及参考文献
本示例是在 Linux 16.04.1-Ubuntu 搭配 VS Code 使用。
填充
Padding 可以给其子节点添加填充(留白),和边距效果类似。
一般用 EdgeInsets 类去实现 padding,这个类提供了下面的几个方法:
- fromLTRB(double left, double top, double right, double bottom):分别指定四个方向的填充。
- all(double value) : 所有方向均使用相同数值的填充。
- only({left, top, right ,bottom }):可以设置具体某个方向的填充(可以同时指定多个方向)。
- symmetric({ vertical, horizontal }):用于设置对称方向的填充,vertical指top和bottom,horizontal指left和right。
class PaddingTest extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("填充介绍"),),
body: Padding(
// 上下左右各添加16像素补白
padding: EdgeInsets.all(16.0),
child: Column(
// 显式指定对齐方式为左对齐,排除对齐干扰
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
// 左边添加8像素补白
padding: const EdgeInsets.only(left: 8.0),
child: Text("Hello world"),
),
Padding(
// 上下各添加8像素补白
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text("I am Jack"),
),
Padding(
// 分别指定四个方向的补白
padding: const EdgeInsets.fromLTRB(20.0,.0,20.0,20.0),
child: Text("Your friend"),
)
],
),
),
);
}
}
尺寸限制类容器
尺寸限制类容器用于限制容器大小
ConstrainedBox
用于对子组件添加额外的约束。
const BoxConstraints({
this.minWidth = 0.0, //最小宽度
this.maxWidth = double.infinity, //最大宽度
this.minHeight = 0.0, //最小高度
this.maxHeight = double.infinity //最大高度
})
SizedBox
用于给子元素指定固定的宽高。SizedBox 实际上是 ConstrainedBox 的一个子类。
SizedBox(
width: 80.0,
height: 80.0,
child: redBox
)
// 等价于
ConstrainedBox(
constraints: BoxConstraints.tightFor(width: 80.0,height: 80.0),
child: redBox,
)
当有多重限制时,对于 minWidth 和 minHeight 来说,是取父子中相应数值较大的。
实际上,多重限制重点在于保证父限制与子限制不冲突。
UnconstrainedBox
不会对子组件产生任何限制,它允许其子组件按照其本身大小绘制。
装饰容器
DecoratedBox 可以在其子组件绘制前(或后)绘制一些装饰(Decoration),如背景、边框、渐变等。
通常使用 BoxDecoration 实现常用的装饰元素的绘制
BoxDecoration({
Color color, //颜色
DecorationImage image,//图片
BoxBorder border, //边框
BorderRadiusGeometry borderRadius, //圆角
List<BoxShadow> boxShadow, //阴影,可以指定多个
Gradient gradient, //渐变
BlendMode backgroundBlendMode, //背景混合模式
BoxShape shape = BoxShape.rectangle, //形状
})
变换
Transform 可以在其子组件绘制时对其应用一些矩阵变换来实现一些特效。
- Transform.translate 接收一个 offset 参数,可以在绘制时沿 x、y 轴对子组件平移指定的距离。
- Transform.rotate 可以对子组件进行旋转变换。
- Transform.scale 可以对子组件进行缩小或放大。
Transform 是在其子组件绘制时的变化,所以占用的空间是不会变化的。如果想要修改相应的空间,需要用到 RotatedBox 进行旋转变换。
Container
Container 是一个组合类容器。
Container({
this.alignment,
this.padding, //容器内补白,属于decoration的装饰范围
Color color, // 背景色
Decoration decoration, // 背景装饰
Decoration foregroundDecoration, //前景装饰
double width,//容器的宽度
double height, //容器的高度
BoxConstraints constraints, //容器大小的限制条件
this.margin,//容器外补白,不属于decoration的装饰范围
this.transform, //变换
this.child,
})
Scaffold、TabBar、底部导航
Scaffold 是一个路由页的骨架,我们使用它可以很容易地拼装出一个完整的页面。
利用 Scaffold 实现顶部导航,底部导航以及抽屉式菜单。
实例:
class ScaffoldRoute extends StatefulWidget {
@override
_ScaffoldRouteState createState() => _ScaffoldRouteState();
}
class _ScaffoldRouteState extends State<ScaffoldRoute>
with SingleTickerProviderStateMixin{
int _selectedIndex = 1;
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
TabController? _tabController = null; //需要定义一个 Controller
List tabs = ["新闻", "历史", "图片"];
List buttomTabs = ["Home", "Business", "School"];
@override
void initState() {
super.initState();
print("initState");
_tabController = TabController(length: tabs.length, vsync: this)
..addListener(() {
double d1 = _tabController!.index.toDouble();
double d2 = _tabController!.animation!.value;
if(d1 != Null && d2 != Null && d1 == d2){
int idx = _tabController!.index;
if(idx != Null) {
print(tabs[idx]);
}
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar( //导航栏
title: Text("App Name"),
actions: <Widget>[ //导航栏右侧菜单
IconButton(icon: Icon(Icons.share), onPressed: () {}),
],
leading: Builder(builder: (context) {
return IconButton(
icon: Icon(Icons.dashboard, color: Colors.white), //自定义图标
onPressed: () {
// 打开抽屉菜单
Scaffold.of(context).openDrawer();
},
);
}),
bottom: TabBar( //生成 Tab 菜单
controller: _tabController,
tabs: tabs.map((e) => Tab(text: e)).toList()
),
),
drawer: new MyDrawer(), //抽屉
body: TabBarView( // 搭配 TabBar 实现同步切换和滑动状态同步
controller: _tabController,
children: tabs.map((e){
return Container(
alignment: Alignment.center,
child: Text(e, textScaleFactor: 5,),
);
}).toList(),
),
// bottomNavigationBar: BottomNavigationBar( //底部导航
// items: <BottomNavigationBarItem>[
// BottomNavigationBarItem(icon: Icon(Icons.home), label: buttomTabs[0]),
// BottomNavigationBarItem(icon: Icon(Icons.business), label: buttomTabs[1]),
// BottomNavigationBarItem(icon: Icon(Icons.school), label: buttomTabs[2]),
// ],
// currentIndex: _selectedIndex,
// fixedColor: Colors.blue,
// onTap: _onItemTapped,
// ),
bottomNavigationBar: BottomAppBar(
color: Colors.white,
shape: CircularNotchedRectangle(), //底部导航栏打一个圆形的洞
child: Row(
children: [
IconButton(icon: Icon(Icons.home), onPressed: () {_onItemTapped(0); },),
SizedBox(), //中间位置空出
IconButton(icon: Icon(Icons.business), onPressed: () {_onItemTapped(1); },),
],
mainAxisAlignment: MainAxisAlignment.spaceAround, //均分底部导航栏横向空间
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: FloatingActionButton( //悬浮按钮
child: Icon(Icons.add),
onPressed:_onAdd
),
);
}
void _onItemTapped(int index) {
print("tapped: " + buttomTabs[index]);
setState(() {
_selectedIndex = index;
});
}
void _onAdd() {
_scaffoldKey.currentState!.openDrawer(); //打开 drawer
}
}
class MyDrawer extends StatelessWidget {
const MyDrawer({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Drawer(
child: MediaQuery.removePadding(
context: context,
// 移除抽屉菜单顶部默认留白
removeTop: true,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 38.0),
child: Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: ClipOval(
child: Image.asset(
"image/20411648.png",
width: 80,
),
),
),
Text(
"Test Image",
style: TextStyle(fontWeight: FontWeight.bold),
)
],
),
),
Expanded(
child: ListView(
children: <Widget>[
ListTile(
leading: const Icon(Icons.add),
title: const Text('Add account'),
onTap: () {
Navigator.pop(context); //关闭 drawer
},
),
ListTile(
leading: const Icon(Icons.settings),
title: const Text('Manage accounts'),
),
],
),
),
],
),
),
);
}
}
裁剪
对组件进行裁剪。
剪裁 Widget | 右对齐 |
---|---|
ClipOval | 子组件为正方形时剪裁为内贴圆形,为矩形时,剪裁为内贴椭圆 |
ClipRRect | 将子组件剪裁为圆角矩形 |
ClipRect | 剪裁子组件到实际占用的矩形大小(溢出部分剪裁) |
// 剪裁为圆形
ClipOval(child: avatar);
// 剪裁为圆角矩形
ClipRRect(
borderRadius: BorderRadius.circular(5.0),
child: avatar,
);
// 将溢出部分剪裁
ClipRect(
child: Align(
alignment: Alignment.topLeft,
widthFactor: .5,//宽度设为原来宽度一半
child: avatar,
),
);