huixiang_app
1 year ago
14 changed files with 1524 additions and 168 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,367 @@ |
|||||||
|
import 'dart:convert'; |
||||||
|
|
||||||
|
import 'package:dio/dio.dart'; |
||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
||||||
|
|
||||||
|
import '../../retrofit/retrofit_api.dart'; |
||||||
|
import '../../utils/captcha_util.dart'; |
||||||
|
import '../../utils/widget_util.dart'; |
||||||
|
|
||||||
|
typedef VoidSuccessCallback = dynamic Function(String v); |
||||||
|
|
||||||
|
class ClickWordCaptcha extends StatefulWidget { |
||||||
|
final VoidSuccessCallback onSuccess; //文字点击后验证成功回调 |
||||||
|
final VoidCallback onFail; //文字点击完成后验证失败回调 |
||||||
|
|
||||||
|
const ClickWordCaptcha({Key key, this.onSuccess, this.onFail}) |
||||||
|
: super(key: key); |
||||||
|
|
||||||
|
@override |
||||||
|
_ClickWordCaptchaState createState() => _ClickWordCaptchaState(); |
||||||
|
} |
||||||
|
|
||||||
|
class _ClickWordCaptchaState extends State<ClickWordCaptcha> { |
||||||
|
ClickWordCaptchaState _clickWordCaptchaState = ClickWordCaptchaState.none; |
||||||
|
List<Offset> _tapOffsetList = []; |
||||||
|
ClickWordCaptchaModel _clickWordCaptchaModel = ClickWordCaptchaModel(); |
||||||
|
|
||||||
|
Color titleColor = Colors.black; |
||||||
|
Color borderColor = Color(0xffdddddd); |
||||||
|
String bottomTitle = ""; |
||||||
|
Size baseSize = Size(310.0, 155.0); |
||||||
|
|
||||||
|
//改变底部样式及字段 |
||||||
|
_changeResultState() { |
||||||
|
switch (_clickWordCaptchaState) { |
||||||
|
case ClickWordCaptchaState.normal: |
||||||
|
titleColor = Colors.black; |
||||||
|
borderColor = Color(0xffdddddd); |
||||||
|
break; |
||||||
|
case ClickWordCaptchaState.success: |
||||||
|
_tapOffsetList = []; |
||||||
|
titleColor = Colors.green; |
||||||
|
borderColor = Colors.green; |
||||||
|
bottomTitle = "验证成功"; |
||||||
|
break; |
||||||
|
case ClickWordCaptchaState.fail: |
||||||
|
_tapOffsetList = []; |
||||||
|
titleColor = Colors.red; |
||||||
|
borderColor = Colors.red; |
||||||
|
bottomTitle = "验证失败"; |
||||||
|
break; |
||||||
|
default: |
||||||
|
titleColor = Colors.black; |
||||||
|
borderColor = Color(0xffdddddd); |
||||||
|
bottomTitle = "数据加载中……"; |
||||||
|
break; |
||||||
|
} |
||||||
|
setState(() {}); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void initState() { |
||||||
|
super.initState(); |
||||||
|
_loadCaptcha(); |
||||||
|
} |
||||||
|
|
||||||
|
//加载验证码 |
||||||
|
_loadCaptcha() async { |
||||||
|
_tapOffsetList = []; |
||||||
|
_clickWordCaptchaState = ClickWordCaptchaState.none; |
||||||
|
_changeResultState(); |
||||||
|
ApiService apiIpService = ApiService(Dio(), context: context); |
||||||
|
ClickWordCaptchaModel baseData = await apiIpService.captchaGet({"captchaType": "clickWord"}).catchError((onError) {}); |
||||||
|
if (baseData == null) { |
||||||
|
_clickWordCaptchaModel.secretKey = ""; |
||||||
|
bottomTitle = "加载失败,请刷新"; |
||||||
|
_clickWordCaptchaState = ClickWordCaptchaState.normal; |
||||||
|
_changeResultState(); |
||||||
|
return; |
||||||
|
} |
||||||
|
else { |
||||||
|
_clickWordCaptchaModel = baseData; |
||||||
|
var baseR = await WidgetUtil.getImageWH( |
||||||
|
image: Image.memory( |
||||||
|
Base64Decoder().convert(_clickWordCaptchaModel.imgStr))); |
||||||
|
baseSize = baseR.size; |
||||||
|
|
||||||
|
bottomTitle = "请依次点击【${_clickWordCaptchaModel.wordStr}】"; |
||||||
|
} |
||||||
|
|
||||||
|
_clickWordCaptchaState = ClickWordCaptchaState.normal; |
||||||
|
_changeResultState(); |
||||||
|
} |
||||||
|
|
||||||
|
//校验验证码 |
||||||
|
_checkCaptcha() async { |
||||||
|
List<Map<String, dynamic>> mousePos = []; |
||||||
|
_tapOffsetList.map((size) { |
||||||
|
mousePos |
||||||
|
.add({"x": size.dx.roundToDouble(), "y": size.dy.roundToDouble()}); |
||||||
|
}).toList(); |
||||||
|
var pointStr = json.encode(mousePos); |
||||||
|
|
||||||
|
var cryptedStr = pointStr; |
||||||
|
|
||||||
|
// secretKey 不为空 进行as加密 |
||||||
|
if (!CaptchaUtil.isEmpty(_clickWordCaptchaModel.secretKey)) { |
||||||
|
cryptedStr = CaptchaUtil.aesEncode( |
||||||
|
key: _clickWordCaptchaModel.secretKey, content: pointStr); |
||||||
|
// var dcrypt = CaptchaUtil.aesDecode( |
||||||
|
// key: _clickWordCaptchaModel.secretKey, content: cryptedStr); |
||||||
|
} |
||||||
|
|
||||||
|
// Map _map = json.decode(dcrypt); |
||||||
|
ApiService apiIpService = ApiService(Dio(), context: context); |
||||||
|
bool baseData = await apiIpService.captchaCheck({ |
||||||
|
"pointJson": cryptedStr, |
||||||
|
"captchaType": "clickWord", |
||||||
|
"token": _clickWordCaptchaModel.token |
||||||
|
}).catchError((onError) {}); |
||||||
|
if (baseData) { |
||||||
|
_checkFail(); |
||||||
|
return; |
||||||
|
} |
||||||
|
//如果不加密 将 token 和 坐标序列化 通过 --- 链接成字符串 |
||||||
|
var captchaVerification = "${_clickWordCaptchaModel.token}---$pointStr"; |
||||||
|
if (!CaptchaUtil.isEmpty(_clickWordCaptchaModel.secretKey)) { |
||||||
|
//如果加密 将 token 和 坐标序列化 通过 --- 链接成字符串 进行加密 加密密钥为 _clickWordCaptchaModel.secretKey |
||||||
|
captchaVerification = CaptchaUtil.aesEncode( |
||||||
|
key: _clickWordCaptchaModel.secretKey, |
||||||
|
content: captchaVerification); |
||||||
|
} |
||||||
|
_checkSuccess(captchaVerification); |
||||||
|
} |
||||||
|
|
||||||
|
//校验失败 |
||||||
|
_checkFail() async { |
||||||
|
_clickWordCaptchaState = ClickWordCaptchaState.fail; |
||||||
|
_changeResultState(); |
||||||
|
|
||||||
|
await Future.delayed(Duration(milliseconds: 1000)); |
||||||
|
_loadCaptcha(); |
||||||
|
//回调 |
||||||
|
if (widget.onFail != null) { |
||||||
|
widget.onFail(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//校验成功 |
||||||
|
_checkSuccess(String pointJson) async { |
||||||
|
_clickWordCaptchaState = ClickWordCaptchaState.success; |
||||||
|
_changeResultState(); |
||||||
|
|
||||||
|
await Future.delayed(Duration(milliseconds: 1000)); |
||||||
|
|
||||||
|
var cryptedStr = CaptchaUtil.aesEncode(key: 'BGxdEUOZkXka4HSj', content: pointJson); |
||||||
|
|
||||||
|
print(cryptedStr); |
||||||
|
//回调 pointJson 是经过es加密之后的信息 |
||||||
|
if (widget.onSuccess != null) { |
||||||
|
widget.onSuccess(cryptedStr); |
||||||
|
} |
||||||
|
//关闭 |
||||||
|
Navigator.pop(context); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
var data = MediaQuery.of(context); |
||||||
|
var dialogWidth = 0.9 * data.size.width; |
||||||
|
var isRatioCross = false; |
||||||
|
if (dialogWidth < 320.0) { |
||||||
|
dialogWidth = data.size.width; |
||||||
|
isRatioCross = true; |
||||||
|
} |
||||||
|
return Scaffold( |
||||||
|
backgroundColor: Colors.transparent, |
||||||
|
body: Center( |
||||||
|
child: Container( |
||||||
|
width: dialogWidth, |
||||||
|
height: 320.h, |
||||||
|
color: Colors.white, |
||||||
|
child: Column( |
||||||
|
children: <Widget>[ |
||||||
|
_topConttainer(), |
||||||
|
_captchaContainer(), |
||||||
|
_bottomContainer() |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
//图片验证码 |
||||||
|
_captchaContainer() { |
||||||
|
List<Widget> _widgetList = []; |
||||||
|
if (!CaptchaUtil.isEmpty(_clickWordCaptchaModel.imgStr)) { |
||||||
|
_widgetList.add(Image( |
||||||
|
width: baseSize.width, |
||||||
|
height: baseSize.height, |
||||||
|
gaplessPlayback: true, |
||||||
|
image: MemoryImage( |
||||||
|
Base64Decoder().convert(_clickWordCaptchaModel.imgStr)))); |
||||||
|
} |
||||||
|
|
||||||
|
double _widgetW = 20; |
||||||
|
for (int i = 0; i < _tapOffsetList.length; i++) { |
||||||
|
Offset offset = _tapOffsetList[i]; |
||||||
|
_widgetList.add(Positioned( |
||||||
|
left: offset.dx - _widgetW * 0.5, |
||||||
|
top: offset.dy - _widgetW * 0.5, |
||||||
|
child: Container( |
||||||
|
alignment: Alignment.center, |
||||||
|
width: _widgetW, |
||||||
|
height: _widgetW, |
||||||
|
decoration: BoxDecoration( |
||||||
|
color: Color(0xCC43A047), |
||||||
|
borderRadius: BorderRadius.all(Radius.circular(_widgetW))), |
||||||
|
child: Text( |
||||||
|
"${i + 1}", |
||||||
|
style: TextStyle(color: Colors.white, fontSize: 15), |
||||||
|
), |
||||||
|
))); |
||||||
|
} |
||||||
|
_widgetList.add(//刷新按钮 |
||||||
|
Positioned( |
||||||
|
top: 0, |
||||||
|
right: 0, |
||||||
|
child: IconButton( |
||||||
|
icon: Icon(Icons.refresh), |
||||||
|
iconSize: 30, |
||||||
|
color: Colors.deepOrangeAccent, |
||||||
|
onPressed: () { |
||||||
|
//刷新 |
||||||
|
_loadCaptcha(); |
||||||
|
}), |
||||||
|
)); |
||||||
|
|
||||||
|
return GestureDetector( |
||||||
|
onTapDown: (TapDownDetails details) { |
||||||
|
debugPrint( |
||||||
|
"onTapDown globalPosition全局坐标系位置: ${details.globalPosition} localPosition组件坐标系位置: ${details.localPosition} "); |
||||||
|
if (!CaptchaUtil.isListEmpty(_clickWordCaptchaModel.wordList) && |
||||||
|
_tapOffsetList.length < _clickWordCaptchaModel.wordList.length) { |
||||||
|
_tapOffsetList.add( |
||||||
|
Offset(details.localPosition.dx, details.localPosition.dy)); |
||||||
|
} |
||||||
|
setState(() {}); |
||||||
|
if (!CaptchaUtil.isListEmpty(_clickWordCaptchaModel.wordList) && |
||||||
|
_tapOffsetList.length == _clickWordCaptchaModel.wordList.length) { |
||||||
|
_checkCaptcha(); |
||||||
|
} |
||||||
|
}, |
||||||
|
child: Container( |
||||||
|
width: baseSize.width, |
||||||
|
height: baseSize.height, |
||||||
|
child: Stack( |
||||||
|
children: _widgetList, |
||||||
|
), |
||||||
|
)); |
||||||
|
} |
||||||
|
|
||||||
|
//底部提示部件 |
||||||
|
_bottomContainer() { |
||||||
|
return Container( |
||||||
|
height: 50.h, |
||||||
|
margin: EdgeInsets.only(top: 10), |
||||||
|
alignment: Alignment.center, |
||||||
|
width: baseSize.width, |
||||||
|
decoration: BoxDecoration( |
||||||
|
borderRadius: BorderRadius.all(Radius.circular(4)), |
||||||
|
border: Border.all(color: borderColor)), |
||||||
|
child: |
||||||
|
Text(bottomTitle, style: TextStyle(fontSize: 18, color: titleColor)), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
//顶部,提示+关闭 |
||||||
|
_topConttainer() { |
||||||
|
return Container( |
||||||
|
padding: EdgeInsets.fromLTRB(10, 0, 10, 0), |
||||||
|
margin: EdgeInsets.only(bottom: 20, top: 5), |
||||||
|
decoration: BoxDecoration( |
||||||
|
border: Border(bottom: BorderSide(width: 1, color: Color(0xffe5e5e5))), |
||||||
|
), |
||||||
|
child: Row( |
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
||||||
|
children: <Widget>[ |
||||||
|
Text( |
||||||
|
'请完成安全验证', |
||||||
|
style: TextStyle(fontSize: 18), |
||||||
|
), |
||||||
|
IconButton( |
||||||
|
icon: Icon(Icons.highlight_off), |
||||||
|
iconSize: 35, |
||||||
|
color: Colors.black54, |
||||||
|
onPressed: () { |
||||||
|
//退出 |
||||||
|
Navigator.pop(context); |
||||||
|
}), |
||||||
|
], |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//校验状态 |
||||||
|
enum ClickWordCaptchaState { |
||||||
|
normal, //默认 可自定义描述 |
||||||
|
success, //成功 |
||||||
|
fail, //失败 |
||||||
|
none, //无状态 用于加载使用 |
||||||
|
} |
||||||
|
|
||||||
|
//请求数据模型 |
||||||
|
class ClickWordCaptchaModel { |
||||||
|
String imgStr; //图表url 目前用base64 data |
||||||
|
String jigsawImageBase64; //图表url 目前用base64 data |
||||||
|
String token; // 获取的token 用于校验 |
||||||
|
List wordList; //显示需要点选的字 |
||||||
|
String wordStr; //显示需要点选的字转换为字符串 |
||||||
|
String secretKey; //加密key |
||||||
|
|
||||||
|
ClickWordCaptchaModel( |
||||||
|
{this.imgStr = "", |
||||||
|
this.jigsawImageBase64 = "", |
||||||
|
this.token = "", |
||||||
|
this.secretKey = "", |
||||||
|
this.wordList = const [], |
||||||
|
this.wordStr = ""}); |
||||||
|
|
||||||
|
//解析数据转换模型 |
||||||
|
static ClickWordCaptchaModel fromMap(Map<String, dynamic> map) { |
||||||
|
ClickWordCaptchaModel captchaModel = ClickWordCaptchaModel(); |
||||||
|
captchaModel.imgStr = map["originalImageBase64"] ?? ""; |
||||||
|
captchaModel.jigsawImageBase64 = map["jigsawImageBase64"] ?? ""; |
||||||
|
captchaModel.token = map["token"] ?? ""; |
||||||
|
captchaModel.secretKey = map["secretKey"] ?? ""; |
||||||
|
captchaModel.wordList = map["wordList"] ?? []; |
||||||
|
|
||||||
|
if (!CaptchaUtil.isListEmpty(captchaModel.wordList)) { |
||||||
|
captchaModel.wordStr = captchaModel.wordList.join(","); |
||||||
|
} |
||||||
|
|
||||||
|
return captchaModel; |
||||||
|
} |
||||||
|
|
||||||
|
//将模型转换 |
||||||
|
Map<String, dynamic> toJson() { |
||||||
|
var map = new Map<String, dynamic>(); |
||||||
|
map['imgStr'] = imgStr; |
||||||
|
map['jigsawImageBase64'] = jigsawImageBase64; |
||||||
|
map['token'] = token; |
||||||
|
map['secretKey'] = token; |
||||||
|
map['wordList'] = wordList; |
||||||
|
map['wordStr'] = wordStr; |
||||||
|
return map; |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
String toString() { |
||||||
|
// TODO: implement toString |
||||||
|
return JsonEncoder.withIndent(' ').convert(toJson()); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,86 @@ |
|||||||
|
import 'dart:convert'; |
||||||
|
import 'package:steel_crypt/steel_crypt.dart'; |
||||||
|
import 'package:convert/convert.dart'; |
||||||
|
import 'package:crypto/crypto.dart'; |
||||||
|
|
||||||
|
class CaptchaUtil{ |
||||||
|
///aes加密 |
||||||
|
/// [key]AesCrypt加密key |
||||||
|
/// [content] 需要加密的内容字符串 |
||||||
|
static String aesEncode({String key, String content}) { |
||||||
|
var aesCrypt = AesCrypt( |
||||||
|
key: base64UrlEncode(key.codeUnits), padding: PaddingAES.pkcs7); |
||||||
|
return aesCrypt.ecb.encrypt(inp: content); |
||||||
|
} |
||||||
|
|
||||||
|
///aes解密 |
||||||
|
/// [key]aes解密key |
||||||
|
/// [content] 需要加密的内容字符串 |
||||||
|
static String aesDecode({String key, String content}) { |
||||||
|
var aesCrypt = AesCrypt( |
||||||
|
key: base64UrlEncode(key.codeUnits), padding: PaddingAES.pkcs7); |
||||||
|
return aesCrypt.ecb.decrypt(enc: content); |
||||||
|
} |
||||||
|
/// isEmpty. |
||||||
|
static bool isEmpty(Object value) { |
||||||
|
if (value == null) return true; |
||||||
|
if (value is String && value.isEmpty) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
//list length == 0 || list == null |
||||||
|
static bool isListEmpty(Object value) { |
||||||
|
if (value == null) return true; |
||||||
|
if (value is List && value.length == 0) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
static String jsonFormat(Map<dynamic, dynamic> map) { |
||||||
|
Map _map = Map<String, Object>.from(map); |
||||||
|
JsonEncoder encoder = JsonEncoder.withIndent(' '); |
||||||
|
return encoder.convert(_map); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
static String generateMd5(String data){ |
||||||
|
var content = new Utf8Encoder().convert(data); |
||||||
|
var digest = md5.convert(content); |
||||||
|
return hex.encode(digest.bytes); |
||||||
|
} |
||||||
|
|
||||||
|
static signData(Object params, tokenStr) async { |
||||||
|
var time = new DateTime.now().millisecondsSinceEpoch; |
||||||
|
String token = tokenStr; |
||||||
|
Map<String, dynamic> reqData = new Map(); |
||||||
|
Map<String, dynamic> paramsObj = new Map(); |
||||||
|
paramsObj = params as Map<String, dynamic>; |
||||||
|
var arr = []; |
||||||
|
//将字典转成数组 |
||||||
|
paramsObj?.forEach((key, value) => arr.add(key)); |
||||||
|
//进行签名校验 |
||||||
|
Map cr = new Map(); |
||||||
|
cr['token'] = token; |
||||||
|
cr['time'] = time.toString(); |
||||||
|
cr['reqData'] = json.encode(paramsObj); |
||||||
|
var array = []; |
||||||
|
cr.forEach((key, value) => array.add(key)); |
||||||
|
array.sort(); |
||||||
|
var str = ''; |
||||||
|
for (var i = 0; i < array.length; i++) { |
||||||
|
var key = array[i]; |
||||||
|
var value = cr[key]; |
||||||
|
str += key + value; |
||||||
|
} |
||||||
|
|
||||||
|
reqData["time"] = time; |
||||||
|
reqData["token"] = token; |
||||||
|
reqData['reqData'] = params; |
||||||
|
reqData['sign'] = generateMd5(str); |
||||||
|
|
||||||
|
return reqData; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,133 @@ |
|||||||
|
import 'dart:async'; |
||||||
|
|
||||||
|
import 'package:flutter/widgets.dart'; |
||||||
|
|
||||||
|
import 'captcha_util.dart'; |
||||||
|
/** |
||||||
|
* @Author: thl |
||||||
|
* @GitHub: https://github.com/Sky24n |
||||||
|
* @Email: 863764940@qq.com |
||||||
|
* @Email: sky24no@gmail.com |
||||||
|
* @Description: Widget Util. |
||||||
|
* @Date: 2018/9/10 |
||||||
|
*/ |
||||||
|
|
||||||
|
/// Widget Util. |
||||||
|
class WidgetUtil { |
||||||
|
bool _hasMeasured = false; |
||||||
|
double _width; |
||||||
|
double _height; |
||||||
|
|
||||||
|
/// Widget rendering listener. |
||||||
|
/// Widget渲染监听. |
||||||
|
/// context: Widget context. |
||||||
|
/// isOnce: true,Continuous monitoring false,Listen only once. |
||||||
|
/// onCallBack: Widget Rect CallBack. |
||||||
|
void asyncPrepare( |
||||||
|
BuildContext context, bool isOnce, ValueChanged<Rect> onCallBack) { |
||||||
|
if (_hasMeasured) return; |
||||||
|
WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { |
||||||
|
RenderObject box = context.findRenderObject(); |
||||||
|
if (box != null) { |
||||||
|
if (isOnce) _hasMeasured = true; |
||||||
|
double width = box.semanticBounds.width; |
||||||
|
double height = box.semanticBounds.height; |
||||||
|
if (_width != width || _height != height) { |
||||||
|
_width = width; |
||||||
|
_height = height; |
||||||
|
if (onCallBack != null) onCallBack(box.semanticBounds); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/// Widget渲染监听. |
||||||
|
void asyncPrepares(bool isOnce, ValueChanged<Rect> onCallBack) { |
||||||
|
if (_hasMeasured) return; |
||||||
|
WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { |
||||||
|
if (isOnce) _hasMeasured = true; |
||||||
|
if (onCallBack != null) onCallBack(null); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
///get Widget Bounds (width, height, left, top, right, bottom and so on).Widgets must be rendered completely. |
||||||
|
///获取widget Rect |
||||||
|
static Rect getWidgetBounds(BuildContext context) { |
||||||
|
RenderObject box = context.findRenderObject(); |
||||||
|
return (box != null) ? box.semanticBounds : Rect.zero; |
||||||
|
} |
||||||
|
|
||||||
|
///Get the coordinates of the widget on the screen.Widgets must be rendered completely. |
||||||
|
///获取widget在屏幕上的坐标,widget必须渲染完成 |
||||||
|
static Offset getWidgetLocalToGlobal(BuildContext context) { |
||||||
|
RenderBox box = context.findRenderObject() as RenderBox; |
||||||
|
return box == null ? Offset.zero : box.localToGlobal(Offset.zero); |
||||||
|
} |
||||||
|
|
||||||
|
/// get image width height,load error return Rect.zero.(unit px) |
||||||
|
/// 获取图片宽高,加载错误情况返回 Rect.zero.(单位 px) |
||||||
|
/// image |
||||||
|
/// url network |
||||||
|
/// local url , package |
||||||
|
static Future<Rect> getImageWH( |
||||||
|
{Image image, String url, String localUrl, String package}) { |
||||||
|
if (CaptchaUtil.isEmpty(image) && |
||||||
|
CaptchaUtil.isEmpty(url) && |
||||||
|
CaptchaUtil.isEmpty(localUrl)) { |
||||||
|
return Future.value(Rect.zero); |
||||||
|
} |
||||||
|
Completer<Rect> completer = Completer<Rect>(); |
||||||
|
Image img = image ?? ((url != null && url.isNotEmpty) |
||||||
|
? Image.network(url) |
||||||
|
: Image.asset(localUrl, package: package)); |
||||||
|
img.image |
||||||
|
.resolve(const ImageConfiguration()) |
||||||
|
.addListener(ImageStreamListener( |
||||||
|
(ImageInfo info, bool _) { |
||||||
|
completer.complete(Rect.fromLTWH(0, 0, info.image.width.toDouble(), |
||||||
|
info.image.height.toDouble())); |
||||||
|
}, |
||||||
|
onError: (Object exception, StackTrace stackTrace) { |
||||||
|
completer.completeError(exception, stackTrace); |
||||||
|
}, |
||||||
|
)); |
||||||
|
return completer.future; |
||||||
|
} |
||||||
|
|
||||||
|
/// get image width height, load error throw exception.(unit px) |
||||||
|
/// 获取图片宽高,加载错误会抛出异常.(单位 px) |
||||||
|
/// image |
||||||
|
/// url network |
||||||
|
/// local url (full path/全路径,example:"assets/images/ali_connors.png",""assets/images/3.0x/ali_connors.png"" ); |
||||||
|
/// package |
||||||
|
static Future<Rect> getImageWHE( |
||||||
|
{Image image, |
||||||
|
String url, |
||||||
|
String localUrl, |
||||||
|
String package}) { |
||||||
|
if (CaptchaUtil.isEmpty(image) && |
||||||
|
CaptchaUtil.isEmpty(url) && |
||||||
|
CaptchaUtil.isEmpty(localUrl)) { |
||||||
|
return Future.error("image is null."); |
||||||
|
} |
||||||
|
Completer<Rect> completer = Completer<Rect>(); |
||||||
|
Image img = image != null |
||||||
|
? image |
||||||
|
: ((url != null && url.isNotEmpty) |
||||||
|
? Image.network(url) |
||||||
|
: Image.asset(localUrl, package: package)); |
||||||
|
img.image |
||||||
|
.resolve(const ImageConfiguration()) |
||||||
|
.addListener(ImageStreamListener( |
||||||
|
(ImageInfo info, bool _) { |
||||||
|
completer.complete(Rect.fromLTWH(0, 0, info.image.width.toDouble(), |
||||||
|
info.image.height.toDouble())); |
||||||
|
}, |
||||||
|
onError: (Object exception, StackTrace stackTrace) { |
||||||
|
completer.completeError(exception, stackTrace); |
||||||
|
}, |
||||||
|
)); |
||||||
|
|
||||||
|
return completer.future; |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue