• 150455

    文章

  • 1009

    评论

  • 13

    友链

  • 最近新加了换肤功能,大家多来逛逛吧~~~~
  • 喜欢这个网站的朋友可以加一下QQ群,我们一起交流技术。

一个Android开发快速入门Flutter (一)


目录

一个Java开发快速入门Dart

Flutter使用简报

一个Android开发快速入门Flutter

前言

    在阅读本篇之前,最好已经对Flutter代码结构有一个初步体验,上面目录中的两篇文章是个不错的开头。

    另外,本篇需要你对于Android开发有一定经验,文章内容是笔者对于这篇的学习笔记,如果你是ios开发或者前端开发官网同样有类似入门教程。

View

    在android中我们的界面都是通过view来组成的,而在Flutter中我们的界面,甚至整个app都是通过组件Widget来组成的。从这个层面上来说,android中的view可能就等于Flutter中的widget。

    但是这里要讨论的更多的是view和widget的不同点。首先在android中我们的界面采用的是命令模式,而在Flutter中界面采用的是声明模式(Flutter使用简报中有介绍。)

    view和widget的声明周期就存在很大的差异,在android中view实例会一直存在,直到界面销毁,或者对象不再被使用。而widget的声明周期更加短暂,一旦这个widget需要改变了,那么这个widget就会被销毁,会重新生成新的widget(对就是这么直接)。你可能会疑惑,每次都重新生成widget不会太耗性能吗?关键就在于Flutter中的widget是轻量级了,不可变的,如果更确切的来说,widget就像是xml中的一个组件定义,本身并不是实例对象。而整个xml文件就是Flutter中的widget tree(就是组件的各种嵌套)

    在Flutter中默认包含了Material和Cupertino两套风格的组件(当然可以自定义其他风格)。前套是android的风格,后一套是ios的风格。

Stateless和Stateful组件

    关于StatelessWidget和StatefulWidget组件在Flutter使用简报中已经有过一定尝试,这里我们再重新拿出来说下,主要原因是这个东西确实算得上是Flutter的设计核心。

    Flutter中的widget就像是设计模式中的状态模式,他会有一个特定的状态类来维护组件当然的状态——State<?>。当然对于StatelessWidget而言,就是缺失了这个State类,这就导致它不会保存自己的状态,也就是我们所说的不可变。

    这里有个东西需要注意,Widget是可以嵌套的,比如在listview组件中嵌套listlite组件。如果listlite组件内部有一个StatefulWidget组件,当这个组件状态变化的时候,实际上listlite组件并没有状态变化,所以它依然可以是StatelessWidget。

Text(
  'I like Flutter!',
  style: TextStyle(fontWeight: FontWeight.bold),
);

    Text一个最基础的StatelessWidget,他没有状态,文字一旦设置之后就无法改变。那么如果我们需要根据点击事件改变文字呢?我们就需要重写一个继承与StatefulWidget的新组件。

PS:纠结了下还是把全部代码贴上来了

import 'package:flutter/material.dart';

void main() {
  runApp(SampleApp());
}

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

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

class _SampleAppPageState extends State<SampleAppPage> {
  // Default placeholder text
  String textToShow = "I Like Flutter";

  //关键在这里,修改文字内容,并且调用setState,该方法会重新调用Build
  void _updateText() {
    setState(() {
      // update the text
      textToShow = "Flutter is Awesome!";
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Sample App"),
      ),
      body: Center(child: Text(textToShow)),
      //添加一个悬浮按钮,该按钮点击后触发_updateText方法
      floatingActionButton: FloatingActionButton(
        onPressed: _updateText,
        tooltip: 'Update Text',
        child: Icon(Icons.update),
      ),
    );
  }
}

    另外在android中我们可以通过addChild和removeChild来动态添加和删除组件,但是在Flutter中由于组件都是不可变的,所以并不存在直接的方法来动态改变,所以的改变都是通过State。比如这个需求,我们可以通过一个bool变量来控制,在不同情况下,build方法输出不同的组件。(这里个人认为是Flutter比较不讨人喜欢的一个方法,代码量会增加,并且会多出很多标志位变量,这回增加阅读的难度,当存在过多的标志位时,你得思考你的家庭住址是否会被以后接手你代码的人知道。)

    

Flutter动画

    基本上android拥有的动画类型Flutter中都有,并且相比较于android中零碎的实现方式,Flutter中可能更成体系一些。本节我们仅仅是对Flutter动画做一个初步了解,后续可能会有其他博客专门分享动画的学习。如果你已经迫不及待想要学习动画的话

    https://flutter.io/docs/development/ui/animations

    https://flutter.io/docs/reference/widgets/animation

    以上两个官方文档是不错的起点。

    我们知道在android中使用xml文件或者animate()方法对view进行相关的动画。

    PS:下面代码有个with关键字,可以查看这里了解用法。

    在Flutter中使用AnimationController对象来进行动画的控制,包括暂停,定位,停止,回播。他需要一个Ticker参数用于在vsync(帧绘制信号)发生的时候生成一个0~1之间的线性插值。

    然后我们创建一个Animation(这里有点像插值器,根据controller中的0~1线性值,然后再根据当前插值器计算最终值)绑定这个Controller。

    最后创建一个动画类型的Widget,包含真正需要动画的Widget,传入上面创建的Animation。

// //使用了material的包,Flutter还提供一个cupertion包,用于表示谷歌和苹果两种界面风格
// //当然我们完全可以自定义自己的风格
// import 'package:flutter/material.dart';
// //引入了一个三方库 english_words ,需要在pubspec.yaml中的dependencies: 下面添加 english_words: ^3.1.0
// import 'package:english_words/english_words.dart';

// //main是dart的主方法,主方法很简单,调用系统方法runApp,运行一个App
// void main() => runApp(MyApp()); 

// //MyApp 就是我们的app了,它继承与StatelessWidget组件
// class MyApp extends StatelessWidget {
//   @override
//   Widget build(BuildContext context) { //build是组件类的基本方法,用于构建这个组件的显示样式
//     return MaterialApp( //一个MaterialApp widget
//       title: 'Welcome to Flutter',
//       home: RandomWords(), //主界面是一个RandomWords类型的组件
//       theme: new ThemeData(primaryColor: Colors.white), //修改app主题
//     );
//   }
// }

// /*
//  * 为了实现StatefulWidget,必须要有一个State类,用于帮助StatefulWidget build它的显示样式。
//  * 实际上,大多数的业务逻辑都是在State中进行处理的。
//  */
// class RandomWordsState extends State<RandomWords>{
//   final _suggestions = <WordPair>[];
//   final _biggerFont = const TextStyle(fontSize: 18.0);
//   final Set<WordPair> _saved = new Set<WordPair>(); //用于存储已经点赞的名字
//   /*
//    * 用于创建一个ListView组件
//    */
//   Widget _buildSuggestions(){
//     //listview的builder工厂方法
//     return ListView.builder(padding: const EdgeInsets.all(16.0),
//       itemBuilder: (context,i){  /* dart写法,类似于匿名类和lambda */
//         //如果是基数,返回分割线
//         if(i.isOdd) return Divider();
//         final index = i ~/ 2; //i除以2 并返回整数结果
//         if(index >= _suggestions.length){
//           _suggestions.addAll(generateWordPairs().take(10));
//         }
//         return _buildRow(_suggestions[index]);
//       }, 
//     );
//   }
//   /*
//    * 创建listview中的每一个item,每个item都是一个组件
//    */
//   Widget _buildRow(WordPair pair) {
//     final bool alreadySaved = _saved.contains(pair); //记录是否已经标记过
//     return ListTile(
//       title: Text(
//         pair.asPascalCase,
//         style: _biggerFont,
//       ),
//       trailing: Icon(alreadySaved? Icons.favorite : Icons.favorite_border,
//             color: alreadySaved ? Colors.red:null,),//创建一个星形icon
//       //icon点击事件
//       onTap: (){setState(() { //使用setState来更新list中item的状态,setState会先运行下面代码,然后再通知list更改item
//               if(alreadySaved){
//                 _saved.remove(pair);
//               }else{
//                 _saved.add(pair);
//               }
//             });},
//     );
//   }
//   /*
//    * 创建RandomWords组件的界面展示。 
//    */
//   @override
//   Widget build(BuildContext context) {
//     return Scaffold (
//       appBar: AppBar(
//         title: Text('Startup Name Generatorrrrrr'),
//         //为app bar增加左上按钮
//         actions: <Widget>[IconButton(icon: const Icon(Icons.list),onPressed: _pushSaved)],
//       ),
//       body: _buildSuggestions(),
//     );
//   }
  
//   /*
//    * appbar 左上按钮点击事件。使用Navigator 跳转到新页面
//    * Navigator 是Flutter中的路由管理器,通过push进行跳转,通过remove进行返回
//    */
//   void _pushSaved(){
//     //在路由管理器 Navigator中push一个新的路由
//     //这个路由是一个页面路由,通过builder构建这个页面
//     Navigator.of(context).push(MaterialPageRoute<void>(builder: (context){
//       //dart语法,每一个被标星的单词都会生成一个ListTile组件
//       final Iterable<ListTile> tiles = _saved.map(
//         (pair){
//           return new ListTile(
//             title: new Text(pair.asPascalCase,style:_biggerFont),
//           );
//         }
//       );
//       //在tiles组件之间插入分割线
//       final List<Widget> divided = ListTile.divideTiles(context: context,tiles: tiles).toList();
//       return new Scaffold(
//         appBar: new AppBar(title: const Text('Saved Suggestions'),),
//         body: new ListView(children: divided,),//生成listview
//       );
//     }));
//   }
// }

// /*
//  * 定义一个用于显示随机单词的组件,这个组件是一个StatefulWidget组件,所以我们必须要提供一个State<RandomWords>的类
//  * StatefulWidget 内部必须重写createState方法。他的widget的build方法交给这个State去写。
//  */
// class RandomWords extends StatefulWidget{
//   @override
//   State<StatefulWidget> createState() {
//     return RandomWordsState();
//   }
// }

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Fade Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyFadeTest(title: 'Fade Demo'),
    );
  }
}

class MyFadeTest extends StatefulWidget {
  MyFadeTest({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyFadeTest createState() => _MyFadeTest();
}

/**
 * 使用了 with,相当于多继承。继承了 TickerProvider
 */
class _MyFadeTest extends State<MyFadeTest> with TickerProviderStateMixin {
  AnimationController controller;
  CurvedAnimation curve;

/**
 * 这个组件被插入到widget tree的时候会被调用,必须要调用super,否则会报错。
 */
  @override
  void initState() {
    super.initState();
    //创建animation控制器,需要传入一个TickerProvider,该类已经使用with继承,所以传入this
    controller = AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
    //将controller绑定到一个动画上,打个比方的话该对象就相当于手表中的发条。
    curve = CurvedAnimation(parent: controller, curve: Curves.easeIn);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
          child: Container(
            //这是一个动画widget,用来做透明度改变的。相当于手表壳子
              child: FadeTransition(
                  opacity: curve, //给手表壳子装上发条
                  child: FlutterLogo( //真正需要做动画的组件,手表的指针。
                    size: 100.0,
                  )))),
      floatingActionButton: FloatingActionButton(
        tooltip: 'Fade',
        child: Icon(Icons.brush),
        onPressed: () {
          controller.forward();//调用controller,开始动画。
        },
      ),
    );
  }
}

Canvas

    在android中,我们可以直接在view中操作canvas在画布上画上各种想要的东西,在Flutter中我们同样可以直接使用画布,而且底层使用的渲染引擎是skia,这也是android使用的图形引擎。

    在Flutter中主要的类是CustomPaint和CustomPainter,前者表示一个widget组件,这个组件需要一个CustomPainter参数,这个参数中有一个回调方法paint,我们就可以在这个方法中通过参数canvas进行绘制。

    一下代码是一个签名版,还是比较有参考意义的,对于Canvas的使用说明非常具有代表性。

// //使用了material的包,Flutter还提供一个cupertion包,用于表示谷歌和苹果两种界面风格
// //当然我们完全可以自定义自己的风格
// import 'package:flutter/material.dart';
// //引入了一个三方库 english_words ,需要在pubspec.yaml中的dependencies: 下面添加 english_words: ^3.1.0
// import 'package:english_words/english_words.dart';

// //main是dart的主方法,主方法很简单,调用系统方法runApp,运行一个App
// void main() => runApp(MyApp()); 

// //MyApp 就是我们的app了,它继承与StatelessWidget组件
// class MyApp extends StatelessWidget {
//   @override
//   Widget build(BuildContext context) { //build是组件类的基本方法,用于构建这个组件的显示样式
//     return MaterialApp( //一个MaterialApp widget
//       title: 'Welcome to Flutter',
//       home: RandomWords(), //主界面是一个RandomWords类型的组件
//       theme: new ThemeData(primaryColor: Colors.white), //修改app主题
//     );
//   }
// }

