
1. 要實(shí)現(xiàn)先要了解的知識 :
(0)flutter 常用組件的使用
(1)flutter 動畫
(2) flutter 控件位置及大小如何獲取
動畫:
這里只隨口一說里邊用到的, 詳細(xì)的flutter動畫介紹后面會再整理。
(1)AnimationController: 動畫的控制: 控制動畫的開始,介紹,時長等。
(2)Animation: 動畫, 可設(shè)置監(jiān)聽動畫的狀態(tài)變化監(jiān)聽。 通過 animation.value 來獲取動畫的值。
控件位置及大小的獲取:
一句話--> 通過 BuildContext , 能拿到BuildContext就可以拿到控件相關(guān)的位置、大小等信息。
至于一個小組件的context 如果??? 一般我了解的有2種,
a. 一個是提取一個小組件為自定義widget,build里邊的context即是我們所需要的。
b. 另外一個相對簡單的就是直接通過 GlobalKey 的 .currentContext 來拿到。
key.currentContext的方式:
RenderBox renderBox = spKey.currentContext.findRenderObject();
// 這個就是控件的大小 size.width, size.height
Size size = renderBox.size;
// offset 就是坐標(biāo)相關(guān)的 offset.dx, offset.dy
Offset offset = renderBox.localToGlobal(Offset(renderBox.size.width * 0.5, renderBox.size.height * 0.5)); // 轉(zhuǎn)化為全局左邊, 這樣更容易我們后邊的處理。
通過提取Widget的方式:
相對于上邊的3行代碼, 就第一行不一樣,RenderBox renderBox = context.findRenderObject(); // context 就是widget 中的BuildContext。
2. 思路
效果是 從點(diǎn)擊的位置 在一小段時間內(nèi) 將一個圖片or紅點(diǎn)or XXX 以一個曲線的方式移動到購物車中,來給人更真實(shí)的 “雞蛋放到框里”的感覺。
我們知道這個移動的紅點(diǎn)(這里就用紅點(diǎn)了,你可以移動任何東西)移動的過程中是要跨越很多其他widget的,比如從列表第一個移動跨越整個列表到列表右下角懸浮的一個“購物車” , 或者更復(fù)雜點(diǎn)的,要移動到下邊主菜單(Tabbar)中的購物車。所以, 我們完成移動的畫布就需要是 “屏幕級”的了。 那我們就用 Stack ,用Stack在原有的頁面上疊加一個用戶看不到的“頁面”, 我們的效果動畫就在這個疊加的Widget上去完成。
動畫的實(shí)現(xiàn): (1)獲取點(diǎn)擊的按鈕的位置a, (2)獲取“購物車”的位置b 這樣我們要做的工作就變成了, 將一個紅點(diǎn)按動畫的方式 從位置a移動到位置b, 這樣問題就簡單了 。 a --> b
3. 實(shí)現(xiàn)(主要代碼)
(1)商品列表加入購物車主要代碼的實(shí)現(xiàn)
Stack(
children: <Widget>[
Container(
child: ListView(
children: goodsList.map((item) {
return GoodsItem(
item: item,
addToShoppingCart: (o) {
count++;
setState(() {
goodsOffset = o;
});
},
);
}).toList()),
),
// 這個就是我們要做動畫移動的“浮層”頁面
AddAnimationContainer(
startOffset: goodsOffset, // 點(diǎn)擊的商品位置
endOffset: spOffset, // 購物車位置
topHeight: topHeight, // 這個看實(shí)際情況,這里是頂部appBar的高度,我們做動畫的時候計算用的 (全局坐標(biāo)- 這個)
),
],
),
(2)移動動畫實(shí)現(xiàn)
double top = 0; // 要移動的紅點(diǎn)距離頂部的距離
double left = 0; // 要移動的紅點(diǎn)距離左邊的距離
AnimationController _controller;
Animation _animation;
@override
void initState() {
super.initState();
_controller =
AnimationController(duration: Duration(milliseconds: 800), vsync: this);
_animation = Tween(begin: 0, end: 1.0).animate(_controller)
..addListener(() {
setState(() {});
})
..addStatusListener((AnimationStatus status) { // 動畫狀態(tài)的監(jiān)聽
if (status == AnimationStatus.completed) { // 動畫完成以后 通知上層回調(diào)
if (widget.onEnd != null) {
widget.onEnd(this.widget);
}
}
});
WidgetsBinding.instance.addPostFrameCallback((_) { // 這個回調(diào)是在組件繪制完成后第一個調(diào)用的,且只調(diào)用一次, 這里做開啟動畫的動作
_controller.forward();
});
}
@override
void dispose() {
// 一定不要忘記做 銷毀 釋放
_controller.dispose();
_animation = null;
_controller = null;
super.dispose();
}
@override
Widget build(BuildContext context) {
// 1\. 開始的坐標(biāo)信息
var startX = widget.startOffset.dx;
var startY = widget.startOffset.dy;
// 2\. 結(jié)束點(diǎn)的坐標(biāo)信息
var endX = widget.endOffset.dx;
var endY = widget.endOffset.dy;
// 3\. 動畫值變化過程中計算 紅點(diǎn)的 實(shí)時位置
var x = startX + (endX - startX) * _animation.value;
var y = startY + (endY - startY) * _animation.value;
// 4\. 根據(jù)實(shí)時位置來確定組件的實(shí)際位置
top = y;
left = x;
return Container(
child: Positioned(
top: top - widget.topHeight,
left: left,
child: Icon( // 這 就是 所說的要移動的 “紅點(diǎn)” 組件,,,可以任意定義
Icons.arrow_drop_down_circle,
color: Colors.red,
size: 18,
),
),
);
}
4. 全部代碼
下載地址一:
https://download.csdn.net/download/chwei_cson/12032825
下載地址二:
https://github.com/chweiGitHub/flutter_demo.git