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

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

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

视频

https://youtu.be/rDrn2S6UWnk

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

前言

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

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

正文

21. AspectRatio 组件有什么作用?

在 Flutter 中,AspectRatio 小部件用于控制其子小部件的宽高比。它能够确保子小部件在不同屏幕尺寸或方向下保持指定的比例,从而使布局更加美观和一致。

主要功能

  • 保持比例
    • AspectRatio 小部件可以指定一个宽高比(例如 16:9、4:3 等),确保其子小部件在布局时遵循这个比例。
  • 自动调整大小
    • 当父小部件的大小发生变化时,AspectRatio 会自动调整子小部件的大小,以保持指定的宽高比。
  • 适应不同屏幕
    • 在响应式布局中,AspectRatio 非常有用,可以确保图像或视频等媒体内容在不同设备和屏幕尺寸上以正确的比例显示。

使用示例

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

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('AspectRatio Example')),
        body: Center(
          child: AspectRatio(
            aspectRatio: 16 / 9, // 设置宽高比为 16:9
            child: Container(
              color: Colors.blue,
              child: Center(
                child: Text(
                  '16:9 Aspect Ratio',
                  style: TextStyle(color: Colors.white, fontSize: 24),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

何时使用 AspectRatio

  • 媒体内容:确保图像、视频等内容在不同屏幕上以正确的比例显示。
  • 布局设计:在设计复杂布局时,保持元素之间的比例关系,使界面更加美观和一致。
  • 响应式设计:在响应式布局中,AspectRatio 可以帮助适应不同设备,确保良好的用户体验。

AspectRatio 是一个非常实用的 Flutter 小部件,可以帮助开发者控制子小部件的宽高比,确保在不同屏幕尺寸和方向下保持一致的布局和美观的外观。


22. 你将如何从其 State 中访问 StatefulWidget 属性?

在 Flutter 中,从 State 中访问 StatefulWidget 的属性非常简单。StatefulWidgetState 类通常会有一个指向其父 StatefulWidget 的引用,可以通过 widget 属性来访问。

示例

以下是一个简单的示例,展示如何从 State 中访问 StatefulWidget 的属性:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyStatefulWidget(title: 'Hello, Flutter!'),
    );
  }
}

class MyStatefulWidget extends StatefulWidget {
  final String title;

  MyStatefulWidget({Key? key, required this.title}) : super(key: key);

  @override
  _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
    // 通过 widget.title 访问 StatefulWidget 的属性
    print('Current title: ${widget.title}');
  }

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

关键点

  • 访问方式:在 State 类中,可以使用 widget 属性访问 StatefulWidget 的属性。这个 widget 属性是 State 类的一个成员,指向当前的 StatefulWidget 实例。
  • 状态管理:通过 setState 更新状态时,仍然可以访问 widget 的属性,确保数据的一致性。

使用场景

  • 动态 UI:当需要根据 StatefulWidget 的属性动态更新 UI 时,可以使用这种方法。
  • 逻辑处理:在处理事件时,可能需要在 State 中访问 StatefulWidget 的属性,以便进行相应的逻辑处理。

State 中访问 StatefulWidget 的属性是 Flutter 中常见的操作,通过 widget 属性可以轻松实现。这种方法对于构建动态和响应式的 UI 非常重要。


23. 请举三个使用 Future 的操作

在 Flutter 中,Future 用于处理异步操作,允许你在代码中定义可能需要时间才能完成的任务。以下是三个常见的使用 Future 的操作示例:

  1. 网络请求

使用 Future 进行网络请求是最常见的用例之一。你可以使用 http 包来发送 GET 请求并获取数据。

示例:

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

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

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

class FetchDataExample extends StatefulWidget {
  @override
  _FetchDataExampleState createState() => _FetchDataExampleState();
}

class _FetchDataExampleState extends State<FetchDataExample> {
  Future<List<dynamic>> fetchData() async {
    final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts'));
    if (response.statusCode == 200) {
      return json.decode(response.body);
    } else {
      throw Exception('Failed to load data');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Fetch Data Example')),
      body: FutureBuilder<List<dynamic>>(
        future: fetchData(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(child: CircularProgressIndicator());
          } else if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          } else {
            return ListView.builder(
              itemCount: snapshot.data!.length,
              itemBuilder: (context, index) {
                return ListTile(title: Text(snapshot.data![index]['title']));
              },
            );
          }
        },
      ),
    );
  }
}
  1. 延时操作

使用 Future.delayed 创建一个延时操作,通常用于模拟网络请求或其他耗时操作。

示例:

import 'package:flutter/material.dart';

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

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

class DelayedExample extends StatefulWidget {
  @override
  _DelayedExampleState createState() => _DelayedExampleState();
}

class _DelayedExampleState extends State<DelayedExample> {
  String _message = 'Waiting...';

  Future<void> _showMessage() async {
    await Future.delayed(Duration(seconds: 2)); // 延迟 2 秒
    setState(() {
      _message = 'Hello after 2 seconds!';
    });
  }

  @override
  void initState() {
    super.initState();
    _showMessage(); // 调用延时操作
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Delayed Example')),
      body: Center(
        child: Text(_message, style: TextStyle(fontSize: 24)),
      ),
    );
  }
}
  1. 文件读取

使用 Future 读取本地文件,通常通过 dart:io 包实现。

示例:

import 'package:flutter/material.dart';
import 'dart:io';
import 'package:path_provider/path_provider.dart';

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

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

class FileReadExample extends StatefulWidget {
  @override
  _FileReadExampleState createState() => _FileReadExampleState();
}

class _FileReadExampleState extends State<FileReadExample> {
  String _fileContent = 'Loading...';

  Future<String> readFile() async {
    final directory = await getApplicationDocumentsDirectory();
    final file = File('${directory.path}/example.txt');
    return file.readAsString();
  }

  @override
  void initState() {
    super.initState();
    readFile().then((content) {
      setState(() {
        _fileContent = content;
      });
    }).catchError((error) {
      setState(() {
        _fileContent = 'Error: $error';
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('File Read Example')),
      body: Center(
        child: Text(_fileContent, style: TextStyle(fontSize: 24)),
      ),
    );
  }
}

以上是三个常见的使用 Future 的操作示例:

  1. 网络请求:通过 http 包获取数据。
  2. 延时操作:使用 Future.delayed 模拟异步操作。
  3. 文件读取:读取本地文件内容。

24. SafeArea 的作用是什么?

在 Flutter 中,SafeArea 是一个小部件,用于确保其子小部件在可视区域内展示,避免被设备的系统 UI(如状态栏、导航栏、刘海等)遮挡。它通过自动添加适当的填充(padding)来确保内容不被这些系统元素覆盖。

主要功能

  • 自动填充
    • SafeArea 会根据设备的屏幕边缘和系统 UI 的位置,自动添加适当的填充,以确保子小部件不会被遮挡。
  • 适应不同设备
    • 在不同的设备和屏幕尺寸下(如 iPhone 的刘海、Android 的状态栏),SafeArea 能够动态调整填充,以适应各种设备的特性。
  • 简化布局
    • 使用 SafeArea 可以减少手动计算填充的需要,让布局更加简洁和易于维护。

使用示例

下面是一个简单的示例,展示如何使用 SafeArea

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('SafeArea Example')),
        body: SafeArea(
          child: Center(
            child: Text(
              'This text is safe from being obscured by system UI.',
              style: TextStyle(fontSize: 24),
            ),
          ),
        ),
      ),
    );
  }
}

何时使用 SafeArea

  • 在顶部有状态栏的界面:确保内容不会被状态栏遮挡。
  • 在底部有导航栏的界面:确保内容不会被底部导航栏遮挡。
  • 在刘海设备上:确保内容不会被刘海部分遮挡。
  • 需要适应不同设备的布局:自动处理不同设备的填充,简化开发工作。

SafeArea 是一个非常实用的小部件,能够帮助开发者确保界面元素在不同设备和屏幕尺寸下都能良好显示,避免被系统 UI 遮挡。通过使用 SafeArea,可以使布局更加安全和易于维护。


25. 何时使用 mainAxisSize ?

在 Flutter 中,mainAxisSize 是一个用于控制主轴(main axis)上小部件大小的属性,通常在 RowColumn 小部件中使用。它决定了子小部件在主轴方向上应该占用的空间。mainAxisSize 有两个值:

  • MainAxisSize.max(默认值):子小部件在主轴上尽可能扩展,填满可用空间。
  • MainAxisSize.min:子小部件在主轴上只占用其内容所需的最小空间。

何时使用 mainAxisSize

  • 控制布局行为
    • 当你希望子小部件在主轴上占用尽可能多的空间时,使用 MainAxisSize.max。这是默认行为,适用于大多数情况。
    • 当你希望子小部件只占用所需的空间,而不是填满可用空间时,使用 MainAxisSize.min
  • 在动态布局中
    • 如果你有动态内容(例如列表或可变数量的子小部件),并希望根据内容的大小调整布局,mainAxisSize.min 可以帮助你控制小部件的大小。
  • 结合其他布局小部件
    • 当与其他布局小部件(如 ExpandedFlexible)结合使用时,mainAxisSize 可以帮助更好地控制空间分配。

示例

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('MainAxisSize Example')),
        body: Column(
          mainAxisSize: MainAxisSize.max, // 默认值
          children: <Widget>[
            Container(color: Colors.red, height: 100),
            Container(color: Colors.green, height: 100),
          ],
        ),
      ),
    );
  }
}
  • 使用 MainAxisSize.min
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('MainAxisSize.min Example')),
        body: Column(
          mainAxisSize: MainAxisSize.min, // 只占用子小部件所需的空间
          children: <Widget>[
            Container(color: Colors.red, height: 100),
            Container(color: Colors.green, height: 100),
          ],
        ),
      ),
    );
  }
}

使用 mainAxisSize 可以帮助你更好地控制 RowColumn 中子小部件在主轴上的空间占用。根据布局需求选择合适的 mainAxisSize 值,可以使你的 Flutter 应用布局更加灵活和美观。


26. SizedBox VS Container 比较?

在 Flutter 中,SizedBoxContainer 都是常用的布局小部件,但它们之间存在一些关键的区别和适用场景。以下是对它们的比较:

基本功能

  • SizedBox
    • 主要用于设置子小部件的固定宽度和/或高度。
    • 通常用于在布局中添加空白或用于强制子小部件的尺寸。
  • Container
    • 功能更强大,不仅可以设置宽度和高度,还可以设置边距、填充、颜色、边框、装饰等。
    • 可以包含其他小部件,常用于构建复杂的 UI 组件。

性能

  • SizedBox
    • 由于其功能简单,SizedBox 具有更好的性能,因为它只处理尺寸,不会涉及额外的绘制和布局逻辑。
  • Container
    • 由于其多种功能,Container 可能会引入额外的开销,尤其是当你只需要设置尺寸时。

使用场景

  • SizedBox
    • 当你只需要指定一个小部件的大小时,使用 SizedBox 更为简洁和高效。
    • 适合用于创建空白空间或简单的尺寸控制。
  • Container
    • 当你需要更复杂的样式或布局时(例如,设置边距、背景颜色、边框等),使用 Container 更为合适。
    • 适合用于构建自定义的 UI 元素。

示例

SizedBox 示例:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('SizedBox Example')),
        body: Center(
          child: SizedBox(
            width: 100,
            height: 100,
            child: Container(color: Colors.blue), // 控制子小部件的尺寸
          ),
        ),
      ),
    );
  }
}

Container 示例:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Container Example')),
        body: Center(
          child: Container(
            width: 100,
            height: 100,
            color: Colors.blue,
            margin: EdgeInsets.all(10), // 设置边距
            padding: EdgeInsets.all(20), // 设置内边距
            child: Text('Hello', style: TextStyle(color: Colors.white)), // 子小部件
          ),
        ),
      ),
    );
  }
}
  • 使用 SizedBox 时选择简单、性能优越的尺寸控制。
  • 使用 Container 时选择更复杂的布局和样式需求。

根据具体的需求选择适合的小部件,可以提高代码的可读性和性能。


27. 列出 flutter 中的 Visibility、Opacity、Offstage 三组件使用场景?

在 Flutter 中,VisibilityOpacityOffstage 都是用于控制小部件在界面上的显示和隐藏的组件,但它们的使用场景和效果各有不同。以下是对这三个组件的使用场景的详细说明:

  1. Visibility
  • 功能:控制小部件的可见性。可以选择隐藏小部件,同时保留其在布局中的空间。
  • 使用场景
    • 条件渲染:根据某些条件(如用户输入或状态变化)显示或隐藏小部件。
    • 保持布局:当你希望隐藏某个小部件但仍然保留其在界面中的位置和空间时,使用 Visibility 是合适的。
    • 动态界面:在动态构建 UI 时,例如在表单中根据用户选择显示或隐藏某些字段。

示例:

Visibility(
  visible: isVisible, // 控制可见性
  child: Text('This text is conditionally visible.'),
)
  1. Opacity
  • 功能:控制小部件的透明度(0.0 到 1.0 之间的值),允许创建渐隐渐现的效果。
  • 使用场景
    • 动画效果:在需要实现渐变效果时,例如在页面切换时淡入淡出。
    • 视觉提示:当你希望某个小部件变得不那么显眼,但仍然保留在布局中时,可以使用 Opacity 来调整其透明度。
    • 状态指示:在应用程序的某些状态下(如加载状态),可以逐渐淡出不需要的元素,同时让其他元素保持可见。

示例:

Opacity(
  opacity: 0.5, // 设置透明度
  child: Text('This text is semi-transparent.'),
)
  1. Offstage
  • 功能:将小部件移出可见区域,完全不占用屏幕空间。与 Visibility 不同,Offstage 会在布局中完全忽略被隐藏的小部件。
  • 使用场景
    • 优化性能:在不需要显示某个小部件时,使用 Offstage 可以提高性能,因为它不会参与布局计算。
    • 条件渲染:在需要时可以快速显示或隐藏小部件,而不需要重新构建整个小部件树。
    • 复杂布局:在复杂的 UI 中,可能需要临时隐藏某些组件,而不希望它们占用空间。

示例:

Offstage(
  offstage: !isVisible, // 控制是否在屏幕外
  child: Text('This text is offstage.'),
)
  • Visibility:控制小部件的显示和隐藏,同时保留其在布局中的空间。
  • Opacity:控制小部件的透明度,适用于渐变效果和视觉提示。
  • Offstage:将小部件完全移出可见区域,不占用任何布局空间,适合优化性能和条件渲染。

根据具体的需求选择合适的组件,可以使你的 Flutter 应用具有更好的用户体验和性能。


28. 我们可以在 Container 中同时使用 Color 和 Decoration 属性吗?

不可以

在 Flutter 中,Container 小部件的 colordecoration 属性不能同时使用。具体来说,如果你同时为这两个属性赋值,decoration 属性将优先于 color 属性。也就是说,color 属性会被忽略。

解释

  • color 属性
    • 用于快速设置容器的背景色。它是一个简化的方式,适合只需要设置颜色的情况。
  • decoration 属性
    • 更为复杂,支持多种装饰效果,比如背景色、边框、阴影、渐变等。如果你设置了 decorationcolor 属性的值会被忽略。

示例

以下是一个示例,展示同时使用 colordecoration 的效果:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Container Example')),
        body: Center(
          child: Container(
            color: Colors.red, // 这个属性会被忽略
            decoration: BoxDecoration(
              color: Colors.blue, // 这个属性会生效
              border: Border.all(color: Colors.black, width: 2),
            ),
            width: 100,
            height: 100,
          ),
        ),
      ),
    );
  }
}

在这个示例中,尽管 color 属性被设置为红色,但最终容器的背景是蓝色,因为 decoration 属性中的 color 会覆盖它。

如果你需要同时设置颜色和其他装饰效果,应该使用 decoration 属性,并在其中设置 color。如果只需要简单的背景色,可以直接使用 color 属性。确保在使用时选择适合的属性,以避免不必要的混淆。


29. 为了让 CrossAxisAlignment.baseline 能够工作,我们还需要设置什么属性?

在 Flutter 中,为了使 CrossAxisAlignment.baseline 正常工作,除了设置 CrossAxisAlignment.baseline 外,你还需要确保设置 textBaseline 属性。这个属性通常在 RowColumn 小部件中使用。

具体要求

  1. 设置 textBaseline
    • 当使用 CrossAxisAlignment.baseline 时,你必须指定 textBaseline,以告诉 Flutter 如何计算基线。可以选择的基线有:
      • TextBaseline.alphabetic:用于大多数文本。
      • TextBaseline.ideographic:用于某些语言的文本。
  2. 子小部件必须是文本
    • 确保在 RowColumn 中的子小部件能够提供基线。这通常是文本小部件(如 Text)或其他可以确定基线的小部件。

示例

下面是一个示例,展示如何使用 CrossAxisAlignment.baseline

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('CrossAxisAlignment.baseline Example')),
        body: Center(
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.baseline,
            textBaseline: TextBaseline.alphabetic, // 确保设置 textBaseline
            children: <Widget>[
              Text(
                'Hello',
                style: TextStyle(fontSize: 40),
              ),
              Text(
                'World',
                style: TextStyle(fontSize: 20),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

要使 CrossAxisAlignment.baseline 工作,你必须:

  • 设置 textBaseline 属性,以指定计算文本基线的方式。
  • 确保子小部件能够提供有效的基线信息,通常是文本小部件。

30. 我们什么时候应该使用 resizeToAvoidBottomInset ?

在 Flutter 中,resizeToAvoidBottomInsetScaffold 的一个属性,用于控制当键盘弹出时,是否调整界面的大小以避免被键盘遮挡。这个属性的使用场景主要包括:

使用场景

  • 输入框在底部
    • 当你的界面中有输入框(如 TextField)并且这些输入框位于屏幕底部时,设置 resizeToAvoidBottomInset: true 可以确保键盘弹出时,输入框不会被遮挡。
  • 聊天应用
    • 在聊天应用中,通常会有一个输入框在底部,用户输入消息时,键盘需要弹出。使用该属性可以确保用户可以看到正在输入的内容。
  • 表单
    • 在包含表单的页面中,当用户需要输入数据时,确保输入框可见是很重要的。在这种情况下,使用 resizeToAvoidBottomInset 可以改善用户体验。
  • 动态布局
    • 当你的界面布局是动态的,可能在不同情况下显示不同的小部件时,使用该属性可以确保布局在键盘弹出时能够适应变化。

示例

以下是一个简单的示例,展示如何使用 resizeToAvoidBottomInset

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('resizeToAvoidBottomInset Example')),
        body: SingleChildScrollView(
          child: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              children: <Widget>[
                TextField(
                  decoration: InputDecoration(labelText: 'Enter your message'),
                ),
                SizedBox(height: 600), // 模拟其他内容
              ],
            ),
          ),
        ),
        resizeToAvoidBottomInset: true, // 确保键盘弹出时调整大小
      ),
    );
  }
}

注意事项

  • 性能:在某些情况下,调整布局可能会引起性能问题,尤其是在有大量小部件的场景中。因此,使用时需要考虑性能影响。
  • 其他情况:如果你的界面不需要调整大小(例如,使用 SingleChildScrollViewListView 等可滚动的小部件),则可以选择不使用该属性。

使用 resizeToAvoidBottomInset 是为了确保在键盘弹出时,用户可以方便地看到和输入内容。适用于包含输入框的场景,特别是在聊天、表单等应用中,能够显著提升用户体验。


小结

感谢阅读本文

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


猫哥 APP

flutter 学习路径


© 猫哥 ducafecat.com

end