// /*
//  * 为了实现StatefulWidget,必须要有一个State类,用于帮助StatefulWidget build它的显示样式。
//  * 实际上,大多数的业务逻辑都是在State中进行处理的。
//  */
// class RandomWordsState extends State<RandomWords>{
//   final _suggestions = <WordPair>[];
//   final _biggerFont = const TextStyle(fontSize: 18.0);
//   final Set<WordPair> _saved = new Set<WordPair>(); //用于存储已经点赞的名字
//   /*
//    * 用于创建一个ListView组件
//    */
//   Widget _buildSuggestions(){
//     //listview的builder工厂方法
//     return ListView.builder(padding: const EdgeInsets.all(16.0),
//       itemBuilder: (context,i){  /* dart写法,类似于匿名类和lambda */
//         //如果是基数,返回分割线
//         if(i.isOdd) return Divider();
//         final index = i ~/ 2; //i除以2 并返回整数结果
//         if(index >= _suggestions.length){
//           _suggestions.addAll(generateWordPairs().take(10));
//         }
//         return _buildRow(_suggestions[index]);
//       }, 
//     );
//   }
//   /*
//    * 创建listview中的每一个item,每个item都是一个组件
//    */
//   Widget _buildRow(WordPair pair) {
//     final bool alreadySaved = _saved.contains(pair); //记录是否已经标记过
//     return ListTile(
//       title: Text(
//         pair.asPascalCase,
//         style: _biggerFont,
//       ),
//       trailing: Icon(alreadySaved? Icons.favorite : Icons.favorite_border,
//             color: alreadySaved ? Colors.red:null,),//创建一个星形icon
//       //icon点击事件
//       onTap: (){setState(() { //使用setState来更新list中item的状态,setState会先运行下面代码,然后再通知list更改item
//               if(alreadySaved){
//                 _saved.remove(pair);
//               }else{
//                 _saved.add(pair);
//               }
//             });},
//     );
//   }
//   /*
//    * 创建RandomWords组件的界面展示。 
//    */
//   @override
//   Widget build(BuildContext context) {
//     return Scaffold (
//       appBar: AppBar(
//         title: Text('Startup Name Generatorrrrrr'),
//         //为app bar增加左上按钮
//         actions: <Widget>[IconButton(icon: const Icon(Icons.list),onPressed: _pushSaved)],
//       ),
//       body: _buildSuggestions(),
//     );
//   }
  
//   /*
//    * appbar 左上按钮点击事件。使用Navigator 跳转到新页面
//    * Navigator 是Flutter中的路由管理器,通过push进行跳转,通过remove进行返回
//    */
//   void _pushSaved(){
//     //在路由管理器 Navigator中push一个新的路由
//     //这个路由是一个页面路由,通过builder构建这个页面
//     Navigator.of(context).push(MaterialPageRoute<void>(builder: (context){
//       //dart语法,每一个被标星的单词都会生成一个ListTile组件
//       final Iterable<ListTile> tiles = _saved.map(
//         (pair){
//           return new ListTile(
//             title: new Text(pair.asPascalCase,style:_biggerFont),
//           );
//         }
//       );
//       //在tiles组件之间插入分割线
//       final List<Widget> divided = ListTile.divideTiles(context: context,tiles: tiles).toList();
//       return new Scaffold(
//         appBar: new AppBar(title: const Text('Saved Suggestions'),),
//         body: new ListView(children: divided,),//生成listview
//       );
//     }));
//   }
// }

// /*
//  * 定义一个用于显示随机单词的组件,这个组件是一个StatefulWidget组件,所以我们必须要提供一个State<RandomWords>的类
//  * StatefulWidget 内部必须重写createState方法。他的widget的build方法交给这个State去写。
//  */
// class RandomWords extends StatefulWidget{
//   @override
//   State<StatefulWidget> createState() {
//     return RandomWordsState();
//   }
// }

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  Widget build(BuildContext context) => Scaffold(body: Signature());
}

class Signature extends StatefulWidget {
  SignatureState createState() => SignatureState();
}

class SignatureState extends State<Signature> {
  List<Offset> _points = <Offset>[];
  Widget build(BuildContext context) {
    //读取手势,记录手势经过的点
    return GestureDetector(
      onPanUpdate: (DragUpdateDetails details) {
        setState(() {
          RenderBox referenceBox = context.findRenderObject();
          Offset localPosition =
          referenceBox.globalToLocal(details.globalPosition);
          _points = List.from(_points)..add(localPosition);
        });
      },
      onPanEnd: (DragEndDetails details) => _points.add(null),
      //包含一个画板组件
      child: CustomPaint(painter: SignaturePainter(_points), size: Size.infinite),
    );
  }
}
//画板组件内部的画布。
class SignaturePainter extends CustomPainter {
  SignaturePainter(this.points);
  final List<Offset> points;
  void paint(Canvas canvas, Size size) {
    var paint = Paint()
      ..color = Colors.black
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 5.0;
    for (int i = 0; i < points.length - 1; i++) {
      if (points[i] != null && points[i + 1] != null)
        canvas.drawLine(points[i], points[i + 1], paint);
    }
  }
  bool shouldRepaint(SignaturePainter other) => other.points != points;
}

自定义组件

    在android中我们可能会自定义view,但是在Flutter中我们并不会自定义view,而是将各种基础的view通过组合生成一个新的widget,这有点像android中继承viewgroup。

 

Intents

    在Flutter中使用Navigator和Rounte来进行页面跳转,但是实际上Flutter中还是可以使用android中的intents,通过这个插件。不过这里,我们还是集中于Flutter提供的Navigator。

    另外,我们需要知道Flutter中通过Navigator进行跳转实际上并不是跳转activity和fragment,所有的跳转场景和界面都在同一个activity中

    所谓的Route实际上是一个抽象的界面或者页面,而Navigator是一个控制和调度多个Route的组件(是的,他也是一个widget)。工作在Navigator中调用push或者pop进行Route页面之间的跳转。

    Flutter中有两种注册route的方式。

    直接navigate,这个在上一篇初体验中我们已经做过了这个尝试。

    在App中通过routes参数进行注册,然后之后直接调用。这就和在AndroidManifest中注册activity很类似了,代码如下    

void main() {
  runApp(MaterialApp(
    home: MyAppHome(), // becomes the route named '/'
    routes: <String, WidgetBuilder> {
      '/a': (BuildContext context) => MyPage(title: 'page A'),
      '/b': (BuildContext context) => MyPage(title: 'page B'),
      '/c': (BuildContext context) => MyPage(title: 'page C'),
    },
  ));
}
//这样调用
Navigator.of(context).pushNamed('/b');

 

在Flutter中解析外部传入的Intent

    实际上这并不算是Flutter中特别定制的功能,Flutter中解析外部传入的intent是需要native端接入的,通过MethodChannel通道进行本地访问。MethodChannel是用来在Flutter和native双端进行相互的方法调用的。本篇的主要目的是了解在Flutter中调用native中提供的方法。必须要知道的是,方法调用都是通过异步完成的。实际上Flutter调用native的步骤是这样的,通过Flutter向native端发送消息,native端接收到消息,运行相关api,然后再通过消息回传给flutter.

    以下是一个从flutter调用native的例子(在native端调用flutter理论上也是一样的,因为flutter中同样提供了setMethodCallHandler方法,但是感觉上同一个名字的MethodChannel 可能不能用来双向通信)。

package com.example.myapp;

import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {
  private String mShareText;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    GeneratedPluginRegistrant.registerWith(this);
    Intent intent = getIntent();
    String action = intent.getAction();
    String type = intent.getType();

    if (Intent.ACTION_SEND.equals(action) && type != null) {
      if ("text/plain".equals(type)) {
        handleSendText(intent); // Handle text being sent
      }
    }
    //创建一个MethodChannel,这个MethodChannel有一个名字,叫做 "app.channel.shared.data"
    MethodChannel methodChannel = new MethodChannel(getFlutterView(), "app.channel.shared.data");
    //使用 setMethodCallHandler 为这个MethodChannel指定一个方法处理函数。
    //同名不同实例methodChannel,或者同一个实例,多次调用该方法,以最后一次调用为准(实际上内部是一个map)
    methodChannel.setMethodCallHandler(
      new MethodCallHandler() {
        @Override
        public void onMethodCall(MethodCall call, MethodChannel.Result result) {
          if (call.method.contentEquals("getSharedText")) { //当接收到getShareText消息的时候
            result.success(sharedText);
            sharedText = null;
          }
        }
      });
  }


  void handleSendText(Intent intent) {
    sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
  }
}
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample Shared App Handler',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

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

class _SampleAppPageState extends State<SampleAppPage> {
  //创建一个名字为 app.channel.shared.data 的 MethodChannel
  static const platform = const MethodChannel('app.channel.shared.data');
  String dataShared = "No data";

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(body: Center(child: Text(dataShared)));
  }
  //通过 invokeMethod 在MethodChannel发送"getSharedText" 消息,并且等待结果
  //由于方法调用是异步的,所以需要使用dart语言提供的async和await功能
  getSharedText() async {
    var sharedData = await platform.invokeMethod("getSharedText");
    if (sharedData != null) {
      setState(() {
        dataShared = sharedData;
      });
    }
  }
}

    关于native通信这篇博客并没有继续深入,我们可以在这里找到一些资料,但是官方文档也没有列出太多信息,后续可能会专门写一篇关于native通信的博文。

 

如何代替startActivityForResult

    我们已经知道Navigator用于跳转了,这里还缺少一个Android中的关键点就是如何替代startActivityForResult这种有返回值的跳转?

    方法很简单,跳转端需要使用异步调用,然后等待返回值:

Map coordinates = await Navigator.of(context).pushNamed('/location');

    然后再'/location'这个Route返回是调用pop并且传入想要返回的参数。

Navigator.of(context).pop({"lat":43.821757,"long":-79.226392});

    需要注意,上面的跳转使用await异步调用,这意味着包含他的方法也需要添加async标记。

 

替代runOnUiThread()

    首先dart语言是一个单线程模型语言,也就是说dart的代码都是通过event loop运行在主线程中。Flutter中的event loop就是android中的 main looper,也就是我们的UI线程。

    单线程模型并不意味着所有的代码都是block形式运行,dart提供了async/await来实现类似线程的功能(类似于koltin中的协程,协程很好用的,c++20之后也会提供该官方支持,java竟然还没有!)。

loadData() async {
  String dataURL = "https://jsonplaceholder.typicode.com/posts";
  http.Response response = await http.get(dataURL);
  setState(() {
    widgets = json.decode(response.body);
  });
}

    比如上面在await等待http返回的时候,ui线程并没有被挂起。

Flutter多线程

    对于协程,或者上面提到的 async/await而言,适合做io操作,或者网络操作。如果真的需要做一些计算密集型操作,或者对于cpu高负载的操作的时候,使用携程并不合适(携程的运行高度依赖cpu调度机制,如果cpu负载高,主ui的被调度的时间片就会少,这个时候就出出现卡顿现象。)

    所以Flutter也提供了线程的概念 Isolate。Isolate可以利用cpu的多个内核来执行长时间运行或者计算密集型任务。

    Isolate是隔离的独立执行线程,不和主执行的内存共享任何堆栈。这意味着我们无法访问主线程中定义的任何变量(比如静态变量等),或者使用setState来更新UI.

import 'dart:convert';
import 'dart:isolate';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return _SampleAppPageState();
  }
}

