20190523のAndroidに関する記事は6件です。

Cloud Vison APIを使って、笑ってはいけないのアレができるAndroidアプリを作ってみた

だいぶ昔ですが、Googleの「Cloud Vison API」を使って、笑ってはいけないのアレができるAndroidアプリを作ってみたので、今更ながら紹介します。

概要

年末恒例、「笑ってはいけないシリーズ」でおなじみ、笑うと「デデーン」という効果音とともに「○○ OUT」の字幕が出てくるシーンを忠実に再現したアプリです。
使い方は簡単で、カメラで撮影するか、ギャラリーから画像を選択するだけです。
選択した画像について、笑っているかどうかの判定処理が行われ、笑っていると判断された場合、「デデーン」という効果音とともに「OUT」の字幕が表示されます。

result.gif

アニメーションGIFだと音が出ないため、音ありバージョンをYoutubeにアップロードしました。
https://www.youtube.com/watch?v=DZPbPsgpHZw

※Google Playにはリリースしていません。楽しみたい方はGitHubにアップしてますので、そちらをクローンしてセットアップしてください。

開発した経緯

面白そうだったからです笑

開発環境

実装について

まず、Cloud Vision APIを使えるようにするための設定が必要です。
手順に関しては以下の公式ドキュメントや、参考URLを見てください。
Cloud Vision API ドキュメント

また、ソースコードは以下にあるGoogleのサンプルファイルをベースに作成しました。
https://github.com/GoogleCloudPlatform/cloud-vision/tree/master/android/CloudVision

ベースの部分は基本的に同じなので解説は省きます。
キモとなる、画像から笑っているかどうかの判定処理の部分について解説します。
APIを呼び出す部分となるため、以下の通りAsyncTaskで非同期処理を行うようにしました。

CallCloudVisionAsyncTask.java
package jp.hiesiea.app.smiledetector;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.graphics.Bitmap;
import android.media.MediaPlayer;
import android.os.AsyncTask;
import android.util.Log;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.TextView;

import com.google.api.client.extensions.android.http.AndroidHttp;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.vision.v1.Vision;
import com.google.api.services.vision.v1.VisionRequestInitializer;
import com.google.api.services.vision.v1.model.AnnotateImageRequest;
import com.google.api.services.vision.v1.model.BatchAnnotateImagesRequest;
import com.google.api.services.vision.v1.model.BatchAnnotateImagesResponse;
import com.google.api.services.vision.v1.model.FaceAnnotation;
import com.google.api.services.vision.v1.model.Feature;
import com.google.api.services.vision.v1.model.Image;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import jp.hiesiea.app.smiledetecrot.R;

public class CallCloudVisionAsyncTask extends AsyncTask<Void, Void, String> {
    private static final String TAG = CallCloudVisionAsyncTask.class.getSimpleName();

    private static final String SOUND_FILE_NAME = "deden.mp3";

    private static final String TYPE_FACE_DETECTION = "FACE_DETECTION";

    private static final String RESULT_VERY_LIKELY = "VERY_LIKELY";
    private static final String RESULT_LIKELY = "LIKELY";

    private VisionRequestInitializer mVisionRequestInitializer;
    private Bitmap mBitmap;
    private Context mContext;

    private TextView mTextView;
    private ProgressDialog mProgressDialog;
    private Animation mAnimation;
    private MediaPlayer mMediaPlayer;

    private MediaPlayer.OnCompletionListener mOnCompletionListener = new MediaPlayer.OnCompletionListener() {
        @Override
        public void onCompletion(MediaPlayer mediaPlayer) {
            mediaPlayer.stop();
            mediaPlayer.reset();
            mediaPlayer.release();
        }
    };

    private Animation.AnimationListener mAnimationListener = new Animation.AnimationListener() {
        @Override
        public void onAnimationStart(Animation animation) {

        }

        @Override
        public void onAnimationEnd(Animation animation) {
            mTextView.setText("");
            animation.reset();
        }

        @Override
        public void onAnimationRepeat(Animation animation) {

        }
    };

    /**
     * CloudVisionAPI用の非同期処理呼び出し
     * @param visionRequestInitializer
     * @param bitmap
     * @param textView
     * @param context
     */
    public CallCloudVisionAsyncTask(
            VisionRequestInitializer visionRequestInitializer,
            Bitmap bitmap,
            TextView textView,
            Context context) {
        mVisionRequestInitializer = visionRequestInitializer;
        mBitmap = bitmap;
        mTextView = textView;
        mContext = context;
        mProgressDialog = new ProgressDialog(context);
        mAnimation = AnimationUtils.loadAnimation(context, R.anim.translate_animation);
        mAnimation.setAnimationListener(mAnimationListener);
        setUpMediaPlayer();
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        setUpProgressDialog();
    }

    @Override
    protected String doInBackground(Void... voids) {
        try {
            HttpTransport httpTransport = AndroidHttp.newCompatibleTransport();
            JsonFactory jsonFactory = GsonFactory.getDefaultInstance();

            Vision.Builder builder =
                    new Vision.Builder(httpTransport, jsonFactory, null);
            builder.setVisionRequestInitializer(mVisionRequestInitializer);

            BatchAnnotateImagesRequest batchAnnotateImagesRequest =
                    new BatchAnnotateImagesRequest();
            batchAnnotateImagesRequest.setRequests(new ArrayList<AnnotateImageRequest>() {{
                AnnotateImageRequest annotateImageRequest = new AnnotateImageRequest();

                // Add the image
                Image base64EncodedImage = new Image();
                // Convert the bitmap to a JPEG
                // Just in case it's a format that Android understands but Cloud Vision
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                mBitmap.compress(Bitmap.CompressFormat.JPEG, 90, byteArrayOutputStream);
                byte[] imageBytes = byteArrayOutputStream.toByteArray();

                // Base64 encode the JPEG
                base64EncodedImage.encodeContent(imageBytes);
                annotateImageRequest.setImage(base64EncodedImage);

                // add the features we want
                annotateImageRequest.setFeatures(new ArrayList<Feature>() {{
                    Feature labelDetection = new Feature();
                    labelDetection.setType(TYPE_FACE_DETECTION);
                    add(labelDetection);
                }});

                // Add the list of one thing to the request
                add(annotateImageRequest);
            }});

            Vision vision = builder.build();
            Vision.Images.Annotate annotateRequest =
                    vision.images().annotate(batchAnnotateImagesRequest);
            // Due to a bug: requests to Vision API containing large images fail when GZipped.
            annotateRequest.setDisableGZipContent(true);
            Log.d(TAG, "created Cloud Vision request object, sending request");

            BatchAnnotateImagesResponse response = annotateRequest.execute();
            if (checkSmile(response)) {
                mMediaPlayer.start();
                return mContext.getResources().getString(R.string.image_smile_out);
            }
            return mContext.getResources().getString(R.string.image_smile_safe);
        } catch (GoogleJsonResponseException e) {
            Log.d(TAG, "failed to make API request because " + e.getContent());
        } catch (IOException e) {
            Log.d(TAG, "failed to make API request because of other IOException " +
                    e.getMessage());
        }
        return "Cloud Vision API request failed. Check logs for details.";
    }

    @Override
    protected void onPostExecute(String result) {
        super.onPostExecute(result);
        mTextView.setText(result);
        mTextView.startAnimation(mAnimation);
        mProgressDialog.dismiss();
    }

    /**
     * 笑顔判定
     * @param response
     * @return
     */
    private boolean checkSmile(BatchAnnotateImagesResponse response) {
        List<FaceAnnotation> faceAnnotations = response.getResponses().get(0).getFaceAnnotations();
        if (faceAnnotations == null) {
            return false;
        }

        for (FaceAnnotation faceAnnotation : faceAnnotations) {
            printLog(faceAnnotation);
            if (faceAnnotation.getJoyLikelihood().equals(RESULT_VERY_LIKELY)
                    || faceAnnotation.getJoyLikelihood().equals(RESULT_LIKELY)) {
                return true;
            }
        }
        return false;
    }

    /**
     * プログレスダイアログの設定および表示
     */
    private void setUpProgressDialog() {
        mProgressDialog.setMessage(mContext.getResources().getString(R.string.loading_message));
        mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        mProgressDialog.setCancelable(false);
        mProgressDialog.show();
    }

    /**
     * 音楽ファイルを再生するための設定
     */
    private void setUpMediaPlayer() {
        mMediaPlayer = new MediaPlayer();
        mMediaPlayer.setOnCompletionListener(mOnCompletionListener);
        try (AssetFileDescriptor assetFileDescriptor = mContext.getAssets().openFd(SOUND_FILE_NAME)) {
            mMediaPlayer.setDataSource(assetFileDescriptor.getFileDescriptor(),
                    assetFileDescriptor.getStartOffset(),
                    assetFileDescriptor.getLength());
            mMediaPlayer.prepare();
        } catch (IOException e) {
            Log.e(TAG, e.getMessage());
        }
    }

    /**
     * 各パラメータのログ出力
     * @param faceAnnotation
     */
    private void printLog(FaceAnnotation faceAnnotation) {
        Log.d(TAG, "getAngerLikelihood : " + faceAnnotation.getAngerLikelihood());
        Log.d(TAG, "getBlurredLikelihood : " + faceAnnotation.getBlurredLikelihood());
        Log.d(TAG, "getHeadwearLikelihood : " + faceAnnotation.getHeadwearLikelihood());
        Log.d(TAG, "getJoyLikelihood : " + faceAnnotation.getJoyLikelihood());
        Log.d(TAG, "getSorrowLikelihood : " + faceAnnotation.getSorrowLikelihood());
        Log.d(TAG, "getSurpriseLikelihood : " + faceAnnotation.getSurpriseLikelihood());
        Log.d(TAG, "getUnderExposedLikelihood : " + faceAnnotation.getUnderExposedLikelihood());
    }
}

笑っているかどうかの判定処理は、checkSmileメソッドで行なっています。
FaceAnnotation#getJoyLikelihood()から、喜びの度合いを取得できます。
そして、喜びの度合いが「VERY_LIKELY(非常に高い)」、または「LIKELY(高い)」の場合に、笑っているものとみなしています。
FaceAnnotationに関しては、以下の公式ドキュメントを参照してください。

https://cloud.google.com/vision/docs/detecting-faces?hl=ja#vision-face-detection-java

GitHub

https://github.com/hiesiea/SmileDetector

参考URL

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TWA(Trusted Web Activity) アプリを aab(Android App Bundle) でリリースするときは気をつけてください!

はじめに

TWA は、 PWA サイトのアプリ版を簡単に作れる便利なやつです。

TWA の仕組みとして、 PWA サイト側に 「信頼して良いか」 を判断するための設定ファイル .well-known/assetlinks.json を配置する必要があります。

今回その設定ファイルの値を間違っていたせいでハマったので、ぼくと同じ人が現れないように記事にしておきます。
誰かの助けになれば幸いです ?

気をつけること

.well-known/assetlinks.jsonsha256_cert_fingerprints の値

どうするのか

スクリーンショット 2019-05-23 13.07.36.png

Google Play Console - リリース管理 - アプリの署名 で、上記画像の該当箇所を確認してください。
これを間違えると、URLバーが永遠に出続けてしまいます。。

似たような見た目で 「アップロード証明書」 という項目がありますがソチラではありません!
(aab ファイルでリリースしていない場合は画像のような画面は表示されないようです。)

(おまけ) apk でリリースする場合

keytool -list -v -keystore Keystoreファイルパス -alias エイリアス名 -storepass パスワード -keypass パスワード

上記コマンドで出力される SHA256 の値を設定すれば良いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google Playを使ったテストに関して[アルファ版、ベータ版、リリース版]

前置き

この記事では、Google Play Consoleを利用したテストを行う際に選択する必要があるアルファ版、ベータ版、リリース版といったアプリバージョンの違いや、内部テスト、クローズドテスト、オープンテストといったテスト方法の違いについてまとめています。
初めてテストを含めてアプリをリリースする方や、実際にリリースは行わないが、プロセスを確認しておきたい方などの参考になれば幸いです。
アプリリリース方法やテストの設定方法等の具体的な手順は、下記にあります他の方々の記事に非常に分かりやすく詳細に記載いただいていますのでそちらをご参照ください。

Google Play での Android アプリの配信方法(インストールとアップデート)を試してみる
Google playでのベータ版配布機能について

Androidアプリのバージョンについて(アルファ版、ベータ版、リリース版)

Google Play上にアプリをリリースするにはGoogle Play Consoleを利用します。Google Play Consoleでは下記の3つのバージョンを選択することができます。リリース版に移行するまでにテストを行い、品質を改善することが目的です。

アルファ版

アプリの安定性が最も低い試験的なバージョン。テストのグループは人数を少なくする。

ベータ版

アプリのリリース版に近い安定したバージョン。テストのグループは人数を多くする。

リリース版

最も安定したバージョン。テストは完了していることが前提です。
出典:アプリのベータ版テストを実施し、初期段階の貴重なフィードバックを得る/適切なテストの種類を選択します。

テストについて(内部テスト、クローズドテスト、オープンテスト)

アルファ版、ベータ版のテストは、テスターの要件に沿ってテスト方法を下記の3つから選択することができます。

内部テスト

  • 開発者自身や品質保証部門など、限られた少数のテスターによるテストを行う場合に選択します。
  • 限られた少数のテスターのみがGoogle Playストアからアプリをインストールできます。
  • テスターはGoogleアカウントかG Suiteアカウントによって登録されます
  • テスターは、有料アプリであっても無料でインストールできます

有料アプリ: オープンテスト版またはクローズド テスト版を使用して有料アプリをテストする場合でも、テスターはアプリを購入する必要があります。内部テスト版を使用して有料アプリをテストする場合、テスターは無料でアプリをインストールできます。

出典:オープンテスト版、クローズド テスト版、内部テスト版をセットアップする

他のテストと大きく違う点は、リリースからテスターが利用できるようになるまでの時間の短さです。数分でテスターをGoogle Playストアからアプリをインストールできるようになります。
他のテストの場合だと、数十分ないし、数時間待つ必要があります。

クローズドテスト

  • 社員や信頼できるユーザーなどの小規模なグループでテストを行う場合に選択します。
  • 内部テストより多くの限定されたテスターが、Google Playストアからアプリをインストールできます。
  • テスターはGoogleアカウントかG Suiteアカウントによって登録されます。
  • テスターは、有料アプリは購入する必要があります

なお、内部テスト、クローズドテストにおいてテスターに登録されていないGoogleアカウントからのアクセスしてもアプリをインストールすることはできません。
image.png

オープンテスト

  • 大規模なグループでテストを行う場合に選択します。
  • クローズドテストより多くの不特定多数のテスターが、Google Playストアからアプリをインストールできます。
  • テスターは、有料アプリは購入する必要があります。

他のテストと大きく違う点はテスターの範囲です。内部テスト、クローズドテストテストはGoogleアカウントまたは、G Suiteアカウントにより明示的にテスターを登録します。対してオープンテストはテスターの最大数を設定するのみで明示的にテスターを指定することはありません。
URLを知るすべてのユーザーがテスターとしてテストを実施することができます。
image.png

各バージョンが選択できるテストの違い

アルファ版、ベータ版で利用できるテストが異なります。
なお、リリース版はテストが完了していることが前提となります。

内部テスト クローズドテスト オープンテスト
アルファ版 ○→✕
ベータ版 ○→✕

赤字の箇所は以前は選択可能でしたが、2019年現在では選択できなくなっています。前述のアルファ版、ベータ版の位置づけに基づいた変更だと思われます。下記 Android Developers中の記載です。

オープン アルファ版やクローズド ベータ版のテストを作成することはできなくなりました。すでに開始されている既存のオープン アルファ版やクローズド ベータ版テストには引き続きアクセスできます。
出典:オープンテスト版、クローズド テスト版、内部テスト版をセットアップする

各テストにおけるテスター範囲の違い

