flutter各种功能实现方法及比较2(更新中......)

2,363 阅读9分钟

使用fluro配置路由

1、安装

fluro: ^1.5.1

2 、配置

首页的id设置成'/'即可,其它界面的就无所谓了

import 'package:fluro/fluro.dart';
import 'package:ui_test_no4/page_one.dart';
import 'package:ui_test_no4/page_two.dart';

class Routes {
  static Router router;//静态声明代表这是一个类成员不是对象成员
  static String page1 = '/';
  static String page2 = '/page2';

  static void configureRoutes(Router router) {
//两个参数,第一个是跳转路由的id,第二个是一个handler,用于接收参数、创建函数
    router.define(
        page1, handler: Handler(handlerFunc: (context, params) => Page1()));
    router.define(
        page2, handler: Handler(handlerFunc: (context, params) {
      var message = params['message']?.first;//取出传参
      return Page2(message);
    }));
    Routes.router = router;
  }
}

3、设置主函数初始化

import 'package:flutter/material.dart';
import 'package:fluro/fluro.dart';
import 'package:ui_test_no4/routes.dart';

///启动
void main() {
  ///初始化并配置路由
  final router = new Router();
  Routes.configureRoutes(router);
  runApp(
      MaterialApp(
          onGenerateRoute: Routes.router.generator
      )
  );
}

4、界面跳转,A跳转到B界面

///跳转到第二个界面
  intentTo(BuildContext context) {
    ///字符串编码
    var json = jsonEncode(Utf8Encoder().convert('来自第一个界面'));
    Routes.router.navigateTo(
        context, '${Routes.page2}?message=$json',//跳转路径,多个参数类似url?a=1&b=2传值
        transition: TransitionType.inFromRight//过场效果
    ).then((result) {//回传值
      if (result != null) {
        message = result;
      }
    });
  }

5、B返回A界面传值

 RaisedButton(onPressed: () => Navigator.pop(context, '来自第二个界面'),
              child: Text('返回上一个界面'))

6 为什么这么写路由?

     为了路由同一管理,之后修改,例如想加上用户登陆时长限制,直接Routes里面修改就好

使用scoped_model

什么是scoped_model

Scoped_model是一个dart第三方库,提供了让您能够轻松地将数据模型从父Widget传递到它的后代的功能。此外,它还会在模型更新时重新渲染使用该模型的所有子项。,而且无论该widget是不是有状态的都可以进行更新,再一次build

实现原理

Scoped model使用了观察者模式,将数据模型放在父代,后代通过找到父代的model进行数据渲染,最后数据改变时将数据传回,父代再通知所有用到了该model的子代去更新状态。

用途

可以进行全局公共数据共享,除了使用scoped_model也可以使用shared_preferences、或者新建一个类将共享数据设置成static类的成员。

  • 使用sp的缺点是效率低,需要读写数据库,同时不可以在状态改变后主动发起更新
  • 使用static类成员的缺点也是不可以主动发起类更新
  • 使用scoped_model的速度上更快,并且可以监听变换后自动发起setState变化

具体使用

1 添加依赖

scoped_model: ^0.3.0

2 、创建Model

import 'package:scoped_model/scoped_model.dart';

class CountModel extends Model{
  int _count = 0;
  get count => _count;
  
  void increment(){
    _count++;
    notifyListeners();
  }
}

3、将model放在顶层

//创建顶层状态
  CountModel countModel = CountModel();

  @override
  Widget build(BuildContext context) {
    return ScopedModel<CountModel>(
      model: countModel,
      child: new MaterialApp(
        home: TopScreen(),
      ),
    );
  }

4、在子界面中获取Model

@override
  Widget build(BuildContext context) {
    return ScopedModelDescendant<CountModel>(
      builder: (context,child,model){
        return Scaffold(
          body: Center(
            child: Text(
              model.count.toString(),
              style: TextStyle(fontSize: 48.0),
            ),
          ),
        );
      },
    );
  }

5 一个简单的demo可以直接运行

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


class CountModel extends Model{
  int _count = 0;
  get count => _count;

  void increment()
  {
    _count++;
    notifyListeners();
  }
}

void main()
{
  CountModel countModel = CountModel();
  runApp(new ScopedModel(model: countModel, child: MaterialApp(
    home: PageA(),
  )));
}

class PageA extends StatefulWidget{
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return new _PageA();
  }
}

class _PageA extends State<PageA>
{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return ScopedModelDescendant<CountModel>(
      builder: (context,child,model)
      {
        return Scaffold(
          appBar: AppBar(
            title: Text(model.count.toString()),
          ),
          body: RaisedButton(
            onPressed: (){model.increment();},
          ),
        );
      },
    );
  }
}

dart优雅避空

语法糖 ?.

它的意思是左边如果为空返回 null,否则返回右边的值。

A?.B
如果 A 等于 null,那么 A?.B 为 null如果 A 不等于 null,那么 A?.B 等价于 A.B

语法糖 ??

它的意思是左边如果为空返回右边的值,否则不处理。

A??B如果 A 等于 null,那么 A??B 为 B如果 A 不等于 null,那么 A??B 为 A

BoxShadow

下面给出每个参数的具体含义

/** 阴影效果
    const BoxShadow({
    Color color = const Color(0xFF000000),//阴影默认颜色,不能与父容器同时设置color
    Offset offset = Offset.zero,//延伸的阴影,向右下偏移的距离
    double blurRadius = 0.0,//延伸距离,会有模糊效果
    this.spreadRadius = 0.0 //延伸距离,不会有模糊效果
    })
 */

举例

boxShadow: [BoxShadow(color: Color(0xFF908F90),offset: Offset(2,2),spreadRadius: 5)],


BottomSheet的使用

参数解释


基本使用

showModalBottomSheet(
      backgroundColor: Colors.transparent,
      context: curContext,
        builder:  (BuildContext context){
        return Container();
      });

Image_picker

安装

image_picker: ^0.6.2+1

配置

只有苹果的需要配置


我的配置如下

<key>NSCameraUsageDescription</key>
<string>it is used to write one question and answer one question</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>it is used to write one question and answer one question</string>
<key>NSMicrophoneUsageDescription</key>
<string>it is used to record a video for publish in the third part</string>

使用

均返回路径

/*拍照*/
_takePhoto() async {
  var image = await ImagePicker.pickImage(source: ImageSource.camera);
  print('拍照返回:' + image.toString());
}
 
/*相册*/
_openGallery() async {
  var image = await ImagePicker.pickImage(source: ImageSource.gallery);
  print('相册返回:' + image.toString());
}
 
/*选取视频*/
_getVideo() async {
  var image = await ImagePicker.pickVideo(source: ImageSource.gallery);
  print('选取视频:' + image.toString());
}
/*拍摄视频*/
_takeVideo() async {
  var image = await ImagePicker.pickVideo(source: ImageSource.camera);
  print('拍摄视频:' + image.toString());
}

flutter阿里云oss对象存储使用dio上传

工具类文件夹,uploadToOss.dart

import 'package:dio/dio.dart';
import 'dart:io';
import 'dart:convert';
import 'package:crypto/crypto.dart';


//使用oss进行文件上传

class OssUpLoad
{
  //验证文本域
  static String policyText =
      '{"expiration": "2020-01-01T12:00:00.000Z","conditions": [["content-length-range", 0, 1048576000]]}';

//进行utf8编码
  static List<int> policyText_utf8 = utf8.encode(policyText);

//进行base64编码
  static String policy_base64 = base64.encode(policyText_utf8);

  //再次进行utf8编码
  static List<int> policy = utf8.encode(policy_base64);
  static String accesskey= '你的accessKey';
  static String accessId = "你的accessId";

  //进行utf8 编码
  static List<int> key2 = utf8.encode(accesskey);

  //通过hmac,使用sha1进行加密
  static List<int> signature_pre  = new Hmac(sha1, key2).convert(policy).bytes;

  //最后一步,将上述所得进行base64 编码
  String signature = base64.encode(signature_pre);

  void uploadFile(File file,String fileName )async
  {
    //要上传的文件,此处从参数传入
    //fileName是oss对象的文件名
    File imageFile = file;

    //dio的请求配置,这一步非常重要!
    BaseOptions options = new BaseOptions();
    options.responseType = ResponseType.plain;

    //创建dio对象
    Dio dio = new Dio(options);


//创建一个formdata,作为dio的参数
    FormData data = new FormData.from({
      'Filename': fileName,//文件名
      'key' : fileName,//key是("文件夹名(对应于oss服务中的文件夹)/" + fileName)
      'policy': policy_base64,
      'OSSAccessKeyId': accessId,//accessId认证身份
      'success_action_status' : '200', //让服务端返回200,不然,默认会返回204
      'signature': signature,
      'file': new UploadFileInfo(imageFile, "imageFileName")
    });

    try {
      //域名修改为自己的,注意使用https协议
      Response response = await dio.post("https://xxxxxxxxx.aliyuncs.com",data: data);
      print(response.headers);
      print(response.data);
    } on DioError catch(e) {
      print(e.message);
      print("00000000000000000000000000000");
      print(e.response.data);
      print(e.response.headers);
      print(e.response.request);
    }
  }
}

