Hello, Boswell
  • Flutter 38~49课总结

Flutter 38~49课总结

1. Flutter中一切皆Widget

2. Widget即对象

var tempList = listData.map((value) {
  return Container(
    decoration: BoxDecoration(
        border: Border.all(
            color: const Color.fromRGBO(233, 233, 233, 0.9), width: 1)),
    child: Column(
      children: <Widget>[
        Image.network(value['imageUrl']),
        const SizedBox(height: 12),
        Text(
          value['title'],
          textAlign: TextAlign.center,
          style: const TextStyle(fontSize: 20),
        )
      ],
    ),
  );
});
const element = (
  <div className="container">
    <Welcome name="Alice" />
    <p>JSX 本质是 JavaScript 对象</p>
  </div>
)

Flutter中的路由

  • Flutter为在屏幕之间导航和处理深度链接提供了一个完整的系统。

  • 在 Flutter 中,屏 (screen) 和 页面 (page) 都叫做 路由 (route),在下文中统称为“路由 (route)”。

  • 在Flutter中通过Navigator组件管理路由导航。

  • 提供了管理堆栈的方法。

    • Navigator.push
    • Navigator.pop
  • Flutter中给我们提供了两种配置路由跳转的方式:

    • 基本路由
    • 命名路由

基本路由跳转

void main() {
  runApp(
    MaterialApp(
      title: '基本路由',
      home: FirstRoute()
    ),
  );
}


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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('First Route')),
      body: Center(
        child: ElevatedButton(
          child: const Text('Open route'),
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => const SecondRoute()),
            );
          },
        ),
      ),
    );
  }
}

重要Wedgit

  • Navigator:通过stack规则来管理一组Widget。
@optionalTypeArgs
Future<T?> push<T extends Object?>( BuildContext context, Route<T> route )
  • MaterialPageRoute: 根据平台和参数提供不同的过渡动画:
    • Android:默认从右侧滑入,退出时从右侧滑出。
    • iOS:默认从底部滑入(类似模态对话框),退出时从底部滑出。
    • 设置 fullscreenDialog: true:在 Android 和 iOS 上均使用从底部滑入的动画。
Inheritance
Object Route<T> OverlayRoute<T> TransitionRoute<T> ModalRoute<T> PageRoute<T> MaterialPageRoute
  • SecondRoute: 指定跳转的页面类

路由返回

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Second Route')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: const Text('Go back!'),
        ),
      ),
    );
  }
}

路由跳转传值

// 方式一:
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => DetailScreen(todo: '参数'),
  ),
);

// 方式二:
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const DetailScreen(),
    settings: RouteSettings(arguments: '参数'),
  ),
);

// 接受页面从构造函数参数获取

重要Widget

  • DetailScreen:通过给跳转页面的类的构造函数传值的方式来传递参数
  • RouteSettings:在构造 Route 时可能有用的数据。

路由返回传值

// 传递方
onPressed: () {
  Navigator.pop(context, 'Yep!');
},

// 接收方
final result = await Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => const SelectionScreen()),
);

命名路由定义

void main() {
  runApp(
    MaterialApp(
      title: 'Named Routes Demo',
      initialRoute: '/',
      routes: {
        '/': (context) => const FirstScreen(),
        '/second': (context) => const SecondScreen(),
      },
    ),
  );
}

路由跳转

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('First Screen')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.pushNamed(context, '/second');
          },
          child: const Text('Launch screen'),
        ),
      ),
    );
  }
}

命名路由传参

// 传递单个参数
Navigator.pushNamed(context, '/user', arguments: 123);

// 传递多个参数(使用 Map)
Navigator.pushNamed(
  context,
  '/product',
  arguments: {
    'id': 456,
    'name': 'iPhone',
    'price': 999.99,
  }
);

路由替换

  • 即将跳转的路由来替换当前的路由
  • pushReplacementNamed
Navigator.of(context).pushReplacementNamed('/registerSecond');

返回到根路由

  • 从多层的嵌套路由返回到根路由
  • pushAndRemoveUntil
