- 投稿日:2021-04-05T23:09:11+09:00
android ボタンを押すとテキストを変える実装
ただのリハビリ備忘録です。 学生時代に触っていたAndroidStudioをを久しぶりに触る 2.x系時代にしか触ってなかったもんで久しぶりにみたら4.x系になってびっくりです。 そんな話は置いておいて久しぶりにAndroidStudio触ったらものの見事に色々忘れているので思い出していく。3、4年ぶりでリハビリ〜〜。 とりあずjavaで書いていきます。(kotolinはわかりません) アプリの作り方などの細かい設定は省略します。 初期MainActivity シュミレータ側の初期画面 プロジェクトを生成したら最初からHelloWorld!!が出力されるようになっています。 初期レイアウト画面 android/res/layout/activity_main.xml ボタン実装 先程のレイアウト画面からボタンを設定していきましょう。 GUIからボタンを配置することも可能ですし、コードで直接書くこともできます。 私は全く覚えていないのでGUIからやっていきます ボタンを配置 シュミレータ画面 MainActivityにボタンの設定を書いていく 下記がボタンを呼び出す宣言文です。 Button button = findViewById(R.id.button); 自分で書いてて何これってなったんで、1つずつ意味を調べたら下記のようになった。 Button型 変数名 = ボタンが定義されているレイアウト(レイアウトで定義したボタンのid名) ボタンのidを定義 ボタンをタップした状態を判定してテキストを変化させる 利用するのは下記4つ OnClickListener()::ボタンを押したぞ判定を行う TextViewコンポーネント::テキストを表示させる booleanの変数 ::ボタンを変化でifを使ってテキストをだし分ける 実際にダァーっと書いたらこんな感じ *一応onClick()はラムダでもかけるらしい すげー進化してるー! 実際に動かしてみるとこんな感じ まとめ プロジェクト作成 ボタンをレイアウトから作成 MainActivityにボタンの型とfindViewByIdを定義 ボタンのOnclickリスナー定義 ボタン変更に必要な変数と、textViewコンポーネントを定義 思ったことをベラベラと書いてしまった。まぁ自分がみるからいいのだが。。。。。なげぇ。。
- 投稿日:2021-04-05T22:39:58+09:00
スマートバナーでスマホWebからアプリへの導線をつくってみた - iOS, Android 両対応-
はじめに WEBアプリのモバイル化に成功した!...なのになぜだ!?全然インストールされない!! 最近まさにそんな経験をしたんですが、ユーザの立場でよくよく考えてみると、アプリのインストールに至るにはいくつか障壁があるということに気が付きました。 リリースされたアプリの存在にそもそも気が付かない アプリのインストールがめんどくさい(ストアいってアプリ名検索して...など) なるべく障壁を低くしてユーザにアプリをインストールしてもらえる方法なんてあるのかよ...と頭を抱えたのですが、少しググったら?な解決策をみつけてしまいました。 それがスマートバナーです。 これ↓ 誰でも1回はみたことあると思います。 WEB版のアプリをスマホブラウザで開くと上部に表示されるアレですね。 というわけで今回はiOS, Androidでスマートバナーを出す方法をまとめました。 「WEB版からアプリへの導線を簡単につくりたい」 「iOSとAndroidの両方で導線をつくりたい」 という方に役立つ記事になると思います。 Safari(iOS) Safari(iOS)版のスマートバナーは、なんとAppleが公式で機能をだしています。 しかも使い方は超簡単。 以下のmetaタグをスマートバナーを表示したい画面のheadタグに埋め込むだけです。 <!-- Safari SmartBanner --> <meta name="apple-itunes-app" content="app-id=[app-id]"> appleのdeveloperアカウントで、自分のアプリの9-10桁のapp-idを確認することができるので、そこからコピペして利用します。 Apple公式スマートバナーのメリットとデメリットは以下になります。 メリット アプリのインストール有無によって表示が変わる(入手 - App Store or インストール済) 表示によってタッチしたときの導線が変わる(インストール済の場合はアプリを開く) app-idを指定するだけでアイコンやアプリ名を自動入力してくれる デメリット Androidでは使えない シミュレータには表示されないので実機での動作確認が必要 Android Androidの場合、iOSと異なり公式のスマートバナーは存在しません。 そのため、サードパーティのライブラリを使用する必要があります。 今回はこちらのライブラリを使用しました。 肝心の使い方なのですが、Safafiのときと同様にmetaタグをheadタグに埋め込むだけです。 <!-- Android SmartBanner --> <meta name="smartbanner:title" content=[アプリ名]> <meta name="smartbanner:author" content=[会社名]> <meta name="smartbanner:price" content=[アプリの値段]> <meta name="smartbanner:price-suffix-google" content=" - Google Play"> <!-- Android用suffix --> <meta name="smartbanner:icon-google" content=[アイコン画像のパス]> <meta name="smartbanner:button" content="開く"> <!-- ボタンの表示名 --> <meta name="smartbanner:button-url-google" content=[Google PlayのアプリURL]> <meta name="smartbanner:enabled-platforms" content="android"> <!-- スマートバナーを利用するOSの指定 --> <meta name="smartbanner:close-label" content="Close"> <link rel="stylesheet" href="css/smartbanner.min.css"> <script src="js/smartbanner.min.js"></script> これでスマートバナーが以下のように表示されます。 サードパーティのスマートバナーのメリットとデメリットは以下になります。 メリット 表示する文をカスタマイズすることができる AndroidとSafariの両方で使える デメリット インストール有無で表示ステータスを変えられない インストール済の場合でもアプリを直接開けない(ストアへの導線だけ) おわりに スマートバナーの導入を検討されている方は、こちらの記事を参考にしていただければ幸いです。
- 投稿日:2021-04-05T21:00:38+09:00
VelocityTrackerを使うときはオフセットに気をつけなさいって話
タッチ操作でViewなどを動かす場合、単にドラッグに合わせて動かすだけでなく、指を離す前の移動速度を計算し、慣性動作をさせると操作性が上がりますね。 AndroidではVelocityTrackerを使うことで、操作の速度を簡単に計算することができます。 ざっくり言うと以下のように使います。 private var velocityTracker: VelocityTracker? = null override fun onTouchEvent(event: MotionEvent): Boolean { val velocityTracker = velocityTracker ?: VelocityTracker.obtain().also { velocityTracker = it } when (event.action) { MotionEvent.ACTION_DOWN -> { velocityTracker.clear() velocityTracker.addMovement(event) } MotionEvent.ACTION_MOVE -> { velocityTracker.addMovement(event) } MotionEvent.ACTION_UP -> { // 引数はms、1000を渡すとpixel/sの速度が得られる velocityTracker.computeCurrentVelocity(1000) val vx = velocityTracker.xVelocity val vy = velocityTracker.yVelocity // ここで計算した速度を使って慣性動作させる velocityTracker.recycle() this.velocityTracker = null } MotionEvent.ACTION_CANCEL -> { velocityTracker.recycle() this.velocityTracker = null } } return true } View自体が動いている場合はオフセット計算が必要 しかし、View自体が動いている場合は、オフセット計算が必要です。 私は、毎回オフセット計算を忘れて時間を無駄にしております。頻繁に使うものでもないですしね。。 ViewのonTouchEventやOnTouchListenerのonTouchに渡されるMotionEventの座標は、そのViewの左上を原点とする相対座標系に変換されています。VelocityTrackerはこの相対的な座標を元に速度を計算しますので、このタッチ操作によって移動しているViewのMotionEventを渡してしまうと、操作の移動速度ではなく、タッチとViewの移動のズレだけを拾うことになるので、おかしな値が計算されてしまいます。 というわけで、親Viewの座標系に変換してVelocityTrackerに渡すか private fun VelocityTracker.addAbsoluteMovement(v: View, event: MotionEvent) { val dx = v.x val dy = v.y event.offsetLocation(dx, dy) addMovement(event) event.offsetLocation(-dx, -dy) } raw情報を元に、絶対座標に変換してVelocityTrackerに渡す必要があります。 private fun VelocityTracker.addAbsoluteMovement(event: MotionEvent) { val dx = event.rawX - event.x val dy = event.rawY - event.y event.offsetLocation(dx, dy) addMovement(event) event.offsetLocation(-dx, -dy) } 注意点としては、offsetLocationで座標系を変換した場合、必ず逆変換を行って元に戻しておきましょう。 以上です。
- 投稿日:2021-04-05T10:24:22+09:00
書籍メモアプリ「PickBook」をFlutterでリリースした【個人開発】
概要 アプリの説明 作ろうと思ったきっかけ アプリの機能について リリース準備について 現状・今後実現していきたいこと、意気込み Flutter頑張ってますtomoと申します。 自分と同じようにアプリを作る方またこれからアプリを作りたいと思っている方に向けて何かのヒントになれば嬉しいです! リリースしたアプリについて PickBookという本の記録を簡単にできるアプリになります。 特徴 バーコードで簡単に書籍登録 テキスト認識を利用して写真を取るだけで簡単にメモ こちらのアプリは今年の初めの方にリリースしました。(Qiita記事書くのサボった) 数字伸びてないので供養気味(PR気味)に紹介しようと思います。 このアプリを作ろうと思ったきっかけ 僕はよく電車の中で本を読むのですが本の内容をちょっとメモしたい時に手間がかかるという課題がありました。 「ペンで書くのは煩わしい」 「本をひらきながらスマホでテキストを打ちにくい」 などなど。 電子書籍にすれば解決はしますが、紙の書籍の方が好きなのでこの課題を解決できるようにアプリを作ろうと思ったのがきっかけです。 アプリの機能について このアプリはFlutterで作成し、アーキテクチャはChangeNotifier + Providerで構築しています。 DBはローカルのSQliteを使用しました。 このアプリの主な機能は二つあります バーコードで簡単に書籍登録 テキスト認識を利用して写真を取るだけで簡単にメモ この二つの機能の実装についてコードを交えつつ説明していきます。 バーコードで書籍登録 このアプリではバーコードを読み取るだけで書籍登録ができるようになっています。 コンセプトとして簡単な操作だけで使えるようにするという目的があったので、この機能を実装しました。 実装方法 実装自体はとてもシンプルで簡単に行うことができました。 実装手順は以下のようになっています。 packageを利用してバーコードをスキャンしISBNコードを取得 Google Books APIに取得したISBNコードを投げる 結果を元にDBに保存 バーコードスキャン バーコードのスキャンにはbarcode_scanという以下のパッケージを利用しました。 barcode_scan | Flutter Package とても簡単にバーコードのスキャンが可能になります。 以下コードになります BookModel.dart Future<void> scanBook() async { try { ScanResult result = await BarcodeScanner.scan(); //キャンセルを弾く if (result.type != ResultType.Cancelled) { //Repository層へ結果を渡す final book = await _bookRepository.fetchBookfromIsbn(result.rawContent); _scanedBook = book; } else { throw ('canceled'); } } この処理はBookを管理するViewModelの一部の処理です。 バーコードスキャンを行い、その結果をRepository層に渡しています。 やっていることは非常にシンプルで、 ScanResult result = await BarcodeScanner.scan(); この処理を行うだけで、カメラが起動しバーコードをスキャンすることができます。 非常にシンプルですね。 あとはresult.rawContentが実際のIsbnコードを持っているので、それを渡してあげるだけです Google Books APIへリクエストを投げる 書籍検索はGoogle Books APIを利用しました。 Google Books API 利用のための登録等も特になく簡単に書籍検索が行えます。 より詳しい利用法に関しては以下の記事が参考になるかと思います。 Google Books API からJSONデータを取得してjQueryで処理する 以下コードになります class BookRepository { BookAPI get _bookAPI => BookAPI(); Future<Book> fetchBookfromIsbn(String isbn) async { final result = await _bookAPI.fetchBookfromIsbn(isbn); if (result == null) { throw ("book not found"); } final node = json.decode(result); return Book.fromJsonResult(node['items'][0]['volumeInfo']); } } class BookAPI { Future<String> fetchBookfromIsbn(String isbn) async { final String url = 'https://www.googleapis.com/books/v1/volumes?q=isbn:'; final result = await http.get(url + isbn); return result.body; } } こちらも非常にシンプルでバーコードスキャンで渡ってきたIsbnコードをGoogleBooksAPIに指定して投げてあげると書籍のデータが返ってくるのでそれを用意したオブジェクトに変換して返しています。 中身のデータに関しては以下をみてみてください。 テキスト認識で簡単にメモ テキスト認識に関してはFirebase MLで実装しました。 特に難しいことはなく(なかったはず)実装できました。 以下実装手順です ImagePickerで画像を取得 画像を認識させてテキストに起こす ImagePickerで画像を取得 image_pickerという以下のパッケージを利用しました image_picker | Flutter Package こちらのパッケージに関してはFlutter触られてる方なら一度は使ったことあるんじゃないかなと思います。 final pickedFile = await picker.getImage(source: ImageSource.camera); これでカメラが起動し画像を取得できます。簡単ですね。 Firebase MLでテキスト認識させる テキスト認識はFirebase MLを利用しました。以下のパッケージを利用することでFirebase MLの画像認識を使うことができます。 firebase_ml_custom | Flutter Package 事前に学習されたモデルがあるのでこれを使えば、自分で学習させることなく数行のコードで実装することができました。Firebase便利! 以下コードになります Future<String> _scanImage() async { //Firebase MLのインスタンス生成 cloudDocumentTextRecognizerは文字認識用 final DocumentTextRecognizer _documentTextRecognizer = FirebaseVision.instance.cloudDocumentTextRecognizer(); //image_pickerで画像を取得 final pickedFile = await picker.getImage(source: ImageSource.camera) // 画像を設定 final FirebaseVisionImage visionImage = FirebaseVisionImage.fromFile(pickedImage) // テキスト認識させる final VisionDocumentText _scanResults = await _documentTextRecognizer.processImage(visionImage); // 認識結果 return _scanResults.text; } これだけで画像を元にしたテキスト認識が可能になります。しっかり文字が目で確認できる画像であればテキスト認識を精度高くしてくれます。 あとはこれをRichTextに入れ、コピー可能にしてフォームに入力させる形にしました。 UIは以下のように実装しています。 ※ 実際の書籍の画像を利用しているので著作権保護のためにぼかしを入れてます これでテキストをなぞってコピーし、以下のフォームにペーストするだけで本の内容がメモできます!やった! リリース準備について リリース準備は以下のことを行いました。 アプリアイコン・スクリーンショットの作成 ストア申請 アイコン・スクリーンショット画像の作成 アイコン アイコンやスクリーンショット画像の作成はいつもCanvaというデザインツールを利用して作成しています。 アイコンは以下のようにしました。今回は特に何も考えず作ってしまったので、いまいちなアイコンになってます。(いつか直したい) アプリアイコン作成時のテンプレとかあると個人開発する上で便利そう。 あとはこれをflutter_launcher_iconsというパッケージを利用してpubspec.yamlで指定してあげて完了です。 スクリーンショット スクリーンショット画像も同じようにCanvaを利用して作っていますが、スマートフォンのモックアップ画像の作成だけはFigmaというデザインツールを使っていつも作成しています。 スマートフォンの外枠のみのテンプレートが落ちてるので、アプリのスクリーンショットをとってその枠にはめるだけでいい感じのモックアップ画像ができます。 あとはそのモックアップ画像をCanvaに入れてテキストや背景色を整えて完成です。割と簡単にそこそこの画像が作成できます。 ストア申請 今回はAndroid版のみの配信になるので、Google play consoleの登録準備を行いました。 これまで何回かアプリをリリースしてきましたが、いまだになれません。 以下のページとかをみて調べながら審査の提出をしています。 現状・今後の意気込み アプリをストアに登録して3ヶ月ほどたちましたが、いまだに全然ダウンロードされていません。 こういうツール系のアプリは競合もたくさんいるのでしっかりPRだったりASO対策をしないとインストール数を伸ばすのは難しいですね。 ただ、今まで作ったアプリは自分が継続的に使うことはなかったんですが、今回は僕が課題に感じていることに対するアプリなので自分でもよく利用していて、非常に便利だなと感じています。 もし僕と同じような課題を抱えている方がいましたら是非「 PickBook 」をダウンロードして使ってみてください! 個人開発でアプリを作る時はいつもアイデアから考えていましたが、今回のアプリで改めてユーザーの課題からしっかり考えてアプリのアイデアを作るということはとても大切だと感じました。 また、その課題が自分が抱えている物ならなお良いです。今回のように最悪インストールが伸びなくても自分が使える物になるので。 これからも個人開発する際は自分の身の回りに課題と感じていることはないかということを日々探しながらアプリを作っていきたいです! 最後に 今回はFlutterで作った本の記録アプリを紹介しました! コードとかで間違っているところがあればコメントで教えてください! また感想などをコメントいただけると嬉しいです! 今後ともFlutterを使った開発頑張っていくのでよろしくお願いします!
- 投稿日:2021-04-05T09:58:04+09:00
Firebase A/B Testing(RemoteConfig)でテスト対象者/非対象者を見分ける方法
やりたいこと Firebase A/B Testing を使って AB テストを実施する際、当然ですかユーザーが A なのか B なのかを見分ける方法は簡単です。しかし、ユーザーがテスト対象者なのか、非対象者なのかを見分ける方法を探すのに少し苦労したのでメモしておきたいと思います。 もし間違っている箇所などありましたらご指摘ください?♂️ テスト対象者/非対象者を見分ける方法 結論から書きます。 /** * AB テスト対象者かどうか判断する。 */ fun isAbTestUser(): Boolean { val remoteConfig = FirebaseRemoteConfig.getInstance() val value = remoteConfig.getValue("variant_key") if (value.source == FirebaseRemoteConfig.VALUE_SOURCE_REMOTE) { return true } else { return false } } 以下の箇所で FirebaseRemoteConfigValue オブジェクトを取得しています。 val value = remoteConfig.getValue("variant_key") FirebaseRemoteConfigValue.source には、以下3つのどれかの値が含まれています。この値が Remote Config サーバーから取得したものだと判断できれば、ユーザーが AB テスト対象者だと判断できます。 VALUE_SOURCE_STATIC(Remote Config で初期値として設定されている値) VALUE_SOURCE_DEFAULT(自分が設定したデフォルト値) VALUE_SOURCE_REMOTE(Remote Config サーバーから取得した値) (細かいことを言えば、ユーザーがテスト対象者に割り当てられたが Remote Config との通信に失敗してデフォルト値が使われた場合は非対象者になってしまうかと思います。このユーザーを対象として扱うか、非対象者として扱うかは要件次第かと思いますので、各自ご判断いただければ。) どうやって判断しているのか? ここからは蛇足となりますが、上記で示した1〜3はどのようにして判別されているのか知るために、少しソースをみてみたいと思います。 まずは getValue() 関数を見てみます。 ConfigGetParameterHandler.java public FirebaseRemoteConfigValue getValue(String key) { String activatedString = getStringFromCache(activatedConfigsCache, key); if (activatedString != null) { callListeners(key, getConfigsFromCache(activatedConfigsCache)); return new FirebaseRemoteConfigValueImpl(activatedString, VALUE_SOURCE_REMOTE); } String defaultsString = getStringFromCache(defaultConfigsCache, key); if (defaultsString != null) { return new FirebaseRemoteConfigValueImpl(defaultsString, VALUE_SOURCE_DEFAULT); } logParameterValueDoesNotExist(key, "FirebaseRemoteConfigValue"); return new FirebaseRemoteConfigValueImpl(DEFAULT_VALUE_FOR_STRING, VALUE_SOURCE_STATIC); } ここから以下のことがわかります。 getStringFromCache(activatedConfigsCache, key) から値が取得できたら VALUE_SOURCE_REMOTE getStringFromCache(defaultConfigsCache, key) から値が取得できたら VALUE_SOURCE_DEFAULT それ以外は VALUE_SOURCE_STATIC ここでポイントとなっているメンバ変数 activatedConfigsCache は ConfigGetParameterHandler クラスのコンストラクタで受け取っている ConfigCacheClient オブジェクトです。 Remote Config から取得した値はインメモリでキャッシュされ、そのキャッシュ値にアクセスするためのクライアントが ConfigCacheClient オブジェクトのようです。 そして activatedConfigsCache の出どころを辿ると RemoteConfigComponent#getCacheClient() に行き着きます。 RemoteConfigComponent.java public synchronized FirebaseRemoteConfig get(String namespace) { ConfigCacheClient fetchedCacheClient = getCacheClient(namespace, FETCH_FILE_NAME); // ↓ これ ConfigCacheClient activatedCacheClient = getCacheClient(namespace, ACTIVATE_FILE_NAME); ConfigCacheClient defaultsCacheClient = getCacheClient(namespace, DEFAULTS_FILE_NAME); ... } ACTIVATE_FILE_NAME は アクティベートされた値が保存されているファイルのファイル名です。namespace とファイル名でファイルの保存場所がわかるので、activatedCacheClient は当該ファイルへアクセスできるようになります。 つまり、Remote Config サーバーから取得して有効化された値は専用のファイル(ACTIVATE_FILE_NAME)にキャッシュされる仕組みになっているため、そこから値が取得できた場合は AB テストの対象者であると判断できる、という寸法です。 まとめ AB テスト対象者を見分けているロジック。 Remote Config サーバーから取得、有効化された値は専用のファイルにキャッシュされる 専用ファイルから値が取得できた = Remote Config から取得した値だと言える Remote Config から値を取得しているということは AB テストの対象者だと言える
- 投稿日:2021-04-05T09:44:03+09:00
Android Studioでjarの出力
実行環境 ・Android Studio 4.1.1 ・Gradle version 6.5 本文 build.gradleにmakeJarとclearJarのtaskを追加します。 build.gradle task clearJar(type: Delete) { delete 'release/' + 'module_name.jar' } task makeJar(type: Copy) { from('build/intermediates/aar_main_jar/release/') into('release/') include('classes.jar') rename('classes.jar', 'module_name.jar') } makeJar.dependsOn(clearJar, build) build.gradleを変更したら一回「Sync Project with Gradle Files」を押してください。 Gradleウインドウのotherの下にmakeJarが現れたはずです。 右クリックしてRunをしたらOK!
- 投稿日:2021-04-05T02:32:18+09:00
【Unity(C#)】OculusQuestでAndroidネイティブの音声認識機能を呼び出せるのか検証
はじめに VRヘビーユーザーからすれば当たり前のことかもしれませんが、 OculusQuestにマイクが搭載されているのはご存じでしたでしょうか。 ボイスチャットに利用されることがほとんどで、 その他の用途で使われている事例をあまり見たことがありませんでした。 (たぶん世の中にはたくさんある) しかし、最近目にした記事にボイスチャット以外の用途でマイクを使った事例がありました。 【参考リンク】:Synamon、ロゼッタと「リアルタイム多言語翻訳システム装備のVRオフィス」を共同開発 一言で説明すると、翻訳VRアプリです。 マイクを音声認識の受け口として利用しています。 そこで、私も勉強がてら"OculusQuestのマイクを利用したVRアプリ作りに挑戦してみたい"と思い、実際に作りました。 勉強がてら作成していた翻訳VRできました!?OculusQuestのマイクで拾った音声を音声認識APIに渡して、認識結果を翻訳APIに渡す、、、というやり方です?次はマルチ対応していきます?#OculusQuest #Unity pic.twitter.com/k95D73gEnh— KENTO⚽️XRエンジニア?Zenn100記事マラソン挑戦中29/100 (@okprogramming) April 3, 2021 Androidネイティブの音声認識機能 Androidには音声認識の機能が搭載されており、開発者がアプリに組み込めるようにその機能が公開されています。 【参考リンク】:RecognitionListener そして、そのAndroidのネイティブの機能をUnityからネイティブプラグインとして呼び出すことができます。 OculusQuestはAndroidベースであり、Androidアプリとしてビルドすることから、 "もしかしたら、Androidのネイティブプラグインを導入すれば動くんじゃないか?"という仮説に辿り着きました。 その検証を行った結果を記していきます。 ネイティブプラグイン作成 まずは下記リンクを参考にネイティブプラグインを作成します。 【参考リンク】:Unity向けAndroidネイティブプラグインの作り方 【参考リンク】: AndroidのSpeechRecognizerをネイティブプラグイン化してUnityで使う 記事の通りでほとんど詰まることなくいけました。 なんか動かん、という場合には地味にやることが多いので原因の特定はそこそこの苦行となります。 ですので面倒かもしれませんが最初からやり直した方が早い場合もあるかもしれません。 Javaで書いたプラグイン側のコードとC#で書いたUnity側のコードだけメモ残します。 プラグイン側のコード プラグイン側のコード package com.kento.speechtest; import java.util.ArrayList; import java.util.Locale; import android.content.Context; import android.os.Bundle; import android.speech.RecognitionListener; import android.speech.RecognizerIntent; import android.speech.SpeechRecognizer; import android.content.Intent; import static com.unity3d.player.UnityPlayer.UnitySendMessage; public class Speech { static public void StartRecognizer(Context context, final String callbackTarget, final String callbackMethod) { Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, Locale.JAPAN.toString()); intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, context.getPackageName()); SpeechRecognizer recognizer = SpeechRecognizer.createSpeechRecognizer(context); recognizer.setRecognitionListener(new RecognitionListener() { @Override public void onReadyForSpeech(Bundle params) { // On Ready for speech. UnitySendMessage(callbackTarget, callbackMethod, "onReadyForSpeech"); } @Override public void onBeginningOfSpeech() { // On begining of speech. UnitySendMessage(callbackTarget, callbackMethod, "onBeginningOfSpeech"); } @Override public void onRmsChanged(float rmsdB) { // On Rms changed. UnitySendMessage(callbackTarget, callbackMethod, "onRmsChanged"); } @Override public void onBufferReceived(byte[] buffer) { // On buffer received. UnitySendMessage(callbackTarget, callbackMethod, "onBufferReceived"); } @Override public void onEndOfSpeech() { // On end of speech. UnitySendMessage(callbackTarget, callbackMethod, "onEndOfSpeech"); } @Override public void onError(int error) { // On error. UnitySendMessage(callbackTarget, callbackMethod, "onError"); } @Override public void onResults(Bundle results) { ArrayList<String> list = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION); String str = ""; for (String s : list) { if (str.length() > 0) { str += "\n"; } str += s; } UnitySendMessage(callbackTarget, callbackMethod, "onResults\n" + str); } @Override public void onPartialResults(Bundle partialResults) { // On partial results. UnitySendMessage(callbackTarget, callbackMethod, "onPartialResults"); } @Override public void onEvent(int eventType, Bundle params) { // On event. UnitySendMessage(callbackTarget, callbackMethod, "onEvent"); } }); recognizer.startListening(intent); } } Unity側のコード Unity側のコード using System; using UnityEngine; using UnityEngine.Android; using UnityEngine.UI; // <summary> /// Androidのネイティブ音声認識機能呼び出し /// </summary> public class AndroidNativeSpeech : MonoBehaviour { [SerializeField] private Text recText; [SerializeField] private Image microPhoneImage; private void Start() { #if UNITY_ANDROID if (!Permission.HasUserAuthorizedPermission(Permission.Microphone)) { Debug.Log("Request"); Permission.RequestUserPermission(Permission.Microphone); } #endif } private void Update() { if (Input.touchCount > 0) { Touch touch = Input.touches[0]; if (touch.phase == TouchPhase.Began) { StartSpeech(); } } } /// <summary> /// 認識開始 /// </summary> private void StartSpeech() { #if UNITY_ANDROID var nativeRecognizer = new AndroidJavaClass("com.kento.speechtest.Speech"); var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); var context = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"); context.Call("runOnUiThread", new AndroidJavaRunnable(() => { nativeRecognizer.CallStatic("StartRecognizer", context,gameObject.name, "CallbackMethod"); })); #endif } /// <summary> /// 音声認識後のコールバックとして実行するメソッド /// </summary> /// <param name="message">認識したメッセージ</param> private void CallbackMethod(string message) { var messages = message.Split('\n'); //ユーザーが話すのを開始した際のコールバック if (messages[0] == "onBeginningOfSpeech") { microPhoneImage.enabled = true; } //認識した音量変化のコールバック if (messages[0] == "onRmsChanged") { recText.text = "認識中..."; } //ユーザーが話すのを終了した際のコールバック if (messages[0] == "onEndOfSpeech") { microPhoneImage.enabled = false; } //認識結果の準備が完了したコールバック if (messages[0] == "onResults") { var msg = ""; for (var i = 1; i < messages.Length; i++) { msg += messages[i] + "\n"; } Debug.Log(msg); recText.text = msg; } } } デモ 音声が無いのでアレですが、無事動作しました。 VRで検証 結果から言うと動きませんでした。 adb logcat -s Unity:*とコマンドプロンプトに入力することで 動作中のUnity製Androidアプリのログを出力できます。(教えてくださった方ありがとうございます!) その方法で実行中のVRアプリのログを確認しましたが、特にエラーを吐くことも無くただただ動いていませんでした。 同じ検証で四苦八苦している先駆者がOculusの公式コミュティに質問を記していました。 (そして、それらしい回答もなく終了していました) 【引用元】:On-device Speech Recognition on the Quest with Unity 冒頭の翻訳VRは下記リンクの手法で実現しました。 【参考リンク】:【Unity(C#)】Watson API × OculusQuestで音声認識 【参考リンク】:【Unity(C#)】Microsoft Translatorの使い方 ちなみに、海外ユーザーは音声入力や音声コマンドが利用可能なようです。 音声コマンドは「ヘイ、フェイスブック!」ってやつです。 【参考リンク】:音声コマンド・音声入力 そのうち、日本語にも対応して開発者に公開されるのを期待します。 2021/04/08 追記 下記アセットを使えばオフラインの音声認識も可能なようです。 【Asset】:Android Speech TTS ただし、日本語はまだ対応していません。 英語での認識についてデモAPKを落としてきて試しましたところなかなか良い精度でした。 通常のネイティブの音声認識機能ではなく、下記ライブラリを使用しているようです。 Kaldi Speech Recognition Toolkit (教えてくださった方、感謝です!) おわりに 今回の検証結果はOculusQuestでAndroidネイティブの音声認識機能は呼び出せないとなりました。 もし成功した方がいらっしゃったら遠慮なくツッコんでください。(あわよくば方法知りたい)