您现在的位置是:网站首页> Flutter

Flutter快速回顾

  • Flutter
  • 2024-11-24
  • 971人已阅读
摘要

Flutter快速回顾

Flutter在线教程1

Flutter在线教程2

跟着学习Flutter

Flutter 与 原生代码 的相互调用

Flutter 页面跳转并返回数据

Flutter关键笔记

Flutter UI组件

Flutter 控制是否显示组件

Flutter 插件开发

Flutter经典例子

Flutter事件监听

Flutter调用原生安卓Service

Flutter 热更新及动态UI生成



Flutter 与 原生代码 的相互调用

目录

1.关于 Flutter 的 Channel

2.Flutter 调用 原生代码

2.1 Android 端的实现

2.2 iOS 端的实现

2.3 Flutter 端的调用

3.原生代码 调用 Flutter

3.1 Flutter 端的实现

3.2 Android 和 iOS 端的调用

1. 关于 Flutter 的 Channel

Flutter 提供了 3 种 Channel 用于 Flutter 与 原生代码做交互,分别是:

类型 用途

MethodChannel 用于 Flutter 与 原生平台之间函数的互相调用

BasicMessageChannel 它传递的是字节数组,使用时自定义编解码器

EventChannel 用于 Flutter 与 原生平台之间事件的通信

我们可以借助 MethodChannel 实现 Flutter 与原生代码的相互调用。

2. 如何在 Flutter 中调用原生代码

在 Flutter 中调用原生代码,需要在原生代码中创建一个 MethodChannel,并对这个 channel 对象设置 MethodCallHandler 即可。

每个 channel 都可以处理多个方法的调用,在 handler 对象中根据方法名做分发即可。


2.1 在Flutter项目的Android 端实现 MethodChannel

Android 端需要在恰当的时机获取到 FlutterEngine 对象,例如在 FlutterActivity 的 configureFlutterEngine 方法中获取。

接着再创建 MethodChannel 通道实例,最后对通道设置 MethodCallHandler 即可:

import io.flutter.embedding.android.FlutterActivity

import io.flutter.embedding.engine.FlutterEngine

import io.flutter.plugin.common.MethodChannel

class MainActivity: FlutterActivity() {


    /* ======================================================= */

    /* Override/Implements Methods                             */

    /* ======================================================= */


    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {

        super.configureFlutterEngine(flutterEngine)


        val messenger = flutterEngine.dartExecutor.binaryMessenger


        // 新建一个 Channel 对象

        val channel = MethodChannel(messenger, "your_channel_name")


        // 为 channel 设置回调

        channel.setMethodCallHandler { call, res ->

            // 根据方法名,分发不同的处理

            when(call.method) {


                "your_method_name" -> {

                    // 获取传入的参数

                    val msg = call.argument<String>("msg")

                    Log.i("ZHP", "正在执行原生方法,传入的参数是:「$msg」")

                    // 通知执行成功

                    res.success("这是执行的结果")

                }


                else -> {

                    // 如果有未识别的方法名,通知执行失败

                    res.error("error_code", "error_message", null)

                }

            }

        }

    }


}

2.2 在 Flutter项目的iOS 端的实现 MethodChannel

iOS 端创建 MethodChannel 的方式和 Android 类似。 不过在 MethodCallHandler 中获取传入的参数和 Android 有差别,并且 FlutterResult 不支持像 Android 那样标记为 success/error :

class MainViewController: FlutterViewController {



    /* ======================================================= */

    /* Override/Implements Methods                             */

    /* ======================================================= */

    

    override func viewDidLoad() {

        super.viewDidLoad()


        // 注册渠道

        let channel = FlutterMethodChannel(name: "your_channel_name", binaryMessenger: self.binaryMessenger)

        channel.setMethodCallHandler { (call, res) in

            // 根据函数名做不同的处理

            switch(call.method) {

                case "your_method_name":

                    yourMethodName(call, res)

                default:

                    res(nil)

            }

        }

    }

    

    

    

    /* ======================================================= */

    /* Private Methods                                         */

    /* ======================================================= */

    

    private func yourMethodName(call: FlutterMethodCall, result: FlutterResult ) {

        // 获取传入的参数字典

        let params = call.arguments as? NSDictionary

        var msg = ""

        if (params != nil) {

            // 获取具体的参数值

            msg = params!["msg"] as? String ?? ""

        }

        // 打印日志

        print("正在执行原生方法,传入的参数是:" + msg)

        // 通知结束

        //result(nil)

        result.success(msg);

    }

}


2.3 Flutter端调用

const platform = const MethodChannel("your_channel_name"");

String returnValue = await platform.invokeMethod("your_method_name",{"msg":"hello world"});

print("从原生Android的java方法返回的值是:"+returnValue);

3. 如何在原生代码中调用 Flutter 中的方法

有些时候,例如原生组件需要回调到 Flutter 中,需要在原生代码中调用 Flutter 的函数。方法还是通过建立 Channel 对象,为 channel 设置 MethodCallHandler,再在原生代码中通过同名 channel 的 invokeMethod 调用即可。


3.1 在 Flutter 中创建 channel 并实现 MethodCallHandler



void _initChannel() {

    var channel = MethodChannel("your_channel_name");

    channel.setMethodCallHandler((call) {

        // 同样也是根据方法名分发不同的函数

        switch(call.method) {

          case "your_method_name": {

            String msg = call.arguments["msg"];

            print("Native 调用 Flutter 成功,参数是:$msg");

            return "成功";

          }

        }

        return null;

    });

}

3.2 在 Android 和 iOS 端调用 Flutter 的方法

通过创建同名 channel,并执行 invokeMethod 方法,即可在原生代码中调用 Flutter 的方法了:


Android:



import io.flutter.embedding.android.FlutterActivity

import io.flutter.embedding.engine.FlutterEngine

import io.flutter.plugin.common.MethodChannel


class MainActivity: FlutterActivity() {


    /* ======================================================= */

    /* Override/Implements Methods                             */

    /* ======================================================= */


    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {

        super.configureFlutterEngine(flutterEngine)


        val messenger = flutterEngine.dartExecutor.binaryMessenger


        // 新建一个 Channel 对象

        val channel = MethodChannel(messenger, "your_channel_name")


        // 调用 Flutter 中的方法

        val params = mapOf(

            "msg" to "这是来自 Android 端的参数"

        )

        channel.invokeMethod("your_method_name", params)

    }


}

iOS:

class MainViewController: FlutterViewController {



    /* ======================================================= */

    /* Override/Implements Methods                             */

    /* ======================================================= */

    

    override func viewDidLoad() {

        super.viewDidLoad()


        // 创建渠道

        let channel = FlutterMethodChannel(name: "your_channel_name", binaryMessenger: self.binaryMessenger)

        

        // 通过渠道调用 Flutter 的方法

        var params: Dictionary<String, String> = ["msg": "这是来自 iOS 端的参数"]

        channel.invokeMethod("your_method_name", arguments: params)

    }

}

好了,这就是 Flutter 与原生 Android、iOS 互相调用函数的方式啦。



Flutter 页面跳转并返回数据

异步请求与返回数据

import 'package:flutter/material.dart';


void main(){

  runApp(MaterialApp(

    title: '页面跳转返回数据',

    home: FirstPage()

  ));

}


class FirstPage extends StatelessWidget {

  @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(title: Text('找小姐姐要电话')),

      body: Center(

        child: RouteButton(),

      )

    );

  }

}


class RouteButton extends StatelessWidget {

  @override

  Widget build(BuildContext context) {

    return RaisedButton(

      onPressed: (){

        _navigateToXiaoJieJie(context);

      },

      child: Text('去找小姐姐'),

    );

  }


  // 加 _ 表示内部的方法

  _navigateToXiaoJieJie(BuildContext context) async{

    final result = await Navigator.push(context, MaterialPageRoute(

        builder: (context)=>XiaoJieJie()

    ));

    

    Scaffold.of(context).showSnackBar(SnackBar(content: Text('$result'),));

  }

}


class XiaoJieJie extends StatelessWidget {

  @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(

        title: Text('我是小姐姐'),

      ),

      body: Center(

        child: Column(

          children: <Widget>[

            RaisedButton(

              child: Text('大长腿小姐姐'),

              onPressed: (){

                Navigator.pop(context, '大长腿小姐姐:13522202020');

              },

            ),

            RaisedButton(

              child: Text('小蛮腰小姐姐'),

              onPressed: (){

                Navigator.pop(context, '小蛮腰小姐姐:13588889999');

              },

            ),

          ],

        ),

      ),

    );

  }

}


Flutter关键笔记

Flutter关键笔记

Widget

布局

1.png

  1. 容纳组件的布局组件:Center  Container Row Column等

  2. 创建用来容纳可见内容的组件,比如 Text  Image Icon 等

  3. 将可见组件添加到布局组件里,通过传递给其属性child 或children

  4. 将布局组件添加到页面组件里,一般在其build方法里完成

    1.png

    1.png

    实例代码


class CardPage extends StatelessWidget {

  @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(

        title: Text('Card'),

      ),

      drawer: JWFDDrawer(),

      body: Center(

        child: SizedBox(

          height: 210,

          child: Card(

            child: Column(

              children: [

                ListTile(

                  title: Text(

                    '1625 Main Street',

                    style: TextStyle(fontWeight: FontWeight.w500),

                  ),

                  subtitle: Text('My City, CA 99984'),

                  leading: Icon(

                    Icons.restaurant_menu,

                    color: Colors.blue[500],

                  ),

                ),

                Divider(),

                ListTile(

                  title: Text(

                    '(408) 555-1212',

                    style: TextStyle(fontWeight: FontWeight.w500),

                  ),

                  leading: Icon(

                    Icons.contact_phone,

                    color: Colors.blue[500],

                  ),

                ),

                ListTile(

                  title: Text('[email protected]'),

                  leading: Icon(

                    Icons.contact_mail,

                    color: Colors.blue[500],

                  ),

                ),

              ],

            ),

          ),

        ),

      ),

    );

  }

}

字体使用使用http://www.iconfont.cn放入购物车下载

1.png

import  'package:flutter/meterial.dart'

class MyIcon{

static const IconData huawei=const iconData(

0xe82e,

fontFamily:'myGoodIcon',

matchTextDirection:true

);

static const IconData oppo=const iconData(

0xe81e,

fontFamily:'myGoodIcon',

matchTextDirection:true

);

....

}

使用

Icon(Mycon.huawei,Color.yellow);//图标


容器使用:


body:Row(

children:<Widget>[

Container(

width:200,

height:300,

color:Colors.red,

),

Container(

width:200,

height:300,

color:Colors.blue,

)

],


),

Flex布局

1.png

1.png

1.png

1.png


Flutter UI组件

Scaffold定义了一个 UI 框架,这个框架包含 头部导航栏,body,右下角浮动按钮,底部导航栏等。

几个典型参数

1,appBar

这个参数用来定义顶部导航栏,传入一个 AppBar 实例即可


2,body

Scaffold 的下一个参数就是 body 了,用来展示 APP 的主体部分。主体部分大部分是通过组合 Container ,Column,Row,Stack来实现的,关于这几个组件的布局以后再说。


3,floatingActionButton

Scaffold 还有一个参数是 floatingActionButton 了,这个参数用来定义浮动在 body 右下角的组件。Flutter 入门的那个例子中就用到了这个参数


4,bottomNavigationBar

我用过的 Scaffold 的最后一个参数是 bottomNavigationBar, 顾名思义,就是底部导航栏,可以传入一个 Flutter 提供的 BottomNavigationBar 来实现导航栏,如下图,看底部导航栏:


Flutter 控制是否显示组件

Flutter 控制是否显示组件

Flutter 控制是否显示组件(Offstage),Offstage的作用很简单,通过一个参数来控制child是否显示,也是比较常用的组件

这里编写一个控制文本显示/隐藏的小示例,需要添加一个控制状态的变量offstage。示例里点击右下角按钮可以显示或隐藏文本内容。完整的示例代码如下:

import 'package:flutter/material.dart';


void main() {

  runApp(MyApp());

}

class MyApp extends StatelessWidget {


  @override

  Widget build(BuildContext context) {

final appTitle = "Offstage控制是否显示组件示例";

return MaterialApp(

  title: appTitle,

  home: MyHomePage(title:appTitle),

);

  }

}


class MyHomePage extends StatefulWidget {

  final String title;


  MyHomePage({Key key,this.title}):super(key:key);


  @override

  _MyHomePageState createState() => _MyHomePageState();

}


class _MyHomePageState extends State<MyHomePage> {

  // 状态控制是否显示文本组件

  bool offstage = true;


  @override

  Widget build(BuildContext context) {

return Scaffold(

  appBar: AppBar(

    title: Text(widget.title),

  ),

  body: Center(

    child: Offstage(

      offstage: offstage,//控制是否显示

      child: Text(

          '我出来啦!',

          style: TextStyle(

            fontSize: 36.0,

          ),

      ),

    ),

  ),

  floatingActionButton: FloatingActionButton(

    onPressed: (){

      // 设置是否显示文本组件

      setState(() {

          offstage = !offstage;

      });

    },

    tooltip: "显示隐藏",

    child: Icon(Icons.flip),

  ),

);

  }

}



Flutter 插件开发

插件的创建

我们可以通过两种方式来创建插件,一种是使用 IDE(Android Studio 或者 Idea)来创建;另一种是通过命令来创建。


使用 IDE 创建插件

在菜单栏中选择 File -> New -> New Flutter Project 会出现如下界面

1.png


选中 Flutter Plugin 然后一路 Next 就可以了。


使用命令创建插件

flutter create --org com.example --template=plugin plugin_name


其中 com.example 是插件包名的一部分,plugin_name 是插件的名称。插件的完整包名为 com.example.plugin_name


插件的目录结构

使用上述两种方式中的任一种创建完成之后,插件的目录结构如下:

2.png

图中包含的几个主要的目录分别为 android,example,ios,lib 这四个目录: - android 目录是一个完整的 Android 工程,用来开发 Android 端的插件功能 - example 目录用来测试 Android 或者 IOS 端的插件功能 - ios 目录是一个完整的 IOS 工程,用来开发 IOS 端的插件功能 - lib 目录中的文件负责和 Android 或者 IOS 端的交互


获取 Android 中的上下文 Context

当我们创建好插件之后,Android 工程 里面会有一个生成好的文件,这里是 FlutterPluginDemoPlugin.java,如下:


/** FlutterPluginDemoPlugin */

public class FlutterPluginDemoPlugin implements MethodCallHandler {

  /** Plugin registration.*/

  public static void registerWith(Registrar registrar) {

    final MethodChannel channel = new MethodChannel(registrar.messenger(), "flutter_plugin_demo");

    channel.setMethodCallHandler(new FlutterPluginDemoPlugin());

  }


  @Override

  public void onMethodCall(MethodCall call, Result result) {

    // 省略部分代码

  }

}


这个类中有一个与 Dart 层对应的 MethodChannel。


这个时候,如果我们添加一个弹出 Toast 的方法。Toast 需要一个 Context 类型的参数,但是这个类中是没有提供类似 this.getContext() 的方法来提供。这个时候,需要使用Registrar 这个类来获取 Context。如下:


public class FlutterPluginDemoPlugin implements MethodCallHandler {

    // 上下文 Context

    private final Context context;


    public FlutterPluginDemoPlugin(Registrar registrar) {

        this.context = registrar.context();

    }


    /**

     * Plugin registration.

     */

    public static void registerWith(Registrar registrar) {

        final MethodChannel channel = new MethodChannel(registrar.messenger(), "flutter_plugin_demo");

        channel.setMethodCallHandler(new FlutterPluginDemoPlugin(registrar));

    }


    @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")) {

            // 弹出 Toast

            Toast.makeText(context, "来自 Android 端的 Toast", Toast.LENGTH_SHORT).show();

        } else {

            result.notImplemented();

        }

    }

}


对应的,在 flutter_plugin_demo.dart 文件中需要新增一个方法来触发弹出 Toast:


class FlutterPluginDemo {

  static const MethodChannel _channel =

      const MethodChannel('flutter_plugin_demo');


  static Future<String> get platformVersion async {

    final String version = await _channel.invokeMethod('getPlatformVersion');

    return version;

  }


  /// 弹出 Toast

  static Future<void> showToast() async {

    await _channel.invokeMethod("showToast");

  }


}


然后在 example 工程中的去调用:


floatingActionButton: FloatingActionButton(

  onPressed: () async {

    /// 调用插件的 Toast 功能

    await FlutterPluginDemo.showToast();

  },

  child: Icon(Icons.add),

),


Dart 调用原生方法时传递参数

将上述的 showToast 方法改成接收一个参数的方法:


/// [message] Toast 的内容

static Future<void> showToast({String message}) async {

    await _channel.invokeMethod("showToast", message);

}


在 Java 层就需要接收这个参数:


String message = String message = call.arguments();

Toast.makeText(context, message, Toast.LENGTH_SHORT).show();


有时候需要传递好多个参数,这个时候可以传递一个 Map,如下:


/// 传递 map 类型的参数

static Future<void> showToast() async {

    Map<String, String> params = Map<String, String>();

    params['name'] = 'Lili';

    params['age'] = '20';

    params['country'] = 'China';

    await _channel.invokeMethod("showToast", params);

}


Java 层接收:


Map<String, String> params = call.arguments();

Toast.makeText(context, params.get("name"), Toast.LENGTH_SHORT).show();


原生向 Dart 层发送通知

这里我们使用 EventChannel 来让原生向 Dart 层发送通知,使用 EventChannel 的步骤如下:


Dart 层定义一个 EventChannel

Dart 层监听该 EventChannel,用来接收该 EventChannel 上的事件

原生端定义一个 EventChannel

原生端向 Dart 层发送消息

Dart 层定义 EventChannel

static const EventChannel _eventChannel = EventChannel("flutter_plugin_event");


Dart 层监听该 EventChannel

// 这里的 data 就是原生端发送过来的数据

_eventChannel.receiveBroadcastStream().listen((data) {

  //streamController.sink.add(data);

});


原生端定义 EventChannel

原生端里面,首先需要定义一个 EventChannel ,然后需要为其设置一个 StreamHandler,在 StreamHandler 的 onListen 方法中会有一个 EventChannel.EventSink 的参数,这个参数可以用来向 Dart 层发送消息。


private EventChannel.EventSink eventSink;

...

final EventChannel eventChannel = new EventChannel(registrar.messenger(), "flutter_plugin_event");

eventChannel.setStreamHandler(new EventChannel.StreamHandler() {

    @Override

    public void onListen(Object o, EventChannel.EventSink eventSink) {

        FlutterPluginDemoPlugin.this.eventSink = eventSink;

    }


    @Override

    public void onCancel(Object o) {

        FlutterPluginDemoPlugin.this.eventSink = null;

    }

});


原生端向 Dart 层发送消息

// 通知 Dart 层

if (null != eventSink) {

    eventSink.success("Dart 调用 原始方法成功");

}


插件中监听 Activity 常用的生命周期方法的回调

生命周期方法回调

有时候我们需要在 Activity 的生命周期方法中干一些事,比如友盟统计的时候,就需要在Activity 的 onResume()和 onPause() 中添加一些代码;


要监听 onCreate(),onStart(),onResume() 等 方法的回调,需要借助 Application.ActivityLifecycleCallbacks 这个接口。


首先写一个类 LifeCycleCallbacks 来 实现 Application.ActivityLifecycleCallbacks 这个接口,然后将其注册到 Application 的上下文中。


Application.ActivityLifecycleCallbacks 接口中提供的生命周期方法如下:


public interface ActivityLifecycleCallbacks {

    void onActivityCreated(Activity activity, Bundle savedInstanceState);

    void onActivityStarted(Activity activity);

    void onActivityResumed(Activity activity);

    void onActivityPaused(Activity activity);

    void onActivityStopped(Activity activity);

    void onActivitySaveInstanceState(Activity activity, Bundle outState);

    void onActivityDestroyed(Activity activity);

}


所以我们只需要写一个类来实现它就可以了。然后在对应的方法里面写对应的代码。


接着就是注册了:


public FlutterPluginDemoPlugin(Registrar registrar) {

    ...

    ...

    // 注册声明周期方法的监听

    ((Application) registrar.context())

        .registerActivityLifecycleCallbacks(new LifeCycleCallbacks());

}


最后在 onActivityDestroyed 生命周期方法中解注册


class LifeCycleCallbacks implements Application.ActivityLifecycleCallbacks {

    ...

    ...

    @Override

    public void onActivityDestroyed(Activity activity) {

        if (activity == registrar.activity()) {

            ((Application) registrar.context()).unregisterActivityLifecycleCallbacks(this);

        }

    }  

}


权限监听回调

public FlutterPluginDemoPlugin(Registrar registrar) {

        ...

        ...

        // 权限监听回调

        registrar.addRequestPermissionsResultListener(new PluginRegistry.RequestPermissionsResultListener() {

        @Override

        public boolean onRequestPermissionsResult(int i, String[] strings, int[] ints) {

            return false;

        }

    });

}


startActivityForResult 的回调

public FlutterPluginDemoPlugin(Registrar registrar) {

    ...

    ...

    // startActivityForResult 回调

    registrar.addActivityResultListener(new PluginRegistry.ActivityResultListener() {

        @Override

        public boolean onActivityResult(int requestCode, int responseCode, Intent intent) {

            return false;

        }

    });

}


编写一个 Delegate 类来处理业务逻辑

上一小节中,我们在 FlutterPluginDemoPlugin这一个类中处理 Activity 的声明周期的回调方法,权限申请的回调方法,Activity 跳转的回调方法。这个时候,FlutterPluginDemoPlugin 这个类的代码就会显得非常的多。


我们可以写一个类来帮助插件类处理这些事情,这里写一个 PluginDelegate 类来实现这个功能:


public class PluginDelegate implements

        Application.ActivityLifecycleCallbacks,

        PluginRegistry.RequestPermissionsResultListener,

        PluginRegistry.ActivityResultListener {


    private final Context context;

    private final Application application;


    public PluginDelegate(PluginRegistry.Registrar registrar) {

        this.context = registrar.context();

        this.application = (Application) context;

    }


    public void methodA(MethodCall call, MethodChannel.Result result){

        // do something...

    }


    public void methodB(MethodCall call, MethodChannel.Result result){

        // do something...

    }

    ...

    ...

    ...

    @Override

    public void onActivityDestroyed(Activity activity) {

        application.unregisterActivityLifecycleCallbacks(this);

    }


    @Override

    public boolean onRequestPermissionsResult(int i, String[] strings, int[] ints) {

        return false;

    }


    @Override

    public boolean onActivityResult(int i, int i1, Intent intent) {

        return false;

    }

}


可以看出 PluginDelegate 类实现了上一小节中需要处理的三种回调的接口,那么我们在 FlutterPluginDemoPlugin 插件类中就可以这样:


public class FlutterPluginDemoPlugin implements MethodCallHandler {

    ...

    ...

    private final PluginDelegate delegate;


    // 构造方法

    public FlutterPluginDemoPlugin(Registrar registrar, PluginDelegate delegate) {

        ...

        this.delegate = delegate;

        ...

        // 声明周期回调

        ((Application) context).registerActivityLifecycleCallbacks(delegate);


        // 权限声明回调

        registrar.addRequestPermissionsResultListener(delegate);


        // 页面跳转回调

        registrar.addActivityResultListener(delegate);

    }


    /**

     * Plugin registration.

     */

    public static void registerWith(Registrar registrar) {

        final MethodChannel channel = new MethodChannel(registrar.messenger(), "flutter_plugin_demo");

        // 初始化PluginDelegate

        final PluginDelegate delegate = new PluginDelegate(registrar);

        channel.setMethodCallHandler(new FlutterPluginDemoPlugin(registrar, delegate));

    }


    @Override

    public void onMethodCall(MethodCall call, Result result) {

        // 调用代理类方法演示

        if (call.method.equals("methodA")) {

            delegate.methodA(call, result);

        }else if(call.method.equals("methodB")){

            delegate.methodB(call, result);

        }

    }

}


插件的依赖方式

官方文档


这里有三种方式用来依赖插件 - pub 依赖 - git 依赖 - 本地依赖


pub 依赖

这种是最常见的方式,直接在 工程的 pubspec.yaml 中依赖


dependencies:

  flutter:

    sdk: flutter

  # 添加 toast 的依赖

  toast: ^0.1.5


git 依赖

很多时候,pub 上的某个插件并不能完全满足我们实际的业务需求,如 UI 或者一些逻辑问题,这个时候我们可以将其源码下载下来,然后根据自己的业务需求对其进行修改。改完之后通常会上传到公司的私有仓库中(GitHub 或者 GitLab),然后在工程中就需要依赖私有仓库中的库


dependencies:

 toast:

    git:

      url: http://xxx/toast.git


还可能依赖该仓库指定分支上的代码,如依赖远程 dev 分支上的代码


dependencies:

  toast:

     git:

      ref: dev

      url: http://xxx/toast.git


本地依赖

有时候需要在项目中测试本地的某个插件,这个时候就可以使用本地依赖的方式来依赖插件


dependencies:

    toast:

        path: user/xxx/toast/


插件的上传

这里是上传到 pub.dev 上面


在上传之前使用如下命令检查插件中的一些问题:


flutter packages pub publish --dry-run


还需要做的就是上传前的需要清理插件,避免插件过大无法上传


flutter clean


使用如下命令进行插件的上传


flutter packages pub publish



Flutter经典例子

import 'package:flutter/material.dart';


void main() {

  runApp(const MyApp());

}


class MyApp extends StatelessWidget {

  const MyApp({Key? key}) : super(key: key);


  // This widget is the root of your application.

  @override

  Widget build(BuildContext context) {

    return MaterialApp(

      title: 'Flutter Demo',

      theme: ThemeData(

        // This is the theme of your application.

        //

        // Try running your application with "flutter run". You'll see the

        // application has a blue toolbar. Then, without quitting the app, try

        // changing the primarySwatch below to Colors.green and then invoke

        // "hot reload" (press "r" in the console where you ran "flutter run",

        // or simply save your changes to "hot reload" in a Flutter IDE).

        // Notice that the counter didn't reset back to zero; the application

        // is not restarted.

        primarySwatch: Colors.blue,

      ),

      home: const MyHomePage(title: 'Flutter演示程序'),

    );

  }

}


class MyHomePage extends StatefulWidget {

  const MyHomePage({Key? key, required this.title}) : super(key: key);


  // This widget is the home page of your application. It is stateful, meaning

  // that it has a State object (defined below) that contains fields that affect

  // how it looks.


  // This class is the configuration for the state. It holds the values (in this

  // case the title) provided by the parent (in this case the App widget) and

  // used by the build method of the State. Fields in a Widget subclass are

  // always marked "final".


  final String title;


  @override

  State<MyHomePage> createState() => _MyHomePageState();

}


class _MyHomePageState extends State<MyHomePage> {

  int _counter = 0;


  void _incrementCounter() {

    setState(() {

      // This call to setState tells the Flutter framework that something has

      // changed in this State, which causes it to rerun the build method below

      // so that the display can reflect the updated values. If we changed

      // _counter without calling setState(), then the build method would not be

      // called again, and so nothing would appear to happen.

      _counter++;

    });

  }


  @override

  Widget build(BuildContext context) {

    // This method is rerun every time setState is called, for instance as done

    // by the _incrementCounter method above.

    //

    // The Flutter framework has been optimized to make rerunning build methods

    // fast, so that you can just rebuild anything that needs updating rather

    // than having to individually change instances of widgets.

    return Scaffold(

      appBar: AppBar(

        // Here we take the value from the MyHomePage object that was created by

        // the App.build method, and use it to set our appbar title.

        title: Text(widget.title), //widget就是MyHomePage

      ),

      body: Center(

        // Center is a layout widget. It takes a single child and positions it

        // in the middle of the parent.

        child: Column(

          // Column is also a layout widget. It takes a list of children and

          // arranges them vertically. By default, it sizes itself to fit its

          // children horizontally, and tries to be as tall as its parent.

          //

          // Invoke "debug painting" (press "p" in the console, choose the

          // "Toggle Debug Paint" action from the Flutter Inspector in Android

          // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)

          // to see the wireframe for each widget.

          //

          // Column has various properties to control how it sizes itself and

          // how it positions its children. Here we use mainAxisAlignment to

          // center the children vertically; the main axis here is the vertical

          // axis because Columns are vertical (the cross axis would be

          // horizontal).

          mainAxisAlignment: MainAxisAlignment.center,

          children: <Widget>[

            const Text(

              '你点击按钮次数:',

            ),

            Text(

              '$_counter',

              style: Theme.of(context).textTheme.headline4,

            ),

          ],

        ),

      ),

      floatingActionButton: FloatingActionButton(

        onPressed: _incrementCounter,

        tooltip: 'Increment',

        child: const Icon(Icons.add),

      ), // This trailing comma makes auto-formatting nicer for build methods.

    );

  }

}


Flutter事件监听

一. 事件监听

在大前端的开发中,必然存在各种各样和用户交互的情况:比如手指点击、手指滑动、双击、长按等等。
所有内容首发于公众号:coderwhy

在Flutter中,手势有两个不同的层次:

  • 第一层:原始指针事件(Pointer Events):描述了屏幕上由触摸板、鼠标、指示笔等触发的指针的位置和移动。
  • 第二层:手势识别(Gesture Detector):这个是在原始事件上的一种封装。
    • 比如我们要监听用户长按,如果自己封装原始事件我们需要监听从用户按下到抬起的时间来判断是否是一次长按事件;
    • 比如我们需要监听用户双击事件,我们需要自己封装监听用户两次按下抬起的时间间隔;
    • 幸运的是各个平台几乎都对它们进行了封装,而Flutter中的手势识别就是对原始指针事件的封装;
    • 包括哪些手势呢?比如点击、双击、长按、拖动等

2.1. 指针事件Pointer

Pointer 代表的是人机界面交互的原始数据。一共有四种指针事件:

Pointer的原理是什么呢?

  • 在指针落下时,框架做了一个 hit test 的操作,确定与屏幕发生接触的位置上有哪些Widget以及分发给最内部的组件去响应;
  • 事件会沿着最内部的组件向组件树的根冒泡分发;
  • 并且不存在用于取消或者停止指针事件进一步分发的机制;

原始指针事件使用Listener来监听:

class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Listener(
        child: Container(
          width: 200,
          height: 200,
          color: Colors.red,
        ),
        onPointerDown: (event) => print("手指按下:$event"),
        onPointerMove: (event) => print("手指移动:$event"),
        onPointerUp: (event) => print("手指抬起:$event"),
      ),
    );
  }
}



1.jpg



2.2. 手势识别Gesture

Gesture是对一系列Pointer的封装,官方建议开发中尽可能使用Gesture,而不是Pointer





Gesture分层非常多的种类:

点击

  • onTapDown:用户发生手指按下的操作
  • onTapUp:用户发生手指抬起的操作
  • onTap:用户点击事件完成
  • onTapCancel:事件按下过程中被取消

双击:

  • onDoubleTap:快速点击了两次

长按:

  • onLongPress:在屏幕上保持了一段时间

纵向拖拽:

  • onVerticalDragStart:指针和屏幕产生接触并可能开始纵向移动;
  • onVerticalDragUpdate:指针和屏幕产生接触,在纵向上发生移动并保持移动;
  • onVerticalDragEnd:指针和屏幕产生接触结束;

横线拖拽:

  • onHorizontalDragStart:指针和屏幕产生接触并可能开始横向移动;
  • onHorizontalDragUpdate:指针和屏幕产生接触,在横向上发生移动并保持移动;
  • onHorizontalDragEnd:指针和屏幕产生接触结束;

移动:

  • onPanStart:指针和屏幕产生接触并可能开始横向移动或者纵向移动。如果设置了 onHorizontalDragStart 或者 onVerticalDragStart,该回调方法会引发崩溃;
  • onPanUpdate:指针和屏幕产生接触,在横向或者纵向上发生移动并保持移动。如果设置了 onHorizontalDragUpdate 或者 onVerticalDragUpdate,该回调方法会引发崩溃。
  • onPanEnd:指针先前和屏幕产生了接触,并且以特定速度移动,此后不再在屏幕接触上发生移动。如果设置了 onHorizontalDragEnd 或者 onVerticalDragEnd,该回调方法会引发崩溃。

从Widget的层面来监听手势,我们需要使用:GestureDetector

  • 当然,我们也可以使用RaisedButton、FlatButton、InkWell等来监听手势
  • globalPosition用于获取相对于屏幕的位置信息
  • localPosition用于获取相对于当前Widget的位置信息
class HYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("手势测试"),
      ),
      body: GestureDetector(
        child: Container(
          width: 200,
          height: 200,
          color: Colors.red,
        ),
        onTap: () {

        },
        onTapDown: (detail) {
          print(detail.globalPosition);
          print(detail.localPosition);
        },
        onTapUp: (detail) {
          print(detail.globalPosition);
          print(detail.localPosition);
        }
      ),
    );
  }
}



2.jpg



二. 跨组件事件

在组件之间如果有事件需要传递,一方面可以一层层来传递,另一方面我们也可以使用一个EventBus工具来完成。

其实EventBus在Vue、React中都是一种非常常见的跨组件通信的方式:

  • EventBus相当于是一种订阅者模式,通过一个全局的对象来管理;
  • 这个EventBus我们可以自己实现,也可以使用第三方的EventBus;

这里我们直接选择第三方的EventBus:

dependencies:
  event_bus: ^1.1.1

第一:我们需要定义一个希望在组件之间传递的对象:

  • 我们可以称之为一个时间对象,也可以是我们平时开发中用的模型对象(model)
class UserInfo {
  String nickname;
  int level;
  
  UserInfo(this.nickname, this.level);
}

第二:创建一个全局的EventBus对象

final eventBus = EventBus();

第三:在某个Widget中,发出事件:

class HYButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      child: Text("HYButton"),
      onPressed: () {
        final info = UserInfo("why", 18);
        eventBus.fire(info);
      },
    );
  }
}

第四:在某个Widget中,监听事件

class HYText extends StatefulWidget {
  @override
  _HYTextState createState() => _HYTextState();
}

class _HYTextState extends State<HYText> {
  String message = "Hello Coderwhy";

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

    eventBus.on<UserInfo>().listen((data) {
      setState(() {
        message = "${data.nickname}-${data.level}";
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Text(message, style: TextStyle(fontSize: 30),);
  }
}
备注:所有内容首发于公众号,之后除了Flutter也会更新其他技术文章,TypeScript、React、Node、uniapp、mpvue、数据结构与算法等等,也会更新一些自己的学习心得等,欢迎大家关注



Flutter调用原生安卓Service

Android和iOS在后台服务的处理方式不同,因此Flutter并没有在这方面做统一处理,而是需要调用原生平台的代码来实现该功能,下面就简单介绍如何在Flutter中与Android的后台服务进行交互。

实现

在Flutter中有一个按钮,点击后会开启后台服务。此外建立了一个MethodChannel,通过该通道去调用Android原生代码里的 startService()。

class MyHomePage extends StatefulWidget {

  @override

  _MyHomePageState createState() => _MyHomePageState();

}


class _MyHomePageState extends State {


  @override

  Widget build(BuildContext context) {

    return Scaffold(

      body: Center(

        child: RaisedButton(

          child: Text("开启后台任务"),

          onPressed: () {

            startService();

          },

        ),

      )

    );

  }


  void startService() async{

    if(Platform.isAndroid) {

      var methodChannel = MethodChannel("com.example.flutterapp");

      String data = await methodChannel.invokeMethod("startService");

      print("data: $data");

    }

  }

}

新建 MyApplication 继承自 FlutterApplication,并在 onCreate() 中创建 NotificationChannel 和 NotificationManager (Android 26 以上需要)

public class MyApplication extends FlutterApplication {


    @Override

    public void onCreate() {

        super.onCreate();


        //Android 8.0  26

        //一种 Notification 对应一个 NotificationChannel

        //在 Application 注册 channel 可以在 app 启动时就完成注册

        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

            NotificationChannel channel = new NotificationChannel("messages", "Messages", NotificationManager.IMPORTANCE_LOW);

            NotificationManager manager = getSystemService(NotificationManager.class);

            manager.createNotificationChannel(channel);

        }

    }

}

新建 MyService 继承自 Service,并在 onCreate() 中进行 Android 版本判断,对 26以上进行特殊处理(记得在 AndroidManifest 里注册 service)

public class MyService extends Service {


    @Override

    public void onCreate() {

        super.onCreate();


        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

            NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "messages")

                    .setContentText("正在后台运行")

                    .setContentTitle("Flutter后台")

                    .setSmallIcon(R.drawable.launch_background);


            startForeground(101, builder.build());

        }

    }


    @Override

    public IBinder onBind(Intent intent) {

        return null;

    }

}

最后,在 MainActivity 中打开 注册 MethodChannel 并提供 startService() 给 Flutter 调用。

public class MainActivity extends FlutterActivity {


  private Intent serviceIntent;


  @Override

  protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    GeneratedPluginRegistrant.registerWith(this);


    serviceIntent = new Intent(MainActivity.this, MyService.class);


    new MethodChannel(getFlutterView(), "com.example.flutterapp")

            .setMethodCallHandler(new MethodChannel.MethodCallHandler() {

              @Override

              public void onMethodCall(@NonNull MethodCall methodCall, @NonNull MethodChannel.Result result) {

                if(methodCall.method.equals("startService")) {

                  startService();

                  result.success("服务已启动");

                }

              }

            });

  }


  @Override

  protected void onDestroy() {

    super.onDestroy();

    stopService(serviceIntent);

  }


  private  void startService() {

    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

      startForegroundService(serviceIntent);

    } else {

      startService(serviceIntent);

    }

  }

}


Flutter 热更新及动态UI生成

探索

以上分析了一些主要的动态化更新思路,这里给出我正在探索的解决方案。那就是 LuaDardo库。


LuaDardo是我用Dart语言编写的Lua虚拟机,它的名字是由葡萄牙语的两个单词组成,可以翻译成“镖中月”。


Lua本身是巴西人开发的一种以嵌入其他宿主语言为目标的简洁的高性能脚本语言,Lua在葡萄牙语中是月亮的意思。该语言多用于游戏开发,用Lua脚本编写业务逻辑,然后调用底层C++游戏引擎实现渲染。


Lua语言设计十分精巧,通过一个栈完成对宿主语言的互操作。LuaDardo直接使用Dart语言编写Lua虚拟机,这可以让我们以高性能的方式完成Lua与Dart语言的互操作。只需要对Flutter的Widget进行一定封装,将Dart类绑定到Lua语言中,即可使用Lua脚本编写UI界面。


另外,LuaDardo直接基于Dart开发,天然的具备跨平台能力,只要有Flutter的地方,就能使用。即使是Flutter的桌面应用,亦可具备这种动态脚本能力。


LuaDardo本身定位是基于Dart语言的虚拟机,我们要用Lua写Flutter界面,还需要对Flutter控件的封装扩展。


flutter_lua_dardo 就是一个这样的扩展库。该库主要用于包装Flutter接口和控件,这使得Flutter能够在需要经常改变UI风格的地方使用远程脚本动态地更新和生成界面。


「请注意,flutter_lua_dardo 只是一个实验性的探索。它只封装了几个简单的Widget。欢迎其他人一起探索,为Lua封装更多的Widget。」


另外的,LuaDardo库已完成了大部分工作,只是Lua 自带的标准库尚未完全编写完毕,协程库、OS库、IO库等尚未开始。


例子

1.png

用法

新建 Lua 脚本 test.lua:


function getContent1()

    return Row:new({

        children={

            GestureDetector:new({

                onTap=function()

                    flutter.debugPrint("--------------onTap--------------")

                end,


                child=Text:new("click here")}),

            Text:new("label1"),

            Text:new("label2"),

            Text:new("label3"),

        },

        mainAxisAlign=MainAxisAlign.spaceEvenly,

    })

end


function getContent2()

    return Column:new({

        children={

            Row:new({

                children={Text:new("Hello"),Text:new("Flutter")},

                mainAxisAlign=MainAxisAlign.spaceAround

            }),

            Image:network('https://gitee.com/arcticfox1919/ImageHosting/raw/master/img/flutter_lua_test.png'

                ,{fit=BoxFit.cover})

        },

        mainAxisSize=MainAxisSize.min,

        crossAxisAlign=CrossAxisAlign.center

    })

end

添加依赖 pubspec.yaml


dependencies:

  flutter_lua_dardo: ^0.0.2

添加Dart代码:


class _MyHomePageState extends State<MyHomePage> {

  LuaState _ls;

  bool isChange = false;

    

  @override

  void initState() {

    loadLua();

    super.initState();

  }

  

  // 加载Lua虚拟机

  void loadLua() async {

    String src = await rootBundle.loadString('assets/test.lua');

    try {

      LuaState ls = LuaState.newState();

      ls.openLibs();

      FlutterLua.open(ls);

      FlutterWidget.open(ls);

      ls.doString(src);

      setState(() {

        _ls = ls;

      });

    } catch (e) {

      print(e);

    }

  }


  @override

  Widget build(BuildContext context) {

    return Scaffold(

      body: Container(

        alignment: Alignment.center,

        child: _ls == null

            ? CircularProgressIndicator()

             // 调用Lua函数,创建UI

            : FlutterWidget.findViewByName<Widget>(

            _ls, isChange ? "getContent2" : "getContent1"),

      ),

      floatingActionButton: FloatingActionButton(

        child: Icon(CupertinoIcons.arrow_swap),

        onPressed: (){

          setState(() {

            isChange = !isChange;

          });

        },

      ),

    );

  }

}

「要了解如何绑定Dart类到Lua,你可以查看这个 示例.」


关于 LuaDardo库, 请查看 这里.




































上一篇:Dart基本语法

下一篇:Flutter实用库收集

Top