本文是 flutter 面试问题的第二讲,高频问答 10 题。

Flutter开发者必备面试问题与答案02

Flutter开发者必备面试问题与答案02

视频

https://youtu.be/XYSxTb0iA9I

https://www.bilibili.com/video/BV1Zk2dYyEBr/

前言

原文 Flutter 完整面试问题及答案02

本文是 flutter 面试问题的第二讲,高频问答 10 题。

正文

11. PageRoute 是什么?

在 Flutter 中,PageRoute 是一个用于管理应用中页面导航的抽象类。它定义了如何在不同的页面之间进行切换,并提供了一些控制页面行为的功能。

主要特点

  • 页面切换PageRoute 负责在不同页面(或屏幕)之间进行导航。它管理了页面的堆栈,使得用户可以前往新页面或返回到之前的页面。
  • 动画效果PageRoute 可以定义页面切换时的动画效果。Flutter 提供了一些内置的路由实现,比如:
    • MaterialPageRoute:用于 Material Design 风格的应用,提供从底部向上推入页面的动画。
    • CupertinoPageRoute:用于 iOS 风格的应用,提供从右向左推入页面的动画。
  • 生命周期管理PageRoute 提供了一些生命周期方法,如 didChangeDependenciesdispose,用于在页面进入和退出时执行特定操作。

使用示例

下面是一个使用 MaterialPageRoute 的简单示例:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home Page')),
      body: Center(
        child: ElevatedButton(
          child: Text('Go to Second Page'),
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => SecondPage()),
            );
          },
        ),
      ),
    );
  }
}

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Second Page')),
      body: Center(
        child: Text('Welcome to the Second Page!'),
      ),
    );
  }
}

自定义 PageRoute

你可以创建自定义的 PageRoute 来实现特定的导航效果。例如,以下是一个简单的自定义路由实现:

class CustomPageRoute extends PageRouteBuilder {
  final Widget page;

  CustomPageRoute({required this.page})
      : super(
          pageBuilder: (context, animation, secondaryAnimation) => page,
          transitionsBuilder: (context, animation, secondaryAnimation, child) {
            const begin = Offset(1.0, 0.0);
            const end = Offset.zero;
            const curve = Curves.easeInOut;

            var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
            var offsetAnimation = animation.drive(tween);

            return SlideTransition(
              position: offsetAnimation,
              child: child,
            );
          },
        );
}

12. 解释 async , await 和 Future ?

在 Flutter 和 Dart 中,asyncawaitFuture 是处理异步编程的关键概念。它们可以帮助你编写非阻塞的代码,使得应用能够在执行长时间运行的任务时保持响应。以下是对这三个概念的详细解释:

1. Future

  • 定义Future 是一个表示可能在未来某个时间点完成的异步操作的对象。它可以用于表示一个延迟的值,通常用于处理异步任务的结果。
  • 状态
    • 未完成(Pending):Future 仍在进行中。
    • 已完成(Completed):Future 执行完毕,可以获取结果。
    • 已失败(Error):Future 执行过程中出现错误。
  • 使用示例
    Future<String> fetchData() async {
      // 模拟网络请求
      await Future.delayed(Duration(seconds: 2));
      return 'Data fetched';
    }
    

2. async

  • 定义async 是一个修饰符,用于声明一个异步函数。使用 async 修饰的函数会返回一个 Future,即使函数内部没有显式地返回 Future
  • 特点
    • async 函数中,你可以使用 await 关键字等待一个 Future 完成。
    • async 函数会自动将返回值包装在一个 Future 中。
  • 使用示例
    Future<void> loadData() async {
      String data = await fetchData();
      print(data); // 输出 'Data fetched'
    }
    

3. await

  • 定义await 是一个关键字,用于在 async 函数中等待一个 Future 完成,并返回其结果。
  • 特点
    • await 只能在 async 函数中使用。
    • 使用 await 会暂停 async 函数的执行,直到 Future 完成,并返回结果。
    • await 让异步代码看起来像同步代码,从而提高代码的可读性。
  • 使用示例
    Future<void> main() async {
      print('Fetching data...');
      await loadData(); // 等待 loadData 完成
      print('Data loaded');
    }
    

整体示例

下面是一个完整的示例,展示了如何使用 asyncawaitFuture

import 'dart:async';

Future<String> fetchData() async {
  // 模拟网络请求
  await Future.delayed(Duration(seconds: 2));
  return 'Data fetched';
}

Future<void> loadData() async {
  String data = await fetchData();
  print(data);
}

