現在的Flutter正是如火中天,昨天Google官方正式釋出了
Flutter1.7
版本,主要包含了對Android X的支援和Play Store的一些更新,一些新的和增強的元件,以及一些問題的修復。
本篇文章我們一起開發一個炫炫的列表展示,伴隨著滾動,背景做一些相應的動畫效果。先看下效果圖:
思路
列表滾動的時候,獲取垂直方向的滾動距離,再將這個值轉化成角度單位帶動齒輪的滾動
入口檔案
Flutter的專案都是從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(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: MyHomePage(title: '列表滾動'),
);
}
}
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
檔案中,有幾個import進來的檔案:
demo-card.dart
卡片widget,列表就是迴圈的這個widgetitems.dart
卡片展示的資料放在這個檔案中,本專案我們寫了點mock資料,真實生產專案的資料更多是從http請求animated-bg.dart
背景齒輪的widget
這個檔案主要使用了一些Flutter的基礎widget,有不清楚的同學可以去官網查下使用方法,
另外,列表渲染的時候需要注意下,我們會使用ScrollController _controller = new ScrollController();
從而獲取垂直方向滾動的距離
卡片的mock資料
為了省事,我們直接將資料放在lib/items.dart
裡,我們模擬了六條資料,main.dart裡的listView的children就是使用這六條資料生成的:
import 'package:flutter/material.dart';
class Item {
String name;
MaterialColor color;
IconData icon;
Item(this.name, this.color, this.icon);
}
List<Item> items = [
Item('壹', Colors.amber, Icons.adjust),
Item('貳', Colors.cyan, Icons.airport_shuttle),
Item('叄', Colors.indigo, Icons.android),
Item('肆', Colors.green, Icons.beach_access),
Item('伍', Colors.pink, Icons.attach_file),
Item('陸', Colors.blue, Icons.bug_report)
];
複製程式碼
三個欄位:
- name 卡片左邊的名字
- color 卡片的背景顏色
- icon 卡片右邊的圖示
卡片Widget
我們在main.dart
裡這麼生成列表的children:items.map((Item _item) => DemoCard(_item)).toList();
對DemoCard傳入引數_item,其實就是React或者Vue裡面的props。不同之處在於,flutter傳入的引數既可以是匿名的也可以是具名的,這裡我們用的是匿名傳參。看下卡片Widget怎麼接收引數:
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),
],
)
],
),
),
),
);
}
}
複製程式碼
定義了一個StatelessWidget,對應React或者Vue就是無狀態元件,接收引數的方式是在構造器上宣告,這種方式和ES6一致:
DemoCard(this.item);
final Item item;
複製程式碼
使用Card元件可以快速的還原一張卡片樣式
elevation
引數控制卡片懸浮高度shape
引數控制卡片圓角color
引數控制卡片背景,item.color.withOpacity(.7)
讓背景透明化30%
然後就是使用Column和Row來控制佈局的展示
背景齒輪的轉動
先看下背景元件的原始碼,再一一解釋:
import 'package:flutter/material.dart';
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: offset / -512,
child: Icon(Icons.settings, size: 512, color: Colors.white),
),
);
},
);
}
}
複製程式碼
這個controller
是在main.dart裡傳下來的,它是ListView的controller,我們用widget.controller.offset
即可拿到垂直方向上的滾動距離。
列表滾動時我們要不停的重新整理齒輪的轉動角度,所以我們選用AnimatedBuilder
元件,元件有兩個重要引數:
- animation 將widget.controller傳給animation
- builder 每次animation改變時,都會重新執行渲染,這就實現了聯動效果
OverflowBox元件可以通過alignment(錨點)很好的控制子元件的顯示位置,這裡我們使用Alignment(4, 3)
將齒輪定位到螢幕左下方。
讓齒輪真正動起來的是Transform.rotate
元件,這裡有個弧長公式要用到:L=α(弧度)× r(半徑),所以我們這麼使用:angle: offset / -512
- 為什麼是512呢,因為我們的齒輪的
size: 512
- 為什麼帶有負號呢,這樣我們就能實現列表向上滾動時齒輪逆時針轉動,列表向下滾動時齒輪順時針滾動
用到的Widget
篇幅有限,不能一一展開講解使用到的元件,有問題的同學自行去官網檢視用法哦
- MaterialApp
- Scaffold
- AppBar
- Stack
- Center
- ListView
- Card
- RawMaterialButton
- Column
- Row
- AnimatedBuilder
- OverflowBox
- Transform
- Icon
相關連結
本篇文章能學到Flutter很多知識,包括:StatelessWidget/StatefulWidget的建立、本地資料的建立和使用、列表的展示和控制、垂直水平佈局等等,想看效果的同學可以直接跑原始碼哦