跳到主要內容

隱式動畫

歡迎來到隱式動畫程式碼實驗室,您將在此學習如何使用 Flutter 小工具,輕鬆地為一組特定的屬性建立動畫。

為了充分利用本程式碼實驗室,您應該具備以下基本知識:

本程式碼實驗室涵蓋以下材料:

  • 使用 AnimatedOpacity 建立淡入效果。
  • 使用 AnimatedContainer 為大小、顏色和邊距的轉換設定動畫。
  • 隱式動畫的概觀和使用它們的技巧。

完成本程式碼實驗室的預計時間:15-30 分鐘。

什麼是隱式動畫?

#

使用 Flutter 的動畫庫,您可以為 UI 中的小工具新增動態效果並建立視覺效果。程式庫中的一個小工具集會為您管理動畫。這些小工具統稱為隱式動畫隱式動畫小工具,它們的名稱源自其所實作的 ImplicitlyAnimatedWidget 類別。使用隱式動畫,您可以透過設定目標值來為小工具屬性設定動畫;每當該目標值變更時,小工具就會將該屬性從舊值轉換為新值。這樣,隱式動畫就會以便利性換取控制權,它們管理動畫效果,因此您不必這麼做。

範例:文字淡入效果

#

以下範例示範如何使用名為 AnimatedOpacity 的隱式動畫小工具,為現有 UI 新增淡入效果。範例開頭沒有動畫程式碼,它由一個包含以下項目的 Material App 首頁畫面組成:

  • 一張貓頭鷹的照片。
  • 一個點擊時不會有任何反應的顯示詳細資訊按鈕。
  • 照片中貓頭鷹的說明文字。

淡入(起始程式碼)

#

若要檢視範例,請按一下執行

// Copyright 2019 the Dart project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.

import 'package:flutter/material.dart';

const owlUrl =
    'https://raw.githubusercontent.com/flutter/website/main/src/content/assets/images/docs/owl.jpg';

class FadeInDemo extends StatefulWidget {
  const FadeInDemo({super.key});

  @override
  State<FadeInDemo> createState() => _FadeInDemoState();
}

class _FadeInDemoState extends State<FadeInDemo> {
  @override
  Widget build(BuildContext context) {
    return ListView(children: <Widget>[
      Image.network(owlUrl),
      TextButton(
        child: const Text(
          'Show Details',
          style: TextStyle(color: Colors.blueAccent),
        ),
        onPressed: () => {},
      ),
      const Column(
        children: [
          Text('Type: Owl'),
          Text('Age: 39'),
          Text('Employment: None'),
        ],
      )
    ]);
  }
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: Center(
          child: FadeInDemo(),
        ),
      ),
    );
  }
}

void main() {
  runApp(
    const MyApp(),
  );
}

使用 AnimatedOpacity 小工具為不透明度設定動畫

#

本節包含可用來為淡入起始程式碼新增隱式動畫的步驟清單。完成這些步驟後,您也可以執行已進行變更的淡入完整程式碼。這些步驟概述如何使用 AnimatedOpacity 小工具來新增下列動畫功能:

  • 貓頭鷹的說明文字會保持隱藏,直到使用者按一下顯示詳細資訊
  • 當使用者按一下顯示詳細資訊時,貓頭鷹的說明文字會淡入。

1. 選取要設定動畫的小工具屬性

#

若要建立淡入效果,您可以使用 AnimatedOpacity 小工具為 opacity 屬性設定動畫。將 Column 小工具包裝在 AnimatedOpacity 小工具中

dart
@override
Widget build(BuildContext context) {
  return ListView(children: <Widget>[
    Image.network(owlUrl),
    TextButton(
      child: const Text(
        'Show Details',
        style: TextStyle(color: Colors.blueAccent),
      ),
      onPressed: () => {},
    ),
    const Column(
      children: [
        Text('Type: Owl'),
        Text('Age: 39'),
        Text('Employment: None'),
      ],
    ),
    AnimatedOpacity(
      child: const Column(
        children: [
          Text('Type: Owl'),
          Text('Age: 39'),
          Text('Employment: None'),
        ],
      ),
    ),
  ]);
}