class _SampleAppPageState extends State<SampleAppPage> {
  List widgets = [];

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Sample App"),
        ),
        body: getBody());
  }

  Widget getBody() {
    if (showLoadingDialog()) {
      return getProgressDialog();
    } else {
      return getListView();
    }
  }

  Widget getProgressDialog() {
    return Center(child: CircularProgressIndicator());
  }

  bool showLoadingDialog() {
    if (widgets.length == 0) {
      return true;
    }

    return false;
  }

  ListView getListView() => ListView.builder(
      itemCount: widgets.length,
      itemBuilder: (BuildContext context, int position) {
        return getRow(position);
      });

  Widget getRow(int i) {
    return Padding(
        padding: EdgeInsets.all(10.0),
        child: Text("Row ${widgets[i]["title"]}"));
  }

  loadData() async {
    //创建一个 ReceivePort 用于接收从Isolate 中传过来的数据
    ReceivePort receivePort = ReceivePort();
    //创建一个Isolate 运行dataLoader方法,输入一个receivePort.sendPort 参数
    //receivePort.sendPort 表示这个ReceivePort的数据填入工具SendPort,所有数据都从这个SendPort填入,然后再receivePort中读取
    await Isolate.spawn(dataLoader, receivePort.sendPort);

    // The 'dataLoader' isolate sends its SendPort as the first message
    //从 receivePort 中读取第一个数据,读取后这个port就关闭了,不能再使用。
    //dataLoader 传过来的第一个message 是一个SendPort,用于向它发送数据
    SendPort sendPort = await receivePort.first;
    //调用sendReceive 向sendPort 发送数据
    List msg = await sendReceive(
        sendPort, "https://jsonplaceholder.typicode.com/posts");

    setState(() {
      widgets = msg;
    });
  }

  // the entry point for the isolate
  static dataLoader(SendPort sendPort) async {
    // Open the ReceivePort for incoming messages.
    ReceivePort port = ReceivePort();
    //向通道发送第一个数据(自己的数据通道发送口)
    // Notify any other isolates what port this isolate listens to.
    sendPort.send(port.sendPort);
    //循环等待收到数据
    await for (var msg in port) {
      String data = msg[0];
      SendPort replyTo = msg[1];

      String dataURL = data;
      http.Response response = await http.get(dataURL);
      // Lots of JSON to parse
      replyTo.send(json.decode(response.body));
    }
  }
  //向sendport 发送数据,并且建立一个新的数据通道ReceivePort,准备接收返回值
  Future sendReceive(SendPort port, msg) {
    ReceivePort response = ReceivePort();
    port.send([msg, response.sendPort]);
    return response.first;
  }
}

    使用ReceivePort和SendPort(这两个一定成对出现,一个ReceivePort,对应一个SendPort,在SendPort中填入数据,在ReceivePort中读取数据,一般只会用在Isolate中。)

OKHttp替代品

     https://pub.dartlang.org/packages/http    

    上面地址是Flutter的http插件,可以用于普通的post和get请求。但是相比于okhttp,功能方面还是少了很多的。如果你觉得http插件无法满足你的需求的时候,可以尝试使用native的方法,然后通过channel调用native。

 

Flutter 资源的存储和使用

    首先在Android中我们有resources和assets的区别,但是在Flutter中,就没有这两个的区别了,所有的都统称为资源。另外,在Flutter对于资源的位置没有特别规定,我们可以放在自己喜欢的文件夹名字下面。

    在Android中不同屏幕密度在资源使用的时候系统会选择不同的文件夹,而在Flutter中使用的方式和ios更加相似,使用1.0x 2.0x这样的方式。

ldpi 0.75x
mdpi 1.0x
hdpi 1.5x
xhdpi 2.0x
xxhdpi 3.0x
xxxhdpi 4.0x

    上表是Android端和Flutter中的对比。

    假设,我们存在资源my_icon.png,将他放入以下文件夹中

images/my_icon.png       // Base: 1.0x image 被称为main assets
images/2.0x/my_icon.png  // 2.0x image
images/3.0x/my_icon.png  // 3.0x image

    然后再pubspec.yaml中注册资源

assets:
 - images/my_icon.png

    之后我们在Flutter中使用这个资源的时候,Flutter就会根据屏幕密度自动选用适合的图标。如果没有适合图标,就会选择其他图标,并进行一定比例的缩放,这和android的处理系统是一样。

    我们可以把它作为一个AssetImage

return AssetImage("images/my_icon.png");

    也可以直接在Image Widget中使用

@override
Widget build(BuildContext context) {
  return Image.asset("images/my_icon.png");
}

    注:这里只是粗略的说明,更加具体的资源使用,可以参考这篇官方文档

Flutter与Android的String资源系统

    很遗憾,目前Flutter并不支持类似android的文字资源系统,我们能做的就是将所有文字手动定义到一个类中,变成静态变量,然后再其他地方调用(是的,非常原始)。

    另外,Flutter也没有国际化的功能,不能根据语言自动选择资源版本(连资源都没有,选个屁啊)。

    但是有一个插件能够用于本地化 intl package    

 

 

 

 


695856371Web网页设计师②群 | 喜欢本站的朋友可以收藏本站,或者加入我们大家一起来交流技术!

0条评论

Loading...


发表评论

电子邮件地址不会被公开。 必填项已用*标注

自定义皮肤 主体内容背景
打开支付宝扫码付款购买视频教程
遇到问题联系客服QQ:419400980
注册梁钟霖个人博客