Flutter高級進階------Flutter Package、Flutter Plugin、Flutter Module


今天來學習Flutter的一些高級技巧,在實際工作中也是要用得上的,比如如何將咱們的代碼發布到https://pub.dev/上造輪子給全球使用,在Flutter中如何調Android和Ios本地代碼,以及Android中如何來調Flutter【由於ios我不會,所以。。】,內容還是很刺激的,下面開始搞起。

Flutter Package:

關於這塊的開發可以參考https://flutterchina.club/developing-packages/,下面則來從0開始來構建一款自己的Flutter包發布到Flutter Package上面。

了解:

關於Flutter Package是啥應該不用過多再解釋了,如果說你想在https://pub.dev/發布自己寫的庫供別人來使用,此時就需要學會Flutter Package的技法了,如下:

,這里貼一下網上所說有:

其中上面說到Package類型有兩種,而這里首先實現的是第一種:Dart包。

項目創建:

咱們先來新建一個Flutter工程,可以采用命令來進行創建,如網上所示:

來吧,試一下:

bogon:workspace xiongwei$ cd flutterstudy/
bogon:flutterstudy xiongwei$ flutter create --template=package shaded_text
Creating project shaded_text...
  shaded_text/LICENSE (created)
  shaded_text/test/shaded_text_test.dart (created)
  shaded_text/shaded_text.iml (created)
  shaded_text/.gitignore (created)
  shaded_text/.metadata (created)
  shaded_text/pubspec.yaml (created)
  shaded_text/README.md (created)
  shaded_text/lib/shaded_text.dart (created)
  shaded_text/.idea/libraries/Dart_SDK.xml (created)
  shaded_text/.idea/modules.xml (created)
  shaded_text/.idea/workspace.xml (created)
  shaded_text/CHANGELOG.md (created)
Running "flutter packages get" in shaded_text...                    1.2s
Wrote 12 files.

All done!
Your package code is in shaded_text/lib/shaded_text.dart
bogon:flutterstudy xiongwei$ 

此時在本地生成的樣子瞅一下:

當然還可以利用Android Studio的向導來創建:

創建之后在Android Studio中咱們來看一下這個跟咱們正常的Flutter Application有啥不一樣呢?

然后再看一下yaml配置文件:

而且還有一個區別,就是此項目貌似沒法運行:

代碼編寫:

效果:

咱們這個包是要實現一個啥效果呢,其實比較簡單,就是一個文本陰影的效果,如下:

其實用到的知識點在https://www.cnblogs.com/webor2006/p/12649906.html已經學習過了,是啥呢?

具體實現:

下面則來實現一下,定義一個Widget:

然后具體的實現相對比較簡單,就不一一說明了,重點是學會如何進行發布,實現如下,先定義相關的成員變量:

library shaded_text;

import 'package:flutter/material.dart';

class ShadedText extends StatelessWidget {
  final String text;
  final Color textColor;
  final Color shadeColor;
  final double xTans;
  final double yTans;

  ShadedText(
      {this.text, this.textColor, this.shadeColor, this.xTans, this.yTans})
      : assert(text != null),
        assert(textColor != null),
        assert(shadeColor != null);

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
      ],
    );
  }
}

接下來則來定義Stack中的元素,這里就跟咱們之前寫項目的時候不太一樣了,因為這個庫最終是要供別人來使用的,所以此時只需提供一個創建的行為,具體創建的細節由調用者來提供,啥意思呢?下面看一下代碼體會一下:

library shaded_text;

import 'package:flutter/material.dart';

typedef ShadeBuilder = Widget Function(
    BuildContext context, String text, Color);

class ShadedText extends StatelessWidget {
  final String text;
  final Color textColor;
  final Color shadeColor;
  final double xTans;
  final double yTans;
  final ShadeBuilder shadeBuilder;

