使用Flutter從頭開始構(gòu)建平滑的滾動(dòng)動(dòng)畫

介紹
在本文中,我們將介紹如何使用Flutter SDK從頭開始創(chuàng)建自定義滾動(dòng)動(dòng)畫。Flutter是一個(gè)功能強(qiáng)大的工具,用于創(chuàng)建運(yùn)行良好的本機(jī)移動(dòng)應(yīng)用程序,并且在創(chuàng)建豐富的用戶體驗(yàn)(如動(dòng)畫)方面具有極大的靈活性。
如果您沒有Flutter環(huán)境,請(qǐng)轉(zhuǎn)到安裝頁面。此處提供了本文中示例項(xiàng)目的源代碼。
入門
對(duì)于本演示,我創(chuàng)建了一個(gè)默認(rèn)的Flutter項(xiàng)目,使用flutter create并僅使用Flutter中直接可用的類,沒有向項(xiàng)目添加依賴項(xiàng)。在許多情況下,可以直接完成任務(wù)(例如自定義動(dòng)畫),而無需額外的庫。
這個(gè)示例應(yīng)用程序的基本思想是創(chuàng)建一個(gè)包含幾個(gè)項(xiàng)的簡(jiǎn)單列表視圖,并且當(dāng)視圖向上或向下滾動(dòng)時(shí),在后臺(tái)順時(shí)針/逆時(shí)針旋轉(zhuǎn)齒輪。我們將通過使用用于列表視圖的滾動(dòng)控制器并將其傳遞到動(dòng)畫背景來實(shí)現(xiàn)此目的,該動(dòng)畫背景構(gòu)建與滾動(dòng)位置同步旋轉(zhuǎn)的自定義窗口小部件。
應(yīng)用入口點(diǎn)
我們將從主應(yīng)用程序文件lib/main.dart開始:
import 'package:flutter/material.dart';
import 'demo-card.dart';
import 'items.dart';
import 'animated-bg.dart';
void main() => runApp(AnimationDemo());
class AnimationDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.deepPurple),
home: MyHomePage(title: 'Flutter Animation Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
ScrollController _controller = new ScrollController();
List<DemoCard> get _cards =>
items.map((Item _item) => DemoCard(_item)).toList();
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(title: Text(widget.title)),
body: Stack(
alignment: AlignmentDirectional.topStart,
children: <Widget>[
AnimatedBackground(controller: _controller),
Center(
child: ListView(
controller: _controller,
children: _cards
)
)
]
)
);
}
}
在main.dart文件中,有以下組件的導(dǎo)入:
- demo-card.dart (從Item構(gòu)建Card小部件)
- items.dart(為演示應(yīng)用程序定義Item類和模擬內(nèi)容)
- animated-bg.dart(構(gòu)建動(dòng)畫背景小部件)
主app類(AnimationDemo)設(shè)置一個(gè)包裝默認(rèn)主頁小部件(MyHomePage)的基本應(yīng)用程序。在MyHomePage類中有一個(gè)_controller屬性,它被初始化為ScrollController類的一個(gè)新實(shí)例,以便稍后傳遞給AnimatedBackground(它在后臺(tái)驅(qū)動(dòng)齒輪旋轉(zhuǎn)動(dòng)畫)和ListView(它呈現(xiàn)滾動(dòng)列表演示項(xiàng)目)。還有一個(gè)_cards屬性,它以items.dart中導(dǎo)入的Item對(duì)象列表開頭,并返回DemoCard列表 要在ListView中呈現(xiàn)的小部件。
物品類
我們檢查的第一個(gè)導(dǎo)入文件是lib/items.dart:
import 'package:flutter/material.dart';
class Item {
String name;
String description;
MaterialColor color;
IconData icon;
Item(this.name, this.description, this.color, this.icon);
}
List<Item> items = [
Item('A', "Something cool", Colors.amber, Icons.ac_unit),
Item('B', "Hey, why not?", Colors.cyan, Icons.add_photo_alternate),
Item('C', "This might be OK", Colors.indigo, Icons.airplay),
Item('D', "Totally awesome", Colors.green, Icons.crop),
Item('E', "Rockin out", Colors.pink, Icons.album),
Item('F', "Take a look", Colors.blue, Icons.adb)
];
該Item類提供了將沿著要的實(shí)例被傳遞的數(shù)據(jù)結(jié)構(gòu)DemoCard在要被渲染的ListView。除了名稱,描述(在演示中當(dāng)前未使用),顏色和為每個(gè)項(xiàng)目呈現(xiàn)的Icon之外,沒有太多其他內(nèi)容。Item對(duì)象列表將作為要在ListView中呈現(xiàn)的簡(jiǎn)單內(nèi)容的列表。
DemoCard類
下一個(gè)要檢查的文件是lib/demo-card.dart:
import 'package:flutter/material.dart';
import 'items.dart';
class DemoCard extends StatelessWidget {
DemoCard(this.item);
final Item item;
static final Shadow _shadow = Shadow(offset: Offset(2.0, 2.0), color: Colors.black26);
final TextStyle _style = TextStyle(color: Colors.white70, shadows: [_shadow]);
@override
Widget build(BuildContext context) {
return Card(
elevation: 3,
shape: RoundedRectangleBorder(
side: BorderSide(width: 1, color: Colors.black26),
borderRadius: BorderRadius.circular(32)
),
color: item.color.withOpacity(.7),
child: Container(
constraints: BoxConstraints.expand(height: 256),
child: RawMaterialButton(
onPressed: () { },
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Text(item.name, style: _style.copyWith(fontSize: 64)),
Icon(item.icon, color: Colors.white70, size: 72),
]
)
],
),
)
)
);
}
}
該DemoCard類接收一個(gè)Item在其構(gòu)造并返回一個(gè)卡片式Widget呈現(xiàn)Text和Icon時(shí)顯示的元素名稱和圖標(biāo)對(duì)每個(gè)項(xiàng)目定義。使用Shadow,TextStyle和RoundedRectangleBorder以及卡片的高程(設(shè)置為3)應(yīng)用一些基本樣式。Column 和Row小部件用于跨卡片中的子元素。
AnimatedBackground類
保存最后的最好,讓我們看看lib/animated-bg.dart:
import 'package:flutter/material.dart';
import 'dart:math' as math;
class AnimatedBackground extends StatefulWidget {
AnimatedBackground({Key key, this.controller}) : super(key: key);
final ScrollController controller;
@override
_AnimatedBackgroundState createState() => _AnimatedBackgroundState();
}
class _AnimatedBackgroundState extends State<AnimatedBackground> {
get offset => widget.controller.hasClients ? widget.controller.offset : 0;
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: widget.controller,
builder: (BuildContext context, Widget child) {
return OverflowBox(
maxWidth: double.infinity,
alignment: Alignment(4, 3),
child: Transform.rotate(
angle: ((math.pi * offset) / -1024),
child: Icon(Icons.settings, size: 512, color: Colors.white)
)
);
}
);
}
}
在文件的頂部,dart:math導(dǎo)入庫以獲得對(duì)π常數(shù)的訪問,以進(jìn)行旋轉(zhuǎn)齒輪的旋轉(zhuǎn)變換計(jì)算。該AnimatedBackground類構(gòu)造函數(shù)將ScrollController將驅(qū)動(dòng)旋轉(zhuǎn)。如果控制器具有客戶端(即控制器已正確初始化并連接到像ListView這樣的實(shí)際滾動(dòng)元素),_offset屬性將返回控制器偏移量,否則它將返回零。該構(gòu)建方法返回一個(gè)AnimatedBuilder,是以控制器(其被用作一個(gè)Listenable在滾動(dòng)事件上重新繪制自己)并構(gòu)建一個(gè)OverflowBox,它被對(duì)齊以將齒輪放在屏幕外。
數(shù)值4并將3齒輪放置在被測(cè)設(shè)備的左下角,即iPhone X?模擬器。在實(shí)踐中,應(yīng)根據(jù)設(shè)備屏幕的高度和寬度計(jì)算Alignment值,以提供精確值,將齒輪放置在任何設(shè)備上的所需位置(左下角,中心偏離側(cè)面等),但為此例如,我們會(huì)保持簡(jiǎn)單。
最后一個(gè)是動(dòng)畫本身發(fā)生的好部分,在Transform類的rotate靜態(tài)方法中。此類按預(yù)期將角度旋轉(zhuǎn)角度。對(duì)于這個(gè)演示,我希望齒輪緩慢移動(dòng)并感覺更多與滾動(dòng)物理網(wǎng)格相比,而不是它具有1:1值的瘋狂飛輪效果,我希望齒輪逆時(shí)針滾動(dòng),就好像它是物理駕駛列表,所以我把偏移量乘起來。然后除以-1024。
結(jié)論
本文和示例項(xiàng)目?jī)H演示了Flutter為創(chuàng)建自定義動(dòng)畫用戶體驗(yàn)提供的一些功能。只使用幾個(gè)基本類和簡(jiǎn)單的數(shù)學(xué),我們有一個(gè)完整的動(dòng)畫,為無聊的東西添加一個(gè)更有趣的元素(如設(shè)置屏幕)。
這些概念可以擴(kuò)展為創(chuàng)建啟動(dòng)畫面,加載動(dòng)畫,頁面轉(zhuǎn)換,通知效果或任何其他可以想象的內(nèi)容。幾乎任何以double參數(shù)為參數(shù)的東西都可以被動(dòng)畫化,從而可以直接實(shí)現(xiàn)效果,例如本例中使用的旋轉(zhuǎn),以及不透明度,位置和許多其他屬性。
感謝閱讀并祝你下一個(gè)Flutter項(xiàng)目好運(yùn)!
源碼:https://github.com/kenreilly/flutter-scroll-animation-demo