国庆后面两天在家学习整理了一波flutter,基本把能撸过能看到的代码都过了一遍, 此文篇幅较长,建议保存(star)再看。传送门: Nealyang personal blog
毕竟前端出生,找(qi)到(shi)了(bing)感(mei)觉(ru)后(men),其实就是一个UI框架,只不过他的引擎基于C++,底层基于Skia渲染,DartVM虚拟机以及Text and so on...css
2018年6月21日Google发布Flutter首个release预览版,做为Google baba大力推出的一种全新的响应式,跨平台,高性能的移动开发框架,势必会火一波~没别的,就是由于Google baba,固然,从目前看来也的确愈来愈火了。
[Questions tagged [flutter]](https://stackoverflow.com/que...html
本文咱们从介绍flutter基本概念到梳理经常使用Widget到经常使用app demos编写到~放弃~,但愿能够帮助每个像我同样的初学者。有误地方还望大神不吝赐教~前端
国际惯例,吹一波先~react
直接移步Flutter官宣pptandroid
做为Flutter入门文章,Dart必然少不了,固然,做为Flutter入门篇,Dart预发基础必然不会过多介绍。git
Dart入门传送门:Dart or Dart2,或者从Dart中文网中学习也不错其实.github
这里咱们说说为啥是Dart。web
许多语言科学家认为,一我的说的天然语言会影响他们的思惟方式。早起Flutter团队评估了十多种语言最终选择了Dart,由于它符合他们构建用户界面的方式。编程
具体选择Dart的缘由,以及向了解Dart的,移步为何Flutter会选择 Dart浏览器
刚开始接触flutter心中不免会有疑惑,不是已经有RN、Weex等各类跨平台移动开发 了,flutter优点在哪呢? 看我从网上盗的图!
有一种说法认为函数式语言和命令式语言的不一样在于命令式语言是给计算机下达指令而函数式语言是向计算机描述逻辑。这种思路在Flutter UI中获得了体现。Flutter不提倡去操做UI,它固然也基本不会提供操做View的API,好比咱们常见的相似TextView.setText(),Button.setOnClick()这种是不会有的。对界面的描述是能够数据化的(相似XML,JSON等),而对界面的操做是很难数据化的,这很重要,响应式须要方即可持续的将数据映射成界面。
在Flutter中用Widget来描述界面,Widget只是View的“配置信息”,编写的时候利用Dart语言一些声明式特性来获得相似结构化标记语言的可读性。Widget根据布局造成一个层次结构。每一个widget嵌入其中,并继承其父项的属性。没有单独的“应用程序”对象,相反,根widget扮演着这个角色。在Flutter中,一切皆为Widget,甚至包括css样式。
<div class="greybox"> Lorem ipsum </div> .greybox { background-color: #e0e0e0; /* grey 300 */ width: 320px; height: 240px; font: 900 24px Georgia; }
在flutter中咱们编写为
var container = new Container( // grey box child: new Text( "Lorem ipsum", style: new TextStyle( fontSize: 24.0 fontWeight: FontWeight.w900, fontFamily: "Georgia", ), ), width: 320.0, height: 240.0, color: Colors.grey[300], );
能够看到咱们css样式中的font定义的样式,在flutter中,须要new TextStyle
,TextStyle就是一个Widget,而且样式必须做用与Container中的child:text上,不存在web中样式的继承。
刚开始接触的同窗就类比于react中扯的,一切皆为组件吧,其实widget是对页面UI的一种描述。他功能类有点似于android中的xml,react中的jsx。widget在渲染的时候会转化成element。Element相比于widget增长了上下文的信息。element是对应widget,在渲染树的实例化节点。因为widget是immutable的,因此同一个widget能够同时描述多个渲染树中的节点。可是Element是描述固定在渲染书中的某一个特定位置的点。简单点说widget做为一种描述是能够复用的,可是element却跟须要绘制的节点一一对应。那element是最终渲染的view么?抱歉,还不是。element绘制时会转化成rendObject。RendObject才是真正通过layout和paint并绘制在屏幕上的对象。在flutter中有三套渲染相关的tree,分别是:widget tree, element tree & rendObject tree。三者的渲染流程以下:
有没有一种 jsx -> virtual Dom -> real dom滴感受呢~
咳咳,后面会介绍基础经常使用的Widget配合一些demo,你们可能对这个体会就会更加清晰一些。
Flutter中不少借鉴了react的思想,甚至包括后面会说到的state。
Widget自己一般由许多更小的、单一的小小widget组成,甚至小到它单一下来并无什么做用的感受,这些Widget几几组合造成一个强大的自定义的大大Widget。
好比一个Container,对于Web FE来讲可能就是个div,而他就是由不少的widget组成,这些widget负责布局、绘制、定位、大小等。咱们可使用各类姿式来组合他们而不是继承他们。类层次结构很浅且很宽,能够最大限度的增长可能组合的数量
上面的图片是Flutter分层框架结构图,对大部分开发者而言,最经常使用的是Widgets层,屏幕上可见与不可见的元素都由Widgets层实现,这些元素被称为Widget。在Widgets层在上层,有两个现成的Widget库,Material库即Material Design的Widget库,Material Design是Google I/O 2014发布的设计语言,目前成为统一Android Mobile、Android Table、Desktop Chrome等平台的设计语言规范。Cupertino库则是一个模仿iOS设计风格的Widget库。
底层是Flutter Engine虚拟机,在这一层次中须要了解一下的是Skia,Skia是Google研发的包括图形、文本、图像、动画等多方面的图形引擎,不只用于Google Chrome浏览器,Android系统也采用Skia做为绘图处理引擎。
GPU渲染:
state生命周期:
做为初学者看上面的图有点云里雾里的,且先作到内心有数~
关于Flutter环境问题这里再也不赘述
此后~大量代码来袭
国际惯例,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记住):
new
无关紧要。类MyAppBar和MyScaffold中使用了Container、Row、Column、Text、IconButton、Icon、BoxDecoration、Center、Expanded等经常使用Widget
String title
而后在类中本身去new Title(title)
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.
还有关于key的部分这里就不作介绍了,其实就相似与react中key的概念,便于diff,提升效率的。
具体能够查看 Key
到这里,咱们看到了Flutter的一些基本用法,Widget的套用、样式的编写、事件的注册,若是再学习下一些路由、请求、缓存是否是就能够本身开发APP了呢
OK,强化下编写界面,咱再来些demo吧~
本身写的后,发现跟官网实现方式不一样,代码地址
具体实现能够参照官网教程
这里再也不赘述,下面咱们说下对于布局的理解和感觉以及经常使用布局widget。
从一个前端的角度来讲,说到画界面,可能仍是对布局这块比较敏感
)
固然,这里咱们仍是说下目前经常使用的flex布局,基本拿到页面从大到小拆分后就是如上图。
因此Widget布局其实也就是Row和Column用的最多,而后因为Flutter一切皆为组件的理念,可能会须要用到别的类css布局的Widget,譬如:Container。其实咱就理解为块元素吧!
下面简单演示下一些经常使用的Widget,这里就不在赘述Row和Column了。传送门:布局Widget
能够添加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来添加一个圆形的灰色边框和边距。而后使用容器将列背景颜色更改成浅灰色。
可滚动的网格布局,理解为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
直接指定列数。
层叠布局,position为absolute的感jio~
使用Stack来组织须要重叠的widget。widget能够彻底或部分重叠底部widget。子列表中的第一个widget是base widget; 随后的子widget被覆盖在基础widget的顶部。Stack的内容不能滚动。有点相似于weex中的设置了absolute的感受。底部组件永远在上面组件的上面。
可滚动的长列表,能够水平或者垂直。
Material风格组件,卡片,AntD啥的组件库常常会出现的那种组件。
在flutter中,Card具备圆角和阴影,更改Card的elevation属性能够控制阴影效果。
Material风格组件,我理解为经常使用的列表Item的样式,最多三行文字,可选的行前、行尾的图标
从目前我我的浅薄的Flutter技能来讲,最大的困难多是找不到合适的Widget去实现想要的布局或者效果,甚至包括css样式做用于那个Widget,譬如Opacity是一个widget而不是一个css样式~
因此对于Flutter,咱们仍是要多折腾,多些demo,相似网上不少仿xxxApp等~
对于画界面,更多的还能够参看下官网教程:Flutter for Web开发者
Flutter一切基于Widget,搞定widget就比如,搞定英语单词同样,单词、词组都贼6了还怕英语?
别急别急,借用张晟哥的图来给你们消消火气~
因此说,Flutter有一个庞大的组件体系,须要花费很是多的时间去梳理。
!更重要的是:多实践
原本最后一章是本身写的一个demo的讲解~
惋惜时间评估不许确,漏评估了假期惰性。。。考虑篇幅,后面补上仿XXX的Demo吧~~
Demo 推荐