Navigator.of(context).pushAndRemoveUntil(
  MaterialPageRoute(builder: (BuildContext context) {
  return const MyHomePage();
}), (route) => false);
@optionalTypeArgs
Future<T?> pushAndRemoveUntil<T extends Object?>( BuildContext context, Route<T> newRoute, RoutePredicate predicate )
  • newRoute:要导航到的目标路由,通常使用 MaterialPageRoute、CupertinoPageRoute 或自定义路由。
  • predicate:一个返回布尔值的回调函数,用于判断哪些路由需要被保留。Flutter 会从当前路由开始逐个检查历史路由,直到找到第一个满足 predicate 返回 true 的路由,然后移除该路由之前的所有路由。

Dialog

  • Dialog 是一种用于在当前页面上显示临时 UI 的组件,通常用于向用户呈现重要信息、获取确认或收集少量输入。它会创建一个浮动在当前内容之上的模态窗口,吸引用户的注意力并阻止背景交互,直到被关闭。
  • 具象弹框:
    • AlertDialog
    • SimpleDialog
  • 抽象弹框:
    • Dialog
  • showModalBottomSheet

AlertDialog

  • 一个Material Design设计系统下的警告对话框,警告对话框(也称为基本对话框)通知用户需要确认的情况。

showDialog

alt text

alt text

用法

import 'package:flutter/material.dart';

void main() => runApp(const AlertDialogExampleApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('AlertDialog Sample')),
        body: const Center(child: DialogExample()),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return TextButton(
      onPressed:
          () => showDialog<String>(
            context: context,
            builder:
                (BuildContext context) => AlertDialog(
                  title: const Text('AlertDialog Title'),
                  content: const Text('AlertDialog description'),
                  actions: <Widget>[
                    TextButton(
                      onPressed: () => Navigator.pop(context, 'Cancel'),
                      child: const Text('Cancel'),
                    ),
                    TextButton(
                      onPressed: () => Navigator.pop(context, 'OK'),
                      child: const Text('OK'),
                    ),
                  ],
                ),
          ),
      child: const Text('Show Dialog'),
    );
  }
}

SimpleDialog

alt text

  • SimpleDialog一个Material Design设计系统下的一个简单的对话框,它为用户提供几个选项之间的选择。一个简单的对话框有一个可选的标题,显示在选项的上方。

alt text

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () async {
        final String? result = await showDialog<String>(
            context: context,
            builder: (BuildContext context) {
              return SimpleDialog(
                title: const Text('Select assignment'),
                children: <Widget>[
                  SimpleDialogOption(
                    onPressed: () {
                      Navigator.pop(context, 'Treasury');
                    },
                    child: const Text('Treasury department'),
                  ),
                  SimpleDialogOption(
                    onPressed: () {
                      Navigator.pop(context, 'State');
                    },
                    child: const Text('State department'),
                  ),
                ],
              );
            });
        if (result != null) {
          print('Selected department: $result');
        }
      },
      child: const Text('弹出SimpleDialog'),
    );
  }
}

SimpleDialogOption

SimpleDialog 中使用的一个选项

alt text

关闭对话框

alt text

showModalBottomSheet

底部弹出对话框

alt text

alt text

用法

import 'package:flutter/material.dart';

void main() => runApp(const BottomSheetApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Bottom Sheet Sample')),
        body: const BottomSheetExample(),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Center(
      child: ElevatedButton(
        child: const Text('showModalBottomSheet'),
        onPressed: () {
          showModalBottomSheet<void>(
            context: context,
            builder: (BuildContext context) {
              return Container(
                height: 200,
                color: Colors.amber,
                child: Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    mainAxisSize: MainAxisSize.min,
                    children: <Widget>[
                      const Text('Modal BottomSheet'),
                      ElevatedButton(
                        child: const Text('Close BottomSheet'),
                        onPressed: () => Navigator.pop(context),
                      ),
                    ],
                  ),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

自定义Dialog

内置的Dialog有自己的固定布局模板,无法做更多的自定义。

  1. 直接使用Dialog Widget开发自定义对话框

alt text

alt text

  1. 我们需要通过继承Dialog类来实现自定义Dialog。

alt text

弹出自定义Dialog

alt text

PageView

PageView 是 Flutter 中用于创建水平或垂直滚动分页效果的组件,常用于轮播图、引导页、图片浏览等场景,本质是一个分页滚动的列表容器。

基本用法

  1. 通过实例化PageView类,传递构造函数的配置参数来使用;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() => runApp(const PageViewExampleApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('PageView Sample')),
        body: const PageViewExample(),
      ),
    );
  }
}

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

  @override
  State<PageViewExample> createState() => _PageViewExampleState();
}

class _PageViewExampleState extends State<PageViewExample> with TickerProviderStateMixin {
  late PageController _pageViewController;
  late TabController _tabController;
  int _currentPageIndex = 0;

  @override
  void initState() {
    super.initState();
    _pageViewController = PageController();
    _tabController = TabController(length: 3, vsync: this);
  }

  @override
  void dispose() {
    super.dispose();
    _pageViewController.dispose();
    _tabController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final TextTheme textTheme = Theme.of(context).textTheme;

    return Stack(
      alignment: Alignment.bottomCenter,
      children: <Widget>[
        PageView(
          controller: _pageViewController,
          onPageChanged: _handlePageViewChanged,
          children: <Widget>[
            Center(child: Text('First Page', style: textTheme.titleLarge)),
            Center(child: Text('Second Page', style: textTheme.titleLarge)),
            Center(child: Text('Third Page', style: textTheme.titleLarge)),
          ],
        ),
        PageIndicator(
          tabController: _tabController,
          currentPageIndex: _currentPageIndex,
          onUpdateCurrentPageIndex: _updateCurrentPageIndex,
          isOnDesktopAndWeb: _isOnDesktopAndWeb,
        ),
      ],
    );
  }

  void _handlePageViewChanged(int currentPageIndex) {
    if (!_isOnDesktopAndWeb) {
      return;
    }
    _tabController.index = currentPageIndex;
    setState(() {
      _currentPageIndex = currentPageIndex;
    });
  }

  void _updateCurrentPageIndex(int index) {
    _tabController.index = index;
    _pageViewController.animateToPage(
      index,
      duration: const Duration(milliseconds: 400),
      curve: Curves.easeInOut,
    );
  }

  bool get _isOnDesktopAndWeb =>
      kIsWeb ||
      switch (defaultTargetPlatform) {
        TargetPlatform.macOS || TargetPlatform.linux || TargetPlatform.windows => true,
        TargetPlatform.android || TargetPlatform.iOS || TargetPlatform.fuchsia => false,
      };
}
  1. 通过调用PageView类的builder静态方法创建PageView实例,且页面的创建是按需的

alt

上拉无线加载功能

参数名描述类型
onPageChanged换页监听事件ValueChanged<int>

无缝轮播图

通过SizedBox组件作为PageView组件的容器,控制宽高。

alt

轮播图的指示灯

通过Container创建元素,配合Positioned布局组件来定位元素。注意定位元素要再Stack组件中使用,因为定位涉及到层的堆栈。

alt

PageView实现动态轮播图

  1. 定时器

alt

  1. PageController:PageView 的控制器。控制哪一个页面展示再PageView中。通过animateToPage方法控制页面索引。

alt

class _PageViewExampleState extends State<PageViewExample> with TickerProviderStateMixin {
  late PageController _pageViewController;
  late TabController _tabController;
  int _currentPageIndex = 0;

  @override
  void initState() {
    super.initState();
    _pageViewController = PageController();
    _tabController = TabController(length: 3, vsync: this);
  }

  @override
  void dispose() {
    super.dispose();
    _pageViewController.dispose();
    _tabController.dispose();
  }

Widget build(BuildContext context) {
    final TextTheme textTheme = Theme.of(context).textTheme;

    return Stack(
      alignment: Alignment.bottomCenter,
      children: <Widget>[
        PageView(
          controller: _pageViewController,
          onPageChanged: _handlePageViewChanged,
          children: <Widget>[
            Center(child: Text('First Page', style: textTheme.titleLarge)),
            Center(child: Text('Second Page', style: textTheme.titleLarge)),
            Center(child: Text('Third Page', style: textTheme.titleLarge)),
          ],
        ),
        PageIndicator(
          tabController: _tabController,
          currentPageIndex: _currentPageIndex,
          onUpdateCurrentPageIndex: _updateCurrentPageIndex,
          isOnDesktopAndWeb: _isOnDesktopAndWeb,
        ),
      ],
    );
  }
}

AutomaticKeepAliveClientMixin

PageView页面每次切换都会销毁每页的子元素,如果想缓存下来,需要使用

altalt

Flutter Key

键是 Widgets, Elements and SemanticsNodes的标识符,不可重复。(DOMid选择器) Key 的子类应该是 LocalKey 或 GlobalKey 的子类。

alt

alt

作用是:

  1. 保存状态
  2. 排序

LocalKey

当前组件初始化创建的List组件有用。

GlobalKey

销毁后再次创建的组件状态有用。

GlobalKey获取子元素,修改子元素

alt

AnimatedList

带有动效的列表。在增删的时候给元素添加动效。

alt text

itemBuilder

alt text

FadeTransition

alt text

import 'package:flutter/material.dart';

class AnimatedListPage extends StatefulWidget {
  const AnimatedListPage({super.key});
  @override
  State<AnimatedListPage> createState() => _AnimatedListPageState();
}

class _AnimatedListPageState extends State<AnimatedListPage> {
  final globalKey = GlobalKey<AnimatedListState>();
  List<String> list = ["第一条数据", "第二条数据"];
  @override
  void initState() {
  // TODO: implement initState
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          list.add("这是一个数据");
          globalKey.currentState!.insertItem(list.length - 1);
        },
        child: const Icon(Icons.add),
      ),
      appBar: AppBar(
        title: const Text("AppBar组件"),
      ),
      body: AnimatedList(
          key: globalKey,
          initialItemCount: list.length,
          itemBuilder: (context, index, animation) {
            return FadeTransition(
              opacity: animation,
              child: ListTile(
                  title: Text(list[index]), trailing: Icon(Icons.delete)),
            );
          }),
    );
  }
}