2. 為動畫屬性初始化狀態變數

#

若要在使用者按一下顯示詳細資訊之前隱藏文字,請將 opacity 的起始值設定為零

dart
class _FadeInDemoState extends State<FadeInDemo> {
  double opacity = 0;

  @override
  Widget build(BuildContext context) {
    return ListView(children: <Widget>[
      // ...
      AnimatedOpacity(
        opacity: opacity,
        child: const Column(

3. 設定動畫的持續時間

#

除了 opacity 參數之外,AnimatedOpacity 也需要一個 duration 以用於其動畫。在此範例中,您可以從 2 秒開始

dart
AnimatedOpacity(
  duration: const Duration(seconds: 2),
  opacity: opacity,
  child: const Column(

4. 設定動畫的觸發器並選取結束值

#

設定當使用者按一下顯示詳細資訊時觸發動畫。若要執行此操作,請使用 TextButtononPressed() 處理常式來變更 opacity 狀態。若要讓 FadeInDemo 小工具在使用者按一下顯示詳細資訊時完全可見,請使用 onPressed() 處理常式將 opacity 設定為 1

dart
TextButton(
  child: const Text(
    'Show Details',
    style: TextStyle(color: Colors.blueAccent),
  ),
  onPressed: () => {},
  onPressed: () => setState(() {
    opacity = 1;
  }),
),

淡入(完成)

#

以下是已完成變更的範例。執行此範例,然後按一下顯示詳細資訊以觸發動畫。

// Copyright 2019 the Dart project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.

import 'package:flutter/material.dart';

const owlUrl =
    'https://raw.githubusercontent.com/flutter/website/main/src/content/assets/images/docs/owl.jpg';

class FadeInDemo extends StatefulWidget {
  const FadeInDemo({super.key});

  @override
  State<FadeInDemo> createState() => _FadeInDemoState();
}

class _FadeInDemoState extends State<FadeInDemo> {
  double opacity = 0;

  @override
  Widget build(BuildContext context) {
    return ListView(children: <Widget>[
      Image.network(owlUrl),
      TextButton(
        child: const Text(
          'Show Details',
          style: TextStyle(color: Colors.blueAccent),
        ),
        onPressed: () => setState(() {
          opacity = 1;
        }),
      ),
      AnimatedOpacity(
        duration: const Duration(seconds: 2),
        opacity: opacity,
        child: const Column(
          children: [
            Text('Type: Owl'),
            Text('Age: 39'),
            Text('Employment: None'),
          ],
        ),
      )
    ]);
  }
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: Center(
          child: FadeInDemo(),
        ),
      ),
    );
  }
}

void main() {
  runApp(
    const MyApp(),
  );
}

整合所有功能

#

文字淡入效果範例示範了 AnimatedOpacity 小工具的下列功能。

  • 它會監聽其 opacity 屬性的狀態變更。
  • opacity 屬性變更時,它會為轉換到 opacity 的新值設定動畫。
  • 它需要 duration 參數來定義值之間的轉換應持續多久。

範例:形狀變換效果

#

以下範例示範如何使用 AnimatedContainer 小工具為多個屬性 (marginborderRadiuscolor) 設定動畫,這些屬性具有不同的類型 (doubleColor)。範例開頭沒有動畫程式碼。它以一個包含以下項目的 Material App 首頁畫面開始:

  • 一個使用 borderRadiusmargincolor 設定的 Container 小工具。這些屬性會設定為每次執行範例時重新產生。
  • 一個點擊時不會有任何反應的變更按鈕。

形狀變換(起始程式碼)

#

若要開始範例,請按一下執行

// Copyright 2019 the Dart project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.

import 'dart:math';

import 'package:flutter/material.dart';

double randomBorderRadius() {
  return Random().nextDouble() * 64;
}

double randomMargin() {
  return Random().nextDouble() * 64;
}

Color randomColor() {
  return Color(0xFFFFFFFF & Random().nextInt(0xFFFFFFFF));
}

class AnimatedContainerDemo extends StatefulWidget {
  const AnimatedContainerDemo({super.key});

  @override
  State<AnimatedContainerDemo> createState() => _AnimatedContainerDemoState();
}

class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
  late Color color;
  late double borderRadius;
  late double margin;

  @override
  void initState() {
    super.initState();
    color = randomColor();
    borderRadius = randomBorderRadius();
    margin = randomMargin();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: <Widget>[
            SizedBox(
              width: 128,
              height: 128,
              child: Container(
                margin: EdgeInsets.all(margin),
                decoration: BoxDecoration(
                  color: color,
                  borderRadius: BorderRadius.circular(borderRadius),
                ),
              ),
            ),
            ElevatedButton(
              child: const Text('Change'),
              onPressed: () => {},
            ),
          ],
        ),
      ),
    );
  }
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: AnimatedContainerDemo(),
    );
  }
}

void main() {
  runApp(
    const MyApp(),
  );
}

使用 AnimatedContainer 設定顏色、borderRadius 和邊距的動畫

#

本節包含可用來為形狀變換起始程式碼新增隱式動畫的步驟清單。完成每個步驟後,您也可以使用已進行的變更來執行完整的形狀變換範例

形狀變換起始程式碼會為 Container 小工具中的每個屬性指派隨機值。關聯的函式會產生相關的值

  • randomColor() 函式會為 color 屬性產生一個 Color
  • randomBorderRadius() 函式會為 borderRadius 屬性產生一個 double
  • randomMargin() 函式會為 margin 屬性產生一個 double

以下步驟使用 AnimatedContainer 小工具來執行下列操作:

  • 每當使用者按一下變更時,轉換為 colorborderRadiusmargin 的新值。
  • 每當設定 colorborderRadiusmargin 的新值時,為轉換設定動畫。

1. 新增隱式動畫

#

Container 小工具變更為 AnimatedContainer 小工具

dart
SizedBox(
  width: 128,
  height: 128,
  child: Container(
  child: AnimatedContainer(
    margin: EdgeInsets.all(margin),
    decoration: BoxDecoration(
      color: color,
      borderRadius: BorderRadius.circular(borderRadius),
    ),
  ),
),

2. 為動畫屬性設定起始值

#

AnimatedContainer 小工具的屬性變更時,它會在這些屬性的舊值和新值之間進行轉換。若要包含使用者按一下變更時觸發的行為,請建立一個 change() 方法。change() 方法可以使用 setState() 方法來設定 colorborderRadiusmargin 狀態變數的新值

dart
void change() {
  setState(() {
    color = randomColor();
    borderRadius = randomBorderRadius();
    margin = randomMargin();
  });
}

@override
Widget build(BuildContext context) {
  // ...

3. 設定動畫的觸發器

#

若要設定在使用者每次按下變更時觸發動畫,請在 onPressed() 處理常式中叫用 change() 方法

dart
ElevatedButton(
  child: const Text('Change'),
  onPressed: () => {},
  onPressed: () => change(),
),

4. 設定持續時間

#

設定動畫的 duration,此動畫會為舊值和新值之間的轉換提供支援

dart
SizedBox(
  width: 128,
  height: 128,
  child: AnimatedContainer(
    margin: EdgeInsets.all(margin),
    decoration: BoxDecoration(
      color: color,
      borderRadius: BorderRadius.circular(borderRadius),
    ),
    duration: const Duration(milliseconds: 400),
  ),
),

形狀變換(完成)

#

以下是已完成變更的範例。執行程式碼,然後按一下變更以觸發動畫。每次按一下變更時,形狀都會動畫轉換為 marginborderRadiuscolor 的新值。

// Copyright 2019 the Dart project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.

import 'dart:math';

import 'package:flutter/material.dart';

const _duration = Duration(milliseconds: 400);

double randomBorderRadius() {
  return Random().nextDouble() * 64;
}

double randomMargin() {
  return Random().nextDouble() * 64;
}

Color randomColor() {
  return Color(0xFFFFFFFF & Random().nextInt(0xFFFFFFFF));
}

class AnimatedContainerDemo extends StatefulWidget {
  const AnimatedContainerDemo({super.key});

  @override
  State<AnimatedContainerDemo> createState() => _AnimatedContainerDemoState();
}

class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
  late Color color;
  late double borderRadius;
  late double margin;

  @override
  void initState() {
    super.initState();
    color = randomColor();
    borderRadius = randomBorderRadius();
    margin = randomMargin();
  }

  void change() {
    setState(() {
      color = randomColor();
      borderRadius = randomBorderRadius();
      margin = randomMargin();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: <Widget>[
            SizedBox(
              width: 128,
              height: 128,
              child: AnimatedContainer(
                margin: EdgeInsets.all(margin),
                decoration: BoxDecoration(
                  color: color,
                  borderRadius: BorderRadius.circular(borderRadius),
                ),
                duration: _duration,
              ),
            ),
            ElevatedButton(
              child: const Text('Change'),
              onPressed: () => change(),
            ),
          ],
        ),
      ),
    );
  }
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: AnimatedContainerDemo(),
    );
  }
}