Future<void> main() async {
  print('Fetching data...');
  await loadData(); // 等待 loadData 完成
  print('Data loaded');
}
  • Future:表示一个异步操作的结果,可以是未完成、已完成或已失败。
  • async:用于声明异步函数,返回一个 Future
  • await:在 async 函数中等待一个 Future 完成,并获取其结果。

通过使用 asyncawaitFuture,你可以轻松地处理异步操作,使代码更加简洁和可读。


13. 你如何动态更新列表视图?

在 Flutter 中,动态更新列表视图通常使用 ListView 组件结合状态管理来实现。以下是几种常见的方法来动态更新列表视图:

1. 使用 StatefulWidget

利用 StatefulWidgetsetState() 方法,可以在更新数据时重新构建列表。

示例:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DynamicList(),
    );
  }
}

class DynamicList extends StatefulWidget {
  @override
  _DynamicListState createState() => _DynamicListState();
}

class _DynamicListState extends State<DynamicList> {
  List<String> items = ['Item 1', 'Item 2', 'Item 3'];

  void _addItem() {
    setState(() {
      items.add('Item ${items.length + 1}');
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Dynamic List')),
      body: ListView.builder(
        itemCount: items.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(items[index]),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _addItem,
        child: Icon(Icons.add),
      ),
    );
  }
}

2. 使用 Provider 或其他状态管理

如果你的应用较复杂,使用状态管理库(如 ProviderBlocRiverpod 等)可以更好地管理状态和更新列表视图。

示例(使用 Provider):

首先,添加 provider 依赖到 pubspec.yaml

dependencies:
  provider: ^6.0.0

然后,创建一个 ChangeNotifier 类来管理状态:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => ItemList(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DynamicList(),
    );
  }
}

class ItemList extends ChangeNotifier {
  List<String> items = ['Item 1', 'Item 2', 'Item 3'];

  void addItem() {
    items.add('Item ${items.length + 1}');
    notifyListeners();
  }
}

class DynamicList extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final itemList = Provider.of<ItemList>(context);

    return Scaffold(
      appBar: AppBar(title: Text('Dynamic List')),
      body: ListView.builder(
        itemCount: itemList.items.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(itemList.items[index]),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          itemList.addItem();
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

3. 使用 StreamBuilder

如果数据来自异步源(如网络请求或数据库),可以使用 StreamBuilder 来动态更新列表。

示例:

import 'dart:async';
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: StreamList(),
    );
  }
}

class StreamList extends StatelessWidget {
  final StreamController<String> _controller = StreamController<String>();

  void _addItem() {
    _controller.sink.add('Item ${DateTime.now()}');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Stream List')),
      body: StreamBuilder<String>(
        stream: _controller.stream,
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            return ListTile(title: Text(snapshot.data!));
          }
          return Center(child: Text('No items'));
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _addItem,
        child: Icon(Icons.add),
      ),
    );
  }

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

14. stream 是什么?

在 Flutter 和 Dart 中,Stream 是一种用于处理异步数据流的机制。它允许你接收一系列异步事件,而不仅仅是单个值。Streams 非常适合处理动态数据源,例如用户输入、网络请求、文件读取等。

主要特点

  • 异步数据处理
    • Stream 允许你以异步的方式接收数据,不会阻塞当前线程。这使得应用在处理数据时仍然能够保持响应。
  • 多个值
    • Future 只返回一个单一值不同,Stream 可以发送多个值,可以是事件、消息或数据。
  • 监听
    • 你可以通过添加监听器(Listener)来接收来自 Stream 的数据。每当 Stream 中有新数据可用时,监听器会被调用。

Stream 的类型

  • 单订阅 Stream
    • 只能有一个订阅者,适合处理一次性事件流,例如从文件读取数据。
  • 广播 Stream
    • 可以有多个订阅者,适合处理需要广播给多个监听者的事件,例如用户输入或网络请求。

基本使用示例

以下是一个简单的 Stream 使用示例,展示了如何创建和监听 Stream:

import 'dart:async';

void main() {
  // 创建一个单订阅 Stream
  Stream<int> numberStream = Stream<int>.periodic(Duration(seconds: 1), (count) => count);

  // 监听 Stream
  numberStream.listen((number) {
    print('Received number: $number');
  });
}

停止监听

你可以通过调用 cancel() 方法来停止监听 Stream:

import 'dart:async';

void main() {
  Stream<int> numberStream = Stream<int>.periodic(Duration(seconds: 1), (count) => count);
  var subscription = numberStream.listen((number) {
    print('Received number: $number');
    if (number >= 5) {
      subscription.cancel(); // 停止监听
    }
  });
}

使用 StreamBuilder

在 Flutter 中,通常使用 StreamBuilder 来构建 UI,自动响应 Stream 的数据变化。下面是一个使用 StreamBuilder 的示例:

import 'dart:async';
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: StreamExample(),
    );
  }
}

class StreamExample extends StatelessWidget {
  final Stream<int> numberStream = Stream<int>.periodic(Duration(seconds: 1), (count) => count);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Stream Example')),
      body: Center(
        child: StreamBuilder<int>(
          stream: numberStream,
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return CircularProgressIndicator();
            } else if (snapshot.hasError) {
              return Text('Error: ${snapshot.error}');
            } else {
              return Text('Received number: ${snapshot.data}');
            }
          },
        ),
      ),
    );
  }
}

15. keys 在 Flutter 中是什么,你什么时候应该使用它?

在 Flutter 中,Keys 是一个用于标识 Widget 的对象,帮助 Flutter 识别和管理 Widget 的状态。Keys 在构建和更新 Widget 时起到重要作用,尤其是在涉及到状态管理、列表和动画时。

Keys 的类型

  • GlobalKey
    • 用于跨 Widget 树访问状态。可以在不同的地方引用同一个 Widget 的状态。
    • 示例:在页面间导航时,保持表单状态。
  • ValueKey
    • 根据给定的值来识别 Widget,通常用于列表中的元素。
    • 示例:在列表中修改顺序时,确保正确更新每个元素的状态。
  • ObjectKey
    • 通过对象的引用来识别 Widget,适用于需要比较对象的情况。
  • UniqueKey
    • 每次创建时都会生成一个唯一的 Key,适合临时 Widget。

使用场景

列表的动态更新

  • 当你在列表中添加、删除或重新排序项时,使用 Keys 可以帮助 Flutter 确定哪些 Widget 需要更新,从而避免不必要的重建。
ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) {
    return ListTile(
      key: ValueKey(items[index]), // 使用 ValueKey
      title: Text(items[index]),
    );
  },
);

保持状态

  • 使用 GlobalKey 时,可以在 Widget 重建时保留其状态。例如,在使用 Form 组件时,可以通过 GlobalKey 访问表单状态。
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();

Form(
  key: _formKey,
  child: TextFormField(
    // ...
  ),
);

动画和过渡

  • 在使用动画时,Keys 可以帮助 Flutter 确定哪个 Widget 应该保持其状态或动画效果,从而实现更流畅的过渡。

构建条件 Widget

  • 当根据某些条件创建 Widget 时,使用 Keys 可以确保 Flutter 正确管理这些 Widget 的状态。

16. GlobalKeys 是什么?

在 Flutter 中,GlobalKey 是一种特殊的 Key,用于跨 Widget 树访问状态和方法。GlobalKey 允许你在不同的 Widget 之间共享状态,特别是在使用 StatefulWidget 时。它的主要用途是确保在 Widget 树重建时仍能保留和访问 Widget 的状态。

GlobalKey 的特点

  • 跨 Widget 树访问
    • GlobalKey 允许你从不同的地方访问同一个 Widget 的状态。使用 GlobalKey,你可以在 Widget 的外部调用其状态方法,比如在表单中验证字段。
  • 唯一性
    • 每个 GlobalKey 都是唯一的,因此 Flutter 能够确保在 Widget 树中识别每个 Widget。
  • 持久性
    • 当 Widget 被重建时,GlobalKey 保持对其状态的引用,因此可以在重建过程中保持状态。

使用场景

  • 表单状态管理
    • 在处理表单时,你可以使用 GlobalKey 来访问和验证表单的状态。
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();

@override
Widget build(BuildContext context) {
  return Form(
    key: _formKey,
    child: Column(
      children: [
        TextFormField(
          validator: (value) {
            if (value == null || value.isEmpty) {
              return 'Please enter some text';
            }
            return null;
          },
        ),
        ElevatedButton(
          onPressed: () {
            if (_formKey.currentState?.validate() ?? false) {
              // 处理表单提交
            }
          },
          child: Text('Submit'),
        ),
      ],
    ),
  );
}
  • 控制 Widget 的状态
    • 使用 GlobalKey 可以直接控制 Widget 的状态方法,比如在动画中调用。
  • 在复杂布局中保持状态
    • 当你的 Widget 树结构复杂,且多个子 Widget 可能会被重建时,GlobalKey 可以帮助你保持子 Widget 的状态。

17. 何时应使用 mainAxisAlignment 和 crossAxisAlignment?

mainAxisAlignment


18. 你什么时候可以使用 double.INFINITY ?

当你希望该小部件的大小与父小部件相同,请允许


19. Ticker 、 Tween 和 AnimationController 是什么?

