歡迎來到隱式動畫編碼實驗,您將學習如何使用 Flutter 小部件,輕鬆地為特定屬性集建立動畫。

要最大程度地從本編碼實驗中受益,您應該具備以下基礎知識:

本編碼實驗涵蓋以下內容:

  • 使用 AnimatedOpacity 建立淡入效果。
  • 使用 AnimatedContainer 為大小、顏色和邊距的過渡新增動畫。
  • 隱式動畫的概述和使用技術。

完成本編碼實驗的估計時間:15-30 分鐘。

什麼是隱式動畫?

#

藉助 Flutter 的動畫庫,您可以為 UI 中的小部件新增動感並建立視覺效果。庫中的一組小部件可以為您管理動畫。這些小部件統稱為隱式動畫隱式動畫小部件,它們的名稱來源於它們實現的ImplicitlyAnimatedWidget 類。透過隱式動畫,您可以透過設定目標值來為小部件的屬性新增動畫;每當該目標值發生變化時,小部件就會將該屬性從舊值動畫到新值。透過這種方式,隱式動畫用便利性換取了控制權——它們管理動畫效果,而您無需手動操作。

示例:淡入文字效果

#

以下示例展示瞭如何使用一個名為 AnimatedOpacity 的隱式動畫小部件為現有 UI 新增淡入效果。該示例開始時沒有動畫程式碼——它包含一個 MaterialApp 主螢幕,其中包含:

  • 一張貓頭鷹的照片。
  • 一個“顯示詳情”按鈕,點選後無任何反應。
  • 照片中貓頭鷹的描述文字。

淡入(初始程式碼)

#

要檢視示例,請點選“執行”。

// 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)的動畫。該示例開始時沒有動畫程式碼。它從一個 MaterialApp 主螢幕開始,該螢幕包含:

  • 一個配置了 borderRadiusmargincolorContainer 小部件。這些屬性的設定將在您每次執行示例時重新生成。
  • 一個“更改”按鈕,點選後無任何反應。

形狀變換(初始程式碼)

#

要開始示例,請點選“執行”。

// 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 動畫顏色、圓角和邊距

#

本節包含一系列步驟,您可以使用它們將隱式動畫新增到形狀變換初始程式碼中。完成每個步驟後,您還可以執行形狀變換完整示例,其中已包含所做的更改。

形狀變換初始程式碼”為 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. 設定時長

#

設定動畫的時長,該動畫用於驅動舊值和新值之間的過渡。

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 引數指定一個值。當您將 easeInOutBack 常量傳遞給 curve 時,動畫會發生變化。

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,隱式動畫將預設為線性曲線

下一步是什麼?

#

恭喜您,您已完成本次編碼實驗!要了解更多資訊,請檢視以下建議: