安装与运行 Windows 下载安装包:https://flutter.io/sdk-archive/#windows
临时镜像:
1 2 export PUB_HOSTED_URL=https://pub.flutter-io.cn export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
在Flutter安装目录的flutter
文件下找到flutter_console.bat
,双击运行并启动flutter命令行 。
将 flutter\bin
添加到环境变量 Path
。
编译桌面端
安装go:https://studygolang.com/dl
安装hover
1 2 3 go env -w GOPROXY=https://goproxy.cn set GO111MODULE=on go get -u -a github.com/go-flutter-desktop/hover
如果出现如下错误提示,则需要升级你的go版本,最低支持1.12
:
1 cmdApp.ProcessState.ExitCode undefined (type *os.ProcessState has no field or method ExitCode)
还需要确保你的电脑具有GLFW的依赖:看这里https://github.com/go-flutter-desktop/hover ,或者这里https://www.glfw.org/docs/latest/compile.html#compile_deps 。
编译 第一次将hover应用于项目时,需要初始化桌面项目。hover init
需要项目路径,通常是你在GitHub或者托管到git服务上的项目路径。如:
1 hover init github.com/iwxyi/QQ-Notification_Reply
这路径不重要以后可以随时更改 执行初始化之后将在项目中创建desktop并添加样板文件,如go代码和默认图标。 默认flutter项目是以main.dart
作为入口文件,编译桌面应用,你需要新建一个名为main_desktop.dart
的文件,并且在runApp(..);
之前加上以下代码:
1 debugDefaultTargetPlatformOverride = TargetPlatform .fuchsia;
然后执行hover run
启动程序即可,项目启动后,在命令行按r
可以热重载。 要构建一个独立的应用程序可以使用hover build
命令。输出在desktop/build/outputs/Windows
目录中。
第一个应用 本笔记来源:Flutter中文网
1、Hello World lib/main.dart
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import 'package:flutter/material.dart' ;void main() => runApp(new MyApp());class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Welcome to Flutter' , home: new Scaffold( appBar: new AppBar( title: new Text('Welcome to Flutter' ), ), body: new Center( child: new Text('Hello World' ), ), ), ); } }
2、Package pubspec.yaml
1 2 3 4 5 6 dependencies: flutter: sdk: flutter cupertino_icons: ^0.1.0 english_words: ^3.1.0
点击右上角的 Packages get,会自动下载依赖包
lib/main.dart
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import 'package:flutter/material.dart' ;import 'package:english_words/english_words.dart' ;void main() => runApp(new MyApp());class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { final wordPair = new WordPair.random(); return new MaterialApp( title: 'Welcome to Flutter' , home: new Scaffold( appBar: new AppBar( title: new Text('Welcome to Flutter' ), ), body: new Center( child: new Text(wordPair.asPascalCase), ), ), ); } }
3、State StatefulWidget类本身是不变的,但是 State类在widget生命周期中始终存在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import 'package:flutter/material.dart' ;import 'package:english_words/english_words.dart' ;void main() => runApp(new MyApp());class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { final wordPair = new WordPair.random(); return new MaterialApp( title: 'Welcome to Flutter' , home: new RandomWords(), ); } } class RandomWords extends StatefulWidget { @override createState() => new RandomWordsState(); } class RandomWordsState extends State <RandomWords > { @override Widget build(BuildContext context) { final wordPair = new WordPair.random(); return new Text(wordPair.asPascalCase); } }
4、ListView ListView设置方法:ListView.builder( itemBuilder:(context, i) { } )
1 2 3 4 5 6 7 8 9 10 Widget _buildList() { return new ListView.builder(itemBuilder: (context, i){ return new ListTile( title: new Text("text" ), trailing: new Icon(Icons.favorite), onTap: (){}, ); }); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 class RandomWordsState extends State <RandomWords > { final _suggestions = <WordPair>[]; @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text('Flutter' ), ), body: _buildSuggestions(), ); } Widget _buildSuggestions() { return new ListView.builder( padding: const EdgeInsets.all(16.0 ), itemBuilder: (context, i) { if (i.isOdd) return new Divider(); final index = i ~/ 2 ; if (index >= _suggestions.length) { _suggestions.addAll(generateWordPairs().take(10 )); } return _buildRow(_suggestions[index]); }, ); } Widget _buildRow(WordPair pair) { return new ListTile( title: new Text( pair.asPascalCase, style: new TextStyle(fontSize: 18.0 ), ), ); } }
5、onTap 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class RandomWordsState extends State <RandomWords > { final _suggestions = <WordPair>[]; final _saved = new Set <WordPair>(); Widget _buildRow(WordPair pair) { final alreadySaved = _saved.contains(pair); return new ListTile( title: new Text( pair.asPascalCase, style: new TextStyle(fontSize: 18.0 ), ), trailing: new Icon( alreadySaved ? Icons.favorite : Icons.favorite_border, color: alreadySaved ? Colors.red : null , ), onTap: (){ setState((){ if (alreadySaved) { _saved.remove(pair); } else { _saved.add(pair); } }); }, ); } }
6、Router 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 class RandomWordsState extends State <RandomWords > { final _suggestions = <WordPair>[]; final _saved = new Set <WordPair>(); final _biggerFont = const TextStyle(fontSize: 18.0 ); @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text('Flutter' ), actions: <Widget>[ new IconButton(icon: new Icon(Icons.list), onPressed: _pushSaved), ], ), body: _buildSuggestions(), ); } void _pushSaved() { Navigator.of(context).push( new MaterialPageRoute(builder: (context){ final tiles = _saved.map((pair) { return new ListTile( title: new Text( pair.asPascalCase, style: _biggerFont, ), ); }); final divided = ListTile .divideTiles( context: context, tiles: tiles ).toList(); return new Scaffold( appBar: new AppBar( title: new Text('Saved Suggestions' ), ), body: new ListView(children: divided), ); }), ); } }
7、Theme 1 2 3 4 5 6 7 8 9 10 11 12 13 class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Welcome to Flutter' , theme: new ThemeData( primaryColor: Colors.white, ), home: new RandomWords(), ); } }
完整代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 import 'package:flutter/material.dart' ;import 'package:english_words/english_words.dart' ;void main() => runApp(new MyApp());class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Welcome to Flutter' , theme: new ThemeData( primaryColor: Colors.white, ), home: new RandomWords(), ); } } class RandomWords extends StatefulWidget { @override createState() => new RandomWordsState(); } class RandomWordsState extends State <RandomWords > { final _suggestions = <WordPair>[]; final _saved = new Set <WordPair>(); final _biggerFont = const TextStyle(fontSize: 18.0 ); @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text('Flutter' ), actions: <Widget>[ new IconButton(icon: new Icon(Icons.list), onPressed: _pushSaved), ], ), body: _buildSuggestions(), ); } Widget _buildSuggestions() { return new ListView.builder( padding: const EdgeInsets.all(16.0 ), itemBuilder: (context, i) { if (i.isOdd) return new Divider(); final index = i ~/ 2 ; if (index >= _suggestions.length) { _suggestions.addAll(generateWordPairs().take(10 )); } return _buildRow(_suggestions[index]); }, ); } Widget _buildRow(WordPair pair) { final alreadySaved = _saved.contains(pair); return new ListTile( title: new Text( pair.asPascalCase, style: _biggerFont, ), trailing: new Icon( alreadySaved ? Icons.favorite : Icons.favorite_border, color: alreadySaved ? Colors.red : null , ), onTap: (){ setState((){ if (alreadySaved) { _saved.remove(pair); } else { _saved.add(pair); } }); }, ); } void _pushSaved() { Navigator.of(context).push( new MaterialPageRoute(builder: (context){ final tiles = _saved.map((pair) { return new ListTile( title: new Text( pair.asPascalCase, style: _biggerFont, ), ); }); final divided = ListTile .divideTiles( context: context, tiles: tiles ).toList(); return new Scaffold( appBar: new AppBar( title: new Text('Saved Suggestions' ), ), body: new ListView(children: divided), ); }), ); } }
Widget目录
Text
Row、Column
Stack
Container:矩形视觉元素
处理手势 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class MyButton extends StatelessWidget { @override Widget build(BuildContext context) { return new GestureDetector( onTap: () { print ('MyButton was tapped!' ); }, child: new Container( height: 36.0 , padding: const EdgeInsets.all(8.0 ), margin: const EdgeInsets.symmetric(horizontal: 8.0 ), decoration: new BoxDecoration( borderRadius: new BorderRadius.circular(5.0 ), color: Colors.lightGreen[500 ], ), child: new Center( child: new Text('Engage' ), ), ), ); } }
每次点击“Increment”按钮,显示的Count+1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class _CounterState extends State <Counter > { int _counter = 0 ; void _increment() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return new Row( children: <Widget>[ new RaisedButton( onPressed: _increment, child: new Text('Increment' ), ), new Text('Count: $_counter ' ), ], ); } }
Stateless+Stateful 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import 'package:flutter/material.dart' ;void main() { runApp(new MaterialApp( title: 'Flutter Tutorial' , home: new Counter(), )); } class CounterDisplay extends StatelessWidget { CounterDisplay({this .count}); final int count; @override Widget build(BuildContext context) { return new Text('Count: $count ' ); } } class CounterIncrementor extends StatelessWidget { CounterIncrementor({this .onPressed}); final VoidCallback onPressed; @override Widget build(BuildContext context) { return new RaisedButton( onPressed: onPressed, child: new Text('Increment' ), ); } } class Counter extends StatefulWidget { @override _CounterState createState() => new _CounterState(); } class _CounterState extends State <Counter > { int _counter = 0 ; void _increment() { setState(() { ++_counter; }); } @override Widget build(BuildContext context) { return new Row(children: <Widget>[ new CounterIncrementor(onPressed: _increment), new CounterDisplay(count: _counter), ]); } }
购物车示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import 'package:flutter/material.dart' ;class Product { const Product({this .name}); final String name; } typedef void CartChangedCallback(Product product, bool inCart);class ShoppingListItem extends StatelessWidget { ShoppingListItem({Product product, this .inCart, this .onCartChanged}) : product = product, super (key: new ObjectKey(product)); final Product product; final bool inCart; final CartChangedCallback onCartChanged; Color _getColor(BuildContext context) { return inCart ? Colors.black54 : Theme.of(context).primaryColor; } TextStyle _getTextStyle(BuildContext context) { if (!inCart) return null ; return new TextStyle( color: Colors.black54, decoration: TextDecoration.lineThrough, ); } @override Widget build(BuildContext context) { return new ListTile( onTap: () { onCartChanged(product, !inCart); }, leading: new CircleAvatar( backgroundColor: _getColor(context), child: new Text(product.name[0 ]), ), title: new Text(product.name, style: _getTextStyle(context)), ); } }
该ShoppingListItem
widget是无状态的。它将其在构造函数中接收到的值存储在final
成员变量中,然后在build
函数中使用它们。 例如,inCart
布尔值表示在两种视觉展示效果之间切换:一个使用当前主题的主色,另一个使用灰色。
当用户点击列表项时,widget不会直接修改其inCart
的值。相反,widget会调用其父widget给它的onCartChanged
回调函数。 此模式可让您在widget层次结构中存储更高的状态,从而使状态持续更长的时间。在极端情况下,存储传给runApp
应用程序的widget的状态将在的整个生命周期中持续存在。
当父项收到onCartChanged
回调时,父项将更新其内部状态,这将触发父项使用新inCart
值重建ShoppingListItem
新实例。 虽然父项ShoppingListItem
在重建时创建了一个新实例,但该操作开销很小 ,因为Flutter框架会将新构建的widget与先前构建的widget进行比较,并仅将差异部分应用于底层RenderObject
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 class ShoppingList extends StatefulWidget { ShoppingList({Key key, this .products}) : super (key: key); final List <Product> products; @override _ShoppingListState createState() => new _ShoppingListState(); } class _ShoppingListState extends State <ShoppingList > { Set <Product> _shoppingCart = new Set <Product>(); void _handleCartChanged(Product product, bool inCart) { setState(() { if (inCart) _shoppingCart.add(product); else _shoppingCart.remove(product); }); } @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text('Shopping List' ), ), body: new ListView( padding: new EdgeInsets.symmetric(vertical: 8.0 ), children: widget.products.map((Product product) { return new ShoppingListItem( product: product, inCart: _shoppingCart.contains(product), onCartChanged: _handleCartChanged, ); }).toList(), ), ); } } void main() { runApp(new MaterialApp( title: 'Shopping App' , home: new ShoppingList( products: <Product>[ new Product(name: 'Eggs' ), new Product(name: 'Flour' ), new Product(name: 'Chocolate chips' ), ], ), )); }
当这个widget的父级重建时,父级将创建一个新的ShoppingList
实例,但是Flutter框架将重用已经在树中的_ShoppingListState
实例,而不是再次调用 createState
创建一个新的。
要访问当前ShoppingList
的属性,_ShoppingListState
可以使用它的widget
属性。 如果父级重建并创建一个新的ShoppingList,那么 _ShoppingListState
也将用新的widget
值重建。如果希望在widget
属性更改时收到通知,则可以覆盖didUpdateWidget
函数,以便将旧的oldWidget
与当前widget
进行比较。
生命周期事件 在StatefulWidget调用createState
之后,框架将新的状态对象插入树中,然后调用状态对象的initState
。 子类化State可以重写initState
,以完成仅需要执行一次的工作。
Key 使用key来控制框架将在widget重建时与哪些其他widget匹配。要求两个widget具有相同的key
和runtimeType
。
如果没有key,当前构建中的第一个条目将始终与前一个构建中的第一个条目同步
通过给列表中的每个条目分配为“语义” key,无限列表可以更高效,因为框架将同步条目与匹配的语义key并因此具有相似(或相同)的可视外观。此外,语义上同步条目意味着在有状态子widget中,保留的状态将附加到相同的语义条目上,而不是附加到相同数字位置上的条目。
全局 Key 使用全局key来唯一标识子widget。全局key在整个widget层次结构中必须是全局唯一的,这与局部key不同,后者只需要在同级中唯一。由于它们是全局唯一的,因此可以使用全局key来检索与widget关联的状态。
零散控件笔记 详细说明:NestedScrollView类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 final _tabs = ['Tab1' , 'Tab2' , 'Tab3' ];DefaultTabController( length: _tabs.length, child: NestedScrollView( headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { return <Widget>[ SliverOverlapAbsorber( handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), child: SliverAppBar( title: const Text('Books' ), pinned: true , expandedHeight: 150.0 , forceElevated: innerBoxIsScrolled, bottom: TabBar( tabs: _tabs.map((String name) => Tab(text: name)).toList(), ), ), ), ]; }, body: TabBarView( children: _tabs.map((String name) { return SafeArea( top: false , bottom: false , child: Builder( builder: (BuildContext context) { return CustomScrollView( key: PageStorageKey<String >(name), slivers: <Widget>[ SliverOverlapInjector( handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), ), SliverPadding( padding: const EdgeInsets.all(8.0 ), sliver: SliverFixedExtentList( itemExtent: 48.0 , delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { return ListTile( title: Text('Item $index ' ), ); }, childCount: 30 , ), ), ), ], ); }, ), ); }).toList(), ), ), )