App Store能夠說是蘋果業內設計的標杆了。git
咱們就來簡單的實現一下 App Store的首頁裏其中的一個小功能。github
先看圖:bash
能夠看到,這裏有兩點須要關注一下:markdown
實現結果:app
在Flutter中的手勢事件分爲兩層。ide
第一層有原始指針事件,它描述了屏幕上指針(例如,觸摸,鼠標和觸控筆)的位置和移動。動畫
第二層有手勢,描述由一個或多個指針移動組成的語義動做。ui
簡單的手勢處理,咱們使用 Flutter 封裝好的GestureDetector
來處理就徹底夠用。this
咱們這裏的圖片縮放效果就用GestureDetector
來處理。spa
先來看一下GestureDetector 給咱們提供了什麼樣的方法:
那咱們知道了這些方法,咱們就能夠來分析一下,哪些適合咱們作這個效果:
咱們能夠看到,當咱們的手指觸碰到卡片的時候就開始縮放,當開始移動或者擡起的時候回彈。
那咱們根據上面 GestureDetector 的方法,能夠看到 onPanDown、onPanCancel 彷佛很是適合咱們的需求。
那咱們就能夠來試一下:
監聽手勢的方法有了,那咱們下面就來寫動畫。
如何讓Card 進行縮放呢,Flutter 有一個 Widget,ScaleTransition
。
照例點開源碼看註釋:
/// Animates the scale of a transformed widget.
複製代碼
對scale進行動畫縮放的組件。
那這就結了,直接在 onPanDown、onPanCancel 方法中寫上動畫就完了:
Widget createItemView(int index) {
var game = _games[index]; // 獲取數據
// 定義動畫控制器
var _animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 200),
);
// 定義動畫
var _animation =
Tween<double>(begin: 1, end: 0.98).animate(_animationController);
return GestureDetector(
onPanDown: (details) {
print('onPanDown');
_animationController.forward(); // 點擊的時候播放動畫
},
onPanCancel: () {
print('onPanCancel');
_animationController.reverse(); // cancel的時候回彈動畫
},
child: Container(
height: 450,
margin: EdgeInsets.symmetric(horizontal: 30, vertical: 10),
child: ScaleTransition(
scale: _animation, // 定義動畫
child: Stack( // 圓角圖片爲背景,上面爲text
children: <Widget>[
Positioned.fill(
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(15)),
child: Image.asset(
game.imageUrl,
fit: BoxFit.cover,
),
),
),
Padding(
padding: const EdgeInsets.all(18.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
game.headText,
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
Expanded(
child: Text(
game.title,
style: TextStyle(
fontSize: 30,
color: Colors.white,
),
),
),
Text(
game.footerText,
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
],
),
)
],
),
)),
);
}
複製代碼
這樣就能夠完成咱們剛上圖的動畫效果了。
這裏有一個須要注意的地方是:
ListView 中必須每個 item 有一個 動畫。
否則全部的item公用一個動畫的話,點擊其中一個,全部的item 都會執行動畫效果。
點擊縮放效果咱們處理完了,下面就應該來跳轉了。
在Android中,5.0之後版本就有了元素共享,能夠實現這種效果。
在Flutter當中咱們可使用 Hero 來實現這個效果。
打開官網看介紹:
A widget that marks its child as being a candidate for hero animations.
When a PageRoute is pushed or popped with the Navigator, the entire screen's content is replaced. An old route disappears and a new route appears. If there's a common visual feature on both routes then it can be helpful for orienting the user for the feature to physically move from one page to the other during the routes' transition. Such an animation is called a hero animation. The hero widgets "fly" in the Navigator's overlay during the transition and while they're in-flight they're, by default, not shown in their original locations in the old and new routes.
To label a widget as such a feature, wrap it in a Hero widget. When navigation happens, the Hero widgets on each route are identified by the HeroController. For each pair of Hero widgets that have the same tag, a hero animation is triggered.
If a Hero is already in flight when navigation occurs, its flight animation will be redirected to its new destination. The widget shown in-flight during the transition is, by default, the destination route's Hero's child.
For a Hero animation to trigger, the Hero has to exist on the very first frame of the new page's animation. Routes must not contain more than one Hero for each tag. 複製代碼
簡單來講:
Hero動畫就是在路由切換時,有一個共享的Widget能夠在新舊路由間切換,因爲共享的Widget在新舊路由頁面上的位置、外觀可能有所差別,因此在路由切換時會逐漸過渡,這樣就會產生一個Hero動畫。
要觸發Hero動畫,Hero必須存在於新頁面動畫的第一幀。
而且一個路由裏只能有一個Hero 的 tag。
複製代碼
說了這麼多,怎麼用?
// Page 1
Hero(
tag: "avatar", //惟一標記,先後兩個路由頁Hero的tag必須相同
child: ClipOval(
child: Image.asset("images/avatar.png",
width: 50.0,),
),
),
// Page 2
Center(
child: Hero(
tag: "avatar", //惟一標記,先後兩個路由頁Hero的tag必須相同
child: Image.asset("images/avatar.png"),
),
)
複製代碼
能夠看到只須要在你想要共享的widget 前加上 Hero,寫上 tag便可。
趕忙試一下:
child: Container(
height: 450,
margin: EdgeInsets.symmetric(horizontal: 30, vertical: 10),
child: ScaleTransition(
scale: _animation,
child: Hero(
tag: 'hero',
child: Stack(
children: <Widget>[
Positioned.fill(
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(15)),
child: Image.asset(
game.imageUrl,
fit: BoxFit.cover,
),
),
),
複製代碼
運行看下:
直接黑屏了是什麼鬼?
看到了報錯信息:
There are multiple heroes that share the same tag within a subtree.
多個hero widget 使用了同一個標籤
複製代碼
那咱們改造一下:
child: Container(
height: 450,
margin: EdgeInsets.symmetric(horizontal: 30, vertical: 10),
child: ScaleTransition(
scale: _animation,
child: Hero(
tag: 'hero${game.title}',
child: Stack(
children: <Widget>[
Positioned.fill(
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(15)),
child: Image.asset(
game.imageUrl,
fit: BoxFit.cover,
),
),
),
// ........代碼省略
複製代碼
咱們使用 ListView 裏的數據來填充tag,這樣就不會重複了,運行一下:
這跳轉的時候文字下面有兩個下劃線是什麼鬼?
查了一下,是由於跳轉的時候,Flutter 把源 Hero 放在了疊加層,而疊加層裏是沒有 Theme的。
簡單理解就是疊加層裏沒有Scaffold,因此就會出現下劃線。
解決辦法以下:
在textStyle中加入 decoration: TextDecoration.none,
如今就徹底沒有問題了:
在初學Flutter 時,咱們確實會出現這樣那樣的問題。
不要心煩,點開源碼,或者去 Flutter 官網找到該類,看一下注釋和demo,問題分分鐘解決。
代碼已經傳至GitHub:github.com/wanglu1209/…