在 Flutter 中,TickerTweenAnimationController 是用于实现动画的关键组件。它们各自有不同的角色和功能,下面是对它们的详细解释:

1. Ticker

  • 定义Ticker 是一个用于生成时间片的对象,它会在每一帧(frame)中调用一个回调函数。它通常与动画相关联,并用于控制动画的更新频率。
  • 工作原理
    • Ticker 会在每一帧调用回调,并提供当前的时间戳。你可以使用这个时间戳来更新动画的状态。
  • 使用场景
    • 通常在自定义动画或使用 AnimationController 时,Ticker 是由 AnimationController 自动创建和管理的。

2. Tween

  • 定义Tween 是一个用于定义动画起始值和结束值的对象。它帮助你在动画的不同状态之间插值(interpolate)。
  • 工作原理
    • Tween 接受两个值,分别是起始值和结束值,然后在这两个值之间生成中间值。你可以使用 Tween 来处理各种类型的值,例如颜色、尺寸、位置等。
  • 使用示例
    Tween<double> tween = Tween<double>(begin: 0.0, end: 1.0);
    double value = tween.transform(0.5); // value = 0.5
    

3. AnimationController

  • 定义AnimationController 是一个特殊的 Animation,它可以控制动画的播放。它负责管理动画的生命周期,包括启动、停止、反转等。
  • 工作原理
    • AnimationController 需要一个 vsync 参数,这通常是 SingleTickerProviderStateMixinTickerProviderStateMixin 的实例。它生成一个从 0.0 到 1.0 的值,表示动画的进度。
  • 使用场景
    • 在需要控制动画的开始、停止和反转时使用 AnimationController

示例:结合使用 Ticker、Tween 和 AnimationController

以下是一个简单的示例,展示如何使用 TickerTweenAnimationController 创建一个动画:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: AnimatedBox());
  }
}

class AnimatedBox extends StatefulWidget {
  @override
  _AnimatedBoxState createState() => _AnimatedBoxState();
}

class _AnimatedBoxState extends State<AnimatedBox> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );

    _animation = Tween<double>(begin: 0.0, end: 300.0).animate(_controller);

    _controller.forward(); // 启动动画
  }

  @override
  void dispose() {
    _controller.dispose(); // 释放资源
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Animation Example')),
      body: Center(
        child: AnimatedBuilder(
          animation: _animation,
          builder: (context, child) {
            return Container(
              width: _animation.value,
              height: _animation.value,
              color: Colors.blue,
            );
          },
        ),
      ),
    );
  }
}
  • Ticker:用于生成时间片,通常与动画相关联。
  • Tween:定义动画的起始值和结束值,用于在值之间插值。
  • AnimationController:控制动画的播放,包括启动、停止和反转。它生成一个时间值(通常在 0.0 到 1.0 之间),用于与 Tween 结合使用。

20. ephemeral 状态是什么?

在 Flutter 中,ephemeral 状态(短暂状态)指的是一种状态,它是局部的、短期的,并且只在当前 Widget 的生命周期内有效。这种状态通常不需要持久化,也不需要在 Widget 树之外共享。

特点

  • 局部性
    • Ephemeral 状态通常只与一个特定的 Widget 相关联。它不会影响其他 Widget。
  • 短暂性
    • 该状态在 Widget 被创建时存在,在 Widget 被销毁时消失。它不需要跨多个 Widget 或屏幕保持。
  • 使用 StatefulWidget
    • Ephemeral 状态通常通过 StatefulWidget 来管理。StatefulWidgetState 对象可以包含所有的局部状态。

使用场景

  • 用户输入:例如,文本框的内容、复选框的选中状态等。
  • 动画状态:例如,动画的当前进度或状态。
  • UI 状态:例如,按钮的启用和禁用状态、加载指示器的可见性等。

示例

下面是一个简单的例子,演示如何在 StatefulWidget 中管理 ephemeral 状态:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: Counter());
  }
}

class Counter extends StatefulWidget {
  @override
  _CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _count = 0; // 这是一个 ephemeral 状态

  void _incrementCounter() {
    setState(() {
      _count++; // 更新局部状态
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Ephemeral State Example')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('You have pushed the button this many times:'),
            Text(
              '$_count',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

Ephemeral 状态是 Flutter 中一种局部且短期存在的状态,适合用于管理特定 Widget 的状态。它通过 StatefulWidget 管理,适用于用户输入、动画和 UI 状态等场景。与之相对的是 app 状态(应用状态),后者是需要在多个 Widget 之间共享的持久状态。


小结

感谢阅读本文

如果有什么建议,请在评论中让我知道。我很乐意改进。


猫哥 APP

flutter 学习路径


© 猫哥 ducafecat.com

end