- 投稿日:2019-02-11T18:41:27+09:00
Material Component LibraryのMaterialButtonのtextAllCapsをテーマで変更する
AppCompatButton同様、MaterialButtonもデフォルトでは
textAllCaps="true"
で全て大文字で表示されます。AppCompatButtonの場合は、以下のようにテーマを指定すれば小文字にすることができました。
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar"> <item name="buttonStyle">@style/AppButton</item> </style> <style name="AppButton" parent="Widget.AppCompat.Button"> <item name="android:textAllCaps">false</item> </style>MaterialButtonの場合は、指定する属性が少し変わるので注意が必要です。
具体的には、materialButtonStyle
という新しい属性を指定します。<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar"> <!-- buttonStyleではなくmaterialButtonStyleを指定すること --> <item name="materialButtonStyle">@style/AppButton</item> </style> <style name="AppButton" parent="Widget.MaterialComponents.Button"> <!-- MaterialComponentで定義された属性を指定するのでandroid:は不要 --> <item name="textAllCaps">false</item> </style>以上です。
- 投稿日:2019-02-11T16:32:05+09:00
three.jsをスマホアプリで動かして暖をとる(react-native-webglの導入手順)
はじめに
この記事では、react-native-webglというライブラリの導入手順について書きます。
react-native-webglは、React NativeでWebGLを使うためのライブラリです。
これを使えばネイティブアプリでthree.jsが使えるようになるので、試しに作ってみました!とりあえずスマホカイロにしてみましたが、誰かかっこいいアプリ作ってくれないかな・・・
iOS: https://itunes.apple.com/us/app/three-js-native/id1447928256
Android: https://play.google.com/store/apps/details?id=com.three
- アプリ内の4種類のシーンのうち、Octahedronをたくさん表示するものが一番あったまれます。
- segments(ポリゴンの分割数)などのパラメータは、最大にするとメモリが足らずアプリが落ちることがありますので、徐々に上げてみてください。
React Nativeの準備
公式のgetting-started通りに準備しましょう。
今回はexpoを使わないので、”Quick Start”ではなく、”Building Projects with Native Code”のタブに従ってください。react-native-webglのインストール
React Nativeの最新はこの記事の執筆時点で0.58ですが、このバージョンではビルドが通らないため、0.57にします。
# init react-native init threejsNative cd threejsNative # react-nativeをv0.57に下げる yarn remove react-native yarn add react-native@0.57 # ネイティブのプロジェクトも新しく作り直す rm -rf ios android react-native ejectその後、react-native-webglのパッケージをインストールしてリンクします。
yarn add react-native-webgl react-native link react-native-webgl
iOS
New Build SystemではGPUImageというプロジェクト周りでエラーが出てしまうため、Legacy Build Systemに変更します。
File -> Workspace Settings... -> Build System -> Legacy Build System
Android
issueを読みつつ諸々を書き換えます。
android/build.gradlebuildscript { ext { - minSdkVersion = 16 + minSdkVersion = 17 } dependencies { + classpath 'de.undercouch:gradle-download-task:3.1.2' }node_modules/react-native-webgl/android/src/main/jni/Application.mk- APP_PLATFORM := android-9 + APP_PLATFORM := android-16 - APP_STL := gnustl_shared + APP_STL := c++_shared - NDK_TOOLCHAIN_VERSION := 4.9node_modules/react-native-webgl/android/build.gradletask packageRNWebGLLibs(dependsOn: buildRNWebGLLib, type: Copy) { - exclude '**/gnustl_shared.so' + exclude '**/libc++_shared.so' } android { - buildToolsVersion '25.0.0' + buildToolsVersion '27.0.3' } dependencies { - compile "com.facebook.react:react-native:+" // From node_modules + implementation "com.facebook.react:react-native:+" // From node_modules }
exclude '**/libc++_shared.so'
でビルドが通らない時はinclude '**/libc++_shared.so'
でうまくいく場合があります。three.jsを読み込む
あとは公式のexampleの通りです。
yarn add three
three.jsconst THREE = require("three"); global.THREE = THREE; if (!window.addEventListener) window.addEventListener = () => { }; require("three/examples/js/renderers/Projector"); export default THREE;App.jsimport React from "react"; import { View } from "react-native"; import { WebGLView } from "react-native-webgl"; import THREE from "./three"; export default class App extends React.Component { requestId: *; componentWillUnmount() { cancelAnimationFrame(this.requestId); } onContextCreate = (gl: WebGLRenderingContext) => { const rngl = gl.getExtension("RN"); const { drawingBufferWidth: width, drawingBufferHeight: height } = gl; const renderer = new THREE.WebGLRenderer({ canvas: { width, height, style: {}, addEventListener: () => {}, removeEventListener: () => {}, clientHeight: height }, context: gl }); renderer.setSize(width, height); renderer.setClearColor(0x000000, 1); let camera, scene; let cube; function init() { // ここにcameraやsceneのコードを書く camera = new THREE.PerspectiveCamera(75, width / height, 1, 1100); camera.position.y = 150; camera.position.z = 500; scene = new THREE.Scene(); let geometry = new THREE.BoxGeometry(200, 200, 200); for (let i = 0; i < geometry.faces.length; i += 2) { let hex = Math.random() * 0xffffff; geometry.faces[i].color.setHex(hex); geometry.faces[i + 1].color.setHex(hex); } let material = new THREE.MeshBasicMaterial({ vertexColors: THREE.FaceColors, overdraw: 0.5 }); cube = new THREE.Mesh(geometry, material); cube.position.y = 150; scene.add(cube); } const animate = () => { this.requestId = requestAnimationFrame(animate); renderer.render(scene, camera); // ここにアニメーションのコードを書く cube.rotation.y += 0.05; gl.flush(); rngl.endFrame(); }; init(); animate(); }; render() { return ( <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}> <WebGLView style={{ width: 300, height: 300 }} onContextCreate={this.onContextCreate} /> </View> ); } }three.jsの部分は通常とほとんど同じように書けます!
実行
iOS/Androidそれぞれでオブジェクトが表示されれば成功です。
react-native run-ios react-native run-android
まとめ
react-native-webglを使って、スマホアプリでthree.jsを動かす方法について書きました。
three.jsを使ったかっこいいアプリがさらに増えていくと嬉しいですね!
- 投稿日:2019-02-11T15:57:02+09:00
【Android】DroidKaigi 2019 まとめ
DroidKaigi とは
エンジニアが主役のAndroidカンファレンス。
DroidKaigi 2019 は 2019年2月7日(木)、8日(金)の2日間開催。以下が公式サイトのリンクです。
DroidKaigi 2019 公式サイトチケットがないと入場できません。ちなみにチケット代は以下でした。
・ 早期割引: 10,000円
・ 一般: 15,000円
・ 学生: 4,000円高いと感じるかもしれませんが、パーティの参加費も含まれています。お昼のお弁当も出ました。
タイムテーブル
各セッションの詳細は公式サイトにタイムテーブが載っています。
公式サイトタイムテーブル各セッションの発表資料を公開していたのでまとめさせていただきました。
リンクがあるところが公開されている資料を見つけられたセッションです。1日目
Hall
Hall A Hall B ウェルカムトーク マルチモジュールなプロジェクトでテストはどう変わる? マルチモジュールプロジェクトでの Dagger2を用いた Dependency Injection Optimize Builds with Android Plugin for Gradle 3.3.0+ Fireside Chat Room1
時間 セッション名 11:20 What is Navigation Architecture Component and Tips 12:50 LiveData と Coroutines で実装する DDD の戦術的設計 14:00 PWAでここまでできる 14:50 Chrome Custom Tabsの仕組みから学ぶプロセス間通信 15:40 Androidエンジニアが抑えておくべきUnicode Emojiの知識 16:30 From Monolithic to Modularized codebase with Dagger Room2
時間 セッション名 12:50 EspressoのテストをAndroidの最新トレンドに対応させよう 14:00 ぼくのかんがえた最強のUsecaseの作り方 ~あるいはビジネスロジックとはなにかという1つの回答~ 14:50 R8、Proguard徹底比較 16:00 詳解定期購入 17:10 シームレスに遷移可能な画面を他のアプリに提供する方法 Room3
時間 セッション名 11:20 Unit test for ViewModel and LiveData 12:50 マテリアルデザインの起源とベースとなる哲学 14:00 Master of Android Theme 14:50 The good and bad of modern app architecture 15:40 Understanding Kotlin Coroutines: コルーチンで進化するアプリケーション開発 16:50 クロスプラットフォームモバイルアプリ開発ツール総ざらい2019 〜Titanium MobileからKotlin/Nativeまで〜 Room4
時間 セッション名 12:50 アプリをさらに成長させるための技術戦略(振り返りとこれから) 14:00 つらいと評判のAndroid BLEを頑張って使い続けた話 14:50 いかにしてビットコインを扱うか 15:40 ちゃんとつくる Google Assistant アプリ 16:30 クロスプラットフォーム開発3種の神器 React Native Room5
時間 セッション名 12:50 Codelabs: Android Fundamental Course Room6
時間 セッション名 12:50 Server-side Kotlin for Frontend 複雑なAndroidアプリ開発に対するアプローチ 14:00 FCMを使った用途に合わせたPush通知設計 14:50 Chromebookで始めるラップトップ向けAndroidアプリ 16:00 Redux for Android 17:10 Chrome + WebAuthn で実現できるパスワードレスなユーザー認証体験と開発者の課題 Room7
時間 セッション名 12:50 Deep Dive to fido.fido2 Packages 14:00 Dexs, R8 & 3.2 14:50 Exploring the Android Transform API 15:40 Grid Systems And Android 16:40 ExoPlayer in RecyclerView(*), a proposal 2日目
Room1
時間 セッション名 10:30 Dialogflowによる自然言語処理(NLP)を用いたボイスコマンド音声認識の精度向上 11:20 Spek2+MockK+JaCoCoでイケてる Unit Test環境を手に入れろ! 12:50 Deep dive into MotionLayout 14:00 Android Studio設定見直してみませんか? 14:50 今日から始める依存性の注入 15:40 Androidにおけるパフォーマンスチューニング実践 16:50 Navigation Architecture Component によるアプリ内遷移の管理 17:40 実践 WorkManager 18:30 Android アプリ開発における、デザイナーとエンジニアのワークフロー Room2
時間 セッション名 10:30 中規模以上のアプリ開発におけるCIレシピとリリースフロー戦略 11:20 Guide to app architectureを踏まえた既存アプリの設計改良 12:50 Trash Talk 14:00 SpekでUnitTestを書こう 14:50 ハマった時に役立つ通信系デバッグの話 15:40 Multi-module Androidアプリケーション 16:50 デザイナーとエンジニアの距離をより近づけるLottie利用術 17:40 AndroidThingsでのプロダクト開発 18:30 build.gradle.ktsに移行しよう Room3
時間 セッション名 10:30 Not Just Rotation: Configuration Changes on Android 11:20 Gradle BOM importでライブラリバージョン管理 12:50 Best practice for text on Android and its internals. 14:00 AndroidVitals徹底活用 14:50 Lifecycle, LiveData, ViewModels - The inner wiring 15:40 Journey of APK from compilation to launch 16:50 UIテスト(Espresso)の高速化をさらにすすめる 17:40 ああ、素晴らしきTDD ~アプリとエンジニアの心に安寧を~ 18:30 巨大なアプリ開発を支えるフラグ管理術 Room4
時間 セッション名 10:30 React NativeとExpoを用いたクロスプラットフォーム開発入門 11:20 Slice Your App: Inside Slices and How to build it 12:50 カスタムブラウザの作り方 ー 実例から学ぶ Chrome と Firefox のカスタムビルド開発 ー 14:00 FridaによるAndroidアプリの動的解析とフッキングの基礎 14:50 15:40 WiFi Direct + VpnServiceでSIM無しAndroidをWeb世界に社会復帰させる話 16:50 WebView+ViewGroupを実現するAOSPメールアプリの内部実装とニュースアプリへの応用 17:40 WebViewを守るSafe Browsingのコントロール 18:30 Google Play Consoleのリリーストラックを有効活用してリリースフローの最適化を行った話 Room5
時間 セッション名 10:30 Android App Improvement Challenge Part1: 機能実装編 12:50 Android App Improvement Challenge Part 2: リファクタ編 15:00 Codelabs: Free Theme Room6
時間 セッション名 10:30 外部デバイスと密に連携するAndroidアプリに最適なアーキテクチャとは? 11:20 Wi-Fi RTTによる屋内測位アプリを作ろう 12:50 All About Test of Flutter 14:00 実践Lottie 14:50 Android Enterpriseで実現できる端末管理の世界 15:40 ゼロから実装する縦書きTextViewとその周辺技術 16:50 2019年の技術であのARアプリを再現する 17:40 FlutterでのWidgetツリーへの状態伝播とアクセス制限の基本戦略 18:30 BLEアプリ設計パターン Room7
時間 セッション名 10:30 From TensorFlow to ML Kit: power your Android application with machine learning 11:20 Code Review as a Collaborative Journey 12:50 14:00 What does "adb lolcat" do? A deep dive into adb 14:50 Troubleshooting your designer's, and vector graphics 15:40 Sharing Code between iOS & Android with Rust 16:50 Fast Prototypes with Flutter + Kotlin/Native 17:40 Building for next billion 18:30 Animations in Flutter 動画
各セッションでは動画撮影をしていました。
後日に公開されるということで、この取り組みも素晴らしいと感じました。
Youtube DroidKaigiチャンネル終わりに
私自身、今回が DroidKaigi 初参戦!
セッションをしていただいていた方々は企業のエンジニアでしたが、企業の宣伝はほとんどないところが素晴らしかったです。全てのセッションの資料を見つけようと思いましたが、見つけられませんでした。
力不足ですいません(。>_<。)見つけることができたら更新します。
おまけ
個人的に良かったのは、パーティに出てきたドロイド君のケーキです(もちろん参加者は食べることができます)
↓写真を貼っておきます。
- 投稿日:2019-02-11T12:58:11+09:00
RxJavaのintervalでonPauseで停止、onResumeで再開させる
やりたいこと
Observable.interval
を使って5秒おきにViewを操作させるアプリがあったとする。
アプリがバックグラウンドになればView操作を停止、フォアグラウンドならView操作を再開させたい。
これをRxJavaで実装してみた。コード
private AtomicBoolean resumed = new AtomicBoolean(); private AtomicBoolean stopped = new AtomicBoolean(); private void start() { Observable.interval(5, TimeUnit.SECONDS) .observeOn(AndroidSchedulers.mainThread()) .takeWhile(new Predicate<Long>() { @Override public boolean test(Long aLong) throws Exception { return !stopped.get(); } }) .filter(new Predicate<Long>() { @Override public boolean test(Long aLong) throws Exception { return resumed.get(); } }) .subscribeWith(new DisposableObserver<Long>() { @Override public void onNext(Long aLong) { logger.d("onNext " + aLong); // ここに動作させたい処理 } @Override public void onError(Throwable e) { logger.e(e.getMessage()); } @Override public void onComplete() { logger.d("onComplete"); } }) } @Override public void onResume() { super.onResume(); resumed.set(true); stopped.set(false); start(); } @Override public void onPause() { super.onPause(); resumed.set(false); stopped.set(true); }感想
希望する動作をしてくれてはいるが、本当はKotlin+Coroutineで実装したかった。
今回はJavaという制約があったので仕方ない。参考リンク
- 投稿日:2019-02-11T12:12:43+09:00
Flutterウィークリー #45
Flutterウィークリーとは?
FlutterファンによるFlutterファンのためのニュースレター
https://flutterweekly.net/この記事は#45の日本語訳です
https://mailchi.mp/flutterweekly/flutter-weekly-45※Google翻訳を使って自動翻訳を行っています。翻訳に問題がある箇所を発見しましたら編集リクエストを送っていただければ幸いです。
読み物&チュートリアル
Flutterスプライトシートアニメーション
https://medium.com/flutter-community/sprite-sheet-animations-in-flutter-1b693630bfb3
Luan NicoがFlutterアプリでスプライトシートを使用する方法を紹介します。紹介: Flutterウィジェット-メーカー、 FlutterのApp-Builderはで書かれたFlutter
Norbertは、ビジュアル環境でウィジェットを作成するためのこのツールを作成しました。クイックヒント:テキストURLをクリック可能なハイパーリンクに変換する
Darshan KawarがFlutterアプリのURLをリンクするための簡単なコツを紹介します。dartfmtを使用してコードスタイルを確認してフォーマットする
https://medium.com/@guitcastro/checking-and-format-your-code-style-using-dartfmt-665cc4189223
dartファイルでリンターを実行し、CI経由でGuilherme Torresで実行する方法についての簡単なメモFlutter - 開発期間を短縮するためのIDEショートカット
https://medium.com/flutter-community/flutter-ide-shortcuts-for-faster-development-2ef45c51085b
AS用のショートカットのリストをチェックして、 Flutter by Pooja Bhaumikを使った作業を改善してください。FlutterとWebコードの共有州管理デモ
Mellati Meftahが、 Flutter状態を処理しながら、 FlutterとWebの間でコードを共有する方法を紹介します。FlutterのWebViewのパワー
https://medium.com/flutter-io/the-power-of-webviews-in-flutter-a56234b57df2
FlutterチームのEmily FortunaによるWebView Widgetの詳細な分析。キャッチャーでFlutterエラーを処理する
https://medium.com/flutter-community/handling-flutter-errors-with-catcher-efce74397862
Jakub Homlala氏は、アプリケーションのエラー処理を支援するための、 FlutterプラグインのCatcherを紹介しています。アーキテクチャなし
https://buildflutter.com/no-architecture/
例としてFlutterを使用したアーキテクチャを使用しない、Adam Pedleyによるモバイル開発への興味深いアプローチ。
Flutter速い!
https://medium.com/flutter-community/flutter-faster-db1e0fef57ba
Greg Perryは、MVCアーキテクチャをFlutterアプリに適用した経験を共有しています。Firebase、クラウドストレージ、 Flutter - Flutter Pub - Medium
https://medium.com/flutterpub/firebase-cloud-storage-and-flutter-fa2e91663b95
あなたのFirebase Cloud StorageにFlutterアプリからファイルを保存する方法に関するAseem Wangooによるチュートリアル。BuiltValueSerializerを使用したカスタムbuilt_valueシリアライザの作成
カスタムシリアライザを作成するGonçaloPalmaによるbuilt_valueの高度な使用法に関する記事。フレアの逆運動学 - 2次元 - 中
https://medium.com/2dimensions/inverse-kinematics-in-flare-777bbb24bc49
Guido Rossoを使用すると、FlareでのIKを理解して、 Flutterアプリに適したアニメーションを作成できます。要素、キー、 Flutterのパフォーマンス
https://medium.com/flutter-community/elements-keys-and-flutters-performance-3ef15c90f607
ウィジェットのキーを処理するとパフォーマンスがどのように向上するかについてのTomekPolańskiによる記事。Flutter + MLKit =❤
https://medium.com/flutter-community/flutter-mlkit-8039ec66b6a
Stefan BlosによるFlutter MLKitの使用に関するチュートリアルビデオ&メディア
Flutterローカル認証指紋とFaceID | Dartパッケージ - YouTube
https://www.youtube.com/watch?v=4-P_Su9O5NM
ローカル認証パッケージを使用してFlutterアプリケーションに指紋認証とFaceIDローカル認証を簡単に導入する方法についてのビデオ。Flutter YouTube検索チュートリアルコース
https://www.youtube.com/playlist?list=PLB6lc7nQ1n4jtXh6TgCEIO4kCfIT0-NZl
Flutter YouTubeの検索機能を模倣する方法についてのReso Coderによるプレイリスト。Flutterインスペクタを使用してスクロール位置を保存する(The Boring Flutter Development Show、Ep。15) - YouTube
https://www.youtube.com/watch?v=ht76lDzPgUQ&feature=youtu.be&linkId=63306304
The Boring Flutter Development Showのこのエピソードでは、EmilyとLaraが、 Flutter Inspectorと、それがどのように使用されるか、そしてページのスクロール位置を維持する方法について話します。Flutter UI - クリーンデザイン - ヘアスタイリストアプリ - YouTube
https://www.youtube.com/watch?v=td_wyIn9b3k&feature=youtu.be
今回はヘアスタイリストアプリである、アプリuiの作成に関するRaja Yoganによるチュートリアル。整列(今週のFlutterウィジェット) - YouTube
https://www.youtube.com/watch?v=g2E7yl3MwMk&t=0s&index=26&list=PLOU2XLYxmsIL0pH0zWe_ZOHgGhZ7UasUE
整列ウィジェットを使用すると、ウィジェットをその親ウィジェットの定義済み領域に配置できます。折りたたみサイドバーとナビゲーション引き出し| Flutter UI - YouTube
https://www.youtube.com/watch?v=2SjvhAUR9aw&feature=youtu.be
Techie Blossomによる、引き出しとして、または足場本体のどこにでも使用できる折りたたみ式の引き出し/サイドバーの作成方法に関するビデオ。Flutter使ってGmailを作成する
https://www.youtube.com/playlist?list=PLWIO1jq0WronOimzw5BGlB3F9TU7Celpd
Impatient DeveloperによるFlutter Gmailクローンの作成に関するプレイリスト。Flutterチュートリアル - Flutter GestureDetectorとInkWell - YouTube
https://www.youtube.com/watch?v=pAA62_x0zKE&feature=youtu.be
Whatsupcodersは、GestureDetectorとInkwellをアプリで使用する方法を私たちに示しています。Flutterトーク
https://blog.codemagic.io/flutter-talks-podcast-fast-beautiful-productive-open/
Flutter専用の新しいPodcast。この最初の号では、Martin Aguinisへのインタビューやそれ以上のことを聞くことができます。ライブラリ&コード
mdi-dart
https://github.com/csharad/mdi-dart
Flutter用の自動生成されたMaterial Design Iconパッケージ。
データビュー
https://github.com/synw/dataview
アプリケーションのドキュメントディレクトリ用のファイルエクスプローラ。
flutter_clipper_experiments
https://github.com/spagni/flutter_clipper_experiments
カスタムクリッピング実験をテストするためのFlutterアプリ。
- 投稿日:2019-02-11T08:13:08+09:00
Chromebookで始めるラップトップ向けAndroidアプリ開発
はじめに
Droidkaigi2019で話してきた内容の完全版です。(スライド)
GitHubのwikiにすべての内容を載せていましたが、そちらだとあまり見られることもないので、こちらに転載しておきます。Chromebook向けのAndroidアプリの開発と、ChromebookでのAndroidアプリの開発についてまとています。
それぞれ、主にCodelabsや参考サイトをまとめた内容となります。ChromeOSとは
Linuxをベースに、Googleが開発しているOSで、ラップトップ(Chromebook)やデスクトップ(Chromebox)向けに提供されている。
簡単な歴史:
- 2009年:Google Chrome OSのオープンソース版である「Chromium OS」(クロミウム・オーエス)のソースコードを公開
- 2016年 Google I/O:Androidアプリのサポートを発表
- 2018年 Google I/O:Linuxのサポートを発表(現在も、beta状態)
AndroidからみたChromebookの特徴
ChromeOSとAndroidの差分について
ChromeOS -> Androidアプリに合わせている部分
Androidアプリ -> ChromeOSに合わせている部分
以下のページで、ChromeOS独自の対応について説明
- 04_ウィンドウ・レイアウトサポート
- 05_入力サポート
ChromeOSでのAndroidアプリのデバッグについて
以下で説明
- 01_ChromeOSのアプリデバッグ方法
- 02_ChromeOS上でのAndroidアプリの開発
Chromebook向けのアプリの対応として、マルチウィンドウ、フリーフォームレイアウトの対応について、複数サイズの画面の対応について記載。
ウィンドウ・レイアウトサポート
Chromebookの画面に関する特徴
ChromebookではPCと同様に、複数ウィンドウの立ち上げと、ウィンドウサイズの変更できることが特徴となります。
APIレベル別のウィンドウ対応
アプリビルド時の
targetSdkVersion
によって挙動が変更される。
API level Window manager behavior <= Android 1.5 (API level 3) 全画面表示固定 Android 1.6 (API level 6) ~ Android 6.0 (API level 23) リサイズが保証されていないアプリとして扱われる。起動時のデフォルトは、縦画面のphoneサイズとして起動する。端末の画面表示切り替えボタンによって、画面サイズが切り替えられた場合はアプリを再起動させる。 Android 7.0 (API level 24) resizable マニフェストに何も指定をしていない場合はデフォルトでリサイズをサポートしているものとして扱い、ウィンドウをアプリの再起動なしでリサイズさせます。アプリの起動時は、デフォルト全画面で、M60以下のChromeOSの場合だけ、Nexus 5Xの縦画面のサイズで表示されます。 Android 7.0 (API level 24) un-resizable マニフェストに、android:resizeableActivity="false"を指定した場合、リサイズが行われないようになります。ただし、特定のユーザー処理が行われるとウィンドウのリサイズ処理が行われます。 Android 7.0 (API level 24) app-controlled マニフェストで指定したとおりの処理が実行されます。 アプリの、最大化状態や画面サイズは記録されます。
Root Activityルール
ChromebookのウィンドウはActivityのスタックごとに別れ、それぞれのスタックごとに、ウィンドウの 向き と サイズ を管理しています。
また、各スタックの一番下のActivity設定が、そのスタックに上のすべてのActivityの属性を決定するため注意必要となります。
例えば、一番下のスタックのRootActivityが画面の向き変更自由で、後からスタックに積まれたSubActivityが縦画面固定の設定をしていても、この設定は無視されます。また、ウィンドウサイズもすでにあるスタックの属性をそのまま受け継ぎます。デバイスモードの場合の注意:
タブレットモードの向きはロックされず、Android通常の画面回転と同様に処理されます。
ただし、targetSdkVersion
がAndroid 6.0(API level 23)以下の場合はロックされます。別ウィンドウで立ち上げたい場合
Activityの立ち上げを別スタックにすればよいため、Intentの
Intent.FLAG_ACTIVITY_NEW_TASK
Flagを指定することで、別ウィンドウ立ち上げが行える。Androidのマルチウィンドウの種類について
マルチウィンドウでは、Activityを別ウィンドウで立ち上げることによる別画面で情報を同時に見せたり、アプリ内の別画面や、複数のアプリの間でドラッグ&ドロップによるデータの共有を行うことができます。
マルチウィンドウは、Android 7(Nouger)で大きく変更が入り、特にPCやTV向けのサポートが多くありました。
複数のウィンドウを表示するには、主に以下のタイプがあります。
スマホ Chromebook フリーフォーム x ○ 分割画面 ○ x ピクチャーインピクチャー ○ x Chromebookでは、フリーフォームのウィンドウのみ対応となる。
Chromebookのウィンドウサイズの種類
Chromebook上でのウィンドウサイズ。
- フリーフォーム
- スマホサイズ
- タブレットサイズ
- ハーフレイアウト -> タブレットサイズ
- 全画面レイアウト -> PCサイズ
ユーザー操作:
- F3キー:全画面<->フリーフォームレイアウトの切替が可能
- 画面の両端へのドラッグ:ハーフレイアウトにすることが可能
- デバイスモードへの変更:タブレットモード<->PCモードの切り替えが可能
- 画面回転 + 画面サイズ変更が発生
- (ウィンドウの端をドラッグ):画面サイズを自由に変更可能
ウィンドウサイズの指定
Android 7.0のウィンドウ設定
Android 7.0 (API level 24) 以降の、すべて対応を行う場合の各種設定。
<manifest> <!-- マルチウィンドウに対応しているかの指定。 Applicationレベルか、Activityレベルで設定 --> <application android:resizeableActivity="[true|false]"> <!-- --> <activity android:name".RootActivity" android:resizeableActivity="[true|false]"> <!-- 画面の左端から、バックボタンを消す変更 --> <meta-data android:name="WindowManagerPreference:SuppressWindowControlNavigationButton" android:value="[true|false]" /> <!-- 起動時のウィンドウサイズ指定 --> <meta-data android:name="WindowManagerPreference:FreeformWindowSize" android:value="[phone|tablet|maximize]" /> <!-- 起動時のウィンドウの向き指定 --> <meta-data android:name="WindowManagerPreference:FreeformWindowOrientation" android:value="[portrait|landscape]" /> </activity> <activity android:name".FreeActivity"> <!-- DP値でウィンドウサイズや位置を指定する場合 --> <layout android:defaultHeight="500dp" android:defaultWidth="600dp" android:gravity="[top|bottom|start|end]" android:minHeight="450dp" android:minWidth="300dp"/> </activity> </application> </manifest>動的に起動するウィンドウサイズを変更する
新たなアクティビティを起動する際に、
ActivityOptions.setLaunchBounds()
を指定したActivityOptionsを使用することによって新しいアクティビティの画面上での位置を指定することができます。
ただし、マルチウィンドウモードではない場合などは無視されます。val options = ActivityOptions.makeBasic().apply { launchBounds = Rect(0, 0, 500, 500) } startActivity(it, options.toBundle())※新規スタックでなければウィンドウサイズ等の変更はできず、既存のスタックの属性が継承されることに注意
分割画面で、ウィンドウを隣に表示する
Intent.FLAG_ACTIVITY_LAUNCH_TO_ADJACENT
ドラッグ&ドロップサポート
複数のウィンドウ表示ができるため、画面内だけではなく、画面感のドラッグ&ドロップも簡単にできるようになるため、Chromebookでは重要なユーザー操作となる。
別アプリとのドラッグ&ドロップもサポートするために、ドラッグするデータの形式などを指定する必要があります。共有できるデータは、主に以下の2種類になります。
- テキスト
- URL
※別アプリへ共有する場合は、相手のアプリがそのMIMETYPEをサポートしている必要があります。
ドラッグする側の処理
- ドラッグするデータの作成
- ClipDataクラスの作成
- 指定できるデータと、それぞれのMIMETYPE
- テキスト:ClipDescription.MIMETYPE_TEXT_PLAIN
- HTMLテキスト:ClipDescription.MIMETYPE_TEXT_HTML
- URL:ClipDescription.MIMETYPE_TEXT_URILIST
- Drag中のViewの指定
View.DragShadowBuilder
クラスを使用するView.startDragAndDrop()
の呼び出しval targetView = it as TextView // 1. ドラッグするデータの作成 val dragContent = "Dragged Text: ${targetView.text}" val item: ClipData.Item = ClipData.Item(dragContent) val dragData: ClipData = ClipData(dragContent, arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN), item) // 2. Drag中のViewの指定 val dragShadow = View.DragShadowBuilder(targetView) // 3. View.startDragAndDrop()の呼び出し targetView.startDragAndDrop(dragData, dragShadow, null, View.DRAG_FLAG_GLOBAL)
View.startDragAndDrop()
は、Android7.0より前に存在したView.startDrag()
のエイリアスで、DropPermissionなどのマルチウィンドウ操作向けのフラグをつけるために拡張されたメソッドです。
DropPermissionsについては、後ほど説明します。その他の操作:
それぞれ、DragAndDrop中のイベントに対してキャンセルとシャドーの変更ができます。
View.cancelDragAndDrop()
:実行中のドラッグ操作をキャンセルします。View.updateDragShadow()
:実行中のドラッグ操作のドラッグシャドウを置き換えます。※ドラッグ操作を開始したアプリだけが呼び出せます。
ドロップされる側の処理
別のウィンドウからイベントを受け付ける可能性があるため、イベント内のデータのMimeTypeなどを確認する必要があります。
DragEvent.ACTION_DRAG_STARTED
とDragEvent.ACTION_DRAG_ENDED
イベントは、他のウィンドウ(別アプリ含む)でドラッグがスタートしたり、終了したタイミングで呼び出されます。view.setOnDragListener { view, event -> when (event.action) { DragEvent.ACTION_DRAG_STARTED -> { // Limit the types of items that can be received if (event.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { // Greenify background colour so user knows this is a target view.setBackgroundColor(Color.argb(55, 0, 255, 0)); return true } false } DragEvent.ACTION_DRAG_ENTERED -> { // Increase green background colour when item is over top of target view.setBackgroundColor(Color.argb(150, 0, 255, 0)) true } DragEvent.ACTION_DRAG_LOCATION -> { true } DragEvent.ACTION_DRAG_EXITED -> { // Increase green background colour when item is over top of target view.setBackgroundColor(Color.argb(55, 0, 255, 0)) true } DragEvent.ACTION_DROP -> { requestDragAndDropPermissions(event) val item: ClipData.Item = event.clipData.getItemAt(0) if (event.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { (view as TextView).also { target -> target.setTextSize(TypedValue.COMPLEX_UNIT_SP, 30f) target.text = item.text } return true } false } DragEvent.ACTION_DRAG_ENDED -> { // Increase green background colour when item is over top of target view.setBackgroundColor(Color.argb(0, 255, 255, 255)) true } else -> false } }DropPermissions
ドラッグ&ドロップでURIを共有する場合、受け取り側でURIがどのようなものかを判断してパーミッションをリクエストすることはできません。そのため、パーミッションをドラッグする側が取得し、それをドロップされる側に渡す仕組みがDropPermissionsです。
ドロップされる側は、渡されたURIに対するパーミションの実装をすることなくURIのデータを扱うことができます。また、パーミッションが付与されるのはDragEventで渡されたデータのURIだけで、他のURIに対しては権限が付与されないためユーザーにとっても安心して利用できる機能になっています。
ドラッグする側の処理
View.startDragAndDrop()
の第4引数のフラグをOR(|)で連結して指定します。
それぞれ、権限周りのフラグは以下の通りです。
- 他アプリとのDrag許可
- View.DRAG_FLAG_GLOBAL
- URIへの権限許可 ※1の指定必須
- View.DRAG_FLAG_GLOBAL_URI_READ:URIに対する読み込み権限を付与
- View.DRAG_FLAG_GLOBAL_URI_WRITE:URIに対する書き込み権限を付与
- URIへの権限許可オプション系 ※2の指定必須
- View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION:
revokeUriPermission
するまで権限を付与する- View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION:フォルダ単位で権限を付与する
例:
targetView.startDragAndDrop(dragData, dragShadow, null, View.DRAG_FLAG_GLOBAL|DRAG_FLAG_GLOBAL_URI_READ)※前提として、
grantUriPermission
などでURIへのパーミッションを取得しているものとします。ドロップされる側の処理
ドロップされる側は、
Activity.requestDragAndDropPermissions(DragEvent)
を呼び出すだけで必要なパーミッションを取得できます。view.setOnDragListener { v, event -> when (event.action) { DragEvent.ACTION_DROP -> { // 権限が取得できない場合や不正な場合は、nullが変える val permissions: DragAndDropPermissions? = requestDragAndDropPermissions(event) // 使用が終わったり、不要になったらreleaseする permissions?.release() true } }デザインについて
GoogleのMaterial Designのの公式サンプルだとレスポンシブにGridを使用したデザインが多い。
RecyclerViewだと表示するアイテムのリストの列数を動的に変更するといった対応が多い。
「レスポンシブ」 + 「コンテンツを固定」 => 「コンテンツ数」 & 「余白」で調整。
詳しくは、Responsive layout gridを見る。Google Material DesignのShrineの例:
ライフサイクル
ウィンドウサイズ変更時のライフサイクル
Android 7.0(API level 24)のマルチウィンドウの標準的なライフサイクルと同様。
画面回転時のライフサイクルと同様に処理され、画面の再構成(onCreate)が行われます。
ウィンドウ切替時のライフサイクル
Android 7のマルチウィンドウの標準的なライフサイクルと同様。
マルチ ウィンドウ モードでは、ユーザーが直前に操作したアクティビティのみが任意の時点でアクティブになる。このアクティビティは、トップレベルにあると見なされ、他のすべてのアクティビティは、表示されていても一時停止状態(onPause)になります。
ただし、一時停止状態ではあるが、表示されているこれらのアクティビティには、表示されていないアクティビティよりも高い優先度が付与されます。 ユーザーが一時停止状態のアクティビティのいずれかを操作した場合、そのアクティビティが再開(onResume呼び出し)され、前のトップレベルのアクティビティが一時停止(onPause呼び出し)します。画面Bにフォーカス変更 | 画面Aの中身をクリック
画面A:onPause(優先度:高) -> onResume
画面B:onResume -> onPause(優先度:高)
画面C(非表示):onPause(優先度:低)動画アプリなどの場合は、onPauseで各種のリソースを破棄している場合はマルチウィンドウの場合だけonStop() で動画を一時停止し、onStartで再生を再開するようにするなどの検討が必要です。
Activity.isInMultiWindowMode() Fragment.isInMultiWindowMode()ウィンドウサイズ変更時のアニメーション
画面切り替え時のアニメーション
ウィンドウサイズ変更に合わせて、別々のレイアウトを表示することが多いですが、そのままではレイアウトがただ切り替わるだけで、画面変更が頻繁に発生するChromebookでは見栄えがよくありません。
自力でのViewのアニメーション実装やレイアウト切り替えは複雑で難しいため、ConstraintLayout2から使用できるConstraintLayoutStateを使用した画面変更時のアニメーションのサポートについて書きます。
- ConstraintLayoutのバージョンを2にアップデート※まだ、beta段階
- レイアウトファイルの修正
constraint_states.xml
ファイルの作成- ConstraintLayoutの設定と、ステート変更のハンドリング
- AndroidManifestへの画面変更時の設定追加
0. レイアウトファイルの修正
app/build.gradle
のdependencies内の、constraintlayoutのバージョンを変更する。dependencies { implementation "androidx.constraintlayout:constraintlayout:2.0.0-alpha2" }1. レイアウトファイルの修正
以下のように、画面サイズなどの条件によってフォルダ分けされているレイアウトファイルを、すべてlayoutフォルダ内に入れます。
- /layout/activity_main.xml
- /layout-land/activity_main.xml -> /layout/activity_main_land.xml
- /layout-w400/activity_main.xml -> /layout/activity_main_w400.xml
- /layout-w600-land/activity_main.xml -> /layout/activity_main_w600_land.xml
※アニメーションさせたいレイアウト要素が、ConstraintLayoutで書かれていない場合はConstraintLayoutへの書き換えが必要。
※layoutのRoot要素がConstraintLayoutである必要はなく、画面内の一部のConstraintLayoutに対して適用することも可能です。2.
constraint_states.xml
ファイルの作成layoutフォルダによる各レイアウトの適用をやめたので、
ConstraintLayoutStates
によって各Viewの状態とlayoutファイルを紐づけます。
res/xml/
フォルダに、constraint_states.xml
ファイルを作成します。<?xml version="1.0" encoding="utf-8"?> <ConstraintLayoutStates xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <!-- 縦画面 --> <State android:id="@+id/constraintStatePortrait" app:constraints="@layout/activity_main"> <!-- 縦画面 x 横幅が395db以下 => スマホの縦画面サイズ --> <Constraints app:constraints="@layout/activity_main" app:region_widthLessThan="395dp" /> <!-- 縦画面 x 横幅が400db以上 => タブレットの縦画面サイズ --> <Constraints app:constraints="@layout/activity_main_w400" app:region_widthMoreThan="400dp" /> </State> <!-- 横画面 --> <State android:id="@+id/constraintStateLandscape" app:constraints="@layout/activity_main_land"> <!-- 横画面 x 横幅が595db以下 => スマホの横画面サイズ --> <Constraints app:constraints="@layout/activity_main_land" app:region_widthLessThan="595dp" /> <!-- 横画面 x 横幅が600db以下 => タブレットの横、PCのフルスクリーンサイズ --> <Constraints app:constraints="@layout/activity_main_w600_land" app:region_widthMoreThan="600dp" /> </State> </ConstraintLayoutStates>3. ConstraintLayoutの設定と、ステート変更のハンドリング
Viewのセットアップ
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 先ほど作成したConstraintLayoutStatesのxmlを指定する constraintMain.setLayoutDescription(R.xml.constraint_states) // stateの変更に合わせて処理を行う場合は、setOnConstraintsChangedを使用することで変更イベントを受け取れる constraintMain.setOnConstraintsChanged(object : ConstraintsChangedListener() { override fun preLayoutChange(state: Int, layoutId: Int) { // レイアウト変更適用前の設定を記述 val changeBounds = ChangeBounds().apply { duration = 600 interpolator = AnticipateOvershootInterpolator(0.2f) } TransitionManager.beginDelayedTransition(constraintMain, changeBounds) when (layoutId) { R.layout.activity_main -> { val reviewLayoutManager = LinearLayoutManager(baseContext, LinearLayoutManager.VERTICAL, false) recyclerReviews.layoutManager = reviewLayoutManager } R.layout.activity_main_land -> { val reviewLayoutManager = GridLayoutManager(baseContext, 2) recyclerReviews.layoutManager = reviewLayoutManager } R.layout.activity_main_w400 -> { val reviewLayoutManager = LinearLayoutManager(baseContext, LinearLayoutManager.VERTICAL, false) recyclerReviews.layoutManager = reviewLayoutManager } R.layout.activity_main_w600_land -> { val reviewLayoutManager = GridLayoutManager(baseContext, 2) recyclerReviews.layoutManager = reviewLayoutManager } } } override fun postLayoutChange(stateId: Int, layoutId: Int) { // レイアウトの変更を適用するために、requestLayoutを呼び出し constraintMain.requestLayout() } }) configurationUpdate(resources.configuration) } // 画面回転によるレイアウト変更を行う場合 override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) configurationUpdate(newConfig) } private fun configurationUpdate(configuration: Configuration) { if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { // 自分で特定のsetStateのレイアウトを指定することもできる constraintMain.setState(R.id.constraintStateLandscape, configuration.screenWidthDp, configuration.screenHeightDp) } else { constraintMain.setState(R.id.constraintStatePortrait, configuration.screenWidthDp, configuration.screenHeightDp) } }4. AndroidManifestへの画面変更時の設定追加
AndroidManifestで、該当のactivityタグに
configChanges
を指定します。<activity android:name=".MainActivity" android:configChanges="screenSize|smallestScreenSize|orientation|screenLayout">
※ConstraintStateでは、ConstraintLayout内のConstraintを更新するため、画面内の要素を変更することができないことに注意してください。
サイズ変更時の描画遅延時の色指定を行う
ユーザーがウィンドウのサイズを変更した際に、ユーザー操作に追従するようにアクティビティのサイズ変更が行われます。
その際に、アプリで新しく表示された領域を描画するまでに時間がかかる場合、windowBackground
属性またはデフォルトのwindowBackgroundFallback
システム属性によって指定された色でこれらの領域が一時的に塗りつぶされます。そのため、色差が大きい場合はActivityの背景色や、アプリのテーマカラーなどを指定しておくことをオススメします。
メッセージ表示
エラーメッセージなどの表示にDialog、Toastなど複数の方法があるが、画面サイズ変更時に追従するかどうかなどはそれぞれ異なるので注意。
リサイズ対応 その他 PupupWindow ☓ 表示後のサイズ変更は自分で行う必要がある Toast △ 端末の画面を基準として、表示が行われる。固定サイズ Dialog ○ 画面内に表示され、リサイズにもしっかりと追従する ウィンドウ対応周りのAndroidデバッグ設定
開発者向けオプション > アプリ項目の各設定を有効化します。
- タイトルにデバッグ情報を表示する
- アクティビティサイズを変更可能にする
- 枠線のドラッグによるウィンドウの自由なサイズ調整を許可
- API レベル24以上のすべてのアプリケーションで自由形式のサイズ変更を有効にする
- 画面の向きが固定されたアプリのサイズ変更
入力サポート
Chromebookの入力周りのサポートについて
入力ソース
- タッチ
- スタイラスペン
- トラックパッド
- マウス
- キーボード
トラックパッド/マウスサポート
マウスカーソルサポート
Android 7.0 から Custom Pointer API が追加された。
基本的な動作については、実装しなくてもButtonのクリックや、TextViewのテキスト選択など大抵のことはデフォルトでカーソルが指定されています。自分でポインターアイコンを設定する場合は、それぞれViewクラスに設定します。
View.setPointerIcon()
にIconをセットする。View.onResolvePointerIcon()
をオーバーライドする。ポインターアイコンは、デフォルトで用意されているシステムアイコンを使用するか、オリジナルのアイコンを指定することができます。
デフォルトのシステムアイコンを指定する場合
android:pointerIcon="hand"or
val icon = PointerIcon.getSystemIcon(application, PointerIcon.TYPE_HAND) view.pointerIcon = iconデフォルトのシステムアイコンの種類
- PointerIcon.TYPE_ARROW
- PointerIcon.TYPE_CONTEXT_MENU
- PointerIcon.TYPE_HAND
- PointerIcon.TYPE_HELP
- PointerIcon.TYPE_WAIT
- PointerIcon.TYPE_CELL
- PointerIcon.TYPE_CROSSHAIR
- PointerIcon.TYPE_TEXT
- PointerIcon.TYPE_VERTICAL_TEXT
- PointerIcon.TYPE_ALIAS
- PointerIcon.TYPE_COPY
- PointerIcon.TYPE_NO_DROP
- PointerIcon.TYPE_ALL_SCROLL
- PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW
- PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW
- PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW
- PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW
- PointerIcon.TYPE_ZOOM_IN
- PointerIcon.TYPE_ZOOM_OUT
- PointerIcon.TYPE_GRAB
- PointerIcon.TYPE_GRABBING
オリジナルのアイコンを使用する場合
pointer-iconタグのxmlファイルを、xmlフォルダの下などに追加する。
例:
res/xml/custom_pointer.xml
<?xml version="1.0" encoding="utf-8"?> <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" android:bitmap="@mipmap/ic_launcher" android:hotSpotX="24px" android:hotSpotY="24px"/>それを、UIロジック側で読み込む。
val icon = PointerIcon.load(application.resources, R.xml.custom_pointer)) view.pointerIcon = icon右クリックサポート
ラップトップだと、longClickListenerなどの代わりに右クリックでのイベントハンドリングを行う。
view.setOnContextClickListener { true }registerForContextMenu(view) override fun onCreateContextMenu(menu: ContextMenu?, v: View?, menuInfo: ContextMenu.ContextMenuInfo?) { super.onCreateContextMenu(menu, v, menuInfo) val inflater = activity?.menuInflater ?: return when (v?.id) { R.id.menu_button -> { // Create cart product context menu inflater.inflate(R.menu.shr_cart_product_context_menu, menu) } } } override fun onContextItemSelected(item: MenuItem?): Boolean { // Action selected menu item. return true }キーボードサポート
Chromebookではキーボードのサポートとして、タブキーによるフォーカス変更、ショートカットキーによる操作を推奨してます。
タブキー、方向キーサポート
- タブによるnextフォーカスの設定
- 方向キーによるそれぞれのフォーカスの設定
Focusable設定
android:focusable="true"or
view.setFocusable = trueまた、必要に応じて背景などのリソースをFocusableなものにしておきます。
タブキーサポート
android:nextFocusForward="@id/next_view"view.nextFocusForwardId = R.id.next_view方向キーサポート
android:nextFocusLeft="@id/view_to_left" android:nextFocusRight="@id/view_to_right" android:nextFocusUp="@id/view_above" android:nextFocusDown="@id/view_below"or
view.nextFocusLeftId = R.id.view_to_left view.nextFocusRightId = R.id.view_to_right view.nextFocusTopId = R.id.view_above view.nextFocusBottomId = R.id.view_belowキーボードナビゲーションクラスタ
Android 8.0から、ViewGroup単位でタブキーのハンドリングを行う方法が追加されました。
画面の構成を以下の図のように、ナビゲーションクラスタ(タブキーで移動させたいグループ)ごとにまとめます。
まとめた各クラスタのrootのViewにだけ、
keyboardNavigationCluster
プロパティを指定します。android:keyboardNavigationCluster="true"or
view.setKeyboardNavigationCluster = true※ネストされていないクラスタが階層の別のレベルに表示されることがあっても、クラスタはネストできません。クラスタをネストしようとすると、フレームワークは最上位の ViewGroup 要素のみをクラスタとして処理します。
つまり、ConstraintLayoutなどで、全クラスタがフラットな状態になるようにViewを組む必要があります。
ショートカットキーサポート
アプリ内でよくある操作を、ショートカットとしてサポートします。
Activity.dispatchKeyShortcutEvent
をオーバーライドすることによって、キーを押された際のイベントをハンドリングすることができます。override fun dispatchKeyShortcutEvent(event: KeyEvent?): Boolean { if (event?.keyCode == KeyEvent.KEYCODE_Z && event.hasModifiers(KeyEvent.META_CTRL_ON)) { // Ctrl + z => undo viewModel.onUndoKeyShortcut() return true } else if (event?.keyCode == KeyEvent.KEYCODE_Z && event.hasModifiers(KeyEvent.META_CTRL_ON or KeyEvent.META_SHIFT_ON)) { // Ctrl + Shift + z => redo viewModel.onRedoKeyShortcut() return true } else { return false } }デフォルトで有効になっているキーボード操作
Escキー
とF1キー
は、スマホのバックボタンと同じ挙動になります。疑似タッチスクリーンの許可
Chrome OS バージョン M53 以降では、android.hardware.touchscreen 機能を明示的に要求しないすべての Android アプリが、android.hardware.faketouch 機能をサポートする Chrome OS デバイスでも機能するようになりました。そのため、何も対応する必要はありません。
逆に、Chrome OS バージョン M52 以下では、デフォルトでAndroidアプリは android.hardware.touchscreen機能を必要とするため、タッチスクリーンがないChromebookだとインストールできないため、疑似タップインターフェースを備えた端末でもアプリを使用できるようにしたい場合はタッチスクリーンが必須ではないことを明示的に宣言する必要があります。<uses-feature android:name="android.hardware.touchscreen" android:required="false" />疑似タップインターフェースを備えたデバイスは、基本的なタップイベントをエミュレートする入力システムをユーザーに提供します。
たとえばユーザーは、マウスまたはリモコンを操作して、画面上のカーソルの移動、リストのスクロール、画面の一部から別の部分への要素のドラッグなどを行うことができます。ChromeOS エミュレーターでのデバッグ方法
参考サイト:https://developer.android.com/topic/arc/emulator?hl=ja
別PCからのDebugについて
外部PCからChromebookに対して、adb接続してアプリをインストールしデバッグする方法です。
初期の状態は、ノーマルモードとなっており外部のPCからのファイル書き込みや、通信などなにもできない状況です。主な手順
- ChromeOSの開発者モード化
- GooglePlayの利用規約に同意
- Android設定の開発者モード化
- ADBデバッグの許可
- 外部PCのAndroidStudioからの接続
- ChromeBookのIPアドレスの確認
- AndroidStudioでの確認
ChromeOSの開発者モード化
開発者モードに切り替えると、 "root" shellへのアクセスができるようになります。
ただし、切り替えると端末が再起動し、端末上のすべてのデータが消えます。
また、ノーマルモードに戻す際にも同じく端末が再起動し、端末上のすべてのデータが消えます。
PC内にあるデータは、GoogleDriveなどにしっかりと保存しておきましょう。参考サイト:https://www.chromium.org/chromium-os/poking-around-your-chrome-os-device
リカバリーモード
デベロッパーモード
各起動後の注意として、毎回の起動時にはCtrl + Dを押して起動します。
でないと、ノーマルモードでの起動となりすべてのデータが消えます。ノーマルモードへの切り替え方法
再起動後、Spaceキー を押して起動します。
ノーマルモードへの切り替え時もデータが消えるため、注意してください。Android設定の開発者モード化
ADB デバッグを有効にするには、次のステップを実行します。
- 画面の右下部にあるクロック アイコンをクリックします。
- [Settings] をクリックします。
- [Android Apps] セクションで、[Manage your Android apps in Settings] という行にある [Settings] リンクをクリックします。これにより、Android アプリの設定が表示されます。
- [About device] をクリックします。
- [Build number] を 7 回クリックして、デベロッパー モードに移行します。
- ウィンドウの左上部にある矢印をクリックして、メインの [Settings] 画面に戻ります。
- 新しい [Developer options] アイテムをクリックし、[ADB debugging] をアクティブにし、[OK] をクリックして、ADB デバッグを有効にします。
外部PCのAndroidStudioからの接続
別のPCから、ChromebookにUSB接続してAndroidStudioでデバイス確認を行うと
100.115.92.2:5555
が表示されるがそれは外部PCからは接続できないので注意。ChromebookのIPを確認する
ChromeOS情報のchronosユーザーのターミナル(
ctrl+alt+T
+ shell)上で、「 ip -4 a 」と入力。chronos@localhost / $ ip -4 a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 2: arcbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 inet 100.115.92.1/30 brd 100.115.92.3 scope global arcbr0 valid_lft forever preferred_lft forever 3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 inet 192.168.128.100/24 brd 192.168.128.255 scope global wlan0 valid_lft forever preferred_lft forever 6: vmtap0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 1000 inet 100.115.92.5/30 brd 100.115.92.7 scope global vmtap0 valid_lft forever preferred_lft forever一番下の、vmtap0のIPの5555番ポートで別のPCから
adb connect
できる。$adb connect 192.168.0.32:5555
あとは、いつも通り起動確認のダイアログがでるのでOKをクリックする。
ChromeOS上でのAndroidアプリの開発
主な手順
- ChromeOSの開発者モード化
- GooglePlayの利用規約に同意
- Android設定の開発者モード化
- ADBデバッグの許可
- (↑までは、ChromeOSでのアプリデバッグ方法を参照)
- Linuxの有効化
- AndroidStudioのインストール
- ファイアウォールの設定
- 端末のAndroidStudioからの接続
Linuxの有効化
参考サイト:https://developer.android.com/topic/arc/studio
AndroidStudioのインストール
ChromeOSでのAndroidStudioのインストール
AndroidStudioの起動
「 ./android-studio/bin/studio.sh 」
ファイアウォール設定
- Chromeブラウザ上で、 Ctrl+Alt+T を押してChrome OS端末を起動する
- 「 shell 」と入力して、bash コマンドシェルを開始する
crosh> shell chronos@localhost / $端末のAndroidStudioからの接続
Linuxの
ターミナル
アプリを起動。{username}@penguin adb kill-server {username}@penguin connect_adb
connect_adb
は、AndroidStudioのインストール時に.bashrcに、以下のように登録されている関数。function connect_adb() { adb connect 100.115.92.2:5555 }
参考リンク
情報サイト
Androidアプリの作り方について
- Chrome OS デバイス
- Apps for Chrome OS overview
- Chromebook 向けアプリの最適化
- Get Your Android App Ready for Chromebooks
- Window management
- マルチ ウィンドウのサポート
- google/rally
- New Features in ConstraintLayout 2.0
- Responsive layout grid
- 入力とナビゲーション
- Chromebook 向けアプリ マニフェストの互換性
- AndroidNのDropPermissionsを使ってアプリケーション間でDragAndDropする
Google I/O
- What’s new in Android apps for Chrome OS (Google I/O '18)
- Android Apps for Chromebooks and Large Screen Devices (Google I/O '17)
Google Codelabs
セットアップ
開発者モード
Androidの開発環境
- Chromebook 向けアプリの最適化 セットアップ
- Chromebookで動作するAndroidアプリにadbを接続する方法
- ChromebookでAndroidアプリをデバッグする方
- How to configure your Chromebook to use ADB
- Connect Android USB Devices in Chrome OS
- Android Container Build
その他
- 投稿日:2019-02-11T07:15:08+09:00
kotlinでAndroid端末内の画像を含むフォルダを検知する
画像系のアプリを作ってる時に端末内の画像を含むフォルダを表示させたかったので作成
ストレージの許可
端末の設定からアプリのストレージ設定を弄らないと内部ストレージにアクセスできない?らしい
詳しくはhttps://direct.fujixerox.co.jp/ap1/sc/beat/ja/support/020211.html内部ストレージのパスを渡す
内部ストレージのパスをImgFileconクラスのimgfileserchに渡す
MainActivity.ktpackage com.example.myapplication import android.os.Bundle import android.support.design.widget.BottomNavigationView import android.support.v7.app.AppCompatActivity import kotlinx.android.synthetic.main.activity_main.* import java.io.File import com.example.myapplication.R.attr.content import android.content.Context import android.os.Environment class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) /*内部ストレージのパス取得*/ val path = Environment.getExternalStorageDirectory().getPath() val dir = File(path) /*内部ストレージのパスを渡す*/ val imgserch=ImgFilecon() imgserch.imgfileserch(dir.toString()) } }画像を含むフォルダを探す
渡されたパス内のディレクトを再帰的に探索してファイルを探す。見つけたファイルが画像の拡張子(.jpgなど)を持っていたらそのファイルのあるディレクトリのパスを出力
余計な画像が出てきてしまうためAndroidフォルダはリジェクトImgFilecon.ktpackage com.example.myapplication import android.os.Parcel import android.os.Parcelable import java.io.File class ImgFilecon() { /*画像の含まれるディレクトリを再帰的に探すメソッド*/ tailrec fun imgfileserch(passname: String): Unit { var flag = false val dir = File(passname) if (dir.listFiles() != null) { /*渡されたパスを探査*/ loop@ for (i in dir.listFiles()) { /*ファイルの時の処理*/ if (i.isFile()) { val ext = Extension() /*画像ファイル判定*/ if (!flag) { when (ext.extensionsplit(i.toString(), ".")) { "jpeg", "JPEG", "jpg", "JPG", "png", "PNG", "gif", "GIF" -> { println("画像の含まれるディレクトリのパス" + dir) flag = true } else -> {} } } } /*ディレクトリの時の処理*/ else if (i.isDirectory) { val extdir = Extension() /*Androidディレクトリは余計な画像が含まれるので表示しない*/ when (extdir.extensionsplit(i.toString(), "0/")) { "/Android" -> { continue@loop } else -> { /*再帰*/ imgfileserch(i.toString()) } } } } } else println("null") } }画像ファイルを判断するために拡張子切り出し
パスと区切り文字を渡すと区切り文字より下の文字列を返すメソッド。今回はファイルパスと区切り文字"."を渡して拡張子を取得した。
Extension.ktpackage com.example.myapplication import android.os.Parcel import android.os.Parcelable class Extension { /*区切り文字で分割するメソッド*/ fun extensionsplit(filename: String, splitps: String): String { /*splitpsで指定した文字のindex*/ val point = filename.lastIndexOf(splitps) if (point != -1) { /*pointより下の部分を切り出す*/ /* 例 xxxx.jpg→jpg*/ return filename.substring(startIndex = point + 1) } else return null.toString() } }デモ
こんな感じで端末内の画像を含むフォルダ一覧がAndroidStudioのRunに表示される参考
ストレージへのアクセス許可:https://direct.fujixerox.co.jp/ap1/sc/beat/ja/support/020211.html
終わりに