トラック1 リスト2 テスター
内部テスト 1件/アプリ 1件/トラック 100人/リスト
クローズドテスト 50件/アプリ 50件/トラック 2000人/リスト
オープンテスト 1件/アプリ - 1000人以上

オープンテストにおいて、テスターを明示的に登録しないため、リストはありません。
またリストには個人を表すGoogleアカウントを登録または、複数人を表すGoogleグループを登録することも可能です。
Googleグループ内の人数に制限はありません。

Google グループや Google+ コミュニティでテスターを管理したりできます。Google グループや Google+ コミュニティに人数制限はありません。

出典:開発チーム向けに追加のクローズド テスト版トラックを作成する

なお、以前は、Google+コミュニティもGoogleグループ同様にリストに登録可能でしたが、2019年現在では選択できなくなっています。これはGoogle+が終了することに基づいていると思われます。

注: 2019 年に予定されている Google+ の終了に伴い、クローズド テスト版のトラックに Google+ コミュニティを追加できなくなりました。Google+ コミュニティを使用してテスターを管理するようにすでに設定している場合は、移行期間中も引き続きご利用いただけます。Google+ コミュニティのテスターを Google グループに移行するか、代わりにメールアドレスでテスターを管理することをおすすめします。

出典:クローズド テスト版: Google グループでテスターを管理する

テスト・リリースを実際に行う前の概要理解に役立てば幸いです。

参考文献

オープンテスト版、クローズド テスト版、内部テスト版をセットアップする
アプリのベータ版テストを実施し、初期段階の貴重なフィードバックを得る


  1. テスト対象のアプリを格納する場所。機能によってバグに対処する開発チームが異なる場合などに、並列して対処するため、追加トラックを設置するといった運用を行う。 

  2. テスターを登録するユーザーリスト。Googleアカウントで個々人を登録することも、Googleグループで複数をまとめて登録することも可能。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Linux/Ubuntu Androidアプリを実機で起動

はじめに

  • Pixel 3aを買ったのでAndroidアプリを実機で起動したかったけどエラーになった
  • 初めての作業だったのでメモっておく
  • ほぼここに書いてある https://developer.android.com/studio/run/device

Andoird端末を開発者モード、USBデバッグオンにする

- 設定 -> 端末情報 -> ビルド番号 を7回タップして開発者モードを表示させる
- 設定 -> システム -> 開発者向けオプション -> USBデバッグ をオンにする
- USBデバッグ許可のダイアログが出たら許可する

ダイアログがでなければUSB抜き差しすれば良さそう

ルートで以下ファイルを作成する

$ sudo touch /etc/udev/rules.d/51-android.rules
# 以下を記述
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", MODE="0666", GROUP="plugdev"
$ chmod a+r /etc/udev/rules.d/51-android.rules

18d1部分はGoogleのベンダーID、上に貼ったページに書いてある
Android端末がUSBに繋がっていればlsusbでも確認できる
18d1:4ee7この部分の:区切り左側の値

$ lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 002: ID 1038:137c SteelSeries ApS 
Bus 001 Device 005: ID 0853:0110 Topre Corporation 
Bus 001 Device 004: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
Bus 001 Device 003: ID 18d1:4ee7 Google Inc. 
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Android StudioでDevice選択が対象の端末になっていればRunボタンをクリックで実機でアプリ起動するはず
Android Studioの再起動などがいるかも

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CameraXのCodelab試してみた

概要

I/Oで発表があったCameraX。Codelabsで公開されていたものを試してみました。ちょっとハマったとこがあったので、メモがてら書いておきます。
Getting Started with CameraX

というのも、I/O Extended 2019 Tokyo@GDG にてCodelabsの課題が上がっていたからです笑
当日はセッションに興味がありCodelabには参加しませんでしたが、また機会があれば参加してみたい。

Codelabの流れ

実際のCodelabをみてもらえたらいいと思うんですが、簡単に流れを書いておきます。Codelabのセクションとはちょっと変えてます。

  1. CameraXをgradleに追加
  2. view finder layoutを準備
  3. Cameraのパーミッションを付与
  4. Permission許可&チェックのコードを記述
  5. view finder(use case)を実装
  6. image capture(use case)を実装
  7. image analysis(use case)を実装

