继续来来操练Flutter的基础,对于Flutter的学习也有一段时间了,实操项目还木有做过,所以待这次基础学完之后就打算用一个项目对之前所学的进行一下巩固,不然光学这些零散的知识点最终还是不会Flutter。
3D效果:
效果:
![](https://img-blog.csdnimg.cn/img_convert/417afd59d16a9f46d27834fc7ed8172b.gif)
有点类似于翻书的效果,不过没有翻书那么立体。
具体实现:
首先绘制一个正方形:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter ListView之3d示例'),
),
body: Center(
child: Container(
color: Colors.red,
width: 200.0,
height: 200.0,
),
),
);
}
}
![](https://img-blog.csdnimg.cn/img_convert/c92709484a75298940f186999672c457.png)
给正方形加上手势:
关于手势这块在上一次https://www.cnblogs.com/webor2006/p/12649906.html已经学习过了,这里就不过多的解释了:
![](https://img-blog.csdnimg.cn/img_convert/2ccda7b4bf52650fabec887831fbc92f.png)
然后加一个手势,我们应该是要能得到当前触摸的坐标点,这里可以用这个命令参数:
![](https://img-blog.csdnimg.cn/img_convert/b549c80d480be0b1d60844114afde85c.png)
了解一下这个参数:
![](https://img-blog.csdnimg.cn/img_convert/6c863da05b670357d097002a102c81c0.png)
啥意思?也就是对于在屏幕中移动的点也能监听到,不管是垂直还是水平还着其它方向的,这里咱们可以定义一个坐标点,然后每次这个手势触发时进行坐标点的更新,如下:
![](https://img-blog.csdnimg.cn/img_convert/a4cfc4405d849dacfc2e080cc374eb0a.png)
然后再加一个双击的事件将其坐标点还原:
![](https://img-blog.csdnimg.cn/img_convert/93c34f2c5875510693e5be1b3f1af01b.png)
实现3D效果:
然后这里给加一个根据手指的位置来进行移动的动画,就是Transform,这个在之前平移时也已经学习过了:
![](https://img-blog.csdnimg.cn/img_convert/324be9c941b2ddbbf61bde61dd8d43e0.png)
运行瞅一下:
![](https://img-blog.csdnimg.cn/img_convert/62a2ba0244c94bc9469c8fd2638c8bac.gif)
抽风了一样,旋转得太快了,我们将旋转的幅度变小一点再看下:
![](https://img-blog.csdnimg.cn/img_convert/4c0c799051e62f66ee6d16a1d2f0cea6.png)
再运行:
![](https://img-blog.csdnimg.cn/img_convert/a99baef4be187c5c87438030a94dc686.gif)
但是木有3D的视角,接下来将其变为3D视角:
![](https://img-blog.csdnimg.cn/img_convert/a9422610b2392ce26b0f01d47f34a7fc.png)
最终的效果就如开始所演示的那样了。
Flipper效果:
效果:
![](https://img-blog.csdnimg.cn/img_convert/b8a4d271d8e53a8c7fa27d359e807f46.gif)
具体实现:
1、画两个矩形:
这里用Card来定义一下,这个卡片组件在之前https://www.cnblogs.com/webor2006/p/12578218.html已经学习过了,直接开撸:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Offset _offset = Offset.zero;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Flipper_widget示例'),
),
body: Card1(),
);
}
}
class Card1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Card(
color: Colors.red,
child: Container(
width: 200.0,
height: 200.0,
child: Center(
child: Text(
'点我看密码',
style: TextStyle(color: Colors.white, fontSize: 30.0),
),
)),
);
}
}
运行:
![](https://img-blog.csdnimg.cn/img_convert/fbee72fd9b32a55871eccfba8172d4e8.png)
校仿着再定义一个:
class Card2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Card(
color: Colors.blue,
child: Container(
width: 200.0,
height: 200.0,
child: Center(
child: Text(
'123456',
style: TextStyle(color: Colors.white, fontSize: 30.0),
),
)),
);
}
}
2、将其用IndexedStack包装起来:
那这两个控件需要重叠起来,类似于Android的FrameLayout, 这块在之前https://www.cnblogs.com/webor2006/p/12578218.html也已经学习过了,下面来整合一下:
![](https://img-blog.csdnimg.cn/img_convert/85053a0200df873c1c18eaf34b9c267c.png)
![](https://img-blog.csdnimg.cn/img_convert/d982ec15e19833c519437b3b620195f8.png)
4、增加手势:
![](https://img-blog.csdnimg.cn/img_convert/17c09477350bcd2c41a02ccbcb7f7e32.png)
5、执行动画:
这里就是点击之后需要执行一下翻转的效果,关于动画这块就不多解释了,这里会涉及到AnimationController、Mixin的用法,可以参考博客https://www.cnblogs.com/webor2006/p/12649906.html,直接上代码:
![](https://img-blog.csdnimg.cn/img_convert/50aac5a93c7b7a070a7f21dc07db3217.png)
接下来则需要将其应用到咱们的控件上,此时就需要使用AnimatedBuilder了,也是之前学习过的,不多说:
![](https://img-blog.csdnimg.cn/img_convert/26182e12f4e2c7dcf9480f3eac15fd31.png)
最终的效果就如之前所示了,稍稍麻烦一点,但是有之前的基础其实也容易理解,关于动画这块没有其它好办法,就得经常用才行。
ListView 3D滚动:ListWheelScrollView
效果:
![](https://img-blog.csdnimg.cn/img_convert/2f9747b6ba0672ae9e44d26679dc5e5b.gif)
还是比较拉风的
具体实现:
1、先使用一下ListWheelScrollView控件:
这里会用到ListWheelScrollView这个控件,先看一下它的简单使用:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
home: HomePage(),
));
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter listview_3d示例'),
),
body: Center(
child: ListWheelScrollView(
itemExtent:
MediaQuery.of(context).size.height * 0.6, //item的高度占整个屏幕的6成
children: List.generate(20, (i) => i)
.map((m) => Text(
m.toString(),
style: TextStyle(fontSize: 30.0),
))
.toList(),
),
));
}
}
运行:
![](https://img-blog.csdnimg.cn/img_convert/144f6df721e3767d447e9eb399284caa.gif)
其实也就是我们平常所看到的日历选择控件。
2、加入3d效果:
接下来改成3d图片的效果,先定义要加载的图片数据:
![](https://img-blog.csdnimg.cn/img_convert/bc43b2f6acda3dea3d3e07291c8d4366.png)
接下来具体的列表项则根据这个图片数据来生成:
class _HomePageState extends State<HomePage> {
var images = [
'assets/images/test.jpg',
'assets/images/test.jpg',
'assets/images/test.jpg',
'assets/images/test.jpg',
'assets/images/test.jpg',
'assets/images/test.jpg',
'assets/images/test.jpg',
'assets/images/test.jpg',
'assets/images/test.jpg',
'assets/images/test.jpg',
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter listview_3d示例'),
),
body: Center(
child: ListWheelScrollView(
perspective: 0.003,
diameterRatio: 2.0, //直径比
itemExtent:
MediaQuery.of(context).size.height * 0.6, //item的高度占整个屏幕的6成
children: images
.map((m) => Card(
clipBehavior: Clip.antiAlias,
shape: RoundedRectangleBorder(
//设置圆角
borderRadius: BorderRadius.circular(20.0)),
child: Stack(
fit: StackFit.expand,
alignment: Alignment.center,
children: <Widget>[
Image.asset(
m,
fit: BoxFit.cover,
),
Positioned(
bottom: 30.0,
left: 30.0,
child: Text(
'test',
style: TextStyle(
color: Colors.white, fontSize: 30.0),
),
)
],
),
))
.toList(),
),
));
}
}
其效果就如开头所演示的。
ListView下拉刷新&上拉加载:
对于这样的效果应该都非常之熟悉了,也是实际项目中必须要用到的,所以来看一下在Flutter中是如何来实现的。
效果:
![](https://img-blog.csdnimg.cn/img_convert/376e143435ef622b910f61f6450a74e9.gif)
具体实现:
1、生成列表数据
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
home: HomePage(),
));
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List list = new List();
@override
void initState() {
super.initState();
list = List.generate(Random().nextInt(20) + 15, (i) => 'Item $i');
}
Future<void> _refresh() {
//TODO 下拉刷新逻辑
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter 下拉刷新、上拉加载示例'),
),
body: Center(
child: RefreshIndicator(
child: ListView.builder(
itemCount: list?.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(list[index]),
);
}),
onRefresh: _refresh,
),
));
}
}
运行:
![](https://img-blog.csdnimg.cn/img_convert/6b59b6d732e95be5f13c9073c4124412.gif)
目前它就具有上拉刷新的效果了,但是我们还木有编写上拉刷新的功能,下面来实现一下。
2、实现上拉刷新:
这里就需要使用async和await来模拟异步请求了,关于async和await的知识点可以参考:https://www.cnblogs.com/webor2006/p/11994645.html, 为了看到效果这里做一个延时:
![](https://img-blog.csdnimg.cn/img_convert/a4785429e7234840b6c7fc29bbec1533.png)
看一下效果:
![](https://img-blog.csdnimg.cn/img_convert/d4bd2ba2d7b8300ecd42e81215492b28.gif)
另外对于下拉刷新的距离可以进行控制,如下:
![](https://img-blog.csdnimg.cn/img_convert/6f8ad2bfbb6cbf6fbd9aac9f9b446b01.png)
默认它是40:
![](https://img-blog.csdnimg.cn/img_convert/99954cdfba584c1470d2daa3feee54cc.png)
然后为了模拟进来列表的数据是从后台加载的,照理应该有一个loading等待的过程,所以这里可以这样改一下子:
class _HomePageState extends State<HomePage> {
List list = new List();
@override
void initState() {
super.initState();
_refresh();
}
Future<void> _refresh() async {
await Future.delayed(Duration(seconds: 3), () {
setState(() {
list =
List.generate(Random().nextInt(20) + 15, (i) => 'refresh Item $i');
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter 下拉刷新、上拉加载示例'),
),
body: Center(
child: RefreshIndicator(
displacement: 20,
child: list == null || list.isEmpty
? Center(
//如果为null则增加一个loading效果
child: CircularProgressIndicator(),
)
: ListView.builder(
itemCount: list?.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(list[index]),
);
}),
onRefresh: _refresh),
));
}
}
运行:
![](https://img-blog.csdnimg.cn/img_convert/b253543f4affae8fb138d4f0fbe3d148.gif)
3、实现下拉加载更多:
接下来则来实现一下分页加载的效果,对于分页加载RefreshIndicator就木有提供现成的效果了,此时就需要监听滑动状态了,怎么做,看下面:
class _HomePageState extends State<HomePage> {
List list = new List();
ScrollController _scrollController;
@override
void initState() {
super.initState();
_refresh();
_scrollController = ScrollController()
..addListener(() {
//判断是否滑到底
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_loadMore();
}
});
}
Future _loadMore() async {
await Future.delayed(Duration(seconds: 2), () {
setState(() {
list.addAll(
List.generate(Random().nextInt(5) + 1, (i) => 'more Item $i'));
});
});
}
Future<void> _refresh() async {
await Future.delayed(Duration(seconds: 3), () {
setState(() {
list =
List.generate(Random().nextInt(20) + 15, (i) => 'refresh Item $i');
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter 下拉刷新、上拉加载示例'),
),
body: Center(
child: RefreshIndicator(
displacement: 20,
child: list == null || list.isEmpty
? Center(
//如果为null则增加一个loading效果
child: CircularProgressIndicator(),
)
: ListView.builder(
controller: _scrollController,
itemCount: list.length + 1, //多加一个是为了实现分页效果
itemBuilder: (context, index) {
if (index == list.length) {
//如果是最后一行则增加个loading
return Padding(
padding: const EdgeInsets.all(10.0),
child: Center(
child: CircularProgressIndicator(),
),
);
}
return ListTile(
title: Text(list[index]),
);
}),
onRefresh: _refresh),
));
}
}
效果就不演示了,如最初的样子。
ListView重排序:ReorderableListView
效果:
其实就是ListView的条目重排序功能,如下:
![](https://img-blog.csdnimg.cn/img_convert/01bc74db29fd71e703635f49cec8adf2.gif)
具体实现:
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
home: HomePage(),
));
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List list = List.generate(Random().nextInt(15) + 1, (i) => 'Item $i');
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter 列表重排序示例'),
),
body: ReorderableListView(
onReorder: _onReorder, //移动的回调
children: list
.map((m) => ListTile(
key: ObjectKey(m),
title: Text(m),
))
.toList(),
));
}
_onReorder(int oldIndex, int newIndex) {
print('oldIndex: $oldIndex, newIndex: $newIndex');
}
}
运行看一下打印日志:
![](https://img-blog.csdnimg.cn/img_convert/cebf78819406cc1d9b968946ae49d291.gif)
接下来则需要处理一下回调的逻辑,达到真正的条目移动的效果,如下:
![](https://img-blog.csdnimg.cn/img_convert/2d1d2cdd16c55cac1b3a2b9a046c23f7.png)
然后运行看一下效果:
![](https://img-blog.csdnimg.cn/img_convert/47a87fbf7e06a061235eaf44ed5cb7f3.gif)
貌似妥妥的,但是接下来如果将其拖到最后一个则会报错:
![](https://img-blog.csdnimg.cn/img_convert/517b15d15294e0db091ecb003fcb8fec.gif)
总共是四个元素,而移动到最后一个位置时的index为5了,所以此时需要做一个容错判断,如下:
![](https://img-blog.csdnimg.cn/img_convert/a419442731107e17412cb39fdec108af.png)
运行:
![](https://img-blog.csdnimg.cn/img_convert/5fa7624532698530078b1adee1e1d2b5.gif)