原生转flutter快速入门

本文是给第一次接触flutter的原生开发iOS/android同窗快速入门的攻略,高手请绕路,轻拍哈。前端

对于原生开发的同窗,对于flutter会比较感兴趣,也许会从网上零星得到一些学习资源,可是比较零散,不构成学习路径,可能也会踩一些坑,为了不少走弯路,又能快速的入门flutter,现将我的的一些实践经历分享一下,供你们批评指正补充。android

1. 安装flutter

1.1 先下载flutter编辑器 android studio,最新版本,解压,安装。。。(如下简称AS)ios

1.2 安装flutter 已iOS为例git

cd ~/development
 unzip ~/Downloads/flutter_macos_v1.9.1+hotfix.2-stable.zip
复制代码
  • 设置PATH环境变量

先编辑bash_profile文件(默认状况下,macOS Mojave(及更早版本)使用Bash shell,所以编辑$HOME/.bashrc)github

$vim ~/.bash_profile
复制代码

添加如下路径shell

export PATH="$PATH:`pwd`/flutter/bin"macos

完整的以下编程

export PATH="/bin:/usr/bin:/usr/local/bin:$PATH"
export PATH="/Users/boob/Documents/flutter/bin:$PATH"
export PATH="/usr/local/opt/openssl/bin:$PATH"
export PATH="/Users/boob/Documents/depot_tools:$PATH"
export PATH="/Users/boob/Library/Android/sdk/platform-tools:$PATH"
export PATH
复制代码

编辑完成使用wq退出,便可执行flutter命令了swift

  • 完成了,能够试试在终端执行 flutter --version看看版本号, which flutter能够查看flutter安装的路径

2. 建立工程并运行

ios的同窗默认安装了xcode,没有的话去安装一个吧,安卓同窗须要下载android studio,后续开发都是须要用as进行开发和调试的。 为了第一次能直接运行flutter,咱们先开一个模拟器,而且把其余真机设备移除,防止后面操做找不到运行目标,或不知道如何选择设备vim

前提操做 打开模拟器命令

open -a Simulator
复制代码

检测pod的版本号是否高于1.6.0

pod --version
复制代码

flutter默认最低支持的pod版本是1.6.0,若是使用到plugin时就会提示版本太低,致使pod失败了

建立工程的命令

flutter create testflutter
复制代码

注意工程名字都要小写,不然会提示你命名出错。

运行

$ cd myapp
$ flutter run
复制代码

此时flutter会编译dart代码,而且签名运行

3. flutter产物介绍

咱们进入到flutter源码目录

ios目录存放ios工程,android则存放android工程 对于ios来讲,编译的产物在ios/Flutter文件夹中 包含了

  • App.framework 这是flutter工程编译出来的ios产物,对于debug编译来讲,flutter_assets包含了全部的可执行产物和资源 对于release编译来讲,可执行的部分在APP文件中,资源存放在flutter_assets中

  • flutter.framework 俗称flutter engine/flutter 引擎,支持上层flutter运行的底层库。

  • xxx.xcconfig 工程配置,flutter命令自动生成的,为了配置flutter路径,flutterframework的路径

  • flutter_export_environment.sh 1.9新增的脚本,配置flutter经常使用的环境变量

4. 热重载 hot reload

这是flutter的吹嘘的几大特性之一,跨平台一致性,热重载。。。 即写完代码能够当即执行。 编写flutter代码咱们使用android studio,官方推荐3.0以上的版本 developer.android.com/studio

咱们可使用最新的,由于已经使用了最新的flutter插件功能,包括断点调试,attach,性能查看分析等。

用AS打开testflutter工程

找到lib路径,这是咱们dart代码存放的路径,flutter项目是用dart语言开发。

如今能够点运行按钮️,直接启动flutter,这个跟在终端启动效果同样。

修改一下源码,把标题改为个人第一个flutter项目,以下

而后按下 cmd+s 保存,便可在模拟器上看到运行结果

5. flutter的默认UI库

默认提供两套ui库,一套是android风格的Material Design 和ios风格的 cupertino (连接是传送门)

下面感觉一下差异

咱们使用一个button试一下 在main.dart的scaffold的中添加代码

CupertinoButton(
              child: Text('CLICK ME'),
              color: Theme.of(context).accentColor,
              onPressed: (){
                print("点击了按钮");
              },
              disabledColor: Theme.of(context).disabledColor,
            )
复制代码

6. 开发思路的转变

到了源码级别,原生的编程思路就须要开始转变了,因为原生开发都是命令式编程,然而前端和flutter是声明式编程的。

对于命令式,是指若是咱们要对一个文本内容和文本颜色改变,咱们就去取到这个文本的text和textcolor 而后对text和textcolor进行赋值。

然而对于声明式,要改文本内容,须要将文本的内容text和文本的控件分别先声明

全部布局的控件都写到 Widget build(BuildContext context) { ... } 方法中,可是 可是控件须要用到是内容而且可能改变的,则使用一个变量记下来.

  • 声明写法
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}
复制代码

final String title; 就是声明了一个title的字符串。

或者

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
    ...
}
复制代码
  • 控件写法与使用
AppBar(
        title: Text(widget.title),
      ),
复制代码
Text(
      '$_counter',
      style: Theme.of(context).textTheme.display1,
    ),
复制代码
  • 改变文本内容 setState

这是和原生最大的差异,须要改变文本的内容则须要使用setState中生效,告诉flutter这时候状态变化了,须要从新刷新。

例子中单击+ 数字+1,能够看到界面上的数字当即更新了

void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }
复制代码

值得注意的是,state频繁刷新也会带来性能问题,不可滥用哦。 其余代码你们能够自行研究,都是声明式编程的运用。

7. 万物皆widget

widget在ui层面至关于原生的uiview,可是不只仅局限于显示视图UIView,也有用于布局相关的。

  • 基础 Widgets

Container、Button、Row和Column、Text、Scaffold、Icon、Image、Stack、TabBar+TabBarView、Widget-输入框TextField

  • 用于布局的 Widgets

Align、Center、Expended、LayoutBuilder、Padding、Wrap

  • 可滚动 Widgets

CustomScrollView、GridView、ListView、PageView、SingleChildScrollView

  • 装饰 Widgets

BoxDecoration、Clip系列、Opacity、SafeArea、高斯模糊BackdropFilter

参考: github.com/chenBingX/C…

8. FLEX布局

咱们知道横向布局使用Row 纵向布局使用Column Wiget 布局对其方式分为主轴和交叉轴,若是是Row布局主轴mainAxisAlignment就是横向,而其交叉轴就是纵轴, 主轴排列方式有6中,start,end,center,spaceAround,spaceBetween,spaceEvenly

  • spaceBetween 左右item靠边,中间等间距
Padding(
              padding: const EdgeInsets.all(0.0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  Container(color: Colors.red, width: 50, height: 50,),
                  Container(color: Colors.blue, width: 50, height: 50,),
                  Container(color: Colors.red, width: 50, height: 50,),
                  Container(color: Colors.blue, width: 50, height: 50,)
                ],
              ),
            )
            
复制代码

效果以下

  • spaceEvenly 表示全部item间距都相等,包括左右item距离边界
Padding(
              padding: const EdgeInsets.all(0.0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  Container(color: Colors.red, width: 50, height: 50,),
                  Container(color: Colors.blue, width: 50, height: 50,),
                  Container(color: Colors.red, width: 50, height: 50,),
                  Container(color: Colors.blue, width: 50, height: 50,)
                ],
              ),
复制代码

效果图以下

  • spaceAround 表示将可用空间均匀地放在孩子之间,以及其中一半,第一个和最后一个孩子以前和以后的空间。
Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  Container(color: Colors.red, width: 50, height: 50,),
                  Container(color: Colors.blue, width: 50, height: 50,),
                  Container(color: Colors.red, width: 50, height: 50,),
                  Container(color: Colors.blue, width: 50, height: 50,)
                ],
              ),
复制代码

效果图以下

start 表示将子级放置在尽量靠近主轴起点的位置。若是此值沿水平方向使用,则[TextDirection]必须为可用于肯定起点是左侧仍是右侧。 若是在垂直方向上使用此值,则[VerticalDirection]必须为可用于肯定起点是顶部仍是底部。

Row(
               mainAxisAlignment: MainAxisAlignment.start,
               crossAxisAlignment: CrossAxisAlignment.center,
               children: <Widget>[
                 Container(color: Colors.red, width: 50, height: 50,),
                 Container(color: Colors.blue, width: 50, height: 50,),
                 Container(color: Colors.red, width: 50, height: 50,),
                 Container(color: Colors.blue, width: 50, height: 50,)
               ],
             ),
复制代码

效果图以下:

  • center 居中
Row(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  Container(color: Colors.red, width: 50, height: 50,),
                  Container(color: Colors.blue, width: 50, height: 50,),
                  Container(color: Colors.red, width: 50, height: 50,),
                  Container(color: Colors.blue, width: 50, height: 50,)
                ],
              ),
复制代码

效果以下

  • end 横向布局则表示靠近主轴的终点,纵向布局则表示靠近纵轴的终点
Row(
                mainAxisAlignment: MainAxisAlignment.end,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  Container(color: Colors.red, width: 50, height: 50,),
                  Container(color: Colors.blue, width: 50, height: 50,),
                  Container(color: Colors.red, width: 50, height: 50,),
                  Container(color: Colors.blue, width: 50, height: 50,)
                ],
              ),
复制代码

9 原生工程如何支持flutter

混合工程方案网上有很多,可是真正的核心只有一个,就是将flutter工程的构建产物加入到原生工程中来。

咱们已经知道了flutter的构建产物有App.framework 其余的就是Flutter.framework,还有就是dart依赖的第三方库,多是plugin也多是dart库,能够在.symbol文件夹中找到(.xxx开头的文件默认是隐藏的,使用shift+cmd+. 将其显示出来)

要想加入到主工程,没法就是将三个东西作成pod的形式,在pod install/update的时候将他pod进来。这样就能作原生开发人员无感知的使用flutter工程。

业界有一个主流的方向,安装产物的本地和远程来划分,本地依赖方式和远程依赖方式。没法仍是把产物放哪的问题。

提供另外一个视角看混合工程可能更好。咱们按照角色划分混合方案,最多三种角色

  • 1、原生开发
  • 2、flutter开发
  • 3、混合开发

这三种角色的诉求分别是,

原生开发无需安装flutter,可是会用到flutter的产物。

其余人员都须要安装flutter,因此需求差很少。 a、安装了flutter的同窗,可能也不想编译flutter产物而是直接使用,b、flutter开发的同窗可能只想编译debug的产物,可是不想上传,他们想debug构建并attach到 c、对于构建的需求是须要使用最新的代码构建release的包。

使用官方的混合方式没法解决以上全部的需求。本身开发混合工程脚本,须要从以上角度考虑。

然而使用方法仅仅是简单的一句话

flutter_application_path = 'xxxflutter'
load File.join(flutter_application_path,  'IOSFlutterConfig', 'start.rb') 
def GirFlutter     
    puts "自动检测flutter是否存在,自动执行不一样的脚本" 
    install_flutter_engine_pod
end
复制代码

以后pod update便可!

10 混合工程与原生工程通讯

分为两个部分,flutter调用native的代码、native调用flutter的功能 官方提供了两种方式 methodchannel、eventchannel。

流程图以下,

method channel

另外plugin就是一种native和flutter通讯的最好的例子,咱们能够直接看他给的例子。

终端执行

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

复制代码

进入myplugin/ios/Classes目录 咱们看到 SwiftMypluginPlugin.swift

import Flutter
import UIKit

public class SwiftMypluginPlugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "myplugin", binaryMessenger: registrar.messenger())
    let instance = SwiftMypluginPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
  }

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    result("iOS " + UIDevice.current.systemVersion)
  }
}

复制代码

能够看到这个插件建立了一个名为myplugin的FlutterMethodChannel,而且经过registar注册到了methodcalldelegate里面了。

在dart那边可使用调起该方法。

class Myplugin {
  static const MethodChannel _channel =
      const MethodChannel('myplugin');

  static Future<String> get platformVersion async {
    final String version = await _channel.invokeMethod('getPlatformVersion');
    return version;
  }
}
复制代码

那么对于native如何调用flutter的代码呢 在methodchannel这个例子中,咱们看到已经给到了一个result过来了,咱们能够拿到这个result result: @escaping FlutterResult,若是有新的事件想发出去,就不断回调这个result也能够。

self.result("第二次回调给flutter")
复制代码

eventchannel

使用流程相似,先注册-> 发送事件

FlutterEventChannel *evenChannal =  [FlutterEventChannel eventChannelWithName:@"flutter_hummer_event" binaryMessenger:[registrar messenger]];
    FlutterEventChannelHandel *handle = [FlutterEventChannelHandel new];
    instance.eventHandel = handle;
    [evenChannal setStreamHandler:handle];
    
。。。
 self.eventHandel.eventsBlock(dic);
复制代码

dart层使用方式

先注册一个通知--> 监听回调

// 注册一个通知
  static const EventChannel eventChannel =
      const EventChannel('flutter_hummer_event');
    eventChannel
        .receiveBroadcastStream(12345)
        .listen(_onEvent, onError: _onError);
        

  // 回调事件
  void _onEvent(Object event) {
      ...
  }
  
  // 错误返回
  void _onError(Object error) {}

复制代码

!! 提示,plugin默认生成swift版的plugin,若是想要选择ios可使用-i objc选项,其余配置选项能够-h查询

flutter create --org com.example --template=plugin -i objc  myplugin22 
复制代码

原创声明

欢迎你们批评指正补充,争取作最好最精的入门教程,持续更新中...

原创不易,如需转载请注明来源,共田君

相关文章
相关标签/搜索