ポイント

  • use caseを作る。

    • Preview(view finder): プレビュー
    • ImageCaputure: 画像保存
    • ImageAnalysis: 画像解析
  • 作ったuse caseを、CameraX.bindToLifecycle(this, preview, imageCapture, analyzerUseCase) でlifecycleOwner(Activity)にbindする

ハマったところ

プレビューが縦に潰れる。。

before.kt
val previewConfig = PreviewConfig.Builder().apply {
        setTargetAspectRatio(Rational(1, 1))
        setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
    }.build()

アスペクトをが1:1で設定しているのに、プレビューが縦に崩れる。。。

結局崩れるのはなぜかわからなかったんですが、公式のサンプル
の通りに実際のViewのディスプレイサイズを取得し設定したら治った。

after.kt
val metrics = DisplayMetrics().also { viewFinder.display.getRealMetrics(it) }
val screenAspectRatio = Rational(metrics.widthPixels, metrics.heightPixels)
val screenSize = Size(metrics.widthPixels, metrics.heightPixels)

val previewConfig = PreviewConfig.Builder().apply {
        setTargetAspectRatio(screenAspectRatio)
        setTargetResolution(screenSize)
    }.build()

Capture時に保存された画像は正常だったところを見ると、Viewに反映される際になにか問題があるのかも。時間が許せばもっと探ってみようかな。。

所感

lifecycleにbindでき、自分で面倒を見ないでいいのがすごく楽だと感じました。
もうすこしドキュメント見て、触ってみようと思えました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CameraXのCodelabs試してみた

概要

I/Oで発表があったCameraX。Codelabsで公開されていたものを試してみました。ちょっとハマったとこがあったので、メモがてら書いておきます。
Getting Started with CameraX

というのも、I/O Extended 2019 Tokyo@GDG にてCodelabsの課題が上がっていたからです笑
当日はセッションに興味がありCodelabsには参加しませんでしたが、また機会があれば参加してみたい。

実装の流れ

実際のCodelabsをみてもらえたらいいと思うんですが、簡単に流れを書いておきます。Codelabsのセクションとはちょっと変えてます。

  1. CameraXをgradleに追加
  2. view finder layoutを準備
  3. Cameraのパーミッションを付与
  4. Permission許可&チェックのコードを記述
  5. view finder(use case)を実装
  6. image capture(use case)を実装
  7. image analysis(use case)を実装

ポイント

  • use caseを作る。

    • Preview(view finder): プレビュー
    • ImageCaputure: 画像保存
    • ImageAnalysis: 画像解析
  • 作ったuse caseを、CameraX.bindToLifecycle(this, preview, imageCapture, analyzerUseCase) でlifecycleOwner(Activity)にbindする

ハマったところ

プレビューが縦に潰れる。。

before.kt
val previewConfig = PreviewConfig.Builder().apply {
        setTargetAspectRatio(Rational(1, 1))
        setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
    }.build()

アスペクトをが1:1で設定しているのに、プレビューが縦に崩れる。。。

結局崩れるのはなぜかわからなかったんですが、公式のサンプル
の通りに実際のViewのディスプレイサイズを取得し設定したら治った。

after.kt
val metrics = DisplayMetrics().also { viewFinder.display.getRealMetrics(it) }
val screenAspectRatio = Rational(metrics.widthPixels, metrics.heightPixels)
val screenSize = Size(metrics.widthPixels, metrics.heightPixels)

val previewConfig = PreviewConfig.Builder().apply {
        setTargetAspectRatio(screenAspectRatio)
        setTargetResolution(screenSize)
    }.build()

Capture時に保存された画像は正常だったところを見ると、Viewに反映される際になにか問題があるのかも。時間が許せばもっと探ってみようかな。。

所感

lifecycleにbindでき、自分で面倒を見ないでいいのがすごく楽だと感じました。
もうすこしドキュメント見て、触ってみようと思えました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む