Flutter学习指南
6.文件存储和网络
5.交互、手势和动画
4.UI布局和控件
3.熟悉Dart语言
2.编写第一个应用
1.开发环境搭建
本文是学习指南系列的第7篇文章,建议大家收藏起来,欢迎分享给他人。
本篇文章我们先一起学习 Flutter 插件的使用,然后通过开发一个 toast 插件来学习它的开发,最后发布到 Pub 上。
插件的使用
Flutter 的库是以 package 的方式来管理。Package 分为两种,Dart package(也叫 library package) 和 plugin package。当我们说 Fluter 包的时候,指的其实也是 Dart 包,它只能使用 Dart 和 Flutter 提供的 API;而当我们说 Flutter 插件时指的是后者,也就是 plugin package。Flutter 插件通常会包含平台特定的代码。对包的使用者来说,两者没有区别。
添加依赖
为了使用一个库,我们首先在 pubspec.yaml 里声明一个依赖:
dependencies:
shared_preferences: ^0.4.2
^0.4.2 表示与 0.4.2 兼容的版本。我们也可以指定依赖库的为特定的版本:
- any:任意版本
- 1.2.3:特定的版本
- <1.2.3:小于 1.2.3 的版本,此外还有 <=、>、>= 可以使用
- '>=1.2.3 <2.0.0':指定一个范围
接下来,在项目的根目录执行 flutter packages get。如果你使用 Android Studio 进行开发,也可以直接在 pubspec.yaml 的编辑页面上面点击 Packages get 按钮。
上面例子的是发布在 pub.dartlang.org 上的库,除此之外,我们也可以使用其他的源:
dependencies:
transmogrify:
hosted:
name: transmogrify
url: http://your-package-server.com
version: ^1.4.0
kittens:
git:
url: git://github.com/munificent/cats.git
ref: some-branch # 可选的
kittens:
git:
url: git://github.com/munificent/cats.git
path: path/to/kittens # 指定路径
# 甚至可以指定一个本地路径
transmogrify:
path: /Users/me/transmogrify
如果你看过 Flutter 的 pubspec,应该会注意到 flutter 是这样声明的:
dependencies:
flutter:
sdk: flutter
sdk 用于导入随 Flutter 一起发布的包,目前只有 flutter。
使用
导入相关的包后,我们就可以使用它的 API 了:
import 'package:shared_preferences/shared_preferences.dart';
void foo() async {
var prefs = await SharedPreferences.getInstance();
var used = prefs.getBool('used');
if (!used) {
prefs.setBool('used', true);
}
}
这种导入方式的问题在于,他把库里所有的符号到导入到了全局的命名空间里面(比方说,在上面的例子里,我们可以直接使用 SharedPreferences)。有时为了防止命名空间的污染,我们可以使用 as 给导入的库一个名字(当然,对 SharedPreferences 其实没有必要使用限定名就是了):
void foo() async {
var prefs = await sp.SharedPreferences.getInstance();
var used = prefs.getBool('used');
if (!used) {
prefs.setBool('used', true);
}
}
了解了 Flutter 包的使用后,下面我们自己来开发一个 flutter 插件。
开发一个插件
学习 Flutter 的过程中,不知道是你是否注意到 Flutter 并没有提供一个 Toast API。为了弥补这个遗憾,在这一节里我们就来开发一个插件,让它支持 Toast。
在开始开发前,我们先来了解一下 Flutter 如何跟平台相关的代码进行通信。
MethodChannel
Flutter 跟平台相关代码可以通过 MethodChannel 进行通信。客户端通过 MethodChannel 将方法调用和参数发生给服务端,服务端也通过 MethodChannel 接收相关的数据。
需要注意的是,上图中的箭头是双向的。也就是说,我们不仅可以从 Flutter 调用 Android/iOS 的代码,也可以从 Android/iOS 调用 Flutter。调用时相关的参数对应如下:
Dart | Android | iOS |
---|---|---|
null | null | nil (NSNull when nested) |
bool | java.lang.Boolean | NSNumber numberWithBool: |
int | java.lang.Integer | NSNumber numberWithInt: |
int, if 32 bits not enough | java.lang.Long | NSNumber numberWithLong: |
double | java.lang.Double | NSNumber numberWithDouble: |
String | java.lang.String | NSString |
Uint8List | byte[] | FlutterStandardTypedData typedDataWithBytes: |
Int32List | int[] | FlutterStandardTypedData typedDataWithInt32: |
Int64List | long[] | FlutterStandardTypedData typedDataWithInt64: |
Float64List | double[] | FlutterStandardTypedData typedDataWithFloat64: |
List | java.util.ArrayList | NSArray |
Map | java.util.HashMap | NSDictionary |
创建项目
这里假设读者使用 Android Studio 开发。
- 在菜单上选择 File -> New -> New Flutter Project
- 在弹出的面板里选择 Flutter Plugin,点击 next
- Project name 我们填入 flutter_toast2018,其他信息读者根据自身需要填写
之所以叫 flutter_toast2018 是因为 Pub 上已经有一个 flutter_toast,所以加上 2018 防止名字冲突。
生成的项目有 4 个主要的目录:
- android:插件本地代码的 Android 端实现
- ios:iOS 端的实现
- lib:Dart 代码。插件的客户将会使用这里实现的接口
- example:插件的使用示例
插件开发
Android 端代码实现
其实在上一步我们生成项目的时候,项目里就已经包含了一个实现了 platformVersion 的 Flutter 插件 demo,有兴趣的读者可以看看学习一下。下面,我们来开发自己的 Toast 插件(注意,我们的实现只支持 Android)。
首先我们来了解一下接口 MethodCallHandler:
public interface MethodCallHandler {
void onMethodCall(MethodCall call, Result result);
}
这个接口用于处理 Flutter 的本地方法调用请求。也就是说,我们需要实现这个接口,当 Flutter 调用我们的时候,弹出一个 toast。
实现这个接口的是 FlutterToast2018Plugin(位于 android 目录下):
public class FlutterToast2018Plugin implements MethodCallHandler {
public static void registerWith(Registrar registrar) {
// "example.com/flutter_toast2018" 是我们 method channel 的名字,Dart 代码里还需要用到它。
// 为了防止命名冲突,可以在它的前面加上域名
final channel = new MethodChannel(registrar.messenger(), "example.com/flutter_toast2018");
channel.setMethodCallHandler(new FlutterToast2018Plugin());
}
@Override
public void onMethodCall(MethodCall call, Result result) {
// TODO
}
}
为了弹出 Toast,我们给 FlutterToast2018Plugin 的构造函数添加一个 Context 参数:
public class FlutterToast2018Plugin implements MethodCallHandler {
private final Context mContext;
public FlutterToast2018Plugin(Context context) {
mContext = context;
}
// 注册 MethodCallHandler
public static void registerWith(Registrar registrar) {
final channel = new MethodChannel(registrar.messenger(), "example.com/flutter_toast2018");
// context 可以从 Registrar 拿到
channel.setMethodCallHandler(new FlutterToast2018Plugin(registrar.context());
}
// ...
}
现在,实现 onMethodCall 方法:
public class FlutterToast2018Plugin implements MethodCallHandler {
// ...
@Override
public void onMethodCall(MethodCall call, Result result) {
// call.method 是方法名,这里我们就叫它 toast
if (call.method.equals("toast")) {
// 调用本地代码的时候,只能传递一个参数。为了传递多个,可以把参数放在一个 map 里面。
// call.arguemnt() 方法支持 Map 和 JSONObject
String content = call.argument("content");
String duration = call.argument("duration");
Toast.makeText(mContext, content,
"short".equals(duration) ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG)
.show();
// 执行成功
result.success(true);
} else {
result.notImplemented();
}
}
}
Flutter 端
Flutter 端需要做的,就是生成一个 MethodChannel,然后通过这个 MethodChannel 调用 toast 方法:
import 'dart:async';
import 'package:flutter/services.dart';
enum ToastDuration {
short, long
}
class FlutterToast {
// 这里的名字要跟 Java 端的对应
static const MethodChannel _channel =
const MethodChannel('example.com/flutter_toast2018');
static Future<bool> toast(String msg, ToastDuration duration) async {
var argument = {
'content': msg,
'duration': duration.toString()
};
// 本地方法是一个异步调用。'toast' 对应我们在前面 Java 代码的 onMethodCall
// 方法里面处理的方法名
var success = await _channel.invokeMethod('toast', argument);
return success;
}
}
使用插件
在这一节我们修改工程里 example 目录下的示例,用它来演示插件的使用:
import 'package:flutter/material.dart';
// 首先导入我们的包
import 'package:flutter_toast2018/flutter_toast2018.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: const Text('Plugin example app'),
),
body: new Center(
child: RaisedButton(
child: Text('toast'),
// 插件的使用跟其他库没有什么区别,直接调用即可
onPressed: () => FlutterToast2018.toast(
'Toast from Flutter', ToastDuration.short
),
),
),
),
);
}
}
发布插件
前面我们说过,pubspec 支持通过本地路径和 Git 导入依赖,但为了更好的管理版本依赖,还是推荐发布插件到 pub.dartlang.org/。在这一节,我们就把前面开发的 toast 插件发布到 Pub 上。
需要注意的是,由于某些众所周知的原因,pub.dartlang.org 需要一把梯子才能上去。虽然我们也可以通过 flutter-io.cn 来发布,但上传的时候需要登录 Google 账号,梯子还是少不了的。
检查配置
首先是 pubspec.yaml。对 Flutter 插件来说,pubspec 里除了插件的依赖,还包含一些元信息,读者可以根据需要,把这些补上:
name: flutter_toast2018
description: A new Flutter plugin for Android Toast.
version: 0.0.1
author: Jekton <ljtong64@gmail.com>
homepage: https://jekton.github.io/
另外,发布到 Pub 上的包需要包含一个 LICENSE,关于 LICENSE 文件,最简单的方法就是在 GitHub 创建仓库的时候选中一个。
检查插件
现在,我们在工程的根目录执行以下命令,检测一下插件有没有什么问题:
flutter packages pub publish --dry-run
如果一切正常,将会输出:
...
Package has 0 warnings.
发布插件
发布插件和上一步一样,只是少了 --dry-run 参数:
flutter packages pub publish
如果是第一次发布,会提示验证 Google 账号。授权后便可以继续上传,如果成功的话,会提示“Successful uploaded package”:
Looks great! Are you ready to upload your package (y/n)? y
Pub needs your authorization to upload packages on your behalf.
In a web browser, go to https://accounts.google.com/o/oauth2/auth?access_type=offline&approval_prompt=force&response_type=code&client_id=xxxxxxxxxxxxxx-8grd2eg9tj9f38os6f1urbcvsq399u8n.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A52589&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email
Then click "Allow access".
Waiting for your authorization...
Successfully authorized.
Uploading...
Successful uploaded package.
前面我们发布的包可以在 pub.dartlang.org/packages/fl… 或 pub.flutter-io.cn/packages/fl… 找到。
相关的代码则是放到了 GitHub 上:
git clone https://github.com/Jekton/flutter_toast2018.git
最后再提一提我们没有讲到的 Flutter package。为了开发一个 Flutter 包,我们在创建项目的时候可以选择 Flutter package。它和 Flutter 插件唯一的区别是Flutter package 不能包含平台特定的代码(只能使用 Dart 和 Flutter API)。除此之外,开发、发布和使用都跟 Flutter 插件没有什么区别。
欢迎扫码关注