这些实用例子课程是从最基础的开始的,但是不是一个小组件一个小部件的教,只要装完flutter的环境的人都适合学习,开发工具用的还是vs Code,当然你们用其他的也可以。
flutter教程网送给学员的一句话: ===>课程还在继续更新~
(每天进步一点点,无形跨出一大步)
视频链接:https://www.bilibili.com/video/av44936399/ (免费,禁止倒卖)
链接: https://pan.baidu.com/s/1PWmINgrtZuI7cUbDxymX8g 提取码: rntk
学习方法:
你们看完教程推荐第一次遍的时候不要直接跟着敲,不然很多思路没有学到,或者没有跟上,第一遍推荐不要加速,不要跟着敲,直接完整看完一遍,和边思考,然后第二遍试着跟着敲,写完一个widget或者void就暂停然后跟着敲一遍出来,第二遍推荐1.5倍速度,
如果时间允许的话就第三遍直接不看教程直接敲出来试试,只为高效学习,不然思路不对怎么学都很容易忘记,浪费时间。
记录笔记:
大家有什么觉得比较重要的点可以用云笔记记录,下次写的时候忘记了可以直接拿出来,没有谁是能看一遍就直接完全记住而且多年不忘的,也没有谁是天生就什么都会的。
文档说明:
本人不创造知识,只做知识的学习者和搬运工,希望能帮到更多共同兴趣的爱好者,为flutter做出小小的贡献。
教程目录:
QQ群 or 微信群:
QQ群:874592746 (直接申请)
微信群:(秒拉进群)
01 登陆界面TextField的焦点及动作
视频链接:https://www.bilibili.com/video/av44936399/?p=2
在多个TextField中获取焦点,并通过键盘跳到下一个焦点的demo,主要讲解获取焦点,
第一个文件,main.dart
import 'package:flutter/material.dart'; import 'textfields_focus_demo.dart'; // 导包 void main() => runApp(MyApp()); class MyApp extends StatelessWidget { final Widget child; MyApp({Key key, this.child}) : super(key: key); @override Widget build(BuildContext context) { return Container( child: MaterialApp( title: 'Flutter demo', theme: ThemeData( primarySwatch: Colors.blue, primaryColor: Colors.blue, ), home: TextFieldDemo(), ), ); } }
导包之后,定义username和password的控制器和焦点,
然后给他初始化,初始化完之后就开始调用了,
/**
* 实现原理:
* 使用FocusNode获取当前textField焦点
* 在TextNode的textInputAction属性中选择键盘action(next/down)
* 对于最后一个之前的TextField:在onSubmitted属性中解除当前focus状态
* 再聚焦下一个FocusNode:FocusScope.of(context).requestFocus( nextFocusNode );
* 对于最后一个TextField,直接解除focus并提交表单
*/
import 'package:flutter/material.dart'; class TextFieldDemo extends StatefulWidget { _TextFieldDemoState createState() => _TextFieldDemoState(); } class _TextFieldDemoState extends State<TextFieldDemo> { FocusNode _namefocusNode, _pwfocusNode; TextEditingController _nameController, _pwController; @override void initState() { super.initState(); _nameController = TextEditingController(); _pwController = TextEditingController(); _namefocusNode = FocusNode(); _pwfocusNode = FocusNode(); } @override Widget build(BuildContext context) { return Scaffold( body: SafeArea(//安全区,主要兼容屏幕 child: ListView( children: <Widget>[ SizedBox(height: 80.0),// 空出80高度 Center(//居中 child: Text( 'Login', style: TextStyle(fontSize: 30.0), ), ), SizedBox(height: 80.0), Padding( padding: EdgeInsets.all(16.0), child: Material( borderRadius: BorderRadius.circular(10.0), child: TextField( focusNode: _namefocusNode, controller: _nameController, obscureText: false, textInputAction: TextInputAction.next, onSubmitted: (input) { _namefocusNode.unfocus(); FocusScope.of(context).requestFocus(_pwfocusNode); }, decoration: InputDecoration( labelText: 'name', ), ), ), ), Padding( padding: EdgeInsets.all(16.0), child: Material( borderRadius: BorderRadius.circular(10.0), child: TextField( focusNode: _pwfocusNode, controller: _pwController, obscureText: true, textInputAction: TextInputAction.done, onSubmitted: (input) { _pwfocusNode.unfocus(); }, decoration: InputDecoration( labelText: 'password' ), ), ), ), ButtonBar( children: <Widget>[ RaisedButton( onPressed: (){}, child: Text('login'), ), ], ) ], ), ), ); } }
02 chip标签系列系列第一个
视频链接:https://www.bilibili.com/video/av44936399/?p=3
*普通chip ,可以自定义样式,那个右边的×是可以点击的,
我们先定义一个main.dart,
import 'package:flutter/material.dart'; import 'chip.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', theme: new ThemeData.light(), home: ChipDemo(), ); } }
然后我们写的是一个动态类,用centerTitle给appBar居中,然后在body写个居中的Chip,里面有个CircleAvatar
import 'package:flutter/material.dart'; class ChipDemo extends StatefulWidget { _ChipDemoState createState() => _ChipDemoState(); } class _ChipDemoState extends State<ChipDemo> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('ChipDemo'), centerTitle: true, ), body: Center( child: Chip( label: Text('选择的标签'), avatar: CircleAvatar( backgroundColor: Colors.grey.shade800, child: Text('01'), ), onDeleted: (){}, ), ), ); } }
03 标签chip系列ActionChip
视频链接:https://www.bilibili.com/video/av44936399/?p=4
这边先写个main函数,跟上节课差不多的,完全是为了让小伙伴们多练习一下,大家最好是自己手动练习一遍。
import 'package:flutter/material.dart'; import 'action_chip.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', theme: new ThemeData.light(), home: ActionChipDemo(), ); } }
然后我们来写他的action_chip,action chip 主要是在chip的基础上提供了一个onPress方法,能够触发一些动作,我们这边除了用了ActionChip还用了个SnackBar,然后给他弹出一个有确定的SnackBar,主要调用了SnackBarAction,然后写了个确定,点击之后也是有返回事件的,
记得是Scaffold.of(context).showSnackBar调用出来的,我这边就直接放源码了。
import 'package:flutter/material.dart'; class ActionChipDemo extends StatefulWidget { @override _ActionChipDemoState createState() => _ActionChipDemoState(); } class _ActionChipDemoState extends State<ActionChipDemo> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('action chip demo'), ), body: Builder( builder: (BuildContext context) { return Center( child: ActionChip( label: Text('ACTION CHIP'), onPressed: () { setState(() { Scaffold.of(context).showSnackBar( SnackBar( content: Text('ON TAP'), action: SnackBarAction( label: '确定', onPressed: (){}, ), ), ); }); }, ), ); }, ), ); } }
04 标签小勾选中filter chip
视频链接:https://www.bilibili.com/video/av44936399/?p=5
这节课要写的是一个FilterChip,也就是点击那个类似于按钮的标签之后是直接变成蓝色并且显示出一个小勾勾,这个也是挺简单的,跟前面两节差距都不是很大,希望大家能get到,具体效果看前面的演示,这里我就直接来呈上main.dart了。
import 'package:flutter/material.dart'; import 'filter_chip.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', theme: new ThemeData.light(), home: FilterChipDemo(), ); } }
然后我们创建完filter_chip.dart之后,直接开始写,
原理介绍:
filter chip在被选中时会出来一个勾勾
有两点需要注意
selected接受一个bool,代表当前是否被选中
onSelected方法会自动传入一个bool,从true开始,true->false->true交替
代码:
import 'package:flutter/material.dart'; class FilterChipDemo extends StatefulWidget { _FilterChipDemoState createState() => _FilterChipDemoState(); } class _FilterChipDemoState extends State<FilterChipDemo> { bool _isSlecte = false; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('title'), ), body: Center( child: FilterChip( label: Text('文字'), selected: _isSlecte, selectedColor: Colors.blue, onSelected: (isSelecte) { setState(() { _isSlecte = isSelecte; }); }, ), ), ); } }
05 返回或退出时弹出提示信息
视频链接:https://www.bilibili.com/video/av44936399/?p=6
这个效果具体怎么实现的呢,检测页面是否被弹出的demo。
说明:
这是一个捕获页面被回pop掉的demo,通过切换main中的import查看不同页面。
其中包含使用WillPopScope实现提示dialog与form自带的dialog页面信息
每个demo前已注明实现原理,
在这里我就直接呈上main.dart文件源码了。
import 'package:flutter/material.dart'; import 'package:will_pop_scope_demo/will_pop_scpoe_demo.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', theme: new ThemeData( primarySwatch: Colors.blue, ), home: new MyHomePage(title: 'Flutter Demo Home Page'), ); } }
可能代码跟教程上的代码有点不一样,因为录制完教程忘记保存源码了,直接删除了,不过差距也不会很大,希望能帮到各位朋友。
实现原理:
使用WillPopScope组件,它会检测到子组件的Navigation的pop事件,并拦截下来。
我们需要在它的onWillPop属性中返回一个新的组件(一般是一个Dialog)处理是否真的pop该页面。
import 'dart:async'; import 'package:flutter/material.dart'; class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => new _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return WillPopScope( onWillPop: _onBackPressed, child: new Scaffold( appBar: new AppBar( title: new Text(widget.title), ), body: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ 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), ), ), ); } Future<bool> _onBackPressed() { return showDialog( context: context, builder: (context) => AlertDialog( title: Text('Do you really want to exit the app?'), actions: <Widget>[ FlatButton( child: Text('No'), onPressed: () => Navigator.pop(context, false), ), FlatButton( child: Text('Yes'), onPressed: () => Navigator.pop(context, true), ), ], )); } }
06 实现下拉刷新图片加载(上)
视频链接:https://www.bilibili.com/video/av44936399/?p=7
上拉加载,下拉刷新,这个在一些app里面很常用,我就来讲一下这个,主要运用到的是RefreshIndicator,然后就是一些循环来判断,我还是先把main.dart放上去把,
import 'package:flutter/material.dart'; import 'refresh_demo.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'refresh', theme: ThemeData( primaryColor: Colors.red, primarySwatch: Colors.red, ), home: RefreshDemo(), ); } }
然后我们就来写一下refresh_demo.dart,他是一个动态的widget类,
然后是需要导入一些包的,分别是:
import 'package:http/http.dart' as http; import 'dart:async'; import 'dart:convert';
然后这节课还需要用到环境文件,pubspec.yaml,
先要给他get到
http: ^0.12.0
这样才能继续写http插件里面的内容,之前好像是不用的,我们这里就教大家来做这个,然后就是定义两个常量一个是images是图片,还有个控制器,具体看源码,
然后我们给他初始化:
@override void initState() { super.initState(); images = List<String>(); _controller = ScrollController(); _ten(); _controller.addListener(() { if (_controller.position.pixels == _controller.position.maxScrollExtent) { _ten(); } }); }初始化之后还要有个销毁的,我们就给他写个:
@override void dispose() { super.dispose(); _controller.dispose(); }
因为录制中遇到了点小问题,这节课剩下的内容在下一节课,分开写了。
07 实现http接口图片加载(下)
视频链接:https://www.bilibili.com/video/av44936399/?p=8
这节课跟上节课是连接在一起的,因为特殊原因停止录制,所以上节课的文档是不完整的,但是main.dart是完整的,在上节课的文档里,我们这节课就来完善一下,
导入包之后写了初始化和销毁之后我们还没写获取10和获取值的void 方法,
这里我们就直接写个获取10的方法,_ten(),主要意思就是写一个i=0,然后判断i是不是小于10,如果是的话就加1,每加一次1就调用一次里面的_value(),也就是执行10次,(i+到10的时候就停止了),
具体代码:
void _ten() { for (int i = 0; i < 10; i++) { _value(); } }
这个方法调用了http中的get,所以防止阻塞我们要写个async,然后里面要写await等待,
接口链接:http://dog.ceo/api/breeds/image/random
然后我们是用一个response来接收ta,之后就是一个判断,如果他的状态码等于20,也就是ta执行成功并且接口是正常的,我们就执行里面的参数,如果不是等于200,也就是接口连接失败,就直接抛异常,写一个图片加载失败。
然后里面的数据就是给images赋值,写了个Setstate,然后取接口里面的images数据。
具体代码:
void _value() async { final response = await http.get( 'http://dog.ceo/api/breeds/image/random', ); if (response.statusCode == 200) { setState(() { images.add(json.decode(response.body)['message']); }); } else { throw Exception('图片加载失败'); } }
然后我们就写了个RefreshIndicator在body里,具体我就不分开写了,直接放整个文件的源码把,大家都能看懂。
import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:async'; import 'dart:convert'; class RefreshDemo extends StatefulWidget { _RefreshDemoState createState() => _RefreshDemoState(); } class _RefreshDemoState extends State<RefreshDemo> { List<String> images; ScrollController _controller; @override void initState() { super.initState(); images = List<String>(); _controller = ScrollController(); _ten(); _controller.addListener(() { if (_controller.position.pixels == _controller.position.maxScrollExtent) { _ten(); } }); } @override void dispose() { super.dispose(); _controller.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('RefreshDemo'), centerTitle: true, ), body: RefreshIndicator( onRefresh: () async { await Future.delayed( Duration(seconds: 1), ); images.clear(); _ten(); }, child: ListView.builder( controller: _controller, itemCount: images.length, itemBuilder: (context, index) { return Container( child: Image.network( images[index], fit: BoxFit.cover, ), ); }, ), ), ); } void _ten() { for (int i = 0; i < 10; i++) { _value(); } } void _value() async { final response = await http.get( 'http://dog.ceo/api/breeds/image/random', ); if (response.statusCode == 200) { setState(() { images.add(json.decode(response.body)['message']); }); } else { throw Exception('图片加载失败'); } } }
08 左滑删除ListView中Item
视频教程:https://www.bilibili.com/video/av44936399/?p=9
* 使用Dismissible组件实现右滑删除
* 它是根据Key来删除ListView中的某一项的
* 请注意ListView.builder中itemBuilder: (context, index)传进的index
* 他不是list中的下标,而是这个组件在当前屏幕上所占的位置
main.dart中在theme主题中给的是dark主题,
import 'package:flutter/material.dart'; import 'demo.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: '左滑删除Demo', theme: ThemeData.dark(), home: MyHomePage(), ); } }
然后来讲解下我们的demo.dart,
直接是在build上面定义了一个常量,List的list,泛型是String,
因为标题和标签用的都是字符串类型的数据,
然后是直接在list初始化的时候给他生成了长度为10的数据,内容是标签:${index},index也就是0-9,一共是10个,
List<String> list; @override void initState() { super.initState(); list = List.generate( 10, (index) => '标签:${index}', ); }
之后build的话写了一个ListView来生成,长度是直接获取了list的长度,构建了一个Dismissible,具体看源码把。
import 'package:flutter/material.dart';
class MyHomePage extends StatefulWidget {
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> list;
@override
void initState() {
super.initState();
list = List.generate(
10,
(index) => '标签:${index}',
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('左滑删除demo'),
),
body: ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) {
return Dismissible(
key: Key(list[index]),
background: Container(
color: Colors.blue,
),
direction: DismissDirection.endToStart,
onDismissed: (direction) {
setState(() {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text('${list[index]} = > 已删除'),
action: SnackBarAction(
onPressed: () {},
label: '确定',
),
),
);
list.removeAt(index);
});
},
child: ListTile(
title: Text('${list[index]}'),
),
);
},
),
);
}
}
09 Widget转化为Image截屏操作
视频链接:https://www.bilibili.com/video/av44936399/?p=10
实现原理:
1.首先我们需要一个RepaintBoundary Widget,
并将它包裹在我们需要 toImage的Widget 当中。
2.给它一个 GlobalKey作为Key ,
3.然后我们将实现一个 _capturePng()方法,他是一个Future,泛型是Uint8List
4.首先让 RenderRepaintBoundary 对象通过GlobalKey.currentContext.findRenderObject();
获取 RepaintBoundary Widget 的子树的RenderObject
然后我们可以使用 RenderRepaintBoundary的toImage 方法
将其转化为 Image,
5.获取原始图像数据后,我们将其转换为ByteData,
6.然后再将ByteData转化为Uint8List之后我们只需要使用 Image.memory(Uint8List) 就能显示获得的图像了,
具体就不说那么多了,看视频把你们,然后我就直接分享代码了。
import 'package:flutter/material.dart'; import 'widget_toimage.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: '界面转图片', theme: ThemeData.dark(), home: WidgetToImage(), ); } }
import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'dart:async'; import 'dart:ui' as ui; import 'dart:typed_data'; class WidgetToImage extends StatefulWidget { _WidgetToImageState createState() => _WidgetToImageState(); } class _WidgetToImageState extends State<WidgetToImage> { GlobalKey _globalKey = GlobalKey(); @override Widget build(BuildContext context) { return RepaintBoundary( key: _globalKey, child: Scaffold( appBar: AppBar( title: Text('界面转图片'), ), body: SingleChildScrollView( child: Column( children: <Widget>[ Container( height: 500.0, child: Image.network( 'http://www.flutterj.com/content/templates/emlog_dux/images/random/1.jpg', fit: BoxFit.cover, ), ), Container( height: 500.0, child: Image.network( 'http://www.flutterj.com/content/templates/emlog_dux/images/random/2.jpg', fit: BoxFit.cover, ), ), Container( height: 500.0, child: Image.network( 'http://www.flutterj.com/content/templates/emlog_dux/images/random/3.jpg', fit: BoxFit.cover, ), ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _capturePng, child: Icon(Icons.fullscreen), ), ), ); } Future<Uint8List> _capturePng() async { try { RenderRepaintBoundary boundary = _globalKey.currentContext.findRenderObject(); ui.Image image = await boundary.toImage( pixelRatio: 3.0, ); ByteData byteData = await image.toByteData( format: ui.ImageByteFormat.png, ); Uint8List uint8list = byteData.buffer.asUint8List(); setState(() { Navigator.of(context).push( MaterialPageRoute(builder: (context) { return Scaffold( appBar: AppBar( title: Text('查看页面'), centerTitle: true, ), body: ListView( children: <Widget>[ Image.memory( uint8list, fit: BoxFit.fitWidth, ), ], ), ); }), ); }); return uint8list; } catch (e) { print(e); } return null; } }
10 去掉水波纹的底部导航栏编写
视频链接:https://www.bilibili.com/video/av44936399/?p=11
看到很多企业的大型项目都是不需要那个水波纹效果的,而且项目经理也可能会提这种需求,所以大家还是学下为好,这个不止是可以让底部导航栏没有水波纹效果,还可以让按钮没有水波纹效果。
那具体是怎么实现的呢,来看下我们的原理:
先用theme把我们的底部导航栏组件包裹起来,
然后里面有个data,也就是数据,我们就设置个themeData(主题颜色),
然后使用局部theme强制设置splash color和highlight color为 透明色,
也就是transparent,
brightness确保与appTheme主题一致
我们看代码:
brightness: Brightness.light, splashColor: Colors.transparent, highlightColor: Colors.transparent,
import 'package:flutter/material.dart'; import 'bottom_bar.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { final Widget child; MyApp({Key key, this.child}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: '去掉水波纹', theme: ThemeData.light(), home: MyHomePage(), ); } }
然后那个底部导航栏大家在前面应该看过教程也写过,我就不多说什么了,
如果没看过的直接看视频,或者直接看代码,这里我就把home里面的代码呈上来了。
import 'package:flutter/material.dart'; class MyHomePage extends StatefulWidget { final Widget child; MyHomePage({Key key, this.child}) : super(key: key); _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { final List pages = [ Center( child: Text( '标签1', style: TextStyle( fontSize: 35.0, color: Colors.black38, ), ), ), Center( child: Text( '标签2', style: TextStyle( fontSize: 35.0, color: Colors.black38, ), ), ), ]; var currentPages; int _currentIndex = 0; @override void initState() { super.initState(); currentPages = pages[_currentIndex]; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('去掉水波纹'), centerTitle: true, ), bottomNavigationBar: Theme( data: ThemeData( brightness: Brightness.light, splashColor: Colors.transparent, highlightColor: Colors.transparent, ), child: BottomNavigationBar( currentIndex: _currentIndex, onTap: (index) { setState(() { _currentIndex = index; currentPages = pages[_currentIndex]; }); }, type: BottomNavigationBarType.fixed, items: [ BottomNavigationBarItem( icon: Icon(Icons.screen_share), title: Text('栏目1'), ), BottomNavigationBarItem( icon: Icon(Icons.screen_share), title: Text('栏目2'), ), ], ), ), body: currentPages, ); } }
11 弹出widget覆盖原界面并设定时间
视频链接:https://www.bilibili.com/video/av44936399/?p=12
这个可以弹出两个widget甚至多个widget来覆盖界面,用的是overlay组件,直接在方法里面写的调用,调用的时候需要传context,具体就不多说什么废话了,来看原理。
原理:
这个demo展示了如何使用一个简单的overlay
overlay组件能够让我们在当前页面上层覆盖一层新的组件
通过 Overlay.of(context) 我们能够获得最近的root的OverlayState
通过 overlayState.insert(overlayEntry)我们能够在当前页面上覆盖一层widget
通过 overlayEntry.remove()能够移除掉覆盖层的overlayEntry.
当add多个overlayEntry的时候,先被add的会在下面,后被add的会在上面
先把main.dart源码呈上把。
import 'package:flutter/material.dart'; import 'overlay_demo.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { final Widget child; MyApp({Key key, this.child}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: '弹出覆盖界面', theme: ThemeData( primarySwatch: Colors.blue, ), home: OverlayDemo(), ); } }
写了个overlay的方法之后里面的overlayEntry我们写的是用一个图片来覆盖,你们也可以用其他的,比如说Container里面加个颜色啊,或者列表鸭,啥的,具体看项目经理提的需求,或者用户或者自己的意愿。
这里直接呈上第二个源码文件:
import 'package:flutter/material.dart'; import 'dart:async'; class OverlayDemo extends StatefulWidget { final Widget child; OverlayDemo({Key key, this.child}) : super(key: key); _OverlayDemoState createState() => _OverlayDemoState(); } class _OverlayDemoState extends State<OverlayDemo> { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Text( '点下面看美女', style: TextStyle(fontSize: 36.0), ), ), floatingActionButton: FloatingActionButton( onPressed: () => showOverlay(context), child: Icon(Icons.zoom_in), ), ); } void showOverlay(context) async { OverlayState overlayState = Overlay.of(context); OverlayEntry overlayEntry = OverlayEntry(builder: (context) { return Center( child: Container( height: 500.0, width: 300.0, child: Image.network( 'https://s9.rr.itc.cn/r/wapChange/20164_19_11/a26nda287933407855.jpg', fit: BoxFit.cover, ), ), ); }); overlayState.insert(overlayEntry); await Future.delayed( Duration(seconds: 5), ); overlayEntry.remove(); } }
12 常用APP多屏开场介绍页面制作
视频链接:https://www.bilibili.com/video/av44936399/?p=13
用的组件:IntroViewsFlutter
页面组件:PageViewModel
依赖:intro_views_flutter: ^2.3.0
先呈上我们的main.dart
import 'package:flutter/material.dart'; import 'home_page.dart'; void main() => runApp(MyHomePage());
对,就是这样,然后我们的homepage就来讲一下,
我们首先弄好依赖环境之后要导入包,不然用不了:
import 'package:intro_views_flutter/intro_views_flutter.dart'; import 'package:intro_views_flutter/Models/page_view_model.dart';
然后我直接把用到的图片给大家把,
下载地址:https://www.lanzous.com/i3dc69i
在home那里我们要加个Build,不然他是没法跳转到介绍页面后面的页面,之后就是用IntroViewsFlutter来写里面的数据,再把我们定义好的pages放进去,pages是一个列表,泛型的是PageViewModel,所以我们页面的里面的数据也要用到PageViewModel,然后里面有很多属性,具体大家直接可以看到,然后我就不多说什么了,直接呈上源码,两个页面的,新页面和homepage的。
import 'package:flutter/material.dart'; import 'package:intro_views_flutter/intro_views_flutter.dart'; import 'package:intro_views_flutter/Models/page_view_model.dart'; import 'new_page.dart'; class MyHomePage extends StatelessWidget { final pages = [ PageViewModel( pageColor: Colors.blueAccent, mainImage: Image.asset('assets/airplane.png'), iconImageAssetPath: 'assets/air-hostess.png', title: Text('机票'), body: Text('Haselfree预订机票,取消全额退款'), ), PageViewModel( pageColor: Colors.lightGreen, mainImage: Image.asset('assets/hotel.png'), iconImageAssetPath: 'assets/waiter.png', title: Text('酒店'), body: Text('我们为您提供舒适的住宿,享受您在美丽的酒店住宿'), ), PageViewModel( pageColor: Colors.blueGrey, mainImage: Image.asset('assets/taxi.png'), iconImageAssetPath: 'assets/taxi-driver.png', title: Text('出租车'), body: Text('通过无现金支付系统轻松预订出租车门口'), ), ]; @override Widget build(BuildContext context) { return MaterialApp( title: '开场介绍', debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.blue, ), home: Builder( builder: (context) => IntroViewsFlutter( pages, onTapDoneButton: () { Navigator.push( context, MaterialPageRoute( builder: (context) => NewPage(), ), ); }, showSkipButton: true, skipText: Text('跳过'), doneText: Text('完成'), pageButtonTextStyles: TextStyle( fontSize: 18.0, color: Colors.white, ), ), ), ); } }
import 'package:flutter/material.dart'; class NewPage extends StatelessWidget { @override Widget build(BuildContext context) { return Container( child: Scaffold( appBar: AppBar( title: Text('开动介绍后'), centerTitle: true, ), body: Center( child: Text( '介绍后页面', style: TextStyle( fontSize: 35.0, ), ), ), ), ); } }
13 拖动方式重新排序项目序列号
视频链接:https://www.bilibili.com/video/av44936399/?p=14
视频链接:https://www.bilibili.com/video/av44936399/?p=15
因为接个电话不小心按暂停按到停止了,然后就重新录了一节,上面两个视频链接结合成一节课的,
这节课要讲的是通过拖动以交互方式重新排序的项目的列表,我们用的组件是ReorderableListView,先直接呈上main.dart源码把。
import 'package:flutter/material.dart'; import 'reorder_demo.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: ReorderDemo(), ); } }
然后我们写home里面的类是直接写了个scaffold的,为了时间下滑然后那个appBar也跟着隐藏就直接加在了ReorderableListView的头部里面,然后我们是只在Scaffold里写了个body参数,其他的没用。
children是直接写了个names 是list列表类型的,泛型是String,因为我们在里面的Card里面要调用的也是文本,
List<String> names = [ '排序序列号1', '排序序列号2', '排序序列号3', '排序序列号4', '排序序列号5', '排序序列号6', '排序序列号7', '排序序列号8', '排序序列号9', '排序序列号10', '排序序列号11', '排序序列号12', '排序序列号13', ];
然后我们的onReorder写的是直接调用一个内部的_onReorder,
children: names.map(_buildCard).toList(), onReorder: _onReorder,
为了给card调整我们在card外面加了个SizedBox然后高度是200,
具体我就直接呈上reorder_demo.dart
import 'package:flutter/material.dart'; class ReorderDemo extends StatefulWidget { _ReorderDemoState createState() => _ReorderDemoState(); } class _ReorderDemoState extends State<ReorderDemo> { List<String> names = [ '排序序列号1', '排序序列号2', '排序序列号3', '排序序列号4', '排序序列号5', '排序序列号6', '排序序列号7', '排序序列号8', '排序序列号9', '排序序列号10', '排序序列号11', '排序序列号12', '排序序列号13', ]; @override Widget build(BuildContext context) { return Scaffold( body: ReorderableListView( header: AppBar( title: Text('重新排序'), centerTitle: true, ), children: names.map(_buildCard).toList(), onReorder: _onReorder, ), ); } Widget _buildCard(String name) { return SizedBox( height: 200.0, key: ObjectKey(name), child: Card( color: Colors.red.withOpacity(0.5), child: Center( child: Text( '$name', style: TextStyle( fontSize: 35.0, color: Colors.white, ), ), ), ), ); } void _onReorder(int oldIndex, newIndex) { if (oldIndex < newIndex) newIndex = newIndex - 1; var name = names.removeAt(oldIndex); names.insert(newIndex, name); setState(() {}); } }
发表评论