三种App目录之间的区别与flutter的获取方式

  • 临时目录:可以使用 getTemporaryDirectory() 来获取临时目录; 系统可随时清除的临时目录(缓存)。在iOS上,这对应于NSTemporaryDirectory() 返回的值。在Android上,这是getCacheDir()返回的值。 
  • 文档目录:可以使用getApplicationDocumentsDirectory()来获取应用程序的文档目录,该目录用于存储只有自己可以访问的文件。只有当应用程序被卸载时,系统才会清除该目录。在iOS上,这对应于NSDocumentDirectory。在Android上,这是AppData目录。
  •  外部存储目录:可以使用getExternalStorageDirectory()来获取外部存储目录,如SD卡;由于iOS不支持外部目录,所以在iOS下调用该方法会抛出UnsupportedError异常,而在Android下结果是android SDK中getExternalStorageDirectory的返回值。 

word文档实现预览功能

   给出链接如下https://blog.csdn.net/qxianx/article/details/81317894

   我们尝试过XDOC云服务,链接https://blog.csdn.net/sqlhub/article/details/84885334

   可以直接使用webview展示,但是发现展示效果不好,也不可以放大缩小。

   最后,采用的方案是后端转成pdf,前端flutter使用flutter_full_pdf_viewer预览

手机选取pdf doc docx文件

一开始使用的是评分99的file_picker插件,但是经常会闪退,后面经过比较使用了flutter_file_picker插件

里面需要填写的参数有

allowedFileExtensions:选择文件后插件根据这个参数进行文件类型检查

allowedUtiTypes:苹果上Uti类型,插件会根据这个参数决定使得iphone手机文件哪些是可以选择的(enabled),其余(disabled),参考链接

allowedMimeTypes:安卓上Mime类型,插件会根据这个参数决定android手机文件哪些是可以选择的(enabled),其余(disabled),参考链接

invalidFileNamesSymbols:如果文件名中含有这些字符,插件会使用'_'替换

我们的需求是选择pdf\doc\docx类型文件,参数设置为

allowedFileExtensions: ['doc','docx','pdf'],
allowedUtiTypes: ['com.adobe.pdf','com.microsoft.word.doc','org.openxmlformats.wordprocessingml.document'],
allowedMimeTypes: ['application/pdf','application/msword','application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
invalidFileNameSymbols: ['/'],

上传文件使用通知显示进度条

功能需求是上传视频到阿里oss存储,但是由于视频本身较大,让用户等待上传过程很不友好,需要放在后台运行,并且使用通知显示上传进度

需要解决的第一个问题:如何获取上传进度?

上传oss使用的是dio框架,具体代码参考本文前部分

经查资料,dio可以实时获取上传进度,使用onSendProgress代码为

Response response = await dio.post("https://beiweijia-bucket-1.oss-cn-beijing.aliyuncs.com",data: data,onSendProgress: (int send,int total)
{

});

需要解决的第二个问题:如果在通知中显示上传进度

使用插件flutter_local_notifications,但是无法更新某个通知的body,后面发现无需去更改,只要使用同一个通知id,一直发拥有新的body的通知即可,新的通知会覆盖使用相同id的旧的通知。

需要解决的第三个问题:进度条通知取消震动

因为进度条的通知会不断发送更新,如果每发一次震动一次很不友好

更改方法为增加enableVibration:false,代码如下

var androidPlatformChannelSpecifics = AndroidNotificationDetails(
    '10000', 'showUploadProgree', 'showUploadProgress',
    importance: Importance.Low, priority: Priority.Low, ticker: 'ticker',
    enableVibration: false,
);

对于android8.0+,还需要重新指定新的channel id,因为如果channel id不变,发送通知是否震动的设置会和该channel第一发送通知设置相同并之后保持不变。

需要解决的第四个问题:通知显示多行文字

发现插件flutter_local_notification的通知只会显示一行文字,无法满足需求,解决方法是添加

style:AndroidNotificationStyle.BigText属性,代码如下

    '10000', 'showUploadProgree', 'showUploadProgress',
    importance: Importance.Low, priority: Priority.Low, ticker: 'ticker',
    enableVibration: false,
    style: AndroidNotificationStyle.BigText
);


使用推送服务

在线推送比较好搞,极光用起来也方便,参考链接

但是离线的极光需要vip,最低版本的一年三万,计划使用mob推送

android平台app后台保活

medium上发现大神写的教程,链接

github对应的demo链接