该文章只是自己这两天看电子书的总结,如果想看一手知识的可以转到电子书https://book.flutterchina.club/chapter7/theme.html。前端开发无非就两步第一是布局、第二是请求数据刷新界面,这两天看完感觉至少写一些简单的界面是没问题了。剩下的请求以及一些第三方库有时间再继续。对于没有打过Flutter代码的朋友我建议看完至少还要自己动手打一下,不用全打,因为打跟看是两回事,有时候看懂了打的时候还是要看一下。
动态组件,后期需要改变显示状态的需要用动态组件
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
@override
Widget build(BuildContext context) {
return Container();
}
}
@override
void initState() {
_controller = AnimationController(vsync: this);
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container();
}
...
静态组件定义后不会再改变,一般很少用到。
Widget(组件)是Element(元素)的封装,flutter最后渲染的是Element。不涉及到原理时了解一下即可
分为四种,分别是基础类组件、布局类组件、容器类组件、滚动类组件
基础组件分为**文本(Text)、图片(Image)、按钮(xxButton)、输入框(TextField)、单选框(Switch)与复选框(CheckBox)、表单(Form)**等等。基本格式为:
//文本
Text("Hello world! I'm Jack. "*4,
maxLines: 1,
overflow: TextOverflow.ellipsis,
);
//按钮的种类有很多
RaisedButton(
child: Text("normal"),
onPressed: () => {},
);
可以发现格式都是类似的,具体不一一讲述,属性类型跟移动端的控件是类似的。唯一需要看的是表单组件
表单:为了方便上传数据时不用对每个输入框都做判断。flutter提供了表单组件。
简单理解:表单给给个输入框提供一个判断是否验证同的属性validator。并且提供提交按钮一个出发的方法触发回调具体代码可以下面地址的介绍
https://book.flutterchina.club/chapter3/input_and_form.html
水平线性布局(Row)、垂直线性布局(Column)、弹性布局(Flex)、流式布局(Wrap|Flow)、层叠布局(Stack|Positioned)
水平跟垂直都是基层与Flex。基本格式如下
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max, //有效,外层Colum高度为整个屏幕
children: [
Container(
color: Colors.red,
child: Column(
mainAxisSize: MainAxisSize.max,//无效,内层Colum高度为实际高度
children: [
Text("hello world "),
Text("I am Jack "),
],
),
)
],
)
基本上跟Android的线性布局一样
例如下面的代码会按1:2比例占据屏幕
Flex(
direction: Axis.horizontal,
children: [
Expanded(
flex: 1,
child: Container(
height: 30.0,
color: Colors.red,
),
),
Expanded(
flex: 2,
child: Container(
height: 30.0,
color: Colors.green,
),
),
],
)
容器类与布局类不同的地方在于一般容器类只接受一个子组件。用于修饰、变换、限制大小、设置边距等等
跟移动端不一样的是,flutter的Padding也是单独抽出来的组件。格式如下
Padding(
//上下左右各添加16像素补白
padding: EdgeInsets.all(16.0),
child: Column()
用于限制组件的最大最小值,格式如下,一个是限制条件的属性、一个是child放的内容
ConstrainedBox(
constraints: BoxConstraints(minWidth: 60.0, minHeight: 100.0), //父
child: UnconstrainedBox( //“去除”父级限制
child: ConstrainedBox(
constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),//子
child: redBox,
),
)
)
类似于Android的shape,可以设置圆角、渐变、阴影等等。格式如下
DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(colors:[Colors.red,Colors.orange[700]]), //背景渐变
borderRadius: BorderRadius.circular(3.0), //3像素圆角
boxShadow: [ //阴影
BoxShadow(
color:Colors.black54,
offset: Offset(2.0,2.0),
blurRadius: 4.0
)
]
),
child: Padding(padding: EdgeInsets.symmetric(horizontal: 80.0, vertical: 18.0),
child: Text("Login", style: TextStyle(color: Colors.white),),
)
)
旋转(rotate)、平移(translate)、缩(scale)
DecoratedBox(
decoration:BoxDecoration(color: Colors.red),
child: Transform.rotate(
angle:90 ,
child: Text("Hello world"),
),
);
这种方式的旋转不会执行build方法,所以背景不会改变性能也较好一些,我的理解是它仅仅改变了child的值,而如果要改变全部则使用RotatedBox
DecoratedBox(
decoration: BoxDecoration(color: Colors.red),
//将Transform.rotate换成RotatedBox
child: RotatedBox(
quarterTurns: 1, //旋转90度(1/4圈)
child: Text("Hello world"),
),
这里没有讲的一个是透明度的变换
Opacity(
opacity: 0.1,
child: new Container(
width: 250.0,
height: 100.0,
decoration: new BoxDecoration(
backgroundColor: const Color(0xff000000),
),
)
这个容器比较强大的是它有padding跟margin以及变换等等不过底层也是用上面的控件实现的
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,
padding,
margin,
})
该容器应该不陌生,项目一创建是就有。它是一个脚手架容器,就是很多容器都定义好了。只要跟着写就有相应的效果,先看看代码
Scaffold(
appBar: AppBar( //导航栏
title: Text("App Name"),
actions: [ //导航栏右侧菜单
IconButton(icon: Icon(Icons.share), onPressed: () {}),
],
),
drawer: new MyDrawer(), //抽屉
bottomNavigationBar: BottomNavigationBar( // 底部导航
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(icon: Icon(Icons.business), title: Text('Business')),
BottomNavigationBarItem(icon: Icon(Icons.school), title: Text('School')),
],
currentIndex: _selectedIndex,
fixedColor: Colors.blue,
onTap: _onItemTapped,
),
floatingActionButton: FloatingActionButton( //悬浮按钮
child: Icon(Icons.add),
onPressed:_onAdd
),
);
从Scaffold的下一级子控件来看,有导航栏(appbar)、侧边栏(drawer)、底部导航栏(bottomNavigationBar)、body(内容)
Android里面也有appbar,效果可以说是一样的属性也类似。
AppBar({
Key key,
this.leading, //导航栏最左侧Widget,常见为抽屉菜单按钮或返回按钮。
this.automaticallyImplyLeading = true, //如果leading为null,是否自动实现默认的leading按钮
this.title,// 页面标题
this.actions, // 导航栏右侧菜单
this.bottom, // 导航栏底部菜单,通常为Tab按钮组
this.elevation = 4.0, // 导航栏阴影
this.centerTitle, //标题是否居中
this.backgroundColor,
... //其它属性见源码注释
})
可能用的比较多的是Appbar下的TabBar。与Android中的TabBar是类似的
bottom: TabBar(
controller: _tabController,
tabs: tabs.map((e) => Tab(text: e)).toList())
跟正常使用容器组件没什么差别
Drawer(
child: Container(
padding: EdgeInsets.zero,
children: [
),
//中间有圆弧的效果
bottomNavigationBar: BottomAppBar(
color: Colors.white,
shape: CircularNotchedRectangle(), // 底部导航栏打一个圆形的洞
child: Row(
children: [
IconButton(icon: Icon(Icons.home)),
SizedBox(), //中间位置空出
IconButton(icon: Icon(Icons.business)),
],
mainAxisAlignment: MainAxisAlignment.spaceAround, //均分底部导航栏横向空间
),
)
SingleChildScrollView、ListView、GridView、ConstomScrollView以及滚动监听ScrollController
SingleChildScrollView({
this.scrollDirection = Axis.vertical, //滚动方向,默认是垂直方向
this.reverse=false,//滚动方向是否反向
this.padding,//边距bool primary,//这是否是与父控件关联的主滚动视图 应该是是否与父控件一起滑动 用来解决滑动冲突
this.physics,//滑动松手后的滑动方式
this.controller,
this.child,//子view
})
Scrollbar(
child: SingleChildScrollView(
padding: EdgeInsets.all(16.0),
child: Center(
child: Column(
//动态创建一个List
children: str.split("")
//每一个字母都用一个Text显示,字体为原来的两倍
.map((c) => Text(c, textScaleFactor: 2.0,))
.toList(),
),
),
)
SingleChildScrollView只能接收一个组件,如果在外面添加Scrollbar的话会有滚动条,不加则没有。
ListView跟Android的Listview是类似的都是用于展示列表数据的
ListView({
...
//可滚动widget公共参数
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
EdgeInsetsGeometry padding,
//ListView各个构造函数的共同参数
double itemExtent,
bool shrinkWrap = false,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
double cacheExtent,
//子widget列表
List children = const [],
})
ListView(
shrinkWrap: true,
padding: const EdgeInsets.all(20.0),
children: [
const Text('列表数据'),
],
);
class ListViewDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
//下划线widget预定义以供复用。
Widget divider1=Divider(color: Colors.blue,);
Widget divider2=Divider(color: Colors.green);
return ListView.separated(
itemCount: 100,
//列表项构造器
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("$index"));
},
//分割器构造器
separatorBuilder: (BuildContext context, int index) {
return index%2==0?divider1:divider2;
},
);
}
}
网格布局同样与Android里面的类似。用法也与ListView大致相同
class _GridViewLayoutState extends State {
@override
Widget build(BuildContext context) {
return GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, //横轴三个子widget
childAspectRatio: 1.0 //宽高比为1
),
children:[
Icon(Icons.ac_unit),
Icon(Icons.airport_shuttle),
Icon(Icons.all_inclusive),
Icon(Icons.beach_access),
Icon(Icons.cake),
Icon(Icons.free_breakfast)
]
);
}
}
不想以后看回来东西太多,点到为止。网格布局只能实现规则的网格。如果要实现瀑布流效果。有个开源的库https://github.com/letsar/flutter_staggered_grid_view
用于把滚动控件放在同一个容器上做到滚动效果一致的胶水控件。它的子控件有对应的滚动控件实现Sliver
Sliver有细片、小片之意,在Flutter中,Sliver通常指具有特定滚动效果的可滚动块。可滚动widget,如ListView、GridView等都有对应的Sliver实现如SliverList、SliverGrid等贴上一段md效果的代码,感觉效果还不错
import 'package:flutter/material.dart';
class CustomScrollViewTestRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
//因为本路由没有使用Scaffold,为了让子级Widget(如Text)使用
//Material Design 默认的样式风格,我们使用Material作为本路由的根。
return Material(
child: CustomScrollView(
slivers: [
//AppBar,包含一个导航栏
SliverAppBar(
pinned: true,
expandedHeight: 250.0,
flexibleSpace: FlexibleSpaceBar(
title: const Text('Demo'),
background: Image.asset(
"./images/avatar.png", fit: BoxFit.cover,),
),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
sliver: new SliverGrid( //Grid
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, //Grid按两列显示
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 4.0,
),
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
//创建子widget
return new Container(
alignment: Alignment.center,
color: Colors.cyan[100 * (index % 9)],
child: new Text('grid item $index'),
);
},
childCount: 20,
),
),
),
//List
new SliverFixedExtentList(
itemExtent: 50.0,
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
//创建列表项
return new Container(
alignment: Alignment.center,
color: Colors.lightBlue[100 * (index % 9)],
child: new Text('list item $index'),
);
},
childCount: 50 //50个列表项
),
),
],
),
);
}
}
前端开发应该都熟悉,在flutter一般监听代码会放在initState中。因为initState不会多次执行。
ScrollController _controller = new ScrollController();
bool showToTopBtn = false; //是否显示“返回到顶部”按钮
@override
void initState() {
//监听滚动事件,打印滚动位置
_controller.addListener(() {
print(_controller.offset); //打印滚动位置
if (_controller.offset < 1000 && showToTopBtn) {
setState(() {
showToTopBtn = false;
});
} else if (_controller.offset >= 1000 && showToTopBtn == false) {
setState(() {
showToTopBtn = true;
});
}
});
}
在flutter中,一切皆组件。所谓的功能型组件其实指的是如事件监听、数据存储等。这种跟界面不是很想关,所以分开理解
有时候点击两次过快时可能是误点需要做处理,就用到这个。
WillPopScope(
onWillPop: () async {
if (_lastPressedAt == null ||
DateTime.now().difference(_lastPressedAt) > Duration(seconds: 1)) {
//两次点击间隔超过1秒则重新计时
_lastPressedAt = DateTime.now();
return false;
}
return true;
},
child: Container(
alignment: Alignment.center,
child: Text("1秒内连续按两次返回键退出"),
)
);
onWillPop是一个回调函数,当用户点击返回按钮时调用(包括导航返回按钮及Android物理返回按钮),该回调需要返回一个Future对象,如果返回的Future最终值为false时,则当前路由不出栈(不会返回),最终值为true时,当前路由出栈退出。我们需要提供这个回调来决定是否退出。
感觉有点像Android的Eventbus
didChangeDependencies这个回调函数当数值变化时会回调。如果封装一下即可实现切换语言和主题等等。具体例子看https://book.flutterchina.club/chapter7/inherited_widget.html。
主题有些代码中会用到,记录一下不会陌生
ThemeData({
Brightness brightness, //深色还是浅色
MaterialColor primarySwatch, //主题颜色样本,见下面介绍
Color primaryColor, //主色,决定导航栏颜色
Color accentColor, //次级色,决定大多数Widget的颜色,如进度条、开关等。
Color cardColor, //卡片颜色
Color dividerColor, //分割线颜色
ButtonThemeData buttonTheme, //按钮主题
Color cursorColor, //输入框光标颜色
Color dialogBackgroundColor,//对话框背景颜色
String fontFamily, //文字字体
TextTheme textTheme,// 字体主题,包括标题、body等文字样式
IconThemeData iconTheme, // Icon的默认样式
TargetPlatform platform, //指定平台,应用特定平台控件风格
...
})
最开始坚持的地方,记录学习与生活的点点滴滴