void main() {
  runApp(
    const MyApp(),
  );
}

使用動畫曲線

#

前面的範例示範了如何

  • 隱式動畫可讓您為特定小工具屬性的值之間的轉換設定動畫。
  • duration 參數可讓您設定動畫完成所需的時間。

隱式動畫也可讓您控制在設定的 duration 期間發生的動畫速率變更。若要定義速率的變更,請將 curve 參數的值設定為 Curve,例如在 Curves 類別中宣告的一個。

前面的範例未指定 curve 參數的值。若未指定曲線值,隱式動畫會套用線性動畫曲線

完整的形狀變換範例中指定 curve 參數的值。當您為 curve 傳遞 easeInOutBack 常數時,動畫會變更

dart
SizedBox(
  width: 128,
  height: 128,
  child: AnimatedContainer(
    margin: EdgeInsets.all(margin),
    decoration: BoxDecoration(
      color: color,
      borderRadius: BorderRadius.circular(borderRadius),
    ),
    duration: _duration,
    curve: Curves.easeInOutBack,
  ),
),

當您將 Curves.easeInOutBack 常數傳遞至 AnimatedContainer 小工具的 curve 屬性時,請注意 marginborderRadiuscolor 的變更速率如何遵循該常數定義的曲線。

整合所有功能

#

完整的形狀變換範例marginborderRadiuscolor 屬性的值之間的轉換設定動畫。AnimatedContainer 小工具會為其任何屬性的變更設定動畫。這些屬性包括您未使用過的屬性,例如 paddingtransform,甚至是 childalignment!透過顯示隱式動畫的其他功能,完整的形狀變換範例是以淡入完整範例為基礎建構。

若要總結隱式動畫

  • 某些隱式動畫 (例如 AnimatedOpacity 小工具) 只會為一個屬性設定動畫。其他隱式動畫 (例如 AnimatedContainer 小工具) 則可以為許多屬性設定動畫。
  • 隱式動畫會在使用提供的 curveduration 變更屬性時,為屬性的舊值和新值之間的轉換設定動畫。
  • 如果您未指定 curve,則隱式動畫預設為線性曲線

下一步?

#

恭喜,您已完成程式碼實驗室!若要深入瞭解,請查看以下建議