flutter入门以及常用Widget介绍
Nealyang opened this issue · 2 comments
前言
关于Flutter入门的文章非常多,常用Widget总结文章也非常多,之前写过相关入门文章,欢迎大家查看:flutter从入门到能寄几玩儿,这里我们还是以上面文章为主,先把项目跑起来,然后走马观花常用布局、常用Widget。
相关文章推荐
Flutter走马观花
关于Flutter环境问题可以查阅上方链接,步骤都非常详细。
此后~大量代码来袭
基础Widget之material版Hello world
国际惯例,hello world
import 'package:flutter/material.dart';
class MyAppBar extends StatelessWidget{
MyAppBar({this.title});//
final Widget title;
@override
Widget build(BuildContext context){
return new Container(
height: 56.0,
padding: const EdgeInsets.symmetric(horizontal:8.0),
decoration: new BoxDecoration(
color:Colors.blue[400]
),
child: Row(
children: <Widget>[
new IconButton(
icon:new Icon(Icons.menu),
tooltip:'Navigation menu',
onPressed: (){
print('点击Menu');
},
),
new Expanded(
child:new Center(
child:title
)
),
new IconButton(
icon:Icon(Icons.search),
tooltip:'Search',
onPressed: (){
print('点击搜索按钮');
},
)
],
),
);
}
}
class MyScaffold extends StatelessWidget{
@override
Widget build(BuildContext context){
return Material(
child: new Column(
children:<Widget>[
new MyAppBar(
title:new Text(
'Hello World',
style:Theme.of(context).primaryTextTheme.title
),
),
new Expanded(
child:new Center(
child:Text('Hello World!!!')
)
)
]
),
);
}
}
void main(){
runApp(
new MaterialApp(
title:'My app',
home:new MyScaffold()
)
);
}
代码地址:https://github.com/Nealyang/flutter
这个UI的确有些对不起人了,上面的title被挡住了。且先不去适配,后面我们使用Material提供的Scaffold即可
第一个例子,重点说下代码(用过的Widget记住):
- 一切都是Widget,且Widget前面的
new
可有可无。 - 类MyAppBar和MyScaffold中使用了Container、Row、Column、Text、IconButton、Icon、BoxDecoration、Center、Expanded等常用Widget
- Container一个拥有绘制、定位、调整大小的 widget。类似于div,我们可以用它来创建矩形视图,container 可以装饰为一个BoxDecoration, 如 background、一个边框、或者一个阴影。 Container 也可以具有边距(margins)、填充(padding)和应用于其大小的约束(constraints)。另外, Container可以使用矩阵在三维空间中对其进行变换。
- Row、Column其实就是flex布局中的flex-direction
- Expanded它会填充尚未被其他子项占用的的剩余可用空间。Expanded可以拥有多个children。然后使用flex参数来确定他们占用剩余空间的比例。更多细节可以参看:flutter控件Flexible和 Expanded的区别
- 先定义了一个MyAppBar的类,构造函数中接受一个Widget的title,其实我们也可以接受
String title
然后在类中自己去new Title(title)
- runApp函数接受给定的Widget并使用其作为widget根。
- widget的主要工作是实现一个build函数,用以构建自身。一个widget通常由一些较低级别widget组成。Flutter框架将依次构建这些widget,直到构建到最底层的子widget时,这些最低层的widget通常为RenderObject,它会计算并描述widget的几何形状。
基本交互之material版Hello world
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// app的根Widget
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
// 这是设置的app主题
// 运行后你可以看到app有一个蓝色的toobar,并且在不退出app的情况下修改代码会热更新
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
// 这是应用中一个基类,继承自StateFulWidget,意味着这个类拥有一个state对象,该对象里的一些字段会影响app的UI
// 这个类是state的一些配置项。通过构造函数来获取值,这个值一般在State中消费,并且使用final关键字。其实类似于react中的defaultProps
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// setState方法告诉Flutter,这个State中有些值发生了变化,以便及时将新值更新到UI上,
// 如果我不通过setState更改_count字段,那么Flutter并不会调用build匿名函数去更新界面
_counter++;
});
}
@override
Widget build(BuildContext context) {
// build方法会在每次setState的时候重新运行,例如上面的_incrementCounter方法被调用
//Flutter已经被优化了重新构建的方法,所以你只会去更新需要去更新的部分,不必去单独更新里面的一些更细小的widget,类似于React中diff
return new Scaffold(
appBar: new AppBar(
// 这里我们使用从App.build方法中初始化MyHomePage时候传入的title值来设置我们的title
title: new Text(widget.title),
),
body: new Center(
// Center是一个布局Widget,他只有一个child(区分row or cloumn等是children),并且会将child的widget居中显示
child: new Column(
// Column也是一个布局widget,他可以有多个子widget
// Column 有很多的属性去控制他的大小以及子widget的位置,这里我们使用mainAxisAlignment来让children在垂直线上居中,
// 这里的主轴就是垂直的,因为Column就是垂直方向的,这里可以大概想象为display:flex,flex-directions:column,align-item,justifyContent。。。
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'Hello World!',
style:TextStyle(
fontSize:24.0,
color: Colors.redAccent,
decorationStyle:TextDecorationStyle.dotted,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic,
decoration: TextDecoration.underline
)
),
new Text(
'You have pushed the button this many times:',
),
new Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(Icons.add),
),//最后这个逗号有利于格式化代码
);
}
}
注释上基本已经加了,这里重点说下,StatefulWidget和StatelessWidget.
- Stateless widgets 是不可变的,这意味着它们的属性不能改变——所有的值都是 final
- Stateful widgets 持有的状态可能在 widget 生命周期中发生变化,实现一个 stateful widget 至少需要两个类:1)一个 StatefulWidget 类;2)一个 State 类,StatefulWidget 类本身是不变的,但是 State 类在 widget 生命周期中始终存在
- 如果需要变化需要重新创建。StatefulWidget可以保存自己的状态。那问题是既然widget都是immutable的,怎么保存状态?其实Flutter是通过引入了State来保存状态。当State的状态改变时,能重新构建本节点以及孩子的Widget树来进行UI变化。注意:如果需要主动改变State的状态,需要通过setState()方法进行触发,单纯改变数据是不会引发UI改变的。
还有关于key的部分这里就不做介绍了,其实就类似与react中key的概念,便于diff,提高效率的。
具体可以查看 Key
到这里,我们看到了Flutter的一些基本用法,Widget的套用、样式的编写、事件的注册,如果再学习下一些路由、请求、缓存是不是就可以自己开发APP了呢!
OK,强化下编写界面,咱再来些demo吧~
布局Widget
自己写的后,发现跟官网实现方式不同,代码地址
具体实现可以参照官网教程
这里不再赘述,下面我们说下对于布局的理解和感受以及常用布局widget。
从一个前端的角度来说,说到画界面,可能还是对布局这块比较敏感
当然,这里我们还是说下目前常用的flex布局,基本拿到页面从大到小拆分后就是如上图。
所以Widget布局其实也就是Row和Column用的最多,然后由于Flutter一切皆为组件的理念,可能会需要用到别的类css布局的Widget,譬如:Container。其实咱就理解为块元素吧!
下面简单演示下一些常用的Widget,这里就不在赘述Row和Column了。传送门:布局Widget
Container
可以添加padding、margin、border、background color、通常用于装饰其他Widget
class MyHomePage extends StatelessWidget{
@override
Widget build(BuildContext context){
Container cell (String imgSrc){
return new Container(
decoration: new BoxDecoration(
border:Border.all(width:6.0,color:Colors.black38),
borderRadius: BorderRadius.all(const Radius.circular(8.0))
),
child: Image.asset(
'images/$imgSrc',
width: 180.0,
height: 180.0,
fit: BoxFit.cover,
),
);
}
return Container(
padding: const EdgeInsets.all(10.0),
color: Colors.grey,
child: new Column(
mainAxisSize: MainAxisSize.min,
children:<Widget>[
new Container(
margin: const EdgeInsets.only(bottom:10.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children:<Widget>[
cell('1.jpg'),
cell('2.jpg')
]
),
),
new Container(
margin: const EdgeInsets.only(bottom:10.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children:<Widget>[
cell('3.jpg'),
cell('4.jpg')
]
),
),
]
),
);
}
}
该布局中每个图像使用一个Container来添加一个圆形的灰色边框和边距。然后使用容器将列背景颜色更改为浅灰色。
GridView
可滚动的网格布局,理解为display:grid
GridView提供两个预制list,当GridView检测到内容太长时,会自动滚动。如果需要构建自定义grid,可是使用GridView.count
或 GridView.extent
来指定允许设置的列数以及指定项最大像素宽度。
List<Container> _buildGridTileList(int count) {
return new List<Container>.generate(
count,
(int index) =>
new Container(child: new Image.asset('images/${index+1}.jpg')));
}
Widget buildGrid() {
return new GridView.extent(
maxCrossAxisExtent: 150.0,
padding: const EdgeInsets.all(4.0),
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
children: _buildGridTileList(10));
}
class MyHomePage extends StatelessWidget{
@override
Widget build(BuildContext context){
return new Center(
child: buildGrid(),
);
}
}
如上是指定maxCrossAxisExtent
,我们可以直接去指定列数,例如官网的代码实例:
new GridView.count(
primary: false,
padding: const EdgeInsets.all(20.0),
crossAxisSpacing: 10.0,
crossAxisCount: 3,
children: <Widget>[
const Text('He\'d have you all unravel at the'),
const Text('Heed not the rabble'),
const Text('Sound of screams but the'),
const Text('Who scream'),
const Text('Revolution is coming...'),
const Text('Revolution, they...'),
],
)
通过crossAxisCount
直接指定列数。
Stack
层叠布局,position为absolute的感jio~
使用Stack来组织需要重叠的widget。widget可以完全或部分重叠底部widget。子列表中的第一个widget是base widget; 随后的子widget被覆盖在基础widget的顶部。Stack的内容不能滚动。有点类似于weex中的设置了absolute的感觉。底部组件永远在上面组件的上面。
ListView
可滚动的长列表,可以水平或者垂直。
Card
Material风格组件,卡片,AntD啥的组件库经常会出现的那种组件。
在flutter中,Card具有圆角和阴影,更改Card的elevation属性可以控制阴影效果。
ListTile
Material风格组件,我理解为常用的列表Item的样式,最多三行文字,可选的行前、行尾的图标
总结
撇开框架设计原理和**不谈,其实技术就是一个工具,所谓实践出真知,我们需要的更多是掌握查阅这些widget的技巧以及常用widget在项目实践中的熟练掌握。
对于画界面,更多的还可以参看下官网教程:Flutter for Web开发者
下面让我们正式开始我们的《Flutter入门实战:从0到1仿写web版掘金App》吧!!!
图片全部够挂了,全局代理都打不开
图片全部够挂了,全局代理都打不开
图片服务到期了吧,不如直接用GitHub