flutter 截图和保存图片到本地和相册,并做分享

一、截图功能

使用RepaintBoundary实现
具体实现:

1、注册全局的key与RepaintBoundary匹配,来标明截图内容

//全局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 == "党政机关"
                                ? 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: '机构职能',
                          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(
                    "网站开办信息",
                    style: MyStyle.text_style_bold,
                  ),
                ),
                Container(
                  color: MyColor.background,
                  height: 0.5,
                ),
                Padding(
                  padding: EdgeInsets.symmetric(horizontal: MyDimens.margin),
                  child: Column(
                    children: [
                      KeyValueSingle(
                          mKey: '网站名称',
                          mValue: result != null && result!.org.length > 0
                              ? result!.web[0].source!.wzmc
                              : ""),
                      Container(
                        color: MyColor.background,
                        height: 0.5,
                      ),
                    
                    ],
                  ),
                ),
              ],
            ),
          ),
          ),
)

注意点:
1、如果直接包裹listView则不能截取全部内容,最好用SingleChildScrollView + colum 结合实现列表 才能截取全部内容
2、如果截取的内容超出屏幕,则必须将RepaintBoundary直接包裹在滑动内容上(colum上),否则屏幕外的内容截取不到
满足以上两个条件,才能实现截取整个widget的内容
3、如果RepaintBoundary包裹的widget没有背景色,在安卓上截图会是默认黑色背景,所以最好添加相应的背景色。

二、图片保存

/*
 * @Author: 王长春
 * @Date: 2022-03-14 09:24:34
 * @LastEditors: 王长春
 * @LastEditTime: 2022-03-17 10:01:41
 * @Description: 截图工具,生成截图,保存到相册或者保存本地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 captureImage() async {
    RenderRepaintBoundary? boundary = boundaryKey.currentContext!
        .findRenderObject() as RenderRepaintBoundary?;
    double dpr = ui.window.devicePixelRatio; // 获取当前设备的像素比
    var image = await boundary!.toImage(pixelRatio: dpr);
    // 将image转化成byte
    ByteData? byteData = await image.toByteData(format: ImageByteFormat.png);

    var filePath = "";

    Uint8List pngBytes = byteData!.buffer.asUint8List();
    // 获取手机存储(getTemporaryDirectory临时存储路径)
    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数据转化unit8
    //   Uint8List images = byteData!.buffer.asUint8List();
    //   // 调用image_gallery_saver的saveImages方法,返回值就是图片保存后的路径
    //   String result = await ImageGallerySaver.saveImage(images);
    //   // 需要去除掉file://开头。生成要使用的file
    //   File saveFile = new File(result.replaceAll("file://", ""));
    //   filePath = saveFile.path;
    //
    //
    // } else if (Platform.isIOS) {
    //   // 图片byte数据转化unit8
    //
    // }

    return filePath;
  }

//申请存本地相册权限
  Future getPormiation() async {
    if (Platform.isIOS) {
      var status = await Permission.photos.status;
      if (status.isDenied) {
        Map statuses = await [
          Permission.photos,
        ].request();
        // saveImage(globalKey);
      }
      return status.isGranted;
    } else {
      var status = await Permission.storage.status;
      if (status.isDenied) {
        Map statuses = await [
          Permission.storage,
        ].request();
      }
      return status.isGranted;
    }
  }

//保存到相册
  void savePhoto() async {
    RenderRepaintBoundary? boundary = boundaryKey.currentContext!
        .findRenderObject() as RenderRepaintBoundary?;

    double dpr = ui.window.devicePixelRatio; // 获取当前设备的像素比
    var image = await boundary!.toImage(pixelRatio: dpr);
    // 将image转化成byte
    ByteData? byteData = await image.toByteData(format: ImageByteFormat.png);
  //获取保存相册权限,如果没有,则申请改权限
    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已授权");
          Uint8List images = byteData!.buffer.asUint8List();
          final result = await ImageGallerySaver.saveImage(images, quality: 60);
          if (result != null) {
            EasyLoading.showToast("保存成功");
          } else {
            print('error');
            // toast("保存失败");
          }
        }
      }
    }else{
      //重新请求--第一次请求权限时,保存方法不会走,需要重新调一次
      savePhoto();
    }
  }
}

调用方法:

//保存本地 RepaintBoundaryUtils().savePhoto();

说明:
我这里涉及到分享以及保存图片到本地相册两个功能。
涉及到的框架有:

  #分享
  share_plus: ^3.0.1

  #微信三方-分享、登录、小程序跳转等(不带支付)
  fluwx_no_pay: ^3.8.1

  #保存图片到相册
  image_gallery_saver: ^1.7.1

  #权限管理
  permission_handler: ^8.1.6

iOS权限设置:

在podfile中添加一下内容:

 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中添加字段





    CFBundleDevelopmentRegion
    $(DEVELOPMENT_LANGUAGE)
    CFBundleExecutable
    $(EXECUTABLE_NAME)
    CFBundleIdentifier
    $(PRODUCT_BUNDLE_IDENTIFIER)
    CFBundleInfoDictionaryVersion
    6.0
    CFBundleName
    organization
    CFBundlePackageType
    APPL
    CFBundleShortVersionString
    $(FLUTTER_BUILD_NAME)
    CFBundleSignature
    ????
    CFBundleURLTypes
    
        
            CFBundleTypeRole
            Editor
            CFBundleURLName
            weixin
            CFBundleURLSchemes
            
                wx543b04c9d1aa9a3a
            
        
    
    CFBundleVersion
    $(FLUTTER_BUILD_NUMBER)
    LSApplicationQueriesSchemes
    
        weixinULAPI
        baidumap
        iosamap
        comgooglemaps
        qqmap
        mqzone
        mqqwpa
        mqzoneopensdkapi19
        mqzoneopensdkapi
        mqzoneopensdk
        mqzoneopensdkapiV2
        mqqapi
        mqq
        wtloginmqq2
        mqqopensdkapiV3
        mqqopensdkapiV2
        mqqOpensdkSSoLogin
        weixin
        wechat
    
    LSRequiresIPhoneOS
    
    NSAppTransportSecurity
    
        NSAllowsArbitraryLoads
        
    
    NSCameraUsageDescription
    需要访问您的相机设置头像
    NSPhotoLibraryAddUsageDescription
    请允许APP保存图片到相册
    NSPhotoLibraryUsageDescription
    需要访问您的相册设置头像
    NSSupportsSuddenTermination
    
    UILaunchStoryboardName
    LaunchScreen
    UIMainStoryboardFile
    Main
    UISupportedInterfaceOrientations
    
        UIInterfaceOrientationPortrait
        UIInterfaceOrientationLandscapeLeft
        UIInterfaceOrientationLandscapeRight
    
    UISupportedInterfaceOrientations~ipad
    
        UIInterfaceOrientationPortrait
        UIInterfaceOrientationPortraitUpsideDown
        UIInterfaceOrientationLandscapeLeft
        UIInterfaceOrientationLandscapeRight
    
    UIViewControllerBasedStatusBarAppearance
    


添加urlSchemes

截屏2022-03-16 19.48.50.png

可以按我的添加,我这里除了基本功能,就是微信sdk分享以及相册相机权限。

安卓权限

AndroidManifest.xml添加一下权限




三、分享:

分享这里提供两种方案;

a、share_plus 分享:不用在第三方平台注册app,这种分享出去的图片,不带app图标以及名称,只有图片
b、微信原生分享:fluwx_no_pay:需在微信开发者账号上注册app,这种分享出去的图片带app图标以及名称。

微信原生分享代码:
/*
 * @Author: 王长春
 * @Date: 2022-03-11 09:43:46
 * @LastEditors: 王长春
 * @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: "这里写你注册的appid",
        doOnAndroid: true,
        doOnIOS: true,
        universalLink: "这里写你注册的universalLink");
  
  }

  static Future wxIsInstalled() async {
    return await fluwx.isWeChatInstalled;
  }



  /**
   * 分享图片到微信,
   * file=本地路径
   * url=网络地址
   * asset=内置在app的资源图片
   * scene=分享场景,1好友会话,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=分享内容
   * scene=分享场景,1好友会话,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=视频网上地址
 * thumbFile=缩略图本地路径
   * scene=分享场景,1好友会话,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=分享场景,1好友会话,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: 王长春
 * @Date: 2022-03-15 15:17:19
 * @LastEditors: 王长春
 * @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();
    //收回键盘,如果有输入的情况下,先收回键盘,再截图
    FocusScope.of(context).requestFocus(FocusNode());
    
    if (wxIsInstalled) {
      //分享
      // WxSdk.ShareText("sdgsfds",title:"标题");
      //获取截图地址
      String filePath = await RepaintBoundaryUtils().captureImage();

      print(filePath);
      WxSdk.ShareImage(title: "机构检索", 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 imagePaths = [];

    //获取截图地址
    String filePath = await RepaintBoundaryUtils().captureImage();
    //Share.shareFiles内可以传多张图片,里面是个数组,所以每次要将数组清空,再将新的截图添加到数组中
    imagePaths.clear();
    imagePaths.add(filePath);
    //分享
    await Share.shareFiles(imagePaths,
        text: "机构详情",
        subject: "",
        sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size);
  }
}

调用:
 //share_plus 分享
                ShareHelper.onSharePlusShare(context);
                //微信SDK分享
                // ShareHelper.onShareWx(context);

以上实现了图片截图以及保存本地分享内容,在安卓和iOS上亲测没问题。
关于iOS微信原生分享,注册universalLink的内容,需要后台配合,我这里只是写了个demo,没具体实现。
可以参考 : https://www.jianshu.com/p/4c96b54ef8d1

你可能感兴趣的:(flutter 截图和保存图片到本地和相册,并做分享)