一、截圖功能
使用RepaintBoundary實(shí)現(xiàn)
具體實(shí)現(xiàn):
1剃法、注冊(cè)全局的key與RepaintBoundary匹配碎捺,來(lái)標(biāo)明截圖內(nèi)容
//全局key-截圖key
final GlobalKey boundaryKey = GlobalKey();
2、將需要截圖的widget包裹在RepaintBoundary組建中贷洲,加入key屬性
SingleChildScrollView(
child:RepaintBoundary(
key: boundaryKey,
child:Container(
color: MyColor.white,
child:Column(
children: [
Padding(
padding: EdgeInsets.fromLTRB(
MyDimens.margin, 10, MyDimens.margin, 10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Visibility(
visible: result != null && result!.org.length > 0,
child: Image(
image: result != null &&
result!.org.length > 0 &&
result!.org[0].source!.jglx == "黨政機(jī)關(guān)"
? AssetImage("assets/images/organ.png")
: AssetImage("assets/images/institution.png"),
width: 20,
height: 20,
)),
Visibility(
visible: result != null && result!.org.length > 0,
child: SizedBox(
width: 5,
)),
Expanded(
child: Text(
_content,
style: MyStyle.text_style_bold,
))
],
),
),
Visibility(
visible: result != null &&
result!.org.length > 0 &&
result!.org[0].source != null &&
result!.web[0].source!.zwym != "--",
child: InkWell(
onTap: () {
launch('' + result!.web[0].source!.zwym.split(',')[0]);
},
child: Padding(
padding:
EdgeInsets.fromLTRB(42, 0, MyDimens.margin, 10),
child: Text(
result != null &&
result!.org[0].source != null &&
result!.org.length > 0
? result!.web[0].source!.zwym.split(',')[0]
: "",
style: MyStyle.text_style_link,
)),
)),
Container(
color: MyColor.background,
height: 0.5,
),
Container(
color: MyColor.background,
height: 10,
),
Padding(
padding: EdgeInsets.all(MyDimens.margin),
child: Text(
"基本信息",
style: MyStyle.text_style_bold,
),
),
Container(
color: MyColor.background,
height: 0.5,
),
Padding(
padding: EdgeInsets.symmetric(horizontal: MyDimens.margin),
child: Column(
children: [
KeyValueSingle(
mKey: '機(jī)構(gòu)職能',
mValue: result != null && result!.org.length > 0
? result!.org[0].source!.jgzz
: ""),
],
),
),
Container(
color: MyColor.background,
height: 10,
),
Padding(
padding: EdgeInsets.all(MyDimens.margin),
child: Text(
"網(wǎng)站開(kāi)辦信息",
style: MyStyle.text_style_bold,
),
),
Container(
color: MyColor.background,
height: 0.5,
),
Padding(
padding: EdgeInsets.symmetric(horizontal: MyDimens.margin),
child: Column(
children: [
KeyValueSingle(
mKey: '網(wǎng)站名稱',
mValue: result != null && result!.org.length > 0
? result!.web[0].source!.wzmc
: ""),
Container(
color: MyColor.background,
height: 0.5,
),
],
),
),
],
),
),
)收厨,
)
注意點(diǎn):
1、如果直接包裹listView則不能截取全部?jī)?nèi)容优构,最好用SingleChildScrollView + colum 結(jié)合實(shí)現(xiàn)列表 才能截取全部?jī)?nèi)容
2诵叁、如果截取的內(nèi)容超出屏幕,則必須將RepaintBoundary直接包裹在滑動(dòng)內(nèi)容上(colum上)钦椭,否則屏幕外的內(nèi)容截取不到
滿足以上兩個(gè)條件拧额,才能實(shí)現(xiàn)截取整個(gè)widget的內(nèi)容
3、如果RepaintBoundary包裹的widget沒(méi)有背景色彪腔,在安卓上截圖會(huì)是默認(rèn)黑色背景侥锦,所以最好添加相應(yīng)的背景色。
二德挣、圖片保存
/*
* @Author: 王長(zhǎng)春
* @Date: 2022-03-14 09:24:34
* @LastEditors: 王長(zhǎng)春
* @LastEditTime: 2022-03-17 10:01:41
* @Description: 截圖工具恭垦,生成截圖,保存到相冊(cè)或者保存本地cash文件夾返回文件路徑(供分享使用)
*/
import 'dart:io';
import 'dart:typed_data';
import 'dart:async';
import 'dart:ui';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
//全局key-截圖key
final GlobalKey boundaryKey = GlobalKey();
class RepaintBoundaryUtils {
//生成截圖
/// 截屏圖片生成圖片流ByteData
Future<String> captureImage() async {
RenderRepaintBoundary? boundary = boundaryKey.currentContext!
.findRenderObject() as RenderRepaintBoundary?;
double dpr = ui.window.devicePixelRatio; // 獲取當(dāng)前設(shè)備的像素比
var image = await boundary!.toImage(pixelRatio: dpr);
// 將image轉(zhuǎn)化成byte
ByteData? byteData = await image.toByteData(format: ImageByteFormat.png);
var filePath = "";
Uint8List pngBytes = byteData!.buffer.asUint8List();
// 獲取手機(jī)存儲(chǔ)(getTemporaryDirectory臨時(shí)存儲(chǔ)路徑)
Directory applicationDir = await getTemporaryDirectory();
// getApplicationDocumentsDirectory();
// 判斷路徑是否存在
bool isDirExist = await Directory(applicationDir.path).exists();
if (!isDirExist) Directory(applicationDir.path).create();
// 直接保存格嗅,返回的就是保存后的文件
File saveFile = await File(
applicationDir.path + "${DateTime.now().toIso8601String()}.jpg")
.writeAsBytes(pngBytes);
filePath = saveFile.path;
// if (Platform.isAndroid) {
// // 如果是Android 的話署照,直接使用image_gallery_saver就可以了
// // 圖片byte數(shù)據(jù)轉(zhuǎn)化unit8
// Uint8List images = byteData!.buffer.asUint8List();
// // 調(diào)用image_gallery_saver的saveImages方法,返回值就是圖片保存后的路徑
// String result = await ImageGallerySaver.saveImage(images);
// // 需要去除掉file://開(kāi)頭吗浩。生成要使用的file
// File saveFile = new File(result.replaceAll("file://", ""));
// filePath = saveFile.path;
//
//
// } else if (Platform.isIOS) {
// // 圖片byte數(shù)據(jù)轉(zhuǎn)化unit8
//
// }
return filePath;
}
//申請(qǐng)存本地相冊(cè)權(quán)限
Future<bool> getPormiation() async {
if (Platform.isIOS) {
var status = await Permission.photos.status;
if (status.isDenied) {
Map<Permission, PermissionStatus> statuses = await [
Permission.photos,
].request();
// saveImage(globalKey);
}
return status.isGranted;
} else {
var status = await Permission.storage.status;
if (status.isDenied) {
Map<Permission, PermissionStatus> statuses = await [
Permission.storage,
].request();
}
return status.isGranted;
}
}
//保存到相冊(cè)
void savePhoto() async {
RenderRepaintBoundary? boundary = boundaryKey.currentContext!
.findRenderObject() as RenderRepaintBoundary?;
double dpr = ui.window.devicePixelRatio; // 獲取當(dāng)前設(shè)備的像素比
var image = await boundary!.toImage(pixelRatio: dpr);
// 將image轉(zhuǎn)化成byte
ByteData? byteData = await image.toByteData(format: ImageByteFormat.png);
//獲取保存相冊(cè)權(quán)限建芙,如果沒(méi)有,則申請(qǐng)改權(quán)限
bool permition = await getPormiation();
var status = await Permission.photos.status;
if (permition) {
if (Platform.isIOS) {
if (status.isGranted) {
Uint8List images = byteData!.buffer.asUint8List();
final result = await ImageGallerySaver.saveImage(images,
quality: 60, name: "hello");
EasyLoading.showToast("保存成功");
}
if (status.isDenied) {
print("IOS拒絕");
}
} else {
//安卓
if (status.isGranted) {
print("Android已授權(quán)");
Uint8List images = byteData!.buffer.asUint8List();
final result = await ImageGallerySaver.saveImage(images, quality: 60);
if (result != null) {
EasyLoading.showToast("保存成功");
} else {
print('error');
// toast("保存失敗");
}
}
}
}else{
//重新請(qǐng)求--第一次請(qǐng)求權(quán)限時(shí)懂扼,保存方法不會(huì)走禁荸,需要重新調(diào)一次
savePhoto();
}
}
}
調(diào)用方法:
//保存本地 RepaintBoundaryUtils().savePhoto();
說(shuō)明:
我這里涉及到分享以及保存圖片到本地相冊(cè)兩個(gè)功能。
涉及到的框架有:
#分享
share_plus: ^3.0.1
#微信三方-分享阀湿、登錄赶熟、小程序跳轉(zhuǎn)等(不帶支付)
fluwx_no_pay: ^3.8.1
#保存圖片到相冊(cè)
image_gallery_saver: ^1.7.1
#權(quán)限管理
permission_handler: ^8.1.6
iOS權(quán)限設(shè)置:
在podfile中添加一下內(nèi)容:
target.build_configurations.each do |config|
# You can remove unused permissions here
# for more infomation: https://github.com/BaseflowIT/flutter-permission-handler/blob/master/permission_handler/ios/Classes/PermissionHandlerEnums.h
# e.g. when you don't need camera permission, just add 'PERMISSION_CAMERA=0'
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse]
'PERMISSION_PHOTOS=1',
]
end
podfile整體做為參考:
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
# You can remove unused permissions here
# for more infomation: https://github.com/BaseflowIT/flutter-permission-handler/blob/master/permission_handler/ios/Classes/PermissionHandlerEnums.h
# e.g. when you don't need camera permission, just add 'PERMISSION_CAMERA=0'
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse]
'PERMISSION_PHOTOS=1',
]
end
end
end
info.plist中添加字段
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>organization</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>weixin</string>
<key>CFBundleURLSchemes</key>
<array>
<string>wx543b04c9d1aa9a3a</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>weixinULAPI</string>
<string>baidumap</string>
<string>iosamap</string>
<string>comgooglemaps</string>
<string>qqmap</string>
<string>mqzone</string>
<string>mqqwpa</string>
<string>mqzoneopensdkapi19</string>
<string>mqzoneopensdkapi</string>
<string>mqzoneopensdk</string>
<string>mqzoneopensdkapiV2</string>
<string>mqqapi</string>
<string>mqq</string>
<string>wtloginmqq2</string>
<string>mqqopensdkapiV3</string>
<string>mqqopensdkapiV2</string>
<string>mqqOpensdkSSoLogin</string>
<string>weixin</string>
<string>wechat</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSCameraUsageDescription</key>
<string>需要訪問(wèn)您的相機(jī)設(shè)置頭像</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>請(qǐng)?jiān)试SAPP保存圖片到相冊(cè)</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要訪問(wèn)您的相冊(cè)設(shè)置頭像</string>
<key>NSSupportsSuddenTermination</key>
<false/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
添加urlSchemes
可以按我的添加,我這里除了基本功能陷嘴,就是微信sdk分享以及相冊(cè)相機(jī)權(quán)限映砖。
安卓權(quán)限
AndroidManifest.xml添加一下權(quán)限
<!-- 開(kāi)啟讀寫(xiě)storage權(quán)限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
三、分享:
分享這里提供兩種方案灾挨;
a邑退、share_plus 分享:不用在第三方平臺(tái)注冊(cè)app竹宋,這種分享出去的圖片,不帶app圖標(biāo)以及名稱地技,只有圖片
b蜈七、微信原生分享:fluwx_no_pay:需在微信開(kāi)發(fā)者賬號(hào)上注冊(cè)app,這種分享出去的圖片帶app圖標(biāo)以及名稱莫矗。
微信原生分享代碼:
/*
* @Author: 王長(zhǎng)春
* @Date: 2022-03-11 09:43:46
* @LastEditors: 王長(zhǎng)春
* @LastEditTime: 2022-03-16 09:56:14
* @Description:
*/
import 'dart:io';
import 'dart:typed_data';
import 'check.dart';
import 'package:fluwx_no_pay/fluwx_no_pay.dart' as fluwx;
class WxSdk {
// static bool wxIsInstalled;
static Future init() async {
fluwx.registerWxApi(
appId: "這里寫(xiě)你注冊(cè)的appid",
doOnAndroid: true,
doOnIOS: true,
universalLink: "這里寫(xiě)你注冊(cè)的universalLink");
}
static Future<bool> wxIsInstalled() async {
return await fluwx.isWeChatInstalled;
}
/**
* 分享圖片到微信飒硅,
* file=本地路徑
* url=網(wǎng)絡(luò)地址
* asset=內(nèi)置在app的資源圖片
* scene=分享場(chǎng)景,1好友會(huì)話作谚,2朋友圈三娩,3收藏
*/
static void ShareImage(
{String? title,
String? decs,
String? file,
String? url,
String? asset,
int scene = 1}) async {
fluwx.WeChatScene wxScene = fluwx.WeChatScene.SESSION;
if (scene == 2) {
wxScene = fluwx.WeChatScene.TIMELINE;
} else if (scene == 3) {
wxScene = fluwx.WeChatScene.FAVORITE;
}
fluwx.WeChatShareImageModel? model;
if (file != null) {
model = fluwx.WeChatShareImageModel(fluwx.WeChatImage.file(File(file)),
title: title, description: decs, scene: wxScene);
} else if (url != null) {
model = fluwx.WeChatShareImageModel(fluwx.WeChatImage.network(url),
title: title, description: decs, scene: wxScene);
} else if (asset != null) {
model = fluwx.WeChatShareImageModel(fluwx.WeChatImage.asset(asset),
title: title, description: decs, scene: wxScene);
} else {
throw Exception("缺少圖片資源信息");
}
fluwx.shareToWeChat(model);
}
/**
* 分享文本
* content=分享內(nèi)容
* scene=分享場(chǎng)景,1好友會(huì)話妹懒,2朋友圈雀监,3收藏
*/
static void ShareText(String content, {String? title, int scene = 1}) {
fluwx.WeChatScene wxScene = fluwx.WeChatScene.SESSION;
if (scene == 2) {
wxScene = fluwx.WeChatScene.TIMELINE;
} else if (scene == 3) {
wxScene = fluwx.WeChatScene.FAVORITE;
}
fluwx.WeChatShareTextModel model =
fluwx.WeChatShareTextModel(content, title: title, scene: wxScene);
fluwx.shareToWeChat(model);
}
/***
* 分享視頻
* videoUrl=視頻網(wǎng)上地址
* thumbFile=縮略圖本地路徑
* scene=分享場(chǎng)景,1好友會(huì)話彬伦,2朋友圈,3收藏
*/
static void ShareVideo(String videoUrl,
{String? thumbFile, String? title, String? desc, int scene = 1}) {
fluwx.WeChatScene wxScene = fluwx.WeChatScene.SESSION;
if (scene == 2) {
wxScene = fluwx.WeChatScene.TIMELINE;
} else if (scene == 3) {
wxScene = fluwx.WeChatScene.FAVORITE;
}
fluwx.WeChatImage? image;
if (thumbFile != null) {
image = fluwx.WeChatImage.file(File(thumbFile));
}
var model = fluwx.WeChatShareVideoModel(
videoUrl: videoUrl,
thumbnail: image,
title: title,
description: desc,
scene: wxScene);
fluwx.shareToWeChat(model);
}
/**
* 分享鏈接
* url=鏈接
* thumbFile=縮略圖本地路徑
* scene=分享場(chǎng)景伊诵,1好友會(huì)話单绑,2朋友圈,3收藏
*/
static void ShareUrl(String url,
{String? thumbFile,
Uint8List? thumbBytes,
String? title,
String? desc,
int scene = 1,
String? networkThumb,
String? assetThumb}) {
desc = desc ?? "";
title = title ?? "";
if (desc.length > 54) {
desc = desc.substring(0, 54) + "...";
}
if (title.length > 20) {
title = title.substring(0, 20) + "...";
}
fluwx.WeChatScene wxScene = fluwx.WeChatScene.SESSION;
if (scene == 2) {
wxScene = fluwx.WeChatScene.TIMELINE;
} else if (scene == 3) {
wxScene = fluwx.WeChatScene.FAVORITE;
}
fluwx.WeChatImage? image ;
if (thumbFile != null) {
image = fluwx.WeChatImage.file(File(thumbFile));
} else if (thumbBytes != null) {
image = fluwx.WeChatImage.binary(thumbBytes);
} else if (strNoEmpty(networkThumb!)) {
image = fluwx.WeChatImage.network(Uri.encodeFull(networkThumb));
} else if (strNoEmpty(assetThumb!)) {
image = fluwx.WeChatImage.asset(assetThumb, suffix: ".png");
}
var model = fluwx.WeChatShareWebPageModel(
url,
thumbnail: image,
title: title,
description: desc,
scene: wxScene,
);
fluwx.shareToWeChat(model);
}
}
分享功能封裝:
/*
* @Author: 王長(zhǎng)春
* @Date: 2022-03-15 15:17:19
* @LastEditors: 王長(zhǎng)春
* @LastEditTime: 2022-03-16 19:15:45
* @Description: 分享工具
*/
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:organization/common/utils/repaintBoundary_utils.dart';
import 'package:organization/common/utils/wechatSDK.dart';
import 'package:share_plus/share_plus.dart';
class ShareHelper {
static bool wxIsInstalled = false;
//微信分享截圖
static void onShareWx(BuildContext context) async {
wxIsInstalled = await WxSdk.wxIsInstalled();
//收回鍵盤(pán)曹宴,如果有輸入的情況下搂橙,先收回鍵盤(pán),再截圖
FocusScope.of(context).requestFocus(FocusNode());
if (wxIsInstalled) {
//分享
// WxSdk.ShareText("sdgsfds",title:"標(biāo)題");
//獲取截圖地址
String filePath = await RepaintBoundaryUtils().captureImage();
print(filePath);
WxSdk.ShareImage(title: "機(jī)構(gòu)檢索", decs: "", file: filePath);
} else {
//提示未安裝微信
EasyLoading.showToast("未安裝微信");
}
}
// static void checkWx() async {
// wxIsInstalled = await WxSdk.wxIsInstalled();
// }
//share_plus分享
static void onSharePlusShare(BuildContext context) async {
FocusScope.of(context).requestFocus(FocusNode());
// A builder is used to retrieve the context immediately
// surrounding the ElevatedButton.
// The context's `findRenderObject` returns the first
// RenderObject in its descendent tree when it's not
// a RenderObjectWidget. The ElevatedButton's RenderObject
// has its position and size after it's built.
final box = context.findRenderObject() as RenderBox?;
List<String> imagePaths = [];
//獲取截圖地址
String filePath = await RepaintBoundaryUtils().captureImage();
//Share.shareFiles內(nèi)可以傳多張圖片笛坦,里面是個(gè)數(shù)組区转,所以每次要將數(shù)組清空,再將新的截圖添加到數(shù)組中
imagePaths.clear();
imagePaths.add(filePath);
//分享
await Share.shareFiles(imagePaths,
text: "機(jī)構(gòu)詳情",
subject: "",
sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size);
}
}
調(diào)用:
//share_plus 分享
ShareHelper.onSharePlusShare(context);
//微信SDK分享
// ShareHelper.onShareWx(context);
以上實(shí)現(xiàn)了圖片截圖以及保存本地分享內(nèi)容版扩,在安卓和iOS上親測(cè)沒(méi)問(wèn)題废离。
關(guān)于iOS微信原生分享,注冊(cè)u(píng)niversalLink的內(nèi)容礁芦,需要后臺(tái)配合蜻韭,我這里只是寫(xiě)了個(gè)demo,沒(méi)具體實(shí)現(xiàn)柿扣。
可以參考 : http://www.reibang.com/p/4c96b54ef8d1