Flutter 中 Package 及 Plugin 开发

·  阅读 866
Flutter 中 Package 及 Plugin 开发

Flutter 三方的工具有两种,一种是包(Package),一种是插件(Plugin)。这两种差别在于 Plugin 不仅包含了 Dart 代码,还包含了 iOS 以及 Android 的原生代码,比如常用的 image_picker。而 Package 仅仅是包含 Dart 代码的库。

Package 开发

  • 通过命令创建 Package

要创建 Dart 包,使用参数 --template=package 来执行 flutter create

flutter create --template=package 'package_name"
复制代码
  • 通过 Android Studio 创建 Package

image.png

通过 Android Studio 创建 Package 的时候 Project type 选择 Package 类型。

image.png

这里我们通过对微信 Demo 中的索引控件代码进行抽取,创建一个自己的 Package 并在项目中进行使用。

代码抽取

library chenxi_chat_index_bar;

import 'package:flutter/material.dart';

class IndexBar extends StatefulWidget {
  final void Function(String str)? indexBarCallBack;
  const IndexBar({this.indexBarCallBack, Key? key}) : super(key : key);

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

//获取选中的 item 文字
int _getIndex(BuildContext context, Offset globalPosition) {
  //拿到点击小部件的盒子,也就是索引条
  RenderBox box = context.findRenderObject() as RenderBox;
  // 拿到 y 值, globalToLocal 当前位置距离索引条左上角 (0, 0) 位置的距离 (x, y)
  double y = box.globalToLocal(globalPosition).dy;
  //算出字符的高度
  var itemHeight = screenHight(context) / 2 / INDEX_WORDS.length;
  //算出第几个 item
  int index = (y ~/ itemHeight).clamp(0, INDEX_WORDS.length - 1);
  return index;
}

class _IndexBarState extends State<IndexBar> {

  Color _bkColor = Color.fromRGBO(1, 1, 1, 0.0);
  Color _textColor = Colors.black;
  double _indicatorY = 0.0;
  String _indicatorText = 'A';
  bool _indicatorHidden = true;

  @override
  Widget build(BuildContext context) {
    final List<Widget> _words = [];
    for (int i = 0; i < INDEX_WORDS.length; i++) {
      _words.add(
          Expanded(child: Text(INDEX_WORDS[i],
            style: TextStyle(fontSize: 10, color: _textColor),))
      );
    }

    return Positioned(
      right: 0.0,
      top: screenHight(context) / 8,
      height: screenHight(context) / 2,
      width:120,
      child: Row(
        children: [
          Container(
            alignment: Alignment(0, _indicatorY),
            width: 100,
            child: _indicatorHidden ? null : Stack(
              alignment: Alignment(-0.2, 0),
              children: [
                Image(image: AssetImage('images/气泡.png'), width: 60,),
                Text(
                  _indicatorText,
                  style: TextStyle(
                    fontSize: 35, color: Colors.white,
                  ),
                )
              ],
            ),
          ), //指示器
          GestureDetector(
            onVerticalDragUpdate: (DragUpdateDetails details){
              final Function(String str) callBack = widget.indexBarCallBack as Function(String str);
              int index = _getIndex(context, details.globalPosition);
              callBack(INDEX_WORDS[index]);
              setState(() {
                _indicatorY = 2.2 / INDEX_WORDS.length * index - 1.1;
                _indicatorText = INDEX_WORDS[index];
                _indicatorHidden = false;
              });
            },
            // 索引条点击
            onVerticalDragDown: (DragDownDetails details){
              final Function(String str) callBack = widget.indexBarCallBack as Function(String str);
              int index = _getIndex(context, details.globalPosition);
              callBack(INDEX_WORDS[index]);
              setState(() {
                _bkColor = Color.fromRGBO(1, 1, 1, 0.4);
                _textColor = Colors.white;

                _indicatorY = 2.2 / INDEX_WORDS.length * index - 1.1;
                _indicatorText = INDEX_WORDS[index];
                _indicatorHidden = false;
              });
            },
            // 索引条点击取消
            onVerticalDragEnd: (DragEndDetails details){
              setState(() {
                _bkColor = Color.fromRGBO(1, 1, 1, 0.0);
                _textColor = Colors.black;

                _indicatorHidden = true;
              });
            },
            child: Container(
              width: 20,
              color: _bkColor,
              child: Column(
                children: _words,
              ),
            ),
          ), //索引条
        ],
      ),
    );
  }
}

//主题色
const Color CahtThemColor = Color.fromRGBO(230, 230, 230, 1.0);

//屏幕宽高
double screenWidth(BuildContext context) => MediaQuery.of(context).size.width;
double screenHight(BuildContext context) => MediaQuery.of(context).size.height;

const INDEX_WORDS = [
'🔍',
'☆',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z'
];
复制代码

首先我们把所有索引控件相关的代码都抽取到了 chenxi_chat_index_bar.dart 文件中。

Package 中资源文件处理

首先是不建议在 Package 中使用资源文件,因为会给别人的使用造成不方便。但是如果是 UI 界面相关的资源这个使用是我们避免不了的,但是也是可以处理。

image.png

首先我们创建一个 images 文件夹,并把图片资源放入进来。

image.png

pubspec.yaml 中进行相关配置。

发布相关配置

image.png

同样是在 pubspec.yaml 中进行发布相关的配置。

  • namePackage 名称
  • descriptionPackage 的描述
  • versionPackage 的版本号
  • homepage :可以放一个自己相关的网址链接,需要注意的是网址可以访问,不然会被扣分。

发布 Package

  1. 检查 Package
flutter packages pub publish --dry-run
复制代码

image.png

cd 到要发布的 Package 文件目录下,如果没有报错就代表没有缺失的信息,可以进行下一步。

  1. 发布
flutter packages pub publish
复制代码

注意:目前发布插件跟包都需要 Google 账号,同时需要翻墙。

Do you want to publish chenxi_chat_index_bar 0.0.1 (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=818368855108-8grd2eg9tj9f38os6f1urbcvsq399u8n.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A56076&code_challenge=jjz5gCzNNt_s5GYyQ3L_Pz4u_mSErJgx9BXoxUlY2XY&code_challenge_method=S256&scope=openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email
Then click "Allow access".
复制代码

有时就算是翻墙也并不能解决问题,因为我们还配置了相关镜像,Flutter 官方就建议通过镜像的配置,所以在发布插件或者包的时候,就会因为镜像出现以下错误。

image.png

那么解决问题也很简单,就是指定服务器发布。

  • 指定服务器发布
flutter packages pub publish --server=https://pub.dartlang.org
复制代码
  • LICENSE 证书问题

image.png

紧接着大家应该会遇到这个错误,就是版权的问题,之前是不需要的,但是现在要求必须要有。可以按下面步骤解决。

image.png

github 上创建仓库,勾选 Choose a license,选择 BSD 3-Clause,点击 Create repository 创建。

image.png

创建完成之后可以看到仓库就有了 LICENSE 文件。

image.png

打开 LICENSE 文件,复制所有内容。

image.png

把复制的内容粘贴到 Package 中的 LICENSE 文件中,再次执行发布命令。

image.png

这里可以看到已经发布成功了。

image.png

pub 官网,我们也可以看到自己发布的 Package 了。

使用自己的 Package

image.png

执行 Pub get 可以看到我们上传的 Package 包已经拉取到了本地。

使用时候引入以下头文件:

import 'package:chenxi_chat_index_bar/chenxi_chat_index_bar.dart' as chenxi;
复制代码

as chenxi 是为了防止类名冲突。

使用代码:

chenxi.IndexBar(indexBarCallBack: (String str) {
            if(_groupOffsetMap[str] != null) {
              print(str);
              _scrollController!.animateTo(_groupOffsetMap[str], duration: Duration(microseconds: 100), curve: Curves.elasticIn);
            }
          }),//悬浮的索引条
复制代码

Package 优化

资源文件处理

image.png

因为 Package 包上传的是 lib 文件,所以在 Package 中需要用到例如图片资源的话,需要把图片资源添加到 lib 路径下。

AssetImage('images/bubble.png', package: 'chenxi_chat_index_bar')
复制代码

使用图片的时候可以指定包名。这里修改完之后需要对 Package 进行更新,需要修改 pubspec.yamlCHANGELOG.md 文件中的版本号重新发布。

  assets:
     - images/
     - packages/chenxi_chat_index_bar/images/bubble.png
复制代码

这样的话在项目中引入 Package 的时候,图片资源配置需要如上所示,会比较麻烦。对于这种情况,可以让外部使用的时候传图片进来。

class IndexBar extends StatefulWidget {
  final void Function(String str)? indexBarCallBack;
  final ImageProvider image;
  const IndexBar(this.image, {this.indexBarCallBack, Key? key}) : super(key : key);
  @override
  _IndexBarState createState() => _IndexBarState();
}
复制代码

展示的时候就直接用外部传入的 image 就好。

Image(image: widget.image, width: 60,),
复制代码

分数优化

image.png

上传 Package 之后,官网会对我们的包进行评分,如果评分太低的话别人使用的时候就会有顾虑。这里总分是 130 分,我们目前得到的是 90 分,然后可以根据官网的提示进行优化。

  • The package description is too short

说明描述太短,这个我们增加下描述就可以了。

  • No example found 没有示例工程

github 上一样,当我们使用一些比较成熟的第三方的时候,都会有示例工程供我们参考。这里我们也创建一个示例工程。

image.png

新建一个 flutter 工程,并引入 chenxi_chat_index_bar 指定本地 path,然后添加示例代码。完成之后我们要上传示例程序。

image.png

在我们要发布的包 chenxi_chat_index_bar 中新建一个 example 文件,并把实例代码文件放到 example 文件下。

当我们发布的包中代码比较多的话可以对代码进行拆分,创建多个子文件。并通过代码建立依赖关系。

  • 指定子文件属于哪个主文件
part of 'chenxi_chat_index_bar.dart';
复制代码
  • 指定主文件包含哪些子文件
part 'index_bar.dart';
复制代码

做完以上操作之后就可以再更新版本号进行重新上传。

Package 链接:chenxi_chat_index_bar

Plugin 开发

Plugin 命令创建

  • 创建插件

要创建插件包请使用 --template=plugin参数执行 flutter create

flutter create --template=plugin 'plugin_name'
复制代码
  • 指定组织名称

使用 --org 选项指定你的组织,并使用反向域名表示法。

Dartpackage 是不需要组织名称的,--org 只有在 --template=plugin 时才生效。

flutter create --org com.example --template=plugin 'plugin_name
复制代码
  • 指定其他语言

由于 Plugin 包含 iOSAndroid 代码, 而他们分别都支持两种语言, iOS 支持 Object-C(默认)SwiftAndroid 支持 Java(默认)Kot in, 所以我们可以使用 -i-aiOSAndroid 指定语言。

flutter create--template=plugin-i swift-ako tL in'plugin_name
复制代码

Android Studio 创建 Plugin

image.png

创建工程的时候我们选择 plugin,这里需要注意的是开发语言要选择自己熟悉的。

image.png

创建完成之后可以看到相对于 Package 这里多了示例工程文件,而且包含 androidios 文件夹。我们示例工程要实现的功能是获取手机的电池电量,这里只实现 ios 的相关代码。

代码实现

  • dart 代码
static Future<String> get platformBatteryLevel async {
    final int batteryLevel = await _channel.invokeMethod('getBatteryLevel');
    return batteryLevel.toString();
  }
复制代码
  • oc 代码
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
  FlutterMethodChannel* channel = [FlutterMethodChannel
      methodChannelWithName:@"chenxi_battery_level"
            binaryMessenger:[registrar messenger]];
  ChenxiBatteryLevelPlugin* instance = [[ChenxiBatteryLevelPlugin alloc] init];
  [registrar addMethodCallDelegate:instance channel:channel];
}

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
  if ([@"getPlatformVersion" isEqualToString:call.method]) {
    result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]);
  } else if ([@"getPlatformBatteryLevel" isEqualToString:call.method]) {
      result(@([self getBatteryLevel]));
  } else {
    result(FlutterMethodNotImplemented);
  }
}

- (int)getBatteryLevel {
    UIDevice *device = UIDevice.currentDevice;
    device.batteryMonitoringEnabled = YES;
    if (device.batteryLevel == UIDeviceOrientationUnknown) {
        return -1;
    }
    return (int)(device.batteryState * 100);
}
复制代码

发布 Plugin

  • 检查 Plugin
flutter packages pub publish --dry-run
复制代码
  • 指定服务器发布
flutter packages pub publish --server=https://pub.dartlang.org
复制代码

Plugin 的发布跟 Package 一样,cdPlugin 文件目录下执行发布命令。

Plugin 链接:chenxi_battery_level

分类:
iOS
分类:
iOS
收藏成功!
已添加到「」, 点击更改