Flutter滾動動畫

如今的Flutter正是如火中天,昨天Google官方正式發佈了Flutter1.7版本,主要包含了對Android X的支持和Play Store的一些更新,一些新的和加強的組件,以及一些問題的修復。android

本篇文章咱們一塊兒開發一個炫炫的列表展現,伴隨着滾動,背景作一些相應的動畫效果。先看下效果圖:git

screenanimation

思路

列表滾動的時候,獲取垂直方向的滾動距離,再將這個值轉化成角度單位帶動齒輪的滾動github

入口文件

Flutter的項目都是從lib/main.dart開始:bash

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進來的文件:markdown

  • demo-card.dart 卡片widget,列表就是循環的這個widget
  • items.dart 卡片展現的數據放在這個文件中,本項目咱們寫了點mock數據,真實生產項目的數據更可能是從http請求
  • animated-bg.dart 背景齒輪的widget

這個文件主要使用了一些Flutter的基礎widget,有不清楚的同窗能夠去官網查下使用方法, 另外,列表渲染的時候須要注意下,咱們會使用ScrollController _controller = new ScrollController();從而獲取垂直方向滾動的距離app

卡片的mock數據

爲了省事,咱們直接將數據放在lib/items.dart裏,咱們模擬了六條數據,main.dart裏的listView的children就是使用這六條數據生成的:less

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)
];

複製代碼

三個字段:ide

  • name 卡片左邊的名字
  • color 卡片的背景顏色
  • icon 卡片右邊的圖標

卡片Widget

咱們在main.dart裏這麼生成列表的children:items.map((Item _item) => DemoCard(_item)).toList();對DemoCard傳入參數_item,其實就是React或者Vue裏面的props。不一樣之處在於,flutter傳入的參數既能夠是匿名的也能夠是具名的,這裏咱們用的是匿名傳參。看下卡片Widget怎麼接收參數:oop

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的建立、本地數據的建立和使用、列表的展現和控制、垂直水平佈局等等,想看效果的同窗能夠直接跑源碼哦

相關文章
相關標籤/搜索