insertItem

alt text

alt text

Flutter中的动画

  1. 隐式动画
  2. 显示动画

Flutter隐式动画

动画背后的实现原理和繁琐的操作细节都被隐藏了,所以叫隐式动画。

  1. AnimatedContainer
  2. AnimatedPadding
  3. AnimatedAlign: 动画版本的 Align,自动转换位置在给定的时间内,只要给定的对齐方式发生变化
import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('AnimatedContainer Sample')),
      body: const AnimatedContainerExample(),
    );
  }
}

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

  @override
  State<AnimatedContainerExample> createState() =>
      _AnimatedContainerExampleState();
}

class _AnimatedContainerExampleState extends State<AnimatedContainerExample> {
  bool selected = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          selected = !selected;
        });
      },
      child: Center(
        child: AnimatedContainer(
          width: selected ? 200.0 : 100.0,
          height: selected ? 100.0 : 200.0,
          color: selected ? Colors.red : Colors.blue,
          alignment:
              selected ? Alignment.center : AlignmentDirectional.topCenter,
          duration: const Duration(seconds: 2),
          curve: Curves.fastOutSlowIn,
          child: const FlutterLogo(size: 75),
        ),
      ),
    );
  }
}

Flutter显式动画

能够参与的动画过程叫做显示动画。通过AnimationController控制动画的暂停,播放,倒放等

  1. ScaleTransition
  2. PositionedTransition:Positioned 的动画版本采用了特定的Animation<RelativeRect>在动画的整个生命周期内将子对象的位置从起始位置转换到结束位置。
  3. SizeTransition
import 'package:flutter/material.dart';

void main() => runApp(const ScaleTransitionExampleApp());

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(home: ScaleTransitionExample());
  }
}

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

  @override
  State<ScaleTransitionExample> createState() => _ScaleTransitionExampleState();
}

class _ScaleTransitionExampleState extends State<ScaleTransitionExample>
    with TickerProviderStateMixin {
  late final AnimationController _controller = AnimationController(
    duration: const Duration(seconds: 2),
    vsync: this,
  )..repeat(reverse: true);
  late final Animation<double> _animation = CurvedAnimation(
    parent: _controller,
    curve: Curves.fastOutSlowIn,
  );

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ScaleTransition(
          scale: _animation,
          child: const Padding(padding: EdgeInsets.all(8.0), child: FlutterLogo(size: 150.0)),
        ),
      ),
    );
  }
}

alt text

执行动画

  1. forward: 正序执行一次
  2. stop:停止动画
  3. reset:重置动画
  4. reverse:倒序执行一次

alt text