18
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Flutterで画面のスクリーンショットを撮る

Posted at

Flutterで描画している画面をどうにかしてプログラムからキャプチャーできないかなー?と思ってFlutterのドキュメントを読み漁っていたら、なんかそれっぽいクラスを見つけました。

SceneはFlutterで描画される各フレームを表していて、SceneBuilderはSceneを組み立てるためのクラスらしいです。

じゃあFlutterの中でこれらを使ってる箇所はどこ?と思ってFlutterのコードを調べてみると……ありました。view.dartの中です。

import 'dart:ui' as ui;

//...中略

final ui.SceneBuilder builder = ui.SceneBuilder();
final ui.Scene scene = layer.buildScene(builder);
if (automaticSystemUiAdjustment)
  _updateSystemChrome();
ui.window.render(scene);

ui.window.renderが実際の画面への描画を行う処理っぽいです。

ところで、SceneクラスにはtoImageというメソッドがあります。これを使えばSceneの内容を画像として取得できそうな気がします。
上記のコードはRenderViewというクラスのメソッドで定義されています。じゃあRenderViewってどっから取ってくればいいの?と思って読み進めていくと……なんとRendererBinding.instance.renderViewでアクセスできそうです。

ということは、こんな感じの処理をすればスクリーンショットが撮影できるのではないか…??

import 'dart:async';
import 'dart:io';
import 'dart:typed_data';

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

/// ByteDataを指定したパスのファイルに保存する。
/// Thanks to https://stackoverflow.com/questions/50119676/how-to-write-a-bytedata-instance-to-a-file-in-dart
Future<void> writeToFile(ByteData data, String path) {
  final buffer = data.buffer;
  return File(path).writeAsBytes(buffer.asUint8List(data.offsetInBytes, data.lengthInBytes));
}

Future<void> screenCapture(String path) async {
  var builder = ui.SceneBuilder();
  var scene = RendererBinding.instance.renderView.layer.buildScene(builder);
  var image = await scene.toImage(ui.window.physicalSize.width.toInt(),
      ui.window.physicalSize.height.toInt());
  scene.dispose();

  var data = await image.toByteData(format: ui.ImageByteFormat.png);
  await writeToFile(data, path);
}

実際にやってみた

lib/main.dart
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;

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

void main() => runApp(MyApp());

Future<void> writeToFile(ByteData data, String path) {
  final buffer = data.buffer;
  return File(path)
      .writeAsBytes(buffer.asUint8List(data.offsetInBytes, data.lengthInBytes));
}

void capture({@required String path}) async {
  var builder = ui.SceneBuilder();
  var scene = RendererBinding.instance.renderView.layer.buildScene(builder);
  var image = await scene.toImage(ui.window.physicalSize.width.toInt(),
      ui.window.physicalSize.height.toInt());
  scene.dispose();

  var data = await image.toByteData(format: ui.ImageByteFormat.png);
  await writeToFile(data, path);
}

class HomePage extends StatefulWidget {
  @override
  HomePageState createState() {
    return HomePageState();
  }
}

class HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Draw test'),
      ),
      body: Center(
        child: RaisedButton(
          onPressed: () {
            capture(path: '/sdcard/image.png');
          },
          child: Text('Hello world'),
        ),
      ),
    );
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomePage(),
    );
  }
}

※外部ストレージ(/sdcard/以下)にファイルを書き出すために、AndroidManifest.xmlに<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>を追加しました。Runtime Permissionを処理するのが面倒だったので、build.gradleのtargetSdkVersionを21にしました。

実行した結果がこちらです。(アプリを実行してHello worldボタンをタップして、adb pull /sdcard/image.pngで端末のファイルを抜き出す)
Hello worldボタンを押した時点でのフレームが描画されていますね!
image.png

18
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?