  ShadedText(
      {this.text,
      this.textColor,
      this.shadeColor,
      this.xTans,
      this.yTans,
      this.shadeBuilder})
      : assert(text != null),
        assert(textColor != null),
        assert(shadeColor != null),
        assert(shadeBuilder != null);

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        shadeBuilder(context, text, textColor), Transform( transform: Matrix4.translationValues(xTans ?? 10.0, yTans ?? 10.0, 0.0), child: shadeBuilder(context, text, shadeColor), ),
      ],
    );
  }
}

其中有一個小小的Dart語法復習一下,如下:

其作用是:

創建示例工程:

通常在發布的時候得給用戶一個示例代碼,所以此時咱們針對咱們所寫的功能進行一下調用,正好可以測一下是否寫得有問題,怎么創建呢?直接在Android Studio的Terminal來創建,如下:

bogon:shaded_text xiongwei$ flutter create example
Creating project example...
  example/ios/Runner.xcworkspace/contents.xcworkspacedata (created)
  example/ios/Runner/Info.plist (created)
  example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png (created)
  example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png (created)
  example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md (created)
  example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json (created)
  example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png (created)
  example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png (created)
  example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png (created)
  example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png (created)
  example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png (created)
  example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png (created)
  example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png (created)
  example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png (created)
  example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (created)
  example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png (created)
  example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png (created)
  example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png (created)
  example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png (created)
  example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png (created)
  example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png (created)
  example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png (created)
  example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png (created)
  example/ios/Runner/Base.lproj/LaunchScreen.storyboard (created)
  example/ios/Runner/Base.lproj/Main.storyboard (created)
  example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata (created)
  example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (created)
  example/ios/Flutter/Debug.xcconfig (created)
  example/ios/Flutter/Release.xcconfig (created)
  example/ios/Flutter/AppFrameworkInfo.plist (created)
  example/test/widget_test.dart (created)
  example/example.iml (created)
  example/.gitignore (created)
  example/.metadata (created)
  example/ios/Runner/AppDelegate.h (created)
  example/ios/Runner/main.m (created)
  example/ios/Runner/AppDelegate.m (created)
  example/ios/Runner.xcodeproj/project.pbxproj (created)
  example/android/app/src/profile/AndroidManifest.xml (created)
  example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png (created)
  example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png (created)
  example/android/app/src/main/res/drawable/launch_background.xml (created)
  example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png (created)
  example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png (created)
  example/android/app/src/main/res/values/styles.xml (created)
  example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png (created)
  example/android/app/src/main/AndroidManifest.xml (created)
  example/android/app/src/debug/AndroidManifest.xml (created)
  example/android/gradle/wrapper/gradle-wrapper.properties (created)
  example/android/gradle.properties (created)
  example/android/settings.gradle (created)
  example/pubspec.yaml (created)
  example/README.md (created)
  example/lib/main.dart (created)
  example/android/app/build.gradle (created)
  example/android/app/src/main/java/com/example/example/MainActivity.java (created)
  example/android/build.gradle (created)
  example/android/example_android.iml (created)
  example/.idea/runConfigurations/main_dart.xml (created)
  example/.idea/libraries/Flutter_for_Android.xml (created)
  example/.idea/libraries/Dart_SDK.xml (created)
  example/.idea/libraries/KotlinJavaRuntime.xml (created)
  example/.idea/modules.xml (created)
  example/.idea/workspace.xml (created)
Running "flutter packages get" in example...                        1.7s
Wrote 66 files.

All done!
[!] Flutter is partially installed; more components are available. (Channel stable, v1.2.1, on Mac OS X 10.15.6 19G73, locale zh-Hans-CN)
[!] Android toolchain - develop for Android devices is partially installed; more components are available. (Android SDK version 29.0.3)
[!] iOS toolchain - develop for iOS devices is partially installed; more components are available. (Xcode 11.2.1)
[!] Android Studio is not available. (not installed)
[!] IntelliJ IDEA Ultimate Edition is partially installed; more components are available. (version 2020.2)
[!] Connected device is not available.

Run "flutter doctor" for information about installing additional components.

In order to run your application, type:

  $ cd example
  $ flutter run

Your application code is in example/lib/main.dart.

bogon:shaded_text xiongwei$ 

