flutter:截屏

1.flutter-截屏组件

import 'dart:typed_data';
import 'dart:ui';

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

class RepaintBoundaryPage extends StatefulWidget {
  @override
  _RepaintBoundaryPageState createState() => _RepaintBoundaryPageState();
}

class _RepaintBoundaryPageState extends State<RepaintBoundaryPage> {
  //全局key
  GlobalKey _rootWidgetKey = GlobalKey();
  //图片数组
  List<Uint8List> _images = List();

  ///截图
  Future<Uint8List> _capturePng(
    GlobalKey globalKey, {
    double pixelRatio = 1.0, //截屏的图片与原图的比例
  }) async {
    try {
      RenderRepaintBoundary boundary =
          globalKey.currentContext.findRenderObject();
      var image = await boundary.toImage(pixelRatio: pixelRatio);
      ByteData byteData = await image.toByteData(format: ImageByteFormat.png);
      Uint8List pngBytes = byteData.buffer.asUint8List();
      return pngBytes;
    } catch (e) {
      print(e);
    }
    return null;
  }

  ///build
  @override
  Widget build(BuildContext context) {
    //RepaintBoundary 截屏组件
    return RepaintBoundary(
      key: _rootWidgetKey,
      child: Scaffold(
        appBar: AppBar(
          title: Text("flutter组件截图"),
        ),
        body: Column(
          children: <Widget>[
            FlatButton(
              onPressed: () {
                //获取截屏图像
                Future<Uint8List> pngBytes = _capturePng(_rootWidgetKey);
                //添加到图片数组中
                pngBytes.then((Uint8List value) {
                  _images.add(value);
                });
                setState(() {});
              },
              child: Text("全屏截图"),
            ),
            Expanded(
              child: ListView.builder(
                itemBuilder: (context, index) {
                  return Image.memory(
                    _images[index],
                    fit: BoxFit.cover,
                  );
                },
                itemCount: _images.length,
                scrollDirection: Axis.horizontal,
              ),
            )
          ],
        ),
      ),
    );
  }
}

2.flutter-截屏插件

screenshot: ^0.2.0
import 'dart:io';

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

class FlutterScreenshotsPage extends StatefulWidget {
  @override
  _FlutterScreenshotsPageState createState() => _FlutterScreenshotsPageState();
}

class _FlutterScreenshotsPageState extends State<FlutterScreenshotsPage> {
  //截屏图片路径
  File _imageFile;

  //截屏控制器
  ScreenshotController screenshotController = ScreenshotController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("flutter插件截屏"),
      ),
      body: Container(
        child: new Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              //截屏组件
              Screenshot(
                controller: screenshotController,
                child: Column(
                  children: <Widget>[
                    Text('截屏的位置'),
                    FlutterLogo(),
                  ],
                ),
              ),
              _imageFile != null ? Image.file(_imageFile) : Container(),
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          _imageFile = null;
          screenshotController
              .capture(delay: Duration(milliseconds: 10))
              .then((File image) async {
            setState(() {
              _imageFile = image;
            });
            print("File Saved to Gallery");
          }).catchError((onError) {
            print(onError);
          });
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

3.flutter-iOS原生截屏

import 'dart:typed_data';

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

class IosScreenshotsPage extends StatefulWidget {
  @override
  _IosScreenshotsPageState createState() => _IosScreenshotsPageState();
}

class _IosScreenshotsPageState extends State<IosScreenshotsPage> {
  //平台信道
  MethodChannel _channel = MethodChannel("Screenshots");

  bool _image = false;
  Uint8List _image8List;

  //图片
  ///initState
  @override
  void initState() {
    //监听原生
    _channel.setMethodCallHandler((call) {
      //接收截屏后的图片
      if (call.method == "endScreenshots") {
        Map map = call.arguments;
        _image8List = map["image"];
        _image = true;
        print("接收原生的图片数据: $_image8List");
        setState(() {});
      }
      return null;
    });
    super.initState();
  }

  ///build
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("iOS原生截屏"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: <Widget>[
            OutlineButton(
              onPressed: () {
                //发送开始截屏到原生
                _channel.invokeMethod("startScreenshots");
              },
              child: Text("发起截屏"),
            ),
            Expanded(
              child: _image
                  ? Image.memory(
                      _image8List,
                      fit: BoxFit.cover,
                    )
                  : Container(),
            ),
          ],
        ),
      ),
    );
  }
}

iOS代码

//
//  ScreenshotsManager.h
//  Runner
//
//  Created by macmini on 2020/9/15.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

/// 协议
@protocol ScreenshotsManagerDelegate <NSObject>

/// 图片保存结果
- (void)isSaveImage:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo;

@end


/// 截屏类
@interface ScreenshotsManager : NSObject

/// 代理
@property(nonatomic,assign)id<ScreenshotsManagerDelegate>delegate;

/// 初始化
+ (instancetype)shareInstance;

/// 返回系统级截屏PNG数据
- (NSData *)screenshotsDataFromUIImagePNGRepresentation;
/// 返回系统级截屏JPG数据
- (NSData *)screenshotsDataFromUIImageJPEGRepresentation;
/// 返回系统级截屏图片
- (UIImage *)imageFromScreenshots;

/// 保存图片到本地
/// @param savedImage 需保存的图片
- (void)saveImageToPhotos:(UIImage*)savedImage;

@end

NS_ASSUME_NONNULL_END

//
//  ScreenshotsManager.m
//  Runner
//
//  Created by macmini on 2020/9/15.
//

#import "ScreenshotsManager.h"


@implementation ScreenshotsManager

///强指针,应用结束才会死
static id _instance;

///外调单例方法
+ (instancetype)shareInstance
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[self alloc] init];
    });
    return _instance;
}

///根本上只建立一个对象,只开辟一个内存
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [super allocWithZone:zone];
    });
    return _instance;
}

///防止copy时,新建立一个对象,保证只开辟一个内存
- (id)copyWithZone:(nullable NSZone *)zone
{
    return _instance;
}


/************************************以上为单例****************************************/


#pragma mark - 系统级截屏
/// 返回系统级截屏PNG数据
- (NSData *)screenshotsDataFromUIImagePNGRepresentation
{
    UIImage *image = [self imageFromScreenshots];
    return UIImagePNGRepresentation(image);
}

/// 返回系统级截屏JPG数据
- (NSData *)screenshotsDataFromUIImageJPEGRepresentation
{
    UIImage *image = [self imageFromScreenshots];
    return UIImageJPEGRepresentation(image, 1.0f);
}

/// 返回系统级截屏图片
- (UIImage *)imageFromScreenshots
{
    CGSize imageSize = CGSizeZero;
    UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
    if (UIInterfaceOrientationIsPortrait(orientation))
    {
        imageSize = [UIScreen mainScreen].bounds.size;
    }
    else {
        imageSize = CGSizeMake([UIScreen mainScreen].bounds.size.height, [UIScreen mainScreen].bounds.size.width);
    }
    
    UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    for (UIWindow *window in [[UIApplication sharedApplication] windows])
    {
        CGContextSaveGState(context);
        CGContextTranslateCTM(context, window.center.x, window.center.y);
        CGContextConcatCTM(context, window.transform);
        CGContextTranslateCTM(context, -window.bounds.size.width * window.layer.anchorPoint.x, -window.bounds.size.height * window.layer.anchorPoint.y);
        if (orientation == UIInterfaceOrientationLandscapeLeft)
        {
            CGContextRotateCTM(context, M_PI_2);
            CGContextTranslateCTM(context, 0, -imageSize.width);
        }
        else if (orientation == UIInterfaceOrientationLandscapeRight)
        {
            CGContextRotateCTM(context, -M_PI_2);
            CGContextTranslateCTM(context, -imageSize.height, 0);
        }
        else if (orientation == UIInterfaceOrientationPortraitUpsideDown)
        {
            CGContextRotateCTM(context, M_PI);
            CGContextTranslateCTM(context, -imageSize.width, -imageSize.height);
        }
        if ([window respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
            [window drawViewHierarchyInRect:window.bounds afterScreenUpdates:YES];
        }
        else {
            [window.layer renderInContext:context];
        }
        CGContextRestoreGState(context);
    }
    
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

#pragma mark - 将图片保存到相册
//保存图片到本地
- (void)saveImageToPhotos:(UIImage*)savedImage
{
    UIImageWriteToSavedPhotosAlbum(savedImage, self, @selector(image:didFinishSavingWithError:contextInfo:), NULL);
}
// 指定回调方法
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
    NSString *msg = nil;
    BOOL isSave;
    
    if(error != NULL) {
        msg = @"保存失败!" ;
        isSave = NO;
    }
    else {
        msg = @"保存成功!" ;
        isSave = YES;
    }
    ///代理回调
    [_delegate isSaveImage:image didFinishSavingWithError:error contextInfo:contextInfo];
}

@end

4.获取UIView的截图

//
//  UIView+Screenshots.h
//  Runner
//
//  Created by macmini on 2020/9/16.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

///截图类
@interface UIView (Screenshots)
/**
 普通的截图
 该API仅可以在未使用layer和OpenGL渲染的视图上使用

 @return 截取的图片
 */
- (UIImage *)nomalSnapshotImage;

/**
 针对有用过OpenGL渲染过的视图截图

 @return 截取的图片
 */
- (UIImage *)openglSnapshotImage;

/**
 截图
 以UIView 的形式返回(_UIReplicantView)

 @return 截取出来的图片转换的视图
 */
- (UIView *)snapshotView;

@end

NS_ASSUME_NONNULL_END

//
//  UIView+Screenshots.m
//  Runner
//
//  Created by macmini on 2020/9/16.
//

#import "UIView+Screenshots.h"

@implementation UIView (Screenshots)

/**
 普通的截图
 该API仅可以在未使用layer和OpenGL渲染的视图上使用

 @return 截取的图片
 */
- (UIImage *)nomalSnapshotImage
{
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);
    [self.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return snapshotImage;
}

/**
 针对有用过OpenGL渲染过的视图截图

 @return 截取的图片
 */
- (UIImage *)openglSnapshotImage
{
    CGSize size = self.bounds.size;
    UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale);
    CGRect rect = self.frame;
    [self drawViewHierarchyInRect:rect afterScreenUpdates:YES];
    UIImage *snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return snapshotImage;
    
}

/**
 截图
 以UIView 的形式返回(_UIReplicantView)

 @return 截取出来的图片转换的视图
 */
- (UIView *)snapshotView
{
    UIView *snapView = [self snapshotViewAfterScreenUpdates:YES];
    return snapView;
}


@end

5.获取UIWebView的截图

//
//  UIWebView+Screenshots.h
//  Runner
//
//  Created by macmini on 2020/9/16.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

///截图
@interface UIWebView (Screenshots)

///UIWebView截图
- (UIImage *)imageForWebView;

@end

NS_ASSUME_NONNULL_END

//
//  UIWebView+Screenshots.m
//  Runner
//
//  Created by macmini on 2020/9/16.
//

#import "UIWebView+Screenshots.h"

@implementation UIWebView (Screenshots)

///UIWebView截图
- (UIImage *)imageForWebView
{
    // 1.获取WebView的宽高
    CGSize boundsSize = self.bounds.size;
    CGFloat boundsWidth = boundsSize.width;
    CGFloat boundsHeight = boundsSize.height;
    
    // 2.获取contentSize
    CGSize contentSize = self.scrollView.contentSize;
    CGFloat contentHeight = contentSize.height;
    // 3.保存原始偏移量,便于截图后复位
    CGPoint offset = self.scrollView.contentOffset;
    // 4.设置最初的偏移量为(0,0);
    [self.scrollView setContentOffset:CGPointMake(0, 0)];
    
    NSMutableArray *images = [NSMutableArray array];
    while (contentHeight > 0) {
        // 5.获取CGContext 5.获取CGContext
        UIGraphicsBeginImageContextWithOptions(boundsSize, NO, 0.0);
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        // 6.渲染要截取的区域
        [self.layer renderInContext:ctx];
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        // 7.截取的图片保存起来
        [images addObject:image];
        
        CGFloat offsetY = self.scrollView.contentOffset.y;
        [self.scrollView setContentOffset:CGPointMake(0, offsetY + boundsHeight)];
        contentHeight -= boundsHeight;
    }
    // 8 webView 恢复到之前的显示区域
    [self.scrollView setContentOffset:offset];
    CGFloat scale = [UIScreen mainScreen].scale;
    CGSize imageSize = CGSizeMake(contentSize.width * scale,
                                  contentSize.height * scale);
    // 9.根据设备的分辨率重新绘制、拼接成完整清晰图片
    UIGraphicsBeginImageContext(imageSize);
    [images enumerateObjectsUsingBlock:^(UIImage *image, NSUInteger idx, BOOL *stop) {
        [image drawInRect:CGRectMake(0,scale * boundsHeight * idx,scale * boundsWidth,scale * boundsHeight)];
    }];
    UIImage *fullImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return fullImage;
}


@end

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342