在Flutter中創(chuàng)建滾動(dòng)動(dòng)畫

使用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)TextIcon時(shí)顯示的元素名稱圖標(biāo)對(duì)每個(gè)項(xiàng)目定義。使用Shadow,TextStyleRoundedRectangleBorder以及卡片的高程(設(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容