此時在工程中就可以看到咱們創建的Flutter項目了,也就是之前咱們學習Flutter所用的命令,當時此時是可以運行的嘍,如下:

此時咱們來調用一下咱們寫的插件,如下:

接下來需要使用咱們自己寫的插件,怎么用呢?其實跟我們在之前做項目使用三方庫一樣,需要到yaml文件中進行引用,但是咱們的庫還木有往外正式發布,所以使用上略有不同,如下:

其中這個"shaded_text"名稱是你插件中這塊的名稱:

接下來則可以來使用了:

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

void main() => runApp(MaterialApp(home: HomePage()));

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Shaded Text Demo'),
      ),
      body: Center(
        child: ShadedText( text: 'Shaded Text',
          textColor: Color(0xffff0000),
          shadeColor: Color(0xff00ff00),
          shadeBuilder: (BuildContext context, String text, Color color) => Container( child: Text( text, style: TextStyle(color: color), ), ), ),
      ),
    );
  }
}

可以看到builder的使用方式跟我們之前在做項目時使用了大量這種方式類似,順間領悟到了原來我們調用的內部機理,接下來運行看一下效果:

直接右鍵運行一下:

之后就可以看到運行的按鈕可用了,效果如下:

嗯,沒啥問題。

發布【比較曲折】:

接下來則到激動人心的時刻了,准備將咱們寫的高大上的DEMO給發布到Flutter的https://pub.dev/上去,能成功么?下面按照文檔上的來:

 

此時咱們先運行dry-run來檢查一下發布准備是否一切都ok:

然后在最后給出了一個錯誤提示了:

也就是這塊配置文件中的這些信息得填一下:

還有最后一個異常需要處理:“

Your package is 146.1 MB. Hosted packages must be smaller than 100 MB. Your .gitignore has no effect since your project does not appear to be in version control.
Sorry, your package is missing some requirements and can't be published yet.

包太大了。。怎么辦,其實上面中也有一個提示,說沒有受版本的控制,那咱們將它加入到git倉庫管理中試一下:

然后添加到本地倉庫中:

此時再來dry-run檢測一次:

 居然名字后面得跟一個郵件。。好吧,改!!

然后這里還可以修改一下CHANGLOG.md文件:

 

另外LICENSE也可以加一下,在github中的開源項目中隨便找一個:

好,此時第三次再來dry-run:

 

完美了,接下來嘗試發布,啥命令來着:

 

試一下:

 

 

發現需要授權。。

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=818368855108-8grd2eg9tj9f38os6f1urbcvsq399u8n.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A56482&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email
Then click "Allow access".

Waiting for your authorization...

此時點擊它提示的鏈接進行GOOGLE賬號授權一下:

接下來在Android Studio的發布命令台上就會收到授權成功的消息了,如下:

然后。。無限在處理中了。。然后最后收到這樣的消息:

啥情況呢,網上搜了一下,按這博主https://blog.csdn.net/perfectnihil/article/details/103366428的意思貌似是要FQ。。正好我也有梯子,那試一下唄:

這個錯誤沒了,另一個失敗又出現了,而且此時只明一個錯誤碼出現沒報具體原因。。網上繼續搜,博主https://blog.csdn.net/qq_32452623/article/details/89471810的意思是用-v來查看一下具體的異常:

輸出日志往前看,發現用了cn中國的域名。。

這時因為咱們在Flutter學習環境搭建的時候設置了中國的鏡像,為了學習順暢嘛,身為國人都懂的,回憶一下https://www.cnblogs.com/webor2006/p/11367345.html: 

所以咱們在發布的時候先將這個鏡像去掉,最終官網是在國外嘛:

先注釋掉,等發布成功了到時再還原,再來發布一次,注意:此時先得重啟一下Android Studio再發布,不然咱們注釋掉的環境變量沒有生效,然后再設置一下之前的代理再發布:

那咱們改一下名稱:

當然example也得改一下:

再來:

終於成功了。。咱們上官網搜一下能否搜到:

 

最后再將Flutter的國內鏡像配置給還原。

Flutter Plugin:

了解:

 接下來學習另一種使用場景,也就是咱們在新建項目向導中的看到的它:

看一下網上對這個插件的解釋:

這里再來看一下網上的一個示意圖:

其中可以看到Flutter能調用特定平台(android或ios),並且特定平台(android或ios)又調調用Flutter,達到一個互通的效果,當然目前咱們這里先只觀注前者,后者在之后也會學習到,在了解了Flutter Plugin它的使用意義之后,接下來咱們則來開始實現咱們的第一個插件。

項目創建:

這里既可以使用Android Studio的向導來創建,也可以使用命令來創建,這里咱們用命令:

咱們試一下:

此時本地就創建了一個flutter_toast插件項目了:

此時咱們用IDE打開它:

然后咱們運行看一下默認的效果:

顯示出了當前平台的版本號。

分析官方DEMO流程:

接下來咱們來簡單分析一下這個默認插件的生成,搞清楚了它,基本上也就掌握了自己來寫插件的思路了,從Flutter本身分析起:

先貼一下全局代碼,如今學到這看這樣的代碼就變得很親切了:

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

import 'package:flutter/services.dart';
import 'package:flutter_toast/flutter_toast.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _platformVersion = 'Unknown';

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

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    String platformVersion;
    // Platform messages may fail, so we use a try/catch PlatformException.
    try {
      platformVersion = await FlutterToast.platformVersion;
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }

    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Center(
          child: Text('Running on: $_platformVersion\n'),
        ),
      ),
    );
  }
}

簡單擼一下:

接下來來看一下插件中是如何來獲取指定平台的版本號的?

而最終方法的實現則是由具體的平台來執行的,所以咱們來看一下android平台是怎么來實現這個Flutter調用的指令的:

此時在Flutter頁面就可以看到方法的結果了,再看一下ios平台的寫法,基本雷同,只是語法的不同:

以上就是整個插件的使用流程。

模仿編寫自己的插件:

接下來咱們依照官方的DEMO來改造第一個自己的插件,這個插件的功能就是彈個Toast出來,下面開始:

此時則在插件中增加一個showToast的方法:

接下來則在平台中來實現showToast的實現邏輯,這里以Android平台為例(ios目前還不會),如下:

 

package com.example.plugin.flutter_toast;

import android.content.Context;
import android.widget.TextView;
import android.widget.Toast;

import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;

/**
 * FlutterToastPlugin
 */
public class FlutterToastPlugin implements MethodCallHandler {
    private Toast toast;
    private Context context;

    private FlutterToastPlugin(Context context) {
        this.context = context;
    }

    /**
     * Plugin registration.
     */
    public static void registerWith(Registrar registrar) {
        final MethodChannel channel = new MethodChannel(registrar.messenger(), "flutter_toast");
        channel.setMethodCallHandler(new FlutterToastPlugin(registrar.context()));
    }

    @Override
    public void onMethodCall(MethodCall call, Result result) {
        if (call.method.equals("getPlatformVersion")) {
            result.success("Android " + android.os.Build.VERSION.RELEASE);
        } else if (call.method.equals("showToast")) {
            String msg = call.argument("msg");
            String duration = call.argument("duration");//long or short
            Number textColor = call.argument("textColor");
            Number textSize = call.argument("textSize");

            toast = Toast.makeText(context, "", Toast.LENGTH_SHORT);
            //set text
            if (msg != null) {
                toast.setText(msg);
            }
            //set duration
            if (duration != null && duration.equals("long")) {
                toast.setDuration(Toast.LENGTH_LONG);
            } else {
                toast.setDuration(Toast.LENGTH_SHORT);
            }
            //set styles
            TextView textView = (TextView) toast.getView().findViewById(android.R.id.message);
            //set text color
            if (textView != null) {
                textView.setTextColor(textColor.intValue());
            }
            //set text size
            if (textView != null) {
                textView.setTextSize(textSize.floatValue());
            }
            toast.show();
        } else {
            result.notImplemented();
        }
    }
}

此時運行看一下,發現報錯了。。

這種情況咋整?其實在未來Flutter的開發中也可能會遇到這種情況,重新卸載安裝一次就可以了,下面看一下:

最后插件的發布跟上面package的一樣,這里就略過了。

Flutter Module:

了解:

最后還有一個咱們木有使用過,如創建向導中的這個:

簡單描述就是在Android或ios原生工程中集成Flutter,這樣就可以在Android原生中使用Flutter中的特性了,比較最典型的一個熱重載,下面則分步驟一點點來實現這樣的效果,還是只以Android平台為例,木辦法,Ios的技能木有get到。。

第一步:創建Android工程

這里新創建一個文件夾,將所有接下來要創建的工程都存放於此,便於統一管理,如下:

然后在這個目錄下創建一個Android工程:

第二步:創建Flutter工程:

在Android工程根目錄的上一級目錄創建Flutter工程,保證Flutter工程與Android工程在同一級。如下:

此時的目錄結構為:

第二步:編譯Flutter工程

此時打開咱們創建的Flutter工程,然后需要進行編譯,如何搞呢?看下面:

第三步:在Android工程中加入Flutter Module的依賴

接下來回到Android工程中添加Flutter Module的依賴:

1、修改Android項目根目錄下的setting.gradle:

 

2、修改app下的build.gradle:

增加flutter的模塊依賴:

第四步:在Android工程中創建Flutter的View

怎么創建呢?看代碼:

居然Flutter中用的不是androidx。。那怎么辦?改變support方式吧:

 

然后還有一個地方得修改一下:

此時再導一下包就可以了:

然后布局文件也得改一下:

那現在已經在Android中內嵌了Flutter的視圖了,那視圖的內容怎么辦呢?當然得由Flutter來提供了。

第五步:在Flutter工程中創建Widget

回到Flutter工程中來進行代碼的編寫:

import 'dart:ui';

import 'package:flutter/material.dart';

void main() => runApp(selectWidget(window.defaultRouteName));

Widget selectWidget(String routeName) {
  switch (routeName) {
    case 'r1'://根據路由的名字來
      return MyFlutterView();
    default:
      return MaterialApp(
        debugShowCheckedModeBanner: false,
        home: Scaffold(
          body: Center(
            child: Text(
              'Unknow Route!',
              style: TextStyle(color: Color(0xffff0000)),
            ),
          ),
        ),
      );
  }
}

class MyFlutterView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Card(
            color: Colors.red,
            shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.all(Radius.circular(15.0))),
            child: Text('My Flutter View'),
          ),
        ),
      ),
    );
  }
}

其中的路由名就是咱們在Android中添加的Flutter中的路由名,如下:

好,接下來就可以來到Android工程中直接運行看下效果了:

提示ndk版本有問題,那下載一下,然后再來運行,依然報錯了:

居然最小只支持8.0。。那咱們改一下最小SDK的版本:

再來運行,發現ndk庫鏈接錯誤:

這個解決起來比較簡單,加一個cpu的類型既可,如下:

再運行:

可以發現加載Flutter View會有一些延時,這個可能得從產品交互的角度來解決,加個loading啥的,至此Android中內嵌Flutter成功搞定。

第六步:讓Flutter模塊支持熱加載

不過目前對於Flutter的熱加載功能還不支持,那就發揮不出它的優勢了,所以接下來加上熱加載功能,也比較簡單,如下:

1、首先在Flutter Module工程目錄下執行flutter attach,開始監聽flutter。

此時它就在等Android工程進行啟動。

2、在Android工程中運行程序,運行成功后可以在終端輸入小寫r熱加載,大寫R熱重啟。

重啟運行Android工程,此時在Flutter的attach命令行中就會給出如下提示了:

接下來咱們就可以在Flutter Module中進行修改實時在Android項目中進行預覽了,下面演示一下:

另外對於Fragment的使用也基本類似,這里就不演示了,至此對於Flutter的高級技法就已經學習完畢啦,這些待實際項目中再來進行實踐,接下來打算開啟一個全新的Flutter的項目操練,要想徹底掌握,別無它法,只能勤加練習~~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM