添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
  • Intent 在 Flutter 中的对应概念是什么?

  • 在 Flutter 中应该如何处理从外部应用接收到的 intent?

  • startActivityForResult() 的对应方法是什么?

  • 异步 UI

  • runOnUiThread() 在 Flutter 中的对应方法是什么?

  • 如何将任务转移到后台线程?

  • OkHttp 在 Flutter 中对应什么库?

  • 如何为耗时任务显示进度?

  • 工程结构和资源文件

  • 在哪里放置分辨率相关的图片文件?

  • 字符串储存在哪里?如何处理本地化?

  • Gradle 文件的对应物是什么?我该如何添加依赖?

  • Activity 和 Fragment

  • Activity 和 Fragment 在 Flutter 中的对应概念是什么?

  • 如何监听 Android Activity 的生命周期事件?

  • LinearLayout 的对应概念是什么?

  • RelativeLayout 的对应概念是什么?

  • ScrollView 的对应概念是什么?

  • 在 Flutter 中如何处理屏幕旋转?

  • 手势监听和触摸事件处理

  • Flutter 中如何为一个 Widget 添加点击监听器?

  • 如何处理 Widget 上的其它手势?

  • 列表视图和适配器

  • ListView 在 Flutter 中的对应概念是什么?

  • 如何知道点击了哪个列表项?

  • 如何动态更新 ListView?

  • 如何为 Text Widget 设置自定义字体?

  • 如何更改 Text Widget 的样式?

  • Input 的「提示」 (hint) 的对应概念是什么?

  • 如何显示验证错误的信息?

  • Flutter 插件

  • 如何使用 GPS 传感器?

  • 如何使用相机?

  • 如何使用 Facebook 登录?

  • 如何使用 Firebase 的功能?

  • 如何创建自己的自定义原生集成插件?

  • 如何在 Flutter 应用中使用 NDK?

  • 主题(Themes)

  • 如何对应用使用主题?

  • 数据库和本地存储

  • 如何使用 Shared Preferences?

  • 在 Flutter 中如何使用 SQLite?

  • 我可以使用什么工具调试我的 Flutter 应用?

  • 如何设置推送通知?

  • Intent 在 Flutter 中的对应概念是什么?

  • 在 Flutter 中应该如何处理从外部应用接收到的 intent?

  • startActivityForResult() 的对应方法是什么?

  • 异步 UI

  • runOnUiThread() 在 Flutter 中的对应方法是什么?

  • 如何将任务转移到后台线程?

  • OkHttp 在 Flutter 中对应什么库?

  • 如何为耗时任务显示进度?

  • 工程结构和资源文件

  • 在哪里放置分辨率相关的图片文件?

  • 字符串储存在哪里?如何处理本地化?

  • Gradle 文件的对应物是什么?我该如何添加依赖?

  • Activity 和 Fragment

  • Activity 和 Fragment 在 Flutter 中的对应概念是什么?

  • 如何监听 Android Activity 的生命周期事件?

  • LinearLayout 的对应概念是什么?

  • RelativeLayout 的对应概念是什么?

  • ScrollView 的对应概念是什么?

  • 在 Flutter 中如何处理屏幕旋转?

  • 手势监听和触摸事件处理

  • Flutter 中如何为一个 Widget 添加点击监听器?

  • 如何处理 Widget 上的其它手势?

  • 列表视图和适配器

  • ListView 在 Flutter 中的对应概念是什么?

  • 如何知道点击了哪个列表项?

  • 如何动态更新 ListView?

  • 如何为 Text Widget 设置自定义字体?

  • 如何更改 Text Widget 的样式?

  • Input 的「提示」 (hint) 的对应概念是什么?

  • 如何显示验证错误的信息?

  • Flutter 插件

  • 如何使用 GPS 传感器?

  • 如何使用相机?

  • 如何使用 Facebook 登录?

  • 如何使用 Firebase 的功能?

  • 如何创建自己的自定义原生集成插件?

  • 如何在 Flutter 应用中使用 NDK?

  • 主题(Themes)

  • 如何对应用使用主题?

  • 数据库和本地存储

  • 如何使用 Shared Preferences?

  • 在 Flutter 中如何使用 SQLite?

  • 我可以使用什么工具调试我的 Flutter 应用?

  • 如何设置推送通知?

  • 然而,widget 和 View 还是有一些差异。首先,widget 有着不一样的生命周期:它们是不可变的,一旦需要变化则生命周期终止。任何时候 widget 或它们的状态变化时, Flutter 框架都会创建一个新的 widget 树的实例。对比来看,一个 Android View 只会绘制一次,除非调用 invalidate 才会重绘。

    Flutter 的 widget 很轻量,部分原因在于它们的不可变性。因为它们本身既非视图,也不会直接绘制任何内容,而是 UI 及其底层创建真正视图对象的语义的描述。

    Flutter 支持 Material Components 库。它提供实现了 Material Design 设计规范 的 widgets。 Material Design 是一套 为所有平台优化 (包括 iOS)的灵活的设计系统。

    Flutter 非常灵活、有表达能力,它可以实现任何设计语言。例如,在 iOS 平台上,你可以使用 Cupertino widgets 创建 Apple 的 iOS 设计语言 风格的界面。

    class SampleApp extends StatelessWidget { const SampleApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), home: const SampleAppPage(), class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State<SampleAppPage> createState() => _SampleAppPageState(); class _SampleAppPageState extends State<SampleAppPage> { // Default placeholder text. String textToShow = 'I Like Flutter'; void _updateText() { setState(() { // Update the text. textToShow = 'Flutter is Awesome!'; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Sample App'), body: Center(child: Text(textToShow)), floatingActionButton: FloatingActionButton( onPressed: _updateText, tooltip: 'Update Text', child: const Icon(Icons.update), 如何布局 Widget?我的 XML 布局文件在哪里?

    在 Android 中,你通过 XML 文件定义布局,但是在 Flutter 中,你要通过一个 widget 树来定义布局的。

    以下示例展示了如何显示一个带有间距的简单 widget:

    child: ElevatedButton( style: ElevatedButton.styleFrom( padding: const EdgeInsets.only(left: 20, right: 30), onPressed: () {}, child: const Text('Hello'),

    你可以在 widget 目录 中查看 Flutter 提供的布局。

    // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), home: const SampleAppPage(), class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State<SampleAppPage> createState() => _SampleAppPageState(); class _SampleAppPageState extends State<SampleAppPage> { // Default value for toggle. bool toggle = true; void _toggle() { setState(() { toggle = !toggle; Widget _getToggleChild() { if (toggle) { return const Text('Toggle One'); } else { return ElevatedButton( onPressed: () {}, child: const Text('Toggle Two'), @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Sample App'), body: Center( child: _getToggleChild(), floatingActionButton: FloatingActionButton( onPressed: _toggle, tooltip: 'Update Text', child: const Icon(Icons.update), Widget 如何实现动画?

    在 Android 中,你既可以通过 XML 文件定义动画,也可以调用 View 对象的 animate() 方法。在 Flutter 里,则使用动画库,通过将 Widget 嵌入一个动画 Widget 的方式实现 Widget 的动画效果。

    Flutter 通过 Animation<double> 的子类 AnimationController 来暂停、播放、停止以及逆向播放动画。它需要一个 Ticker 在垂直同步 (vsync) 的时候发出信号,并且在运行的时候创建一个介于 0 和 1 之间的线性插值。然后你就可以创建一个或多个 Animation ,并将它们绑定到控制器上。

    例如,你可以使用 CurvedAnimation 来实现一个曲线插值的动画。在这种情况下,控制器决定了动画进度, CurvedAnimation 计算用于替换控制器默认线性动画的曲线值。与 Widget 一样,Flutter 中的动画效果也可以组合使用。

    在构建 Widget 树的时候,你需要将 Animation 对象赋值给某个 Widget 的动画属性,例如 FadeTransition 的不透明度属性,并让控制器开始动画。

    下面的例子展示了如何实现一个点击 FloatingActionButton 时将一个 Widget 渐变为一个图标的 FadeTransition

    class FadeAppTest extends StatelessWidget { const FadeAppTest({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Fade Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), home: const MyFadeTest(title: 'Fade Demo'), class MyFadeTest extends StatefulWidget { const MyFadeTest({super.key, required this.title}); final String title; @override State<MyFadeTest> createState() => _MyFadeTest(); class _MyFadeTest extends State<MyFadeTest> with TickerProviderStateMixin { late AnimationController controller; late CurvedAnimation curve; @override void initState() { super.initState(); controller = AnimationController( duration: const Duration(milliseconds: 2000), vsync: this, curve = CurvedAnimation( parent: controller, curve: Curves.easeIn, @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), body: Center( child: FadeTransition( opacity: curve, child: const FlutterLogo( size: 100, floatingActionButton: FloatingActionButton( tooltip: 'Fade', onPressed: () { controller.forward(); child: const Icon(Icons.brush),

    获取更多内容,请查看 动画 Widget 动画指南 以及 动画概览

    在 Android 中,你可以使用 Canvas Drawable 将图片和形状绘制到屏幕上。 Flutter 也有一个类似于 Canvas 的 API,因为它基于相同的底层渲染引擎 Skia。因此,在 Flutter 中用画布 (canvas) 进行绘制对于 Android 开发者来说是一件非常熟悉的工作。

    Flutter 有两个帮助你用画布 (canvas) 进行绘制的类: CustomPaint CustomPainter ,后者可以实现自定义的绘制算法。

    如果想学习在 Flutter 中如何实现一个签名功能,可以查看 Collin 在 Custom Paint 上的回答。

    import 'package:flutter/material.dart';
    void main() => runApp(const MaterialApp(home: DemoApp()));
    class DemoApp extends StatelessWidget {
      const DemoApp({super.key});
      @override
      Widget build(BuildContext context) => const Scaffold(body: Signature());
    class Signature extends StatefulWidget {
      const Signature({super.key});
      @override
      SignatureState createState() => SignatureState();
    class SignatureState extends State<Signature> {
      List<Offset?> _points = <Offset>[];
      @override
      Widget build(BuildContext context) {
        return GestureDetector(
          onPanUpdate: (details) {
            setState(() {
              RenderBox? referenceBox = context.findRenderObject() as RenderBox;
              Offset localPosition =
                  referenceBox.globalToLocal(details.globalPosition);
              _points = List.from(_points)..add(localPosition);
          onPanEnd: (details) => _points.add(null),
          child: CustomPaint(
            painter: SignaturePainter(_points),
            size: Size.infinite,
    class SignaturePainter extends CustomPainter {
      SignaturePainter(this.points);
      final List<Offset?> points;
      @override
      void paint(Canvas canvas, Size size) {
        var paint = Paint()
          ..color = Colors.black
          ..strokeCap = StrokeCap.round
          ..strokeWidth = 5;
        for (int i = 0; i < points.length - 1; i++) {
          if (points[i] != null && points[i + 1] != null) {
            canvas.drawLine(points[i]!, points[i + 1]!, paint);
      @override
      bool shouldRepaint(SignaturePainter oldDelegate) =>
          oldDelegate.points != points;
    如何创建自定义 Widget?
    

    在 Android 中,一般通过继承 View 类,或者使用已有的视图类,再重载或实现以达到特定效果的方法。

    在 Flutter 中,通过 组合 更小的 Widget 来创建自定义 Widget(而不是继承它们)。这和 Android 中实现一个自定义的 ViewGroup 有些类似,所有的构建 UI 的模块代码都已存在,不过由你提供不同的行为—例如,自定义布局 (layout) 逻辑。

    举例来说,你该如何创建一个在构造器接收标签参数的 CustomButton?你要组合 ElevatedButton 和一个标签来创建自定义按钮,而不是继承 ElevatedButton

    在 Android 中,Intent 主要有两个使用场景:在 Activity 之前进行导航,以及组件间通信。 Flutter 却没有 intent 这样的概念,但是你依然可以通过原生集成 (插件) 来启动 intent。

    Flutter 实际上并没有 Activity 和 Fragment 的对应概念。在 Flutter 中你需要使用 NavigatorRoute 在同一个 Activity 内的不同界面间进行跳转。

    Route 是应用内屏幕和页面的抽象,Navigator 是管理路径 route 的工具。一个 route 对象大致对应于一个 Activity,但是它的含义是不一样的。 Navigator 可以通过对 route 进行压栈和弹栈操作实现页面的跳转。 Navigator 的工作原理和栈相似,你可以将想要跳转到的 route 压栈 (push()),想要返回的时候将 route 出栈 (pop())。

    在 Android 中,在应用的 AndroidManifest.xml 文件中声明 Activity。

    在 Flutter 中,你有多种不同的方式在页面间导航:

    runApp(MaterialApp( home: const MyAppHome(), // Becomes the route named '/'. routes: <String, WidgetBuilder>{ '/a': (context) => const MyPage(title: 'page A'), '/b': (context) => const MyPage(title: 'page B'), '/c': (context) => const MyPage(title: 'page C'),

    通过路由名 压栈 (push) 到 Navigator 中来跳转到这个 route。

    android:name=".MainActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> <!-- ... --> <intent-filter> <action android:name="android.intent.action.SEND" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="text/plain" /> </intent-filter> </activity>

    接着在 MainActivity 中处理 intent,提取出其它 intent 分享的文本并保存。当 Flutter 准备好处理的时候,它会使用一个平台通道请求数据,数据便会从原生端发送过来:

    package com.example.shared;
    import android.content.Intent;
    import android.os.Bundle;
    import androidx.annotation.NonNull;
    import io.flutter.plugin.common.MethodChannel;
    import io.flutter.embedding.android.FlutterActivity;
    import io.flutter.embedding.engine.FlutterEngine;
    import io.flutter.plugins.GeneratedPluginRegistrant;
    public class MainActivity extends FlutterActivity {
      private String sharedText;
      private static final String CHANNEL = "app.channel.shared.data";
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent intent = getIntent();
        String action = intent.getAction();
        String type = intent.getType();
        if (Intent.ACTION_SEND.equals(action) && type != null) {
          if ("text/plain".equals(type)) {
            handleSendText(intent); // Handle text being sent
      @Override
      public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
          GeneratedPluginRegistrant.registerWith(flutterEngine);
          new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
                  .setMethodCallHandler(
                          (call, result) -> {
                              if (call.method.contentEquals("getSharedText")) {
                                  result.success(sharedText);
                                  sharedText = null;
      void handleSendText(Intent intent) {
        sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
    

    最后,当 widget 渲染的时候,从 Flutter 这端请求数据:

    // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample Shared App Handler', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), home: const SampleAppPage(), class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State<SampleAppPage> createState() => _SampleAppPageState(); class _SampleAppPageState extends State<SampleAppPage> { static const platform = MethodChannel('app.channel.shared.data'); String dataShared = 'No data'; @override void initState() { super.initState(); getSharedText(); @override Widget build(BuildContext context) { return Scaffold(body: Center(child: Text(dataShared))); Future<void> getSharedText() async { var sharedData = await platform.invokeMethod('getSharedText'); if (sharedData != null) { setState(() { dataShared = sharedData; startActivityForResult() 的对应方法是什么?

    Navigator 类负责 Flutter 的导航,并用来接收被压栈的 route 的返回值。这是通过在 push() 后返回的 Futureawait 来实现的。

    例如,要打开一个让用户选择位置的路由,你可以这样做:

    Future<void> loadData() async {
      var dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
      http.Response response = await http.get(dataURL);
      setState(() {
        widgets = jsonDecode(response.body);
    

    一旦用 await 修饰的网络操作完成,再调用 setState() 更新 UI,这会触发 widget 子树的重建并更新数据。

    下面的例子展示了异步加载数据并将之展示在 ListView 内:

    title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), home: const SampleAppPage(), class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State<SampleAppPage> createState() => _SampleAppPageState(); class _SampleAppPageState extends State<SampleAppPage> { List widgets = []; @override void initState() { super.initState(); loadData(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Sample App'), body: ListView.builder( itemCount: widgets.length, itemBuilder: (context, position) { return getRow(position); Widget getRow(int i) { return Padding( padding: const EdgeInsets.all(10), child: Text("Row ${widgets[i]["title"]}"), Future<void> loadData() async { var dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts'); http.Response response = await http.get(dataURL); setState(() { widgets = jsonDecode(response.body);

    参考下一节内容获取更多关于后台任务以及 Flutter 与 Android 的差异的信息。

    在 Android 中,当你想要访问一个网络资源却又不想阻塞主线程并避免 ANR 的时候,你一般会将任务放到一个后台线程中运行。例如,你可以使用 AsyncTaskLiveDataIntentServiceJobScheduler 任务或者通过 RxJava 的管道用调度器将任务切换到后台线程中。

    由于 Flutter 是单线程并且运行一个事件循环(类似 Node.js),你无须担心线程的管理以及后台线程的创建。如果你在执行和 I/O 绑定的任务,例如存储访问或者网络请求,那么你可以安全地使用 async/await,并无后顾之忧。再例如,你需要执行消耗 CPU 的计算密集型工作,那么你可以将其转移到一个 Isolate 上以避免阻塞事件循环,就像你在 Android 中会将任何任务放到主线程之外一样。

    对于和 I/O 绑定的任务,将方法声明为 async 方法,并在方法内 await 一个长时间运行的任务:

    Future<void> loadData() async {
      var dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
      http.Response response = await http.get(dataURL);
      setState(() {
        widgets = jsonDecode(response.body);
    

    这就是你一般应该如何执行网络和数据库操作,它们都属于 I/O 操作。

    在 Android 中,当你继承 AsyncTask 的时候,你一般会覆写三个方法: onPreExecute()doInBackground()onPostExecute()。 Flutter 中没有对应的 API,你只需要 await 一个耗时方法调用, Dart 的事件循环就会帮你处理剩下的事情。

    然而,有时候你可能需要处理大量的数据并挂起你的 UI。在 Flutter 中,可以通过使用 Isolate 来利用多核处理器的优势执行耗时或计算密集的任务。

    Isolate 是独立执行的线程,不会和主执行内存堆分享内存。这意味着你无法访问主线程的变量,或者调用 setState() 更新 UI。与 Android 中线程的概念不同,isolate 如其名所示,它们无法分享内存(例如通过静态变量的形式)。

    下面的例子展示了一个简单的 isolate 是如何将数据分享给主线程来更新 UI 的。

    Future<void> loadData() async {
      ReceivePort receivePort = ReceivePort();
      await Isolate.spawn(dataLoader, receivePort.sendPort);
      // The 'echo' isolate sends its SendPort as the first message.
      SendPort sendPort = await receivePort.first;
      List msg = await sendReceive(
        sendPort,
        'https://jsonplaceholder.typicode.com/posts',
      setState(() {
        widgets = msg;
    // The entry point for the isolate.
    static Future<void> dataLoader(SendPort sendPort) async {
      // Open the ReceivePort for incoming messages.
      ReceivePort port = ReceivePort();
      // Notify any other isolates what port this isolate listens to.
      sendPort.send(port.sendPort);
      await for (var msg in port) {
        String data = msg[0];
        SendPort replyTo = msg[1];
        String dataURL = data;
        http.Response response = await http.get(Uri.parse(dataURL));
        // Lots of JSON to parse
        replyTo.send(jsonDecode(response.body));
    Future sendReceive(SendPort port, msg) {
      ReceivePort response = ReceivePort();
      port.send([msg, response.sendPort]);
      return response.first;
    

    这里的 dataLoader() 就是运行在自己独立执行线程内的 Isolate。在 Isolate 中你可以执行更多的 CPU 密集型操作(例如解析一个大的 JSON 数据),或者执行计算密集型的数学运算,例如加密或信号处理。

    你可以运行下面这个完整的例子:

    title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), home: const SampleAppPage(), class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State<SampleAppPage> createState() => _SampleAppPageState(); class _SampleAppPageState extends State<SampleAppPage> { List widgets = []; @override void initState() { super.initState(); loadData(); Widget getBody() { bool showLoadingDialog = widgets.isEmpty; if (showLoadingDialog) { return getProgressDialog(); } else { return getListView(); Widget getProgressDialog() { return const Center(child: CircularProgressIndicator()); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Sample App'), body: getBody(), ListView getListView() { return ListView.builder( itemCount: widgets.length, itemBuilder: (context, position) { return getRow(position); Widget getRow(int i) { return Padding( padding: const EdgeInsets.all(10), child: Text("Row ${widgets[i]["title"]}"), Future<void> loadData() async { ReceivePort receivePort = ReceivePort(); await Isolate.spawn(dataLoader, receivePort.sendPort); // The 'echo' isolate sends its SendPort as the first message. SendPort sendPort = await receivePort.first; List msg = await sendReceive( sendPort, 'https://jsonplaceholder.typicode.com/posts', setState(() { widgets = msg; // The entry point for the isolate. static Future<void> dataLoader(SendPort sendPort) async { // Open the ReceivePort for incoming messages. ReceivePort port = ReceivePort(); // Notify any other isolates what port this isolate listens to. sendPort.send(port.sendPort); await for (var msg in port) { String data = msg[0]; SendPort replyTo = msg[1]; String dataURL = data; http.Response response = await http.get(Uri.parse(dataURL)); // Lots of JSON to parse replyTo.send(jsonDecode(response.body)); Future sendReceive(SendPort port, msg) { ReceivePort response = ReceivePort(); port.send([msg, response.sendPort]); return response.first; OkHttp 在 Flutter 中对应什么库?

    Flutter 中使用流行的 http package 进行网络请求是很简单的。

    虽然 http 包没有 OkHttp 中的所有功能,但是它抽象了很多通常你会自己实现的网络功能,这使其本身在执行网络请求时简单易用。

    要使用 http,请在 pubspec.yaml 文件中添加依赖:

    $ flutter pub add http
    

    如果要发起一个网络请求,在异步 (async) 方法 http.get() 上调用 await 即可:

    Future<void> loadData() async { var dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts'); http.Response response = await http.get(dataURL); developer.log(response.body); 如何为耗时任务显示进度?

    在 Android 中你通常会在后台执行一个耗时任务的时候显示一个 ProgressBar 在界面上。

    在 Flutter 中,我们使用 ProgressIndicator widget。通过代码逻辑使用一个布尔标记值控制进度条的渲染。

    在下面的例子中,build 方法被拆分成三个不同的方法。如果 showLoadingDialog() 返回 true(当 widgets.length == 0),渲染 ProgressIndicator。否则,在 ListView 里渲染网络请求返回的数据。

    title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), home: const SampleAppPage(), class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State<SampleAppPage> createState() => _SampleAppPageState(); class _SampleAppPageState extends State<SampleAppPage> { List widgets = []; @override void initState() { super.initState(); loadData(); Widget getBody() { bool showLoadingDialog = widgets.isEmpty; if (showLoadingDialog) { return getProgressDialog(); } else { return getListView(); Widget getProgressDialog() { return const Center(child: CircularProgressIndicator()); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Sample App'), body: getBody(), ListView getListView() { return ListView.builder( itemCount: widgets.length, itemBuilder: (context, position) { return getRow(position); Widget getRow(int i) { return Padding( padding: const EdgeInsets.all(10), child: Text("Row ${widgets[i]["title"]}"), Future<void> loadData() async { var dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts'); http.Response response = await http.get(dataURL); setState(() { widgets = jsonDecode(response.body); 工程结构和资源文件
    val flutterAssetStream = assetManager.open("flutter_assets/assets/my_flutter_asset.png")
    

    Flutter 依然无法访问原生资源文件 (resources),也无法访问原生资产文件 (assets)。

    如果你要向 Flutter 项目中添加一个新的叫 my_icon.png 的图片资源,并且将其放入我们随便起名的叫做 images 的文件夹中,你需要将基础图片(1.0x)放在 images 文件夹中,并将其它倍数的图片放入以特定倍数作为名称的子文件夹中:

    images/my_icon.png       // Base: 1.0x image
    images/2.0x/my_icon.png  // 2.0x image
    images/3.0x/my_icon.png  // 3.0x image
    

    接下来,你需要在 pubspec.yaml 文件中定义这些图片:

    assets:
     - images/my_icon.jpeg
    

    然后你就可以使用 AssetImage 访问你的图片了:

    虽然在你的 Flutter 项目的 android 文件夹下有 Gradle 文件,但是它们只用于给对应平台的集成添加原生依赖。一般来说,在 pubspec.yaml 文件中定义在 Flutter 里使用的外部依赖。 pub.dev 是查找 Flutter packages 的好地方。

    在 Android 中,你可以覆写 Activity 的生命周期方法来监听其生命周期,也可以在 Application 上注册 ActivityLifecycleCallbacks。在 Flutter 中,这两种方法都没有,但是你可以通过绑定 WidgetsBinding 观察者并监听 didChangeAppLifecycleState() 的变化事件来监听生命周期。

    可以被观察的生命周期事件有:

    你可能已经注意到,只有一小部分的 Activity 生命周期事件是可用的,虽然 FlutterActivity 在内部捕获了几乎所有的 Activity 生命周期事件并将它们发送给 Flutter 引擎,但是它们大部分都向你屏蔽了。 Flutter 为你管理引擎的启动和停止,在大部分情况下几乎没有理由要在 Flutter 一端监听 Activity 的生命周期。如果你需要通过监听生命周期来获取或释放原生的资源,无论如何都应该在原生端做这件事。

    下面的例子展示了如何监听容器 Activity 的生命周期状态:

    class _LifecycleWatcherState extends State<LifecycleWatcher> with WidgetsBindingObserver { AppLifecycleState? _lastLifecycleState; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); @override void didChangeAppLifecycleState(AppLifecycleState state) { setState(() { _lastLifecycleState = state; @override Widget build(BuildContext context) { if (_lastLifecycleState == null) { return const Text( 'This widget has not observed any lifecycle changes.', textDirection: TextDirection.ltr, return Text( 'The most recent lifecycle state this widget observed was: $_lastLifecycleState.', textDirection: TextDirection.ltr, void main() { runApp(const Center(child: LifecycleWatcher())); Widget build(BuildContext context) { return const Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text('Row One'), Text('Row Two'), Text('Row Three'), Text('Row Four'),
    @override
    Widget build(BuildContext context) {
      return const Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('Column One'),
          Text('Column Two'),
          Text('Column Three'),
          Text('Column Four'),
    

    如果想学习更多的构建线性布局的内容,请阅读社区贡献的 Medium 文章 给 Android 开发者的 Flutter 指南:如何在 Flutter 中设计线性布局

    class _SampleAppState extends State<SampleApp> with SingleTickerProviderStateMixin { late AnimationController controller; late CurvedAnimation curve; @override void initState() { super.initState(); controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 2000), curve = CurvedAnimation( parent: controller, curve: Curves.easeIn, @override Widget build(BuildContext context) { return Scaffold( body: Center( child: GestureDetector( onDoubleTap: () { if (controller.isCompleted) { controller.reverse(); } else { controller.forward(); child: RotationTransition( turns: curve, child: const FlutterLogo( size: 200, 列表视图和适配器 class SampleApp extends StatelessWidget { const SampleApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), home: const SampleAppPage(), class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State<SampleAppPage> createState() => _SampleAppPageState(); class _SampleAppPageState extends State<SampleAppPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Sample App'), body: ListView(children: _getListData()), List<Widget> _getListData() { List<Widget> widgets = []; for (int i = 0; i < 100; i++) { widgets.add(Padding( padding: const EdgeInsets.all(10), child: Text('Row $i'), return widgets; 如何知道点击了哪个列表项?

    在 Android 中,ListView 有一个可以帮助你定位哪个列表项被点击了的方法 onItemClickListener。在 Flutter 中,则使用传入 widget 的触摸监听。

    class SampleApp extends StatelessWidget { const SampleApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), home: const SampleAppPage(), class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State<SampleAppPage> createState() => _SampleAppPageState(); class _SampleAppPageState extends State<SampleAppPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Sample App'), body: ListView(children: _getListData()), List<Widget> _getListData() { List<Widget> widgets = []; for (int i = 0; i < 100; i++) { widgets.add( GestureDetector( onTap: () { developer.log('row tapped'); child: Padding( padding: const EdgeInsets.all(10), child: Text('Row $i'), return widgets; 如何动态更新 ListView?

    在 Android 中,你需要更新 adapter 并调用 notifyDataSetChanged

    在 Flutter 中,如果你准备在 setState() 里更新一组 widget,你很快会发现你的数据并没有更新到界面上。这是因为当 setState() 被调用的时候, Flutter 渲染引擎会查看 Widget 树是否有任何更改。当引擎检查到 ListView,他会执行 == 检查,并判断两个 ListView 是一样的。没有任何更改,所以也就不需要更新。

    更新 ListView 的一个简单方法是,在 setState() 里创建一个新的 List,并将数据从旧列表拷贝到新列表。虽然这个方法很简单,就如下面例子所示,但是并不推荐在大数据集的时候使用。

    class SampleApp extends StatelessWidget { const SampleApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), home: const SampleAppPage(), class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State<SampleAppPage> createState() => _SampleAppPageState(); class _SampleAppPageState extends State<SampleAppPage> { List<Widget> widgets = []; @override void initState() { super.initState(); for (int i = 0; i < 100; i++) { widgets.add(getRow(i)); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Sample App'), body: ListView(children: widgets), Widget getRow(int i) { return GestureDetector( onTap: () { setState(() { widgets = List.from(widgets); widgets.add(getRow(widgets.length)); developer.log('row $i'); child: Padding( padding: const EdgeInsets.all(10), child: Text('Row $i'),

    推荐的高效且有效的创建一个列表的方法是使用 ListView.Builder。这个方法非常适用于动态列表或者拥有大量数据的列表。这基本上就是 Android 里的 RecyclerView,会为你自动回收列表项:

    class SampleApp extends StatelessWidget { const SampleApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), home: const SampleAppPage(), class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State<SampleAppPage> createState() => _SampleAppPageState(); class _SampleAppPageState extends State<SampleAppPage> { List<Widget> widgets = []; @override void initState() { super.initState(); for (int i = 0; i < 100; i++) { widgets.add(getRow(i)); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Sample App'), body: ListView.builder( itemCount: widgets.length, itemBuilder: (context, position) { return getRow(position); Widget getRow(int i) { return GestureDetector( onTap: () { setState(() { widgets.add(getRow(widgets.length)); developer.log('row $i'); child: Padding( padding: const EdgeInsets.all(10), child: Text('Row $i'),

    不用创建一个 “ListView”,而是创建接收两个参数的 ListView.Builder,两个参数分别是列表的初始长度和一个 ItemBuilder 方法。

    ItemBuilder 方法和 Android adapter 里的 getView 方法类似;它通过位置返回你期望在这个位置渲染的列表项。

    最后也是最重要的一条,需要注意 onTap() 方法不再重建列表项,但是会执行 .add 操作。

    class SampleApp extends StatelessWidget { const SampleApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), home: const SampleAppPage(), class SampleAppPage extends StatefulWidget { const SampleAppPage({super.key}); @override State<SampleAppPage> createState() => _SampleAppPageState(); class _SampleAppPageState extends State<SampleAppPage> { String? _errorText; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Sample App'), body: Center( child: TextField( onSubmitted: (text) { setState(() { if (!isEmail(text)) { _errorText = 'Error: This is not an email'; } else { _errorText = null; decoration: InputDecoration( hintText: 'This is a hint', errorText: _getErrorText(), String? _getErrorText() { return _errorText; bool isEmail(String em) { String emailRegexp = r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|' r'(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|' r'(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$'; RegExp regExp = RegExp(emailRegexp); return regExp.hasMatch(em); Flutter 插件

    Flutter 提供开箱即用的优美的 Material Design 实现,可以满足你通常需要的各种样式和主题的需求。不同于 Android 中你在 XML 文件中定义主题并在 AndroidManifest.xml 中将其赋值给你的应用, Flutter 中是在顶层 Widget 上声明主题。

    为了在应用中利用好 Material 组件,你可以在应用中声明一个顶层 Widget MaterialApp 作为入口。 MaterialApp 是一个包装了一系列 Widget 的为你给予便利的 Widget,而这些 Widget 通常是实现 Material Design 的应用所必须的。它基于 WidgetsApp 并添加了 Material 相关的功能。

    你也可以使用 WidgetApp 作为应用的 Widget,它会提供一些相同的功能,但是不如 MaterialApp 提供的功能丰富。

    如果要自定义任意子组件的颜色或者样式,给 MaterialApp Widget 传入一个 ThemeData 对象即可。例如,在下面的代码中,主色调设置为深紫色,文本选中颜色设置为红色。

    title: 'Sample App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), textSelectionTheme: const TextSelectionThemeData(selectionColor: Colors.red), home: const SampleAppPage(), 数据库和本地存储 import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; void main() { runApp( const MaterialApp( home: Scaffold( body: Center( child: ElevatedButton( onPressed: _incrementCounter, child: Text('Increment Counter'), Future<void> _incrementCounter() async { SharedPreferences prefs = await SharedPreferences.getInstance(); int counter = (prefs.getInt('counter') ?? 0) + 1; await prefs.setInt('counter', counter); 在 Flutter 中如何使用 SQLite?

    在 Android 中,你会使用 SQLite 来存储可以通过 SQL 进行查询的结构化数据。

    在 Flutter 中,使用 SQFlite 插件实现此功能。