Flutter知识点

Views

在Flutter中,View相当于是Widget。Widget仅支持一帧,并且在每一帧上,Flutter的框架都会创建一个Widget实例树(相当于一次性绘制整个界面)。在Flutter中的widget是不可变的,这允许widget变得超级轻量。

Widget状态

在Flutter中Widget是不可变的,不会直接更新,而必须使用Widget的状态。这是Stateful和Stateless widget的概念的来源。一个Stateless Widget就像它的名字,是一个没有状态信息的widget。

这里要注意的重要一点是无状态和有状态widget的核心特性是相同的。每一帧它们都会重新构建,不同之处在于StatefulWidget有一个State对象,它可以跨帧存储状态数据并恢复它

如果你有疑问,那么要记住这个规则:如果一个widget发生了变化(例如用户与它交互),它就是有状态的。但是,如果一个子widget对变化做出反应,而其父widget对变化没有反应,那么包含的父widget仍然可以是无状态的widget。

Widget编写

在Android中,您通过XML编写布局,但在Flutter中,使用widget树来编写布局。
可以查看Flutter所提供的所有布局: Flutter widget layout

在Android中,您可以从父级控件调用addChild或removeChild以动态添加或删除View。 在Flutter中,因为widget是不可变的,所以没有addChild。相反,您可以传入一个函数,该函数返回一个widget给父项,并通过布尔值控制该widget的创建。

在Flutter中,可以通过动画库给widget添加动画,将widget包装到Animation中。与Android相似,在Flutter中,您有一个AnimationController和一个Interpolator, 它是Animation类的扩展,例如CurvedAnimation。您将控制器和动画传递到AnimationWidget中,并告诉控制器启动动画。
See https://flutter.io/widgets/animation/ and https://flutter.io/tutorials/animation for more specific details.

Flutter有两个类可以帮助您绘制画布,CustomPaint和CustomPainter,它们实现您的算法以绘制到画布。
在这个人气较高的的StackOverFlow答案中,您可以看到签名painter是如何实现的:请参阅https://stackoverflow.com/questions/46241071/create-signature-area-for-mobile-app-in-dart-flutter

如何构建自定义 Widgets

在Flutter中,一个自定义widget通常是通过组合其它widget来实现的,而不是继承

我们来看看如何构建持有一个label的CustomButton。这是通过将Text与RaisedButton组合来实现的,而不是扩展RaisedButton并重写其绘制方法实现:

class CustomButton extends StatelessWidget {
  final String label;
  CustomButton(this.label);

  @override
  Widget build(BuildContext context) {
    return new RaisedButton(onPressed: () {}, child: new Text(label));
  }
}

Intents

Flutter不具有Intents的概念,但如果需要的话,Flutter可以通过Native整合来触发Intents。

要在Flutter中切换屏幕,您可以访问路由以绘制新的Widget。 管理多个屏幕有两个核心概念和类:Route 和 Navigator。Route是应用程序的“屏幕”或“页面”的抽象(可以认为是Activity), Navigator是管理Route的Widget。Navigator可以通过push和pop route以实现页面切换。

在Flutter中,可以将具有指定Route的Map传递到顶层MaterialApp实例

void main() {
  runApp(new MaterialApp(
    home: new MyAppHome(), // becomes the route named '/'
    routes: <String, WidgetBuilder> {
      '/a': (BuildContext context) => new MyPage(title: 'page A'),
      '/b': (BuildContext context) => new MyPage(title: 'page B'),
      '/c': (BuildContext context) => new MyPage(title: 'page C'),
    },
  ));
}

然后,您可以通过Navigator来切换到命名路由的页面。

Navigator.of(context).pushNamed('/b');

Intents的另一个主要的用途是调用外部组件,如Camera或File picker。为此,您需要和native集成(或使用现有的库)

处理外部传入的Intents

Flutter可以通过直接与Android层通信并请求共享的数据来处理来自Android的Intents

在这个例子中,我们注册文本共享intent,所以其他应用程序可以共享文本到我们的Flutter应用程序

这个应用程序的基本流程是我们首先处理Android端的共享文本数据,然后等待Flutter请求数据,然后通过MethodChannel发送

首先在在AndroidManifest.xml中注册我们想要处理的intent:

<activity
       android:name=".MainActivity"
       android:launchMode="singleTop"
       android:theme="@style/LaunchTheme"
       android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
       android:hardwareAccelerated="true"
       android:windowSoftInputMode="adjustResize">
       <!-- This keeps the window background of the activity showing
            until Flutter renders its first frame. It can be removed if
            there is no splash screen (such as the default splash screen
            defined in @style/LaunchTheme). -->
       <meta-data
           android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
           android:value="true" />
       <intent-filter>
           <action android:name="android.intent.action.MAIN"/>
           <category android:name="android.intent.category.LAUNCHER"/>
       </intent-filter>
       <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在完成准备就绪时请求它。

public class MainActivity extends FlutterActivity {
    String sharedText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GeneratedPluginRegistrant.registerWith(this);
        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
            }
        }

        new MethodChannel(getFlutterView(), "app.channel.shared.data").setMethodCallHandler(new MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
                if (methodCall.method.contentEquals("getSharedText")) {
                    result.success(sharedText);
                    sharedText = null;
                }
            }
        });
    }


    void handleSendText(Intent intent) {
        sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
    }
}

最后,在Flutter中,在渲染Flutter视图时请求数据:

class _SampleAppPageState extends State<SampleAppPage> {
  static const platform = const MethodChannel('app.channel.shared.data');
  String dataShared = "No data";

  @override
  void initState() {
    super.initState();
    getSharedText();
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(body: new Center(child: new Text(dataShared)));
  }

  getSharedText() async {
    var sharedData = await platform.invokeMethod("getSharedText");
    if (sharedData != null) {
      setState(() {
        dataShared = sharedData;
      });
    }
  }
}

startActivityForResult 在Flutter中等价于什么

处理Flutter中所有路由的Navigator类可用于从已经push到栈的路由中获取结果。 这可以通过等待push返回的Future来完成。例如,如果您要启动让用户选择其位置的位置的路由,则可以执行以下操作:

Map coordinates = await Navigator.of(context).pushNamed('/location');

然后在你的位置路由中,一旦用户选择了他们的位置,你可以将结果”pop”出栈:

Navigator.of(context).pop({"lat":43.821757,"long":-79.226392});

异步UI

runOnUiThread 在Flutter中等价于什么

Dart是单线程执行模型,支持Isolates(在另一个线程上运行Dart代码的方式)、事件循环和异步编程。 除非您启动一个Isolate,否则您的Dart代码将在主UI线程中运行,并由事件循环驱动(译者语:和JavaScript一样)。

AsyncTask和IntentService在Flutter中等价于什么

由于Flutter是单线程的,运行一个事件循环(如Node.js),所以您不必担心线程管理或者使用AsyncTasks、IntentServices。

要异步运行代码,可以将函数声明为异步函数,并在该函数中等待这个耗时任务

loadData() async {
  String dataURL = "https://jsonplaceholder.typicode.com/posts";
  http.Response response = await http.get(dataURL);
  setState(() {
    widgets = JSON.decode(response.body);
  });
}

这就是典型的进行网络或数据库调用的方式

在Android上,当您继承AsyncTask时,通常会覆盖3个方法,OnPreExecute、doInBackground和onPostExecute。 在Flutter中没有这种模式的等价物,因为您只需等待一个长时间运行的函数,而Dart的事件循环将负责其余的事情。但是,有时您可能需要处理大量数据,导致UI可能会挂起。在这种情况下,与AsyncTask一样,在Flutter中,可以利用多个CPU内核来执行耗时或计算密集型任务。这是通过使用Isolates来完成的。

Isolate是一个独立的执行线程,它运行时不会与主线程共享任何内存。这意味着你不能从该线程访问变量或通过调用setState来更新你的UI。

OkHttp在Flutter中等价于什么

当使用受欢迎的“http”package时,Flutter进行网络信非常简单。

虽然“http” package 没有实现OkHttp的所有功能,但“http” package 抽象出了许多常用的API,可以简单有效的发起网络请求。

您可以通过在pubspec.yaml中添加依赖项来使用它

dependencies:
  ...
  http: '>=0.11.3+12'

在Android中,当您执行耗时任务时,通常会显示进度指示器。在Flutter中,这可以通过渲染Progress Indicator widget来实现。您可以通过编程方式显示Progress Indicator , 通过布尔值通知Flutter在耗时任务发起之前更新其状态。

项目结构和资源

在哪里存储分辨率相关的图片文件? HDPI/XXHDPI

Flutter遵循像iOS这样简单的3种分辨率格式: 1x, 2x, and 3x.

创建一个名为images的文件夹,并为每个图像文件生成一个@2x和@3x文件,并将它们放置在如下这样的文件夹中:

…/my_icon.png
…/2.0x/my_icon.png
…/3.0x/my_icon.png

然后,您需要在pubspec.yaml文件中声明这些图片:

assets:
 - images/a_dot_burr.jpeg
 - images/a_dot_ham.jpeg

然后您可以使用AssetImage访问您的图像

return new AssetImage("images/a_dot_burr.jpeg");

在哪里存储字符串? 如何存储不同的语言

目前,最好的做法是创建一个名为Strings的类

class Strings{
  static String welcomeMessage = "Welcome To Flutter";
}

然后在你的代码中,你可以像访问你的字符串一样:

new Text(Strings.welcomeMessage)

Flutter对Android的可访问性提供了基本的支持,虽然这个功能正在进行中。

鼓励Flutter开发者使用intl package进行国际化和本地化

Android Gradle vs Flutter pubspec.yaml

在Android中,您可以在Gradle文件来添加依赖项。

在Flutter中,虽然在Flutter项目中的Android文件夹下有Gradle文件,但只有在添加平台相关所需的依赖关系时才使用这些文件。 否则,应该使用pubspec.yaml声明用于Flutter的外部依赖项。

发现好的flutter packages的一个好地方 Pub

Activities 和 Fragments

在Flutter中,这两个概念都等同于Widget。

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

在Android中,您可以覆盖Activity的方法来捕获Activity的生命周期回调。

在Flutter中您可以通过挂接到WidgetsBinding观察并监听didChangeAppLifecycleState更改事件来监听生命周期事件

您可以监听到的生命周期事件是

  • resumed - 应用程序可见并响应用户输入。这是来自Android的onResume
  • inactive - 应用程序处于非活动状态,并且未接收用户输入。此事件在Android上未使用,仅适用于iOS
  • paused - 应用程序当前对用户不可见,不响应用户输入,并在后台运行。这是来自Android的暂停
  • suspending - 该应用程序将暂时中止。这在iOS上未使用

    class _LifecycleWatcherState extends State with WidgetsBindingObserver {

    AppLifecycleState _lastLifecyleState;
    
    @override
    void initState() {
      super.initState();
      WidgetsBinding.instance.addObserver(this);
    }
    
    @override
    void dispose() {
      WidgetsBinding.instance.removeObserver(this);
      super.dispose();
    }
    
    @override
    void didChangeAppLifecycleState(AppLifecycleState state) {
      setState(() {
        _lastLifecyleState = state;
      });
    }
    
    @override
    Widget build(BuildContext context) {
      if (_lastLifecyleState == null)
        return new Text('This widget has not observed any lifecycle changes.', textDirection: TextDirection.ltr);
      return new Text('The most recent lifecycle state this widget observed was: $_lastLifecyleState.',
          textDirection: TextDirection.ltr);
    }
    

    }

Layouts

LinearLayout在Flutter中相当于什么

在Android中,使用LinearLayout来使您的控件呈水平或垂直排列。在Flutter中,您可以使用Row或Co​​lumn来实现相同的结果:

@override
Widget build(BuildContext context) {
  return new Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      new Text('Row One'),
      new Text('Row Two'),
      new Text('Row Three'),
      new Text('Row Four'),
    ],
  );
}

RelativeLayout在Flutter中等价于什么

RelativeLayout用于使widget相对于彼此位置排列。在Flutter中,有几种方法可以实现相同的结果

您可以通过使用Column、Row和Stack的组合来实现RelativeLayout的效果。您可以为widget构造函数指定相对于父组件的布局规则。

一个在Flutter中构建RelativeLayout的好例子,请参考在StackOverflow上: https://stackoverflow.com/questions/44396075/equivalent-of-relativelayout-in -flutter

ScrollView在Flutter中等价于什么

在Android中,ScrollView允许您包含一个子控件,以便在用户设备的屏幕比控件内容小的情况下,使它们可以滚动。

在Flutter中,最简单的方法是使用ListView。但在Flutter中,一个ListView既是一个ScrollView,也是一个Android ListView

手势检测和触摸事件处理

如何将一个onClick监听器添加到Flutter中的widget

在Flutter中,添加触摸监听器有两种方法:

  1. 如果Widget支持事件监听,则可以将一个函数传递给它并进行处理。例如,RaisedButton有一个onPressed参数:

    @override
    Widget build(BuildContext context) {
      return new RaisedButton(
          onPressed: () {
            print("click");
          },
          child: new Text("Button"));
    }
    
  2. 如果Widget不支持事件监听,则可以将该Widget包装到GestureDetector中,并将处理函数传递给onTap参数:

    class SampleApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new Scaffold(
            body: new Center(
          child: new GestureDetector(
            child: new FlutterLogo(
              size: 200.0,
            ),
            onTap: () {
              print("tap");
            },
          ),
        ));
      }
    }
    

如何处理widget上的其他手势

使用GestureDetector,可以监听多种手势,例如:

Tap

onTapDown
onTapUp
onTap
onTapCancel

Double tap

onDoubleTap 用户快速连续两次在同一位置轻敲屏幕.

长按

onLongPress

垂直拖动

onVerticalDragStart
onVerticalDragUpdate
onVerticalDragEnd

水平拖拽

onHorizontalDragStart
onHorizontalDragUpdate
onHorizontalDragEnd

Listview & Adapter

ListView在Flutter中相当于什么

在Flutter中,ListView就是一个ListView!

在Android ListView中,您可以创建一个适配器,然后您可以将它传递给ListView,该适配器将使用适配器返回的内容来展示每一行。 然而,你必须确保在合适的时机回收行,否则,你会得到各种疯狂的视觉和内存问题。

在Flutter中,由于Flutter的不可变的widget模型,将一个Widgets列表传递给的ListView,而Flutter将负责确保它们快速平滑地滚动。

怎么知道哪个列表项被点击

在Android中,ListView有一个方法’onItemClickListener’来确定哪个列表项被点击。 Flutter中可以更轻松地通过您传入的处理回调来进行操作:

_getListData() {
   List<Widget> widgets = [];
   for (int i = 0; i < 100; i++) {
     widgets.add(new GestureDetector(
       child: new Padding(
           padding: new EdgeInsets.all(10.0),
           child: new Text("Row $i")),
       onTap: () {
         print('row tapped');
       },
     ));
   }
   return widgets;
 }

如何动态更新ListView

需要更新适配器并调用notifyDataSetChanged。在Flutter中,如果setState()中更新widget列表,您会发现没有变化, 这是因为当setState被调用时,Flutter渲染引擎会遍历所有的widget以查看它们是否已经改变。 当遍历到你的ListView时,它会做一个==运算,以查看两个ListView是否相同,因为没有任何改变,因此没有更新数据。

要更新您的ListView,然后在setState中创建一个新的List()并将所有旧数据复制到新列表中。这是实现更新的简单方法(译者语:此时状态改变,ListView被重新构建

然而,推荐的方法是使用ListView.Builder。当您拥有动态列表或包含大量数据的列表时,此方法非常有用。 这实际上相当于在Android上使用RecyclerView,它会自动为您回收列表元素:

@override
 Widget build(BuildContext context) {
   return new Scaffold(
       appBar: new AppBar(
         title: new Text("Sample App"),
       ),
       body: new ListView.builder(
           itemCount: widgets.length,
           itemBuilder: (BuildContext context, int position) {
             return getRow(position);
           }));
 }

Widget getRow(int i) {
   return new GestureDetector(
     child: new Padding(
         padding: new EdgeInsets.all(10.0),
         child: new Text("Row $i")),
     onTap: () {
       setState(() {
         widgets.add(getRow(widgets.length + 1));
         print('row $i');
       });
     },
   );
 }

我们不是创建一个“新的ListView”,而是创建一个新的ListView.builder,它接受两个参数,即列表的初始长度和一个ItemBuilder函数。ItemBuilder函数非常类似于Android适配器中的getView函数,它需要一个位置并返回要为该位置渲染的行。

最后,但最重要的是,如果您注意到onTap函数,在里面,我们不会再重新创建列表,而只是添加新元素到列表。

使用 Text

如何在 Text widget上设置自定义字体

在Android SDK(从Android O开始)中,创建一个Font资源文件并将其传递到TextView的FontFamily参数中。

在Flutter中,首先你需要把你的字体文件放在项目文件夹中(最好的做法是创建一个名为assets的文件夹)

接下来在pubspec.yaml文件中,声明字体:

fonts:
   - family: MyCustomFont
     fonts:
       - asset: fonts/MyCustomFont.ttf
       - style: italic

最后,将字体应用到Text widget:

@override
Widget build(BuildContext context) {
  return new Scaffold(
    appBar: new AppBar(
      title: new Text("Sample App"),
    ),
    body: new Center(
      child: new Text(
        'This is a custom font text',
        style: new TextStyle(fontFamily: 'MyCustomFont'),
      ),
    ),
  );
}

如何在Text上定义样式

Text的样式参数需要一个TextStyle对象,您可以在其中自定义许多参数。

表单输入

Input的”hint”在flutter中相当于什么

在Flutter中,您可以通过向Text Widget的装饰构造函数参数添加InputDecoration对象,轻松地为输入框显示占位符文本

body: new Center(
  child: new TextField(
    decoration: new InputDecoration(hintText: "This is a hint"),
  )
)

如何显示验证错误

就像您如何使用“hint”一样,您可以将InputDecoration对象传递给Text的装饰构造函数。

但是,您不希望首先显示错误,并且通常会在用户输入一些无效数据时显示该错误。这可以通过更新状态并传递一个新的InputDecoration对象来完成:

class _SampleAppPageState extends State<SampleAppPage> {
  String _errorText;

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Sample App"),
      ),
      body: new Center(
        child: new TextField(
          onSubmitted: (String text) {
            setState(() {
              if (!isEmail(text)) {
                _errorText = 'Error: This is not an email';
              } else {
                _errorText = null;
              }
            });
          },
          decoration: new InputDecoration(hintText: "This is a hint", errorText: _getErrorText()),
        ),
      ),
    );
  }

  _getErrorText() {
    return _errorText;
  }

  bool isEmail(String em) {
    String emailRegexp =
        r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';

    RegExp regExp = new RegExp(p);

    return regExp.hasMatch(em);
  }
}

Flutter 插件

如何使用 GPS sensor
要访问GPS传感器,您可以使用社区插件 https://pub.dartlang.org/packages/location

如何访问相机
访问相机的流行社区插件是 https://pub.dartlang.org/packages/image_picker

如何使用Facebook登陆
要访问Facebook Connect功能,您可以使用 https://pub.dartlang.org/packages/flutter_facebook_connect .

如何构建自定义集成Native功能
如果有Flutter或其社区插件缺失的平台特定功能,那么您可以自己按照以下教程构建https://flutterchina.club/developing-packages/
简而言之,Flutter的插件架构就像在Android中使用Event bus一样:您可以发出消息并让接收者进行处理并将结果返回给您,在这种情况下,接收者将是iOS或Android。

如何在我的Flutter应用程序中使用NDK
自定义插件首先会与Android应用程序通信,您可以在其中调用native标记的函数。一旦Native完成了相应操作,就可以将响应消息发回给Flutter并呈现结果。

主题

Flutter很好的实现了一个美丽的Material Design,它会满足很多样式和主题的需求。 与Android中使用XML声明主题不同,在Flutter中,您可以通过顶层widget声明主题

MaterialApp是一个方便的widget,它包装了许多Material Design应用通常需要的widget,它通过添加Material特定功能构建在WidgetsApp上。

如果你不想使用Material Components,那么你可以声明一个顶级widget-WidgetsApp,它是一个便利的类,它包装了许多应用程序通常需要的widget。

要自定义Material Components的颜色和样式,您可以将ThemeData对象传递到MaterialApp widget中,例如在下面的代码中,您可以看到主色板设置为蓝色,并且所有选择区域的文本颜色都应为红色。

class SampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Sample App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
        textSelectionColor: Colors.red
      ),
      home: new SampleAppPage(),
    );
  }
}

Scaffold 是 Material library 中提供的一个widget, 它提供了默认的导航栏、标题和包含主屏幕widget树的body属性。widget树可以很复杂。

数据库和本地存储

如何在Flutter中访问Shared Preferences ?

在Flutter中,您可以通过使用插件Shared_Preferences来访问此功能

这个插件包装了Shared Preferences和NSUserDefaults(与iOS相同)的功能

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

void main() {
  runApp(
    new MaterialApp(
      home: new Scaffold(
        body: new Center(
          child: new RaisedButton(
            onPressed: _incrementCounter,
            child: new Text('Increment Counter'),
          ),
        ),
      ),
    ),
  );
}

_incrementCounter() async {
  SharedPreferences prefs = await SharedPreferences.getInstance();
  int counter = (prefs.getInt('counter') ?? 0) + 1;
  print('Pressed $counter times.');
  prefs.setInt('counter', counter);
}

如何在Flutter中访问SQLite

在Android中,您可以使用SQLite存储,通过SQL查询的结构化数据。

在Flutter中,您可以使用SQFlite插件来访问SQFlite此功能

通知

在Android中,您可以使用Firebase云消息传递为您的应用设置推送通知。

在Flutter中,您可以使用Firebase_Messaging插件访问此功能

注意:在中国无法使用Firebase服务。

yaml

YAML是一个类似 XML的标记语言。YAML强调以数据为中心,并不是以标识语言为重点

规范:

  • 大小写敏感
  • 缩进代表层级,使用空格,默认2个空格(flutter工具做了处理,tab也可以)
  • #表示注释内容
  • : 表示键值对,注意后面要空格
  • {} 表示键值表
  • 表示列表,注意后面要空格
  • [] 表示数组,注意每项之间有空格
  • ? 表示复杂的键

    # 依赖库
    dependencies:
      flutter:
        sdk: flutter
      cupertino_icons: ^0.1.2
    
    flutter:
      #使用Material图标
      uses-material-design: true
      #assets文件
      assets:
        - images/1.png
        - images/2.gif
      #字体样式
      fonts:
         #family与fonts是一个整体,列表的一项
         - family: Schyler
           fonts:
             - asset: fonts/Schyler-Regular.ttf
             - asset: fonts/Schyler-Italic.ttf
               style: italic
         - family: Trajan Pro
           fonts:
             - asset: fonts/TrajanPro.ttf
             - asset: fonts/TrajanPro_Bold.ttf
               weight: 700
    

-可转化为[],:也可以转化为{},转化后类似json

dependencies: {flutter: {sdk: flutter}, cupertino_icons: ^0.1.2}
assets: [images/1.png, images/2.gif]

参考资料

Flutter for Android 开发者