首页
留言
关于
友链
更多
足迹
Search
1
SpringMVC+Spring+MyBatis整合完整版Web实例(附数据)
2,914 阅读
2
关于在Flutter实现Google地图的方法
1,888 阅读
3
druid报异常 “sql injection violation, part alway true condition not allow”的解决方案
1,381 阅读
4
git删除remote
1,347 阅读
5
MyBatis的TooManyResultsException异常的解决办法
1,151 阅读
发现
技术
生活
户外
登录
Search
标签搜索
Git
JavaScript
Flutter
Oracle
Git学习
Java
MySQL
SQL Server
秦岭户外
IntelliJ IDEA
Spring Boot
Flutter 2.0
对称加密算法
Google地图
Maven
ES6
linux
Tomcat
Redis
Spring
Bai Keyang
累计撰写
288
篇文章
累计收到
277
条评论
首页
栏目
发现
技术
生活
户外
页面
留言
关于
友链
足迹
搜索到
10
篇与
Flutter
的结果
2023-02-22
[Flutter]Dart 运算符 / 与 ~/ 的区别
Dart 运算符 / 与 ~/ 的区别:运算符/:除,返回值为double类型;运算符 ~/:整除,返回值为int类型。int a1 = 6; int a2 = 4; print('除: ' + (a1/a2).toString()); print('整除:' + (a1~/a2).toString());
2023年02月22日
60 阅读
0 评论
0 点赞
2023-02-22
在Flutter中final和const的区别
首先在说这个问题之前,可以先看一段代码:main() { // 同时赋值已经确定好的值const和final是没有区别的 const con = '2020-09-01'; final nal = '2020-09-01'; // 赋予不确定的值const和final就不一样了 // const 在编译的时候值都必须是确定的 const _date = new DateTime.now(); //错误的, 会报错 // final是在运行的时候才赋值 final _date1 = new DateTime.now(); //正确的, 不会报错 }使用final修饰的变量必须进行初始化,一旦被赋值以后,不能被再次赋值,但是这个初始化的值在编译时是不确定的,只有在运行时,才能确定其值例如Flutter官方教程中,有这么一行代码:final wordPair = WordPair.random();const定义时,需要是个明确的值,修饰的变量它会在编译器以至于应用整个生命周期内都是不可变的常量,在内存中也只会创建一次,之后的每次调用都会复用第一次创建的对象。例如:每次调用都会复用第一次创建的对象。例如:main() { foo(); } foo() { const _city = "西安"; var list = const[1,2,3,4,5]; var point = const User("XiaoMing", 17); const num = 1+2; const time = DateTime.now();//错误用法 这样会报错 } class User { final String userName; final int age; const User(this.userName, this.age); }const导致的不可变特性是可以传递的。如果有一个final修饰的成员变量,这个成员变量包含了一个集合,那么这个集合仍然是可变的, 但是如果包含的是const修饰的集合,那么集合内所有东西都是递归不可变的。例如:main() { final list0 = [1,2,3]; list0[0] = 4; print(list0.toString());//打印[4,2,3] var list1 = const[1,2,3]; list1[0] = 4;//这里会报错 }扩展:static修饰符也能用来修饰变量, 被它修饰的成员变量,方法是属于类级别的, 并不属于对象。static变量直到运行期使用时才会进行实例化。
2023年02月22日
79 阅读
0 评论
0 点赞
2023-02-15
flutter配置Android状态栏透明
开发Flutter项目中,在Android平台下让状态栏透明,如下配置:import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; void main () { runApp(const MyApp()); /// 配置如下: /// Android状态栏透明 if (Platform.isAndroid) { SystemUiOverlayStyle systemUiOverlayStyle = const SystemUiOverlayStyle(statusBarColor: Colors.transparent); SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle); } } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override MyAppState createState() => MyAppState(); } class MyAppState extends State<MyApp> with WidgetsBindingObserver { @override void initState() { super.initState(); } @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: Scaffold( backgroundColor: Colors.blueAccent, body: SafeArea( child: Column( children: [ Text("测试页面"), ], ), ), ), ); } }
2023年02月15日
88 阅读
0 评论
0 点赞
2022-08-12
Flutter2.x 将Http请求结果转为Dart
在Flutter 2.x版本中将Http返回的结果转Dart稍微和前几个版本有些许差异,具体演示说明如下。Ps:在这里我用一个网上的开源api来示例请求接口地址:https://v1.hitokoto.cn/接口返回的Json对象如下:{ "id":3931, "uuid":"afc68395-6e1c-429c-b322-f1b7f4d43eba", "hitokoto":"嘿!活着是件很好的事", "type":"e", "from":"原创", "from_who":null, "creator":"Great old ones", "creator_uid":2073, "reviewer":0, "commit_from":"web", "created_at":"1538200036", "length":10 }json对应的类对象如下:class OneSentence { int id; String? hitokoto; String uuid; String? type; String? from; String? fromWho; String? creator; int? creatorUid; int reviewer; String? commitFrom; String? createdAt; int length; OneSentence( this.id, this.hitokoto, this.uuid, this.type, this.from, this.fromWho, this.creator, this.creatorUid, this.reviewer, this.commitFrom, this.createdAt, this.length); factory OneSentence.fromJson(Map<String, dynamic> json) { return OneSentence( json['id'], json['hitokoto'], json['uuid'], json['type'], json['from'], json['from_who'], json['creator'], json['creator_uid'], json['reviewer'], json['commit_from'], json['created_at'], json['length']); } Map<String, dynamic> toJson() { final Map<String, dynamic> data = new Map<String, dynamic>(); data['id'] = this.id; data['hitokoto'] = this.hitokoto; data['uuid'] = this.uuid; data['type'] = this.type; data['from'] = this.from; data['from_who'] = this.fromWho; data['creator'] = this.creator; data['creator_uid'] = this.creatorUid; data['reviewer'] = this.reviewer; data['commit_from'] = this.commitFrom; data['created_at'] = this.createdAt; data['length'] = this.length; return data; } } 请求http获取api结果:import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_demo/bean/OneSentence.dart'; class HttpPage extends StatefulWidget { const HttpPage({Key? key}) : super(key: key); @override _HttpPageState createState() => _HttpPageState(); } class _HttpPageState extends State<HttpPage> { String _text = ""; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Http请求"), leading: GestureDetector( onTap: () { Navigator.pop(context); }, child: Icon(Icons.arrow_back), ), ), body: Container( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded(child: ListView( children: [ Text(_text), ElevatedButton( onPressed: () => httpGet('https://v1.hitokoto.cn/', getOneSentence), child: Text('一言API')), ], )) ], ), ), ); } // 解析并转换 OneSentence getOneSentence(String res) { String _result = res; JsonCodec j = new JsonCodec(); OneSentence ons = OneSentence.fromJson(j.decode(_result)); setState(() { this._text = j.encode(ons.toJson()); }); return ons; } // 请求API并获取请求结果 void httpGet(String url, Function call) { HttpClient _httpClient = new HttpClient(); String _result = ""; _httpClient.getUrl(Uri.parse(url)) .then((HttpClientRequest request) => request.close()) .then((HttpClientResponse response) { if(response.statusCode == 200) { response.transform(utf8.decoder).join().then((value) { _result = value; call(_result); } ); } else { print('error'); _result = 'error'; call(_result); } } ); } }
2022年08月12日
141 阅读
0 评论
0 点赞
2021-12-15
使用WebView出现net::ERR_CLEARTEXT_NOT_PERMITTED
开发应用时(在Android11.0【Android API 30】环境下)使用Webview插件加载页面出现如下图情况: 用模拟器尝试了下其他几个版本,发现8.0版本以下的都是可以正常显示网页的,于是上网查了下原因是说从Android9.0(也就是API 28)开始,默认情况下禁用明文支持。所以url无法在webview中加载。解决办法就是在AndroidManifest中application节点内添加android:usesCleartextTraffic="true",如下:<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.baikeyang.blog"> <application android:label="blog" android:icon="@mipmap/ic_launcher" android:usesCleartextTraffic="true"> ...... </application> </manifest> 添加完成该项配置后,重启运行项目,网页就会正常显示,如下: 如果添加完配置刷新没有效果,clean一下项目,然后重新启动运行就可以了。
2021年12月15日
449 阅读
0 评论
0 点赞
2021-12-06
Flutter 2.0空安全之手动关闭/开启
Flutter 2默认启用了空安全,所以通过Flutter 2创建的项目是已经开启了空安全的检查的,另外,也可以可以通过下面命令来查看你的Flutter SDK版本:flutter doctor那么,如何手动开启和关闭空区安全的?environment: sdk: ">=2.12.0 <3.0.0" //sdk >=2.12.0表示开启空安全检查提示:一旦项目开启了空安全检查,那么你的代码包括项目所依赖的三方插件必须是要支持空安全的否则是无法正常编译的。开启空安全之后,然后运行下项目你会看到很多的报错,然后定位到报错的文件,对项目进行空安全适配。如果想关闭空安全检查,可以将SDK的支持范围调整到2.12.0以下即可,如:environment: sdk: ">=2.7.0 <3.0.0"
2021年12月06日
315 阅读
0 评论
0 点赞
2021-12-05
Flutter 2.0空安全之最小必备知识
Flutter 2.0空安全之最小必备知识从Flutter 2开始,Flutter便在配置中默认启用了空安全,通过将空检查合并到类型系统中,可以在开发过程中捕获这些错误,从而防止再生产环境导致的崩溃。{dotted startColor="#ff6c6c" endColor="#1989fa"/}什么是空安全时至今日,空安全已经是一个屡见不鲜的话题,目前像主流的编程语言Kotlin、Swift、Rust 等都对空安全有自己的支持。Dart从2.12版本开始支持了空安全,通过空安全开发人员可以有效避免null错误崩溃。空安全性可以说是Dart语言的重要补充,它通过区分可空类型和非可空类型进一步增强了类型系统。引入空安全的好处可以将原本运行时的空值引用错误将变为编辑时的分析错误;增强程序的健壮性,有效避免由Null而导致的崩溃;跟随Dart和Flutter的发展趋势,为程序的后续迭代不留坑;空安全最小必备知识空安全的原则引入空安全前后Dart类型系统的变化可空(?)类型的使用延迟初始化(late)的使用空值断言操作符(!)的使用空安全的原则Dart 的空安全支持基于以下三条核心原则:默认不可空:除非您将变量显式声明为可空,否则它一定是非空的类型;渐进迁移:您可以自由地选择何时进行迁移,多少代码会进行迁移;完全可靠:Dart 的空安全是非常可靠的,意味着编译期间包含了很多优化,如果类型系统推断出某个变量不为空,那么它 永远 不为空。当您将整个项目和其依赖完全迁移至空安全后,您会享有健全性带来的所有优势——更少的 BUG、更小的二进制文件以及更快的执行速度。引入空安全前后Dart类型系统的变化在引入空安全前Dart的类型系统是这样的: 这意味着在之前,所有的类型都可以为Null,也就是Nul类型被看作是所有类型的子类。在引入空安全之后: 可以看出,最大的变化是将Null类型独立出来了, 这意味着Null不在是其它类型的子类型,所以对于一个非Null类型的变量传递一个Null值时会报类型转换错误 。提示:在使用了空安全的Flutter或Dart项目中你会经常看到 ?.、!、late 的大量应用,那么他们分别是什么又改如何使用呢?请看下文的分析可空(?)类型的使用我们可以通过将 ? 跟在类型的后面来表示它后面的变量或参数可接受Null:class CommonModel { String? firstName; //可空的成员变量 int getNameLen(String? lastName /*可空的参数*/) { int firstLen = firstName?.length ?? 0; int lastLen = lastName?.length ?? 0; return firstLen + lastLen; } }对于可空的变量或参数在使用的时候需要通过Dart 的避空运算符 ?. 来进行访问,否则会抛出编译错误。当程序启用空安全后,类的成员变量默认是不可空的,所以对于一个非空的成员变量需要指定其初始化方式class CommonModel { List names=[];//定义时初始化 final List colors;//在构造方法中初始化 late List urls;//延时初始化 CommonModel(this.colors); ...延迟初始化(late)的使用对于无法在定义时进行初始化,并且又想避免使用 ?. ,那么延迟初始化可以帮到你。通过 late 修饰的变量,可以让开发者选择初始化的时机,并且在使用这个变量时可以不用 ?. 。 late List urls;//延时初始化 setUrls(List urls){ this.urls=urls; } int getUrlLen(){ return urls.length; }延时初始化虽然能为我们编码带来一定便利,但如果使用不当会带来空异常的问题,所以在使用的时候一定保证赋值和访问的顺序,切莫颠倒。扩展: 延迟初始化(late)使用范式在Flutter中State的 initState 方法中初始化的一些变量是比较适合使用late来进行延时初始化的,因为在Widget生命周期中 initState 方法是最先执行的,所以它里面初始化的变量通过 late 修饰后既能保障使用时的便利,又能防止空异常,下面就来看下具体的用法:class _SpeakPageState extends State<SpeakPage> with SingleTickerProviderStateMixin { String speakTips = '长按说话'; String speakResult = ''; late Animation<double> animation; late AnimationController controller; @override void initState() { controller = AnimationController( super.initState(); vsync: this, duration: Duration(milliseconds: 1000)); animation = CurvedAnimation(parent: controller, curve: Curves.easeIn) ..addStatusListener((status) { if (status == AnimationStatus.completed) { controller.reverse(); } else if (status == AnimationStatus.dismissed) { controller.forward(); } }); } ...空值断言操作符(!)的使用当我们排除变量或参数的可空的可能后,可以通过 ! 来告诉编译器这个可空的变量或参数不可空,这对我们进行方法传参或将可空参数传递给一个不可空的入参时特别有用: Widget get _listView { return ListView( children: <Widget>[ _banner, Padding( padding: EdgeInsets.fromLTRB(7, 4, 7, 4), child: LocalNav(localNavList: localNavList), ), if (gridNavModel != null) Padding( padding: EdgeInsets.fromLTRB(7, 0, 7, 4), child: GridNav(gridNavModel: gridNavModel!)), Padding( padding: EdgeInsets.fromLTRB(7, 0, 7, 4), child: SubNav(subNavList: subNavList)), if (salesBoxModel != null) Padding( padding: EdgeInsets.fromLTRB(7, 0, 7, 4), child: SalesBox(salesBox: salesBoxModel!)), ], ); }上述代码是根据gridNavModel与salesBoxModel模块数据是否为空时动态创建的列表,在确保变量不为空的情况下使用了空值断言操作符!。除此之外, ! 还有一个常见的用处:bool isEmptyList(Object object) { if (object is! List) return false; return object.isEmpty; }用在这里表示取反,上述代码等价于:bool isEmptyList(Object object) { if (!(object is List)) return false; return object.isEmpty; }
2021年12月05日
170 阅读
0 评论
0 点赞
2021-11-28
Flutter 音频播放
在Flutter中目前使用比较多的音频插件就是audioplayers啦。 插件地址:audioplayers 在项目的pubspec.yaml中引入audioplayers插件:dependencies: flutter: sdk: flutter ...... # 引入audioplayers插件:当前官方最新版本为0.20.1 audioplayers: ^0.20.1初始化AudioPlayer :AudioPlayer player = new AudioPlayer ();开始播放 :开始播放音频player.play('http://image.bkybk.com/bg.m4a');暂停播放 :音频播放对象在播放状态才能暂停,在其它状态调用此方法无任何作用player.pause();继续播放 :音频播放对象在暂停状态才能恢复播放,在其它状态调用此方法无任何作用。player.resume();停止播放 :停止播放音频,音频播放对象在播放或暂停状态才能停止播放,在其它状态调用此方法无任何作用。停止播放后如果需要继续播放,则需调用play方法重新开始播放。// 方法1 player.stop(); // 方法2 player.setReleaseMode(ReleaseMode.STOP); player.release();指定播放位置 :跳到指定位置播放音频// 从音频的 96 秒开始播放 player.seek(new Duration(milliseconds: 96000))设置音量 :设置播放音量的大小// 设置音量 30 player.setVolume(0.3)上面是关于AudioPlayer在项目中常用的声明、方法和配置,下面是关于AudioPlayer插件的具体使用:// 引入包 import 'package:audioplayers/audioplayers.dart'; import 'package:flutter/material.dart'; class AudioPage extends StatefulWidget { @override _AudioPageState createState() => _AudioPageState(); } class _AudioPageState extends State<AudioPage> { String totalTime = ''; String goTime = ''; String playState = ''; double _sliderValue = 30; bool isPlay = false; // 音频列表 List _playList = [ 'http://bkybk.com/static/data/bg.mp3', 'http://image.bkybk.com/bg.m4a' ]; // 初始化AudioPlayer AudioPlayer advancedPlayer = AudioPlayer(); @override void initState() { // TODO: implement initState print('------------initState---------------'); super.initState(); advancedPlayer.onDurationChanged.listen((Duration d) { setState(() { totalTime = d.toString(); playState = '正在播放'; }); }); advancedPlayer.onPlayerCompletion.listen((event) { setState(() { playState = '播放已完成'; }); }); advancedPlayer.onAudioPositionChanged.listen((p) async { // p参数可以获取当前进度,也是可以调整的,比如p.inMilliseconds setState(() { goTime = p.toString(); }); }); // 代码中引发意外错误时调用此函数。 advancedPlayer.onPlayerError.listen((msg) { print('audioPlayer error : $msg'); // setState(() { // playerState = PlayerState.stopped; // duration = Duration(seconds: 0); // position = Duration(seconds: 0); // }); }); } /// 当依赖的State对象改变时会调用 /// a 在第一次构建widget时,在initState()之后立即调用此方法 /// b 如果StatefulWidget依赖于InheritedWidget,那么当 当前的State所依赖InheritedWidget中的变量改变时会再次调用它 /// 拓展:InheritedWidget可以高效的将数据在Widget树中向下传递、共享; @override void didChangeDependencies() { print('--------------didChangeDependencies--------------'); super.didChangeDependencies(); } @override void dispose() { // TODO: implement dispose super.dispose(); advancedPlayer.stop(); advancedPlayer.dispose(); } @override Widget build(BuildContext context) { String? localFilePath; String? localAudioCacheURI; return new Scaffold( appBar: AppBar( title: Text('Audio 组件演示'), leading: GestureDetector( onTap: () { Navigator.pop(context); }, child: Icon(Icons.arrow_back), ), ), body: Container( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( child: ListView( children: [ ElevatedButton( onPressed: () => play(advancedPlayer, 0), child: Text('播放音乐1')), ElevatedButton( onPressed: () => play(advancedPlayer, 1), child: Text('播放音乐2')), ElevatedButton( onPressed: () => pause(advancedPlayer), child: Text('暂停')), ElevatedButton( onPressed: () => stop(advancedPlayer), child: Text('停止')), ElevatedButton( onPressed: () => skip(advancedPlayer, 96300), child: Text('从96秒播放')), ElevatedButton( onPressed: () => resume(advancedPlayer), child: Text('继续播放')), Text('音频总长:$totalTime'), Text('当前播放:$goTime'), Text('播放状态:$playState'), Text('调整音量:'), Slider( min: 0, max: 100, value: _sliderValue, onChanged: (v) { setState(() { _sliderValue = v; }); setVolume(advancedPlayer, v); }, ), ElevatedButton( onPressed: () { countFunction(advancedPlayer); }, child: Text('其他常用(控制台输出)')) ], ), ) ], ), ), ); } countFunction(AudioPlayer audioPlayer) { setState(() { _count++; }); // 获取当前播放状态 PlayerState ps = audioPlayer.state; print(ps); // 判断目标是否为本地资源 bool isLocal = audioPlayer.isLocalUrl("http://image.bkybk.com/bg.m4a"); print(isLocal); // 设置音量 audioPlayer.setVolume(0.23); } /** * 设置音量 */ setVolume(AudioPlayer audioPlayer, double value) { audioPlayer.setVolume(value / 100); } /** * 开始播放音频 */ play(AudioPlayer audioPlayer, int index) async { if (isPlay) { // 当音乐正在播放时,先停止当前播放; int stopResult = await audioPlayer.stop(); if (stopResult == 1) { isPlay = false; int playResult = await audioPlayer.play(_playList[index], volume: 0.3); if (playResult == 1) { // success print('play success'); setState(() { isPlay = true; playState = '正在加载资源......'; }); } else { print('play failed'); } } } else { int playResult = await audioPlayer.play(_playList[index], volume: 0.3); if (playResult == 1) { // success print('play success'); setState(() { isPlay = true; playState = '正在加载资源......'; }); } else { print('play failed'); } } } /** * 暂停播放音频 */ pause(AudioPlayer audioPlayer) async { // 音频播放对象在播放状态才能暂停,在其它状态调用此方法无任何作用 int result = await audioPlayer.pause(); if (result == 1) { // success print('pause success'); setState(() { isPlay = false; playState = '播放暂停'; }); } else { print('pause failed'); } } /** * 停止播放音频 */ stop(AudioPlayer audioPlayer) async { // 停止播放音频,音频播放对象在播放或暂停状态才能停止播放,在其它状态调用此方法无任何作用。 // 停止播放后如果需要继续播放,则需调用play方法重新开始播放。 // 停止播放的方法一: int stopResult = await audioPlayer.stop(); // 停止播放的方法二: // audioPlayer.setReleaseMode(ReleaseMode.STOP); // int stopResult = await audioPlayer.release(); if (stopResult == 1) { setState(() { isPlay = false; playState = '播放停止'; }); } else { print('release failed'); } super.deactivate(); } /** * 跳到指定位置播放音频 */ skip(AudioPlayer audioPlayer, int startMilliseconds) async { // 跳到指定位置播放音频,音频播放对象在播放或暂停状态才能跳到指定播放音频,在其它状态调用此方法无任何作用。 int result = await audioPlayer.seek(new Duration(milliseconds: startMilliseconds)); if (result == 1) { print('go to success'); // await audioPlayer.resume(); } else { print('go to failed'); } } /** * 恢复播放音频 */ resume(AudioPlayer audioPlayer) async { // 音频播放对象在暂停状态才能恢复播放,在其它状态调用此方法无任何作用。 int result = await audioPlayer.resume(); if (result == 1) { print('resume success'); // await audioPlayer.resume(); } else { print('resume failed'); } } } 代码的实际运行效果:
2021年11月28日
485 阅读
0 评论
0 点赞
2021-09-20
Flutter页面生命周期理解与分析
在Flutter的页面生命周期,着重分析StatefulWidget这个组件。因为无状态的组件StatelessWidget只有createElement 与 build两个生命周期方法,相对比较好理解,这里就不在赘述了。StatefulWidget的生命周期方法按照时期顺序不同,可以分为三组,分别为:初始化时期、更新时期、销毁时期1. 初始化时期的方法 createState:当我们构建一个新的StatefulWidget时,这个会立即调用;并且这个方法必须被覆盖 initState:这是创建Widget时调用的除构造方法外的第一个方法; 类似于Android的:onCreate() 与 IOS 的 viewDidLoad();在这个方法中通常会做一些初始化,比如channel的初始化,监听器的初始化等2. 更新时期的方法 didChangeDependencies:当依赖的State对象改变时会调用。在第一次构建widget时,在initState()之后立即调用此方法;如果StatefulWidget依赖于InheritedWidget,那么当当前的State所依赖InheritedWidget中的变量改变时会再次调用它拓展:InheritedWidget可以高效的将数据在Widget树中向下传递、共享;build:每个State都需要实现的一个方法,这是一个必须实现的方法,在这里实现你要呈现的页面内容,它会在didChangeDependencies()之后立即调用。另外当调用setState后也会再次调用该方法。didUpdateWidget:这是一个不常用到的生命周期方法,当父组件需要重新绘制时才会调用;该方法会携带一个oldWidget参数,可以将与当前的Widget进行对比以便执行一些额外的逻辑,如if(oldWidget.xxx != widget.xxx) {...}3. 销毁时期的方法 deactivate:很少使用,在组件被移除的时候调用dispose之前调用 dispose:通常,组件被销毁时调用。通常在方法中执行一些资源的释放工作,比如监听器的卸载、channel的销毁等下面是关于Flutter生命周期的一个具体的代码理解,我在每个方法上面也做了注释,方便理解学习:import 'package:flutter/material.dart'; /// Flutter Widget的生命周期重点讲解StatefulWidget的生命周期 /// 因为无状态的widget StatelessWidget只有createElement 与 build两个生命周期方法 /// StatefulWidget的生命周期方法按照时期不同可以分为三组: /// 1、初始化时期 /// createState、initState /// 2、更新时期 /// didChangeDependencies、build、didUpdateWidget /// 3、销毁期 /// deactivate、dispose class WidgetLifecycle extends StatefulWidget { // 当我们构建一个新的StatefulWidget时,这个会立即调用 // 并且这个方法必须被覆盖 @override _WidgetLifecycleState createState() => _WidgetLifecycleState(); } class _WidgetLifecycleState extends State<WidgetLifecycle> { int _count = 0; /// 这是创建Widget时调用的除构造方法外的第一个方法 /// 类似于Android的:onCreate() 与 IOS 的 viewDidLoad(); /// 在这个方法中通常会做一些初始化,比如channel的初始化,监听器的初始化等 @override void initState() { // TODO: implement initState print('------------initState---------------'); super.initState(); } /// 当依赖的State对象改变时会调用 /// a 在第一次构建widget时,在initState()之后立即调用此方法 /// b 如果StatefulWidget依赖于InheritedWidget,那么当 当前的State所依赖InheritedWidget中的变量改变时会再次调用它 /// 拓展:InheritedWidget可以高效的将数据在Widget树中向下传递、共享; @override void didChangeDependencies() { print('--------------didChangeDependencies--------------'); super.didChangeDependencies(); } /// 每个State都需要实现的一个方法 /// 这是一个必须实现的方法,在这里实现你要呈现的页面内容 /// 它会在didChangeDependencies()之后立即调用 /// 另外当调用setState后也会再次调用该方法 @override Widget build(BuildContext context) { print('--------------build-------------'); return Scaffold( appBar: AppBar( title: Text('Flutter页面生命周期'), leading: GestureDetector( onTap: () { Navigator.pop(context); }, child: Icon(Icons.arrow_back), ), ), body: Center( child: Column( children: [ ElevatedButton( onPressed: () { setState(() { _count++; }); }, child: Text( '点我', style: TextStyle(fontSize: 26), )), Text('$_count') ], ), ), ); } /// 这是一个不常用到的生命周期方法,当父组件需要重新绘制时才会调用; /// 该方法会携带一个oldWidget参数,可以将与当前的Widget进行对比以便执行一些额外的逻辑,如 /// if(oldWidget.xxx != widget.xxx) {...} @override void didUpdateWidget(covariant WidgetLifecycle oldWidget) { print('--------------didUpdateWidget--------------'); super.didUpdateWidget(oldWidget); } /// 很少使用,在组件被移除的时候调用dispose之前调用 @override void deactivate() { print('-------------deactivate--------------'); super.deactivate(); } /// 通常,组件被销毁时调用 /// 通常在方法中执行一些资源的释放工作,比如监听器的卸载、channel的销毁等 @override void dispose() { print('------------dispose-------------'); super.dispose(); } }
2021年09月20日
477 阅读
0 评论
0 点赞
2019-12-03
关于在Flutter实现Google地图的方法
由于项目需要开发一款国际化APP(多语种)。公司是一家国内的车联网企业,车辆地图监控为项目的基础功能模块。在国内项目用的是高德,高德国际化支持太差劲,在国际化中直接被PASS掉了。为了能达到全球化的位置监控展示,尝试过MapBox,最后考虑种种还是使用Google地图。话说,Google在国内的现状目前是啥样我想大家也都清楚,在9月份的某一天突然发现国内的Google地图网页也被重定向google.cn。看来这是真的打算撤退的节奏啊。偶然间在网上看到说 用电脑访问 http://www.google.cn//maps 可以访问,真的神奇了。虽然Google国内地图被关闭了,但是好在API可以正常使用。项目是开发一款基于IOS/Android的移动端软件,在调研前期选择了Flutter去开发,因为看到Flutter对IOS/Android的支持还是不错的,一套代码编译为两个平台的软件。当然,Google Map 官方也当然提供的有Flutter 的插件 google_maps_flutter,开箱即用很是方便。直接上手。基于google_maps_flutter的开发准备工作:1、首先需要Google地图的API Key2、手机需要Google 服务全家桶(不懂请自行度娘喔~ )添加依赖:dependencies: google_maps_flutter: ^0.5.21+12插件的使用方法在【https://pub.dev/packages/google_maps_flutter】中也介绍的很详细。直接copy代码走起,如下~import 'dart:async'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Google Maps Demo', home: MapSample(), ); } } class MapSample extends StatefulWidget { @override State<MapSample> createState() => MapSampleState(); } class MapSampleState extends State<MapSample> { Completer<GoogleMapController> _controller = Completer(); static final CameraPosition _kGooglePlex = CameraPosition( target: LatLng(37.42796133580664, -122.085749655962), zoom: 14.4746, ); static final CameraPosition _kLake = CameraPosition( bearing: 192.8334901395799, target: LatLng(37.43296265331129, -122.08832357078792), tilt: 59.440717697143555, zoom: 19.151926040649414); @override Widget build(BuildContext context) { return new Scaffold( body: GoogleMap( mapType: MapType.hybrid, initialCameraPosition: _kGooglePlex, onMapCreated: (GoogleMapController controller) { _controller.complete(controller); }, ), floatingActionButton: FloatingActionButton.extended( onPressed: _goToTheLake, label: Text('To the lake!'), icon: Icon(Icons.directions_boat), ), ); } Future<void> _goToTheLake() async { final GoogleMapController controller = await _controller.future; controller.animateCamera(CameraUpdate.newCameraPosition(_kLake)); } }在运行代码之前需要配置API Key :Android:在android/app/src/main/AndroidManifest.xml中配置:<application> ... <meta-data android:name="com.google.android.geo.API_KEY" android:value="您的Key"/> </application>IOS :在Info.plist中配置如下:<dict> ...... <key>io.flutter.embedded_views_preview</key> <true/> </dict>两种配置Key的方法:方法一:在ios/Runner/AppDelegate.m中配置Key:#include "AppDelegate.h" #include "GeneratedPluginRegistrant.h" #import "GoogleMaps/GoogleMaps.h" @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [GMSServices provideAPIKey:@"您的Key"]; [GeneratedPluginRegistrant registerWithRegistry:self]; return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end方法二:在ios/Runner/AppDelegate.swift中配置Keyimport UIKit import Flutter import GoogleMaps @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? ) -> Bool { GMSServices.provideAPIKey("您的Key") GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } }OK,开始运行。 嗯~???? 设备不支持?我的模拟器没有安装Google 服务全家桶。目前国内的Android手机都已经将Google内置服务去掉了。然而Google地图在Android却需要Google服务的支持。这就有些尴尬了。总不可能以后凡是使用该APP的用户都强迫安装这个吧?客户首先就不买单。于是开始第二种尝试,用HTML实现地图并嵌套在APP中。方案实现思路:使用webview嵌套一个页面,在页面中展示Google地图。通过页面JS回调Flutter里面的方法;Flutter通过webview_flutter插件提供的evaluateJavascript函数调用页面上的方法操作页面地图(比如地图缩放、地图绘制、地图查找)准备工作:1、首先需要Google地图的API Key2、依赖dependencies: webview_flutter: ^0.3.15+1代码:class Lnglat { double lng; double lat; Lnglat(this.lng, this.lat); @override String toString() { return 'Lnglat{lng: $lng, lat: $lat}'; } Map<String, dynamic> toJson() { Map<String, dynamic> _m = new Map<String, dynamic>(); _m["lng"] = this.lng; _m["lat"] = this.lat; return _m; } factory Lnglat.fromJson(Map<String, dynamic> json) { return Lnglat( json['lng'], json['lat']); } }class InfoWindow { String content; ///信息的最大宽度,与内容的宽度无关。仅当在调用open之前设置此值时,才会考虑此值。 ///若要在更改内容时更改最大宽度,请调用close、setOptions,然后调用open。 double maxWidth; ///所有InfoWindows都按其zIndex的顺序显示在地图上,较高的值显示在较低值的InfoWindows前面。 ///默认情况下,InfoWindows根据纬度显示,较低纬度的InfoWindows出现在较高纬度的InfoWindows前面。 ///信息窗口始终显示在标记前面。 int zIndex; InfoWindow({this.content, this.maxWidth, this.zIndex}); @override String toString() { return 'InfoWindow{content: $content, maxWidth: $maxWidth, zIndex: $zIndex}'; } Map<String, dynamic> toJson() { Map<String, dynamic> _m = new Map<String, dynamic>(); _m["content"] = this.content; _m["maxWidth"] = this.maxWidth; _m["zIndex"] = this.zIndex; return _m; } factory InfoWindow.fromJson(Map<String, dynamic> json) { return InfoWindow( content: json['content'], maxWidth: json['maxWidth'], zIndex: json['zIndex']); } }import 'InfoWindow.dart'; import 'Lnglat.dart'; class Marker { /// 点标记在地图上显示的位置,默认为地图中心点 Lnglat position; /// 需在点标记中显示的图标 String icon; /// 鼠标滑过点标记时的文字提示,不设置则鼠标滑过点标无文字提示 String title; /// 添加文本标注 String label; /// 点标记是否可点击(默认为true) bool clickable; /// 设置点标记是否可拖拽移动(默认为false) bool crossOnDrag; /// Marker 点击事件 final click; /// Marker点击显示窗体信息 InfoWindow clickInfoWindow; Marker(this.position, { this.title, this.label, this.icon, this.clickable = true, this.crossOnDrag = false, this.clickInfoWindow, this.click}); @override String toString() { return 'Marker{position: $position, icon: $icon, title: $title, label: $label, clickable: $clickable, crossOnDrag: $crossOnDrag, ' 'clickInfoWindow: $clickInfoWindow }'; } Map<String, dynamic> toJson() { Map<String, dynamic> _m = new Map<String, dynamic>(); _m["position"] = this.position.toJson(); _m["icon"] = this.icon; _m["title"] = this.title; _m["label"] = this.label; _m["clickable"] = this.clickable; _m["crossOnDrag"] = this.crossOnDrag; _m["clickInfoWindow"] = (null != this.clickInfoWindow) ? this.clickInfoWindow.toJson():null; return _m; } factory Marker.fromJson(Map<String, dynamic> json) { return Marker( json['position'], title: json['title'], label: json['label'], icon: json['icon'], clickable: json['clickable'], crossOnDrag: json['crossOnDrag']); } }import 'Marker.dart'; import 'Lnglat.dart'; class Line { List<Lnglat> path; // 线路颜色 String color; // 线路宽 double width; // 是否允许播放(默认false) bool isAllowPlay; // 线路端点开启 bool endPointEnable; // 起点(预留参数;当endPointEnable 为true时,该参数生效) Marker start; // 终点(预留参数;当endPointEnable 为true时,该参数生效) Marker end; // 是否自动播放(默认false;当isAllowPlay 为true时,该参数生效) bool isMoveAlong; Line(this.path, {this.color, this.width, this.isAllowPlay, this.start, this.end, this.isMoveAlong = false, this.endPointEnable = false,}); @override String toString() { return 'Line{path: $path, isMoveAlong: $isMoveAlong}'; } Map<String, dynamic> toJson() { Map<String, dynamic> _m = new Map<String, dynamic>(); List<dynamic> _path = []; if(null != this.path && this.path.length > 0){ for(int i = 0;i < this.path.length;i++){ _path.add(this.path[i].toJson()); } } _m["path"] = _path; _m["color"] = this.color; _m["width"] = this.width; _m["isAllowPlay"] = this.isAllowPlay; _m["start"] = this.start.toJson(); _m["end"] = this.end.toJson(); _m["isMoveAlong"] = this.isMoveAlong; _m["endPointEnable"] = this.endPointEnable; return _m; } factory Line.fromJson(Map<String, dynamic> json) { return Line( json['path'], color: json['color'], width: json['width'], isAllowPlay: json['isAllowPlay'], start: json['start'], end: json['end'], isMoveAlong: json['isMoveAlong'], endPointEnable: json["endPointEnable"]); } }enum MapLocale { /// 地图支持语言种类详见:https://developers.google.cn/maps/faq#languagesupport /// English (EN) United States en_us, /// Chinese (ZH) Simplified zh_cn, }import 'dart:ui'; import 'gmap/Line.dart'; import 'gmap/Lnglat.dart'; import 'gmap/MapLocale.dart'; import 'gmap/Marker.dart'; class MapOption { // 地图语言(默认英文) MapLocale locale; // 地图默认中心点 Lnglat center; // 显示控件(默认 开启) bool controlEnable; // 允许拖拽地图(默认 开启) bool dragEnable; // 双击缩放地图(默认 开启) bool doubleClickZoom; // 地图点 List<Marker> markers; // 地图线 List<Line> lines; // 地图缩放级别 num zoom; // 地图加载完成回调 final complete; // Marker点击回调 final markerClick; MapOption({this.locale = MapLocale.en_us, this.center, this.markers, this.lines, this.zoom, this.complete, this.markerClick, this.controlEnable = true, this.dragEnable = true, this.doubleClickZoom = true,}); @override String toString() { return 'GMapOption{center: $center, markers: $markers, lines: $lines, zoom: $zoom, controlEnable: $controlEnable, dragEnable:$dragEnable, doubleClickZoom:$doubleClickZoom }'; } Map<String, dynamic> toJson () { Map<String, dynamic> _m = new Map<String, dynamic>(); _m["center"] = this.center.toJson(); List<dynamic> _markers = []; if(null!= this.markers && this.markers.length > 0){ for(int i = 0;i < this.markers.length;i++){ _markers.add(this.markers[i].toJson()); } } _m["markers"] = _markers; List<dynamic> _lines = []; if(null != this.lines && this.lines.length > 0) { for(int i = 0;i < this.lines.length;i++){ _lines.add(this.lines[i].toJson()); } } _m["lines"] = _lines; _m["zoom"] = this.zoom; _m["dragEnable"] = this.dragEnable; _m["controlEnable"] = this.controlEnable; _m['doubleClickZoom'] = this.doubleClickZoom; return _m; } factory MapOption.fromJson(Map<String, dynamic> json) { return MapOption( center: json['center'], markers: json['markers'], lines: json['lines'], zoom: json['zoom'], dragEnable: json['dragEnable'], controlEnable: json['controlEnable'], doubleClickZoom: json['doubleClickZoom'] ); } }import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'Config.dart'; import 'MapOption.dart'; import 'gmap/Line.dart'; import 'gmap/MapLocale.dart'; import 'gmap/Marker.dart'; import 'package:webview_flutter/webview_flutter.dart'; class MapView extends StatefulWidget { MapOption mapOption; String cityName; AppBar appBar; WebViewController viewController; MapView({this.mapOption, this.cityName, this.appBar}); @override _MapViewState createState() => _MapViewState(); Object exceJavascript(String excute){ if(null != viewController){ viewController.evaluateJavascript('callbackDemo("Demo回调Demo回调Demo回调Demo回调Demo回调");').then((result) { //print('您可以在此处处理JS结果1'); return '您可以在此处处理JS结果1'; }); } } } class _MapViewState extends State<MapView> { bool _loading = true; WebView webViewx; WebViewController _vc; dynamic overlayEncode(dynamic item) { if(item is Marker) { return item.toJson(); }else if(item is Line) { return item.toJson(); } return item; } ///js与flutter交互 JavascriptChannel _alertJavascriptChannel(BuildContext context) { return JavascriptChannel( name: 'Toast',//invoke要和网页协商一致 onMessageReceived: (JavascriptMessage message) { if(null == widget.viewController){ setState(() { widget.viewController = _vc; }); } var msg = json.decode(message.message); //print(msg['isFirstComplete']); if(null != msg['isFirstComplete'] && true == msg['isFirstComplete']){ // 地图首次加载 //print('地图首次加载完成'); if(null != widget.mapOption.markers && widget.mapOption.markers.length > 0){/// 回调页面绘制点的方法 var _markers = json.encode(widget.mapOption.markers, toEncodable: overlayEncode); widget.viewController.evaluateJavascript('setMarkers(\''+_markers+'\');').then((result) { //print('您可以在此处处理绘制Marker的业务逻辑'); if(null != widget.mapOption.zoom && widget.mapOption.zoom > 0 && widget.mapOption.zoom < 22){ widget.viewController.evaluateJavascript('setZoom(\''+widget.mapOption.zoom.toString()+'\');').then((result) { //print('您可以在此处处理地图缩放的业务逻辑'); }); } }); }else if(null != widget.mapOption.zoom && widget.mapOption.zoom > 0 && widget.mapOption.zoom < 22){ widget.viewController.evaluateJavascript('setZoom(\''+widget.mapOption.zoom.toString()+'\');').then((result) { //print('您可以在此处处理地图缩放的业务逻辑'); }); } widget.mapOption.complete(widget.viewController, msg); }else{ // 业务回调 if(null != msg['MARKER_CLICK'] && true == msg['MARKER_CLICK']){// Marker Click 回调 //print('Marker Click 回调'); widget.mapOption.markerClick(widget.viewController, msg); } } }); } String getLanguage(MapLocale _mapLocale) { String _language = ""; switch (_mapLocale) { case MapLocale.en_us: _language = "en"; break; case MapLocale.zh_cn: _language = "zh"; break; default: _language = "en"; } return _language; } @override void initState() { String _serverUrl = "http://map.bkybk.com/gmap_flutter.html?"; var _UrlParam = ""; if(null != widget.mapOption.center){ _UrlParam += "center=" + widget.mapOption.center.lng.toString() + "," + widget.mapOption.center.lat.toString(); } if(null != widget.mapOption.controlEnable && false == widget.mapOption.controlEnable){ if(0 < _UrlParam.length){ _UrlParam += "&"; } _UrlParam += "controlEnable=0"; } if(null != widget.mapOption.dragEnable && false == widget.mapOption.dragEnable){ if(0 < _UrlParam.length){ _UrlParam += "&"; } _UrlParam += "dragEnable=0"; } if(null != widget.mapOption.doubleClickZoom && false == widget.mapOption.doubleClickZoom){ if(0 < _UrlParam.length){ _UrlParam += "&"; } _UrlParam += "doubleClickZoom=0"; } if(Platform.isIOS){ if(0 < _UrlParam.length){ _UrlParam += "&"; } _UrlParam += "platform=IOS"; } else { if(0 < _UrlParam.length) { _UrlParam += "&"; } _UrlParam += "platform=Android"; } /// 地图语言 if(0 < _UrlParam.length){ _UrlParam += "&"; } String _language = getLanguage(widget.mapOption.locale); _UrlParam += "language="+_language; _serverUrl = _serverUrl + _UrlParam; super.initState(); //使用插件 FaiWebViewWidget webViewx = WebView( initialUrl: _serverUrl,///初始化url javascriptMode: JavascriptMode.unrestricted,///JS执行模式), onWebViewCreated: (WebViewController webViewController) {///在WebView创建完成后调用,只会被调用一次 //setState(() { _vc = webViewController; widget.viewController = webViewController; //}); //widget._viewController = webViewController; }, onPageFinished: (String url) {///页面加载完成回调 setState(() { _loading = false; }); // TODO 去掉遮罩(遮罩层未实现) }, javascriptChannels: <JavascriptChannel>[///JS和Flutter通信的Channel; _alertJavascriptChannel(context), ].toSet(), navigationDelegate: (NavigationRequest request) {//路由委托(可以通过在此处拦截url实现JS调用Flutter部分); ///通过拦截url来实现js与flutter交互 if (request.url.startsWith('js://webview')) { // Fluttertoast.showToast(msg:'JS调用了Flutter By navigationDelegate'); print('blocking navigation to $request}'); return NavigationDecision.prevent;///阻止路由替换,不能跳转,因为这是js交互给我们发送的消息 } return NavigationDecision.navigate;///允许路由替换 }, ); } @override void dispose() { super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: widget.appBar!=null?widget.appBar:null, body: buildRefreshHexWidget(), ); } Widget buildRefreshHexWidget() { return RefreshIndicator( //下拉刷新触发方法 onRefresh: () async{ print('refresh'); }, //设置webViewWidget child:webViewx, ); } }import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'common/Config.dart'; import 'common/MapOption.dart'; import 'common/MapView.dart'; import 'common/gmap/Lnglat.dart'; import 'common/gmap/MapLocale.dart'; void main(List<String> args) { App(); } class App extends StatefulWidget { @override AppState createState() => AppState(); } class AppState extends State<App> { static GlobalKey<NavigatorState> navigatorKey = GlobalKey(); @override void initState() { super.initState(); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('Google Maps examples')), backgroundColor: Colors.grey.shade200, body: MapView( appBar: null, mapOption: MapOption( locale: MapLocale.zh_cn, controlEnable: false, dragEnable: true, doubleClickZoom: true, center: Lnglat(116.396663, 39.912321), complete: (_viewController, value) { // 地图加载完成后回调 }, markerClick: (_viewController, res) { // 地图上marker点击回调 }), cityName: "北京", ), ), ); } }以上核心代码是我进行过整理和一定的简单封装,主要是方便使用。开始运行。 搞定!!!嵌套的页面地址是http://map.bkybk.com/gmap_flutter.html,关于这个html的源码我这里就贴了,感兴趣的可以自己去把页面的代码复制出来喔~
2019年12月03日
1,888 阅读
4 评论
0 点赞