- 投稿日:2019-03-07T20:18:52+09:00
FlutterでローカルからNotification
はじめに
Push通知ではなくアプリケーション上から通知を出すためにどうするのか調べました。
環境
Android Studio
Flutter1.0今回はiOSを表示させる環境用意しなかったので、Androidのみに言及しています。
iOSだと動かない箇所が出てくると思います。Dependencyの追加
Flutterには通知を簡単に実現するための
flutter_local_notificationsが用意されているので、それをpubspec.yamlに追加してあげます。
バージョンがまだ0.5.1なので、バージョンアップ後に動かなくなる可能性がありますので、この先読む方はご注意ください。dependencies: flutter_local_notifications: ^0.5.1アプリ側のトリガーを元に通知を行う
アプリ側のイベントを契機に通知を行う方法について見ていきます。
まずは必要となるpackageをimportしてあげます。
import 'package:flutter_local_notifications/flutter_local_notifications.dart';次からは
State<T>内部に実装する必要があるため、必要な実装は先に行なっておきます。
私はFlutterを勉強しながら継ぎ足し製法でソースを拡充させているため、それを使います。
なお、色んなコードが入ってきてしまっているため、本ページでは全体のコードはのせませんがご了承ください。
State<T>の実装が完了したらメンバ変数としてFlutterLocalNotificationsPluginを追加します。
様々なサンプルだとメンバ変数になってませんが、これを使って実際の通知を行う必要があるため、メンバ変数としないと無理です。FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin; NotificationDetails platformChannelSpecifics;まずは通知を行うための初期化処理を行います。
初期化はState<T>のinitState内に実装します。
ここでの注意点はサンプルをコピーしただけではエラーとなるため、実装が必要になる部分があることです。また、実際の通知する際に使用するオブジェクトもここで生成しておいた方が良いです。
AndroidNotificationDetailsのコンストラクタはAndroid8.0以上のチャンネル機能用の通知用があるので、8.0以上を目的で作る場合は必須となります。@override void initState() { super.initState(); _refresh(); flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); var initializationSettingsAndroid = AndroidInitializationSettings('app_icon'); var initializationSettingsIOS = IOSInitializationSettings( onDidReceiveLocalNotification: onDidReceiveLocationLocation); var initializationSettings = InitializationSettings( initializationSettingsAndroid, initializationSettingsIOS); flutterLocalNotificationsPlugin.initialize(initializationSettings, onSelectNotification: onSelectNotification); var androidPlatformChannelSpecifics = AndroidNotificationDetails( 'your channel id', 'your channel name', 'your channel description', importance: Importance.Max, priority: Priority.High); var iOSPlatformChannelSpecifics = IOSNotificationDetails(); platformChannelSpecifics = NotificationDetails( androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics); }そして必要な箇所を追加します。
onDidReceiveLocationLocationはiOS用のようなので、本ページに必要ない実装ですがなぜか実装してあったので、合わせてのせておきます。
onSelectNotificationについてが今回重要な点となります。
このメソッドは通知をクリックした際に呼ばれるイベントとなっているため、その動作について実装が必要となります。今回は
Navigator.pushを使用して別のWidgetを呼び出してます。
そもそもNotificationからの起動時は今の画面の次の画面として起動するか、一から起動してから次の画面として起動するかという動作をする感じなので、Future.syncなどからWidgetを作っても現状では動作がどう変わるかが良くわかりませんでした。何かパラメータを渡す際は引数の
payloadを使って行う必要があります。
ここはString固定なので、複雑なデータを渡す際はJsonなんかで渡す必要があります。Future onSelectNotification(String payload) async { await Navigator.push( context, MaterialPageRoute( builder: (context) => Text(payload), maintainState : false,), ); } Future onDidReceiveLocationLocation( int id, String title, String body, String payload) async { await showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text(title), content: Text(body), actions: <Widget>[ FlatButton( child: Text(payload), onPressed: () { Navigator.of(context).pop(); }, ), ], ); } ); }最後に実際に通知を行う箇所です。
initStateで生成したオブジェクトを使用して通知します。
onSelectNotificationに渡ってくるpayloadはpushのpayloadで設定したものと同じものなので、通知後にパラメータを渡したい場合はここで設定してあげてください。Future _onNotification() async { await flutterLocalNotificationsPlugin.show( 0, 'plain title', 'plain body', platformChannelSpecifics, payload: 'item id 2'); }これで通知できるようになりました。
通知をスケジューリングする
アプリケーショントリガーですぐに実行することが出来るようになりましたが、そのトリガーを元にすぐに出さずにあとで出すようにしたい場合にスケジューリングさせることが出来ます。
その場合にどうするべきかを見ていきます。AndroidManifest.xmlの修正
スケジューリングされた通知を行うのに
AlarmManager使用しているらしく、それが使用できるようにManifestを修正してあげる必要があります。
修正内容は公式書いてあるので、その通りに追加してあげます。
AlermManagerを使用している場合はアプリが起動していなくても起動してくれるので、停止状態でも出せるようになります。なお、
com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiverを追加するとAndroid Studio上でエラーになりますが、特に問題ないので無視してください。<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> </intent-filter> </receiver> <receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />dartファイル
コード的には
scheduleメソッドを使用して、時間を指定あげるだけの違いとなります。Future _onNotification() { return flutterLocalNotificationsPlugin.schedule( 0, 'plain title', 'plain body', DateTime.now().add(Duration(seconds: 5)), platformChannelSpecifics, payload: 'item id 2'); }繰り返し通知
Manifestの修正はしておいてください。
コード的にはperiodicallyShowを使えば実現できます。
指定するRepeatIntervalはenumなので、簡単に繰り返す期間の指定が可能です。Future _onNotification() { return flutterLocalNotificationsPlugin.periodicallyShow( 0, 'plain title', 'plain body', RepeatInterval.EveryMinute, platformChannelSpecifics, payload: 'item id 2'); }特定の時間で繰り返す
毎日定時に起動させたい場合は
showDailyAtTimeを使用します。Future _onNotification() { var date = DateTime.now().add(Duration(seconds: 5)); return flutterLocalNotificationsPlugin.showDailyAtTime( 0, 'plain title', 'plain body', Time(date.hour, date.minute, date.second), platformChannelSpecifics, payload: 'item id 2'); }特定の曜日、特定の時間で繰り返す
特定の曜日も追加したい場合は
showWeeklyAtDayAndTimeで曜日と時間を指定します。Future _onNotification() { var date = DateTime.now().add(Duration(seconds: 5)); return flutterLocalNotificationsPlugin.showWeeklyAtDayAndTime( 0, 'plain title', 'plain body', Day.Sunday, Time(date.hour, date.minute, date.second), platformChannelSpecifics, payload: 'item id 2'); }おわりに
本当は起動時にNotificationを設定する方法も合わせて見ていたのですが、没としました。
今の所は状態変化のタイミングに合わせて通知設定をするというのが一番良いです。
面倒だから起動と同時に設定できるようにしたいなーと思ってたんですが、しょうがないので別の方法考えます。
- 投稿日:2019-03-07T19:52:25+09:00
Android Test Night #6 内容まとめ
はじめに
Test NightコミュニティのAndroid Test Night 第6回目(2019/3/7)のまとめです。
本イベントはAndroidにおけるテスト周りに関する知識を共有することを目的としたものです。
テスト周りに関するものであれば何でもOKです。 例をあげるとすれば以下のようなものなどです。
- テストをはじめてみた&ここで苦労した
- このテスティングフレームワークはここがハマりどころ
- テスティングフレームワークをこうやって使い分けている
- こうやって工夫してテストしている
- オレが考えるAndroidアプリにおけるテスタビリティの高い設計
- 弊社のCI/CD環境はこんな感じにしている
- DroidKaigiで話せなかったネタをここで
ハッシュタグは#android_test_nightです!
LT
モバイルテスターの世界戦向けトレーニング - Curiosity and Knowledge
スピーカー
- mkwrd
内容
- SeleniumConf Tokyoの宣伝
- 日本初開催
- 4月18日、19日
- テスト自動化の標準となりつつあるSeleniumを学ぼう
- 160以上のCFPがきた。セッションの選定に時間かけた。
- 参加者は海を超えて
- 自分のレベルを図れる
- 「エンジニアの戦場は世界だ」
- モバイルテスターの世界向き要素
- モバイルアプリ開発は世界の潮流
- 世界にでるのにモバイルテスターほど適した資格はない
- モバイルテスターの資格
- ISTQB
- JSTQBは日本向けの加盟組織
- AGILE,CORE,SPECIALISTなどたくさんある
- ASTQB
- ISTQB以外の資格も扱う。
- MobileTesting資格を独自に実施
- CBTなので日本でも受験できる
資料
AzureDevOpsで始めるAndroidのCI/CD
スピーカー
- nakasho
内容
Azure DevOps
- 組織のOwnerを簡単に切り替えられる。お客さんへの納品が簡単
- 料金
- 3360円
- Free
- 5ユーザまで無料って?
- 無料枠利用者5人まで + VSサブスクライバーは人数制限なし + 編集権限なしの人も制限なし
- AzureRepos
- 容量無制限のGitリポジトリがもらえる
- PJに複数のリポジトリ作成が可能
- ブランチごとに権限設定可能
- AzurePipeline
- AzureReposGit,GitHub,Bitbucket、SVNなど使える
- ビルドテンプレートもいろいろ。今回はAndroidの説明
- AndroidSigningの設定すればビルドのあとに署名もしてくれる
- AppCenterTestやDistributeの設定もできる
- 毎月1800分まで無料
- 自前でAgentをインストールすることも可能。これつかえば時間の制限なし
資料
Make CI/CD More Comfortable Before/After Code Testing
スピーカー
- red_fat_daruma
内容
- モチベーション
- テストは開発プロセスで重要。
Definition
- 開発を加速させるもの
エラーに対して柔軟でないものは悪
- badパターン
- Lintが落ちたらテストが実行されないCI
- Lintはテストを止めるものではない
- 一つのタスクが失敗したらすべてが失敗する
これらを解決させるにはコマンドのステータスを確認して必要に応じてCI/CDを失敗させる
- プロダクションではエラーは落とす必要のあるがステージングでは実行されてほしい
- Gradle tasksのcontinueオプションを使う
- Jcneterのリトライロジックを入れる
- Dangerのようなレポートシステムを使う。テスト失敗の内容を知りたいから。
ジョブの分け方。このように分けるのが理想
- VitalJob
- TestJob
- DeploymentJob
ビルドの中間結果を保存・ロードする
- メジャーなCIサービスにはキャッシュシステムがある。
- 依存関係のキャッシュ
CIサービスのキャッシュの使いどころ
- 単一PRでのビルド間キャッシュ
- CIのキャッシュシステムは使うべきではない
- ビルドの中間情報はGithubイシューとHTMLを利用する
- 複数PR上でのビルド間キャッシュ
- 最適なソリューションがわからないので解決策があれば教えて欲しい
DeployGate
- CDN通してるのでダウンロード速度が速い
- CIでデプロイしたらレスポンスをカールで叩くようになっている。よって人が初回ダウンロードするときにはキャッシュつかえる
- DistributionTracks
- GooglePlayStoreの内部トラックのようなバージョン別ダウンロードページ
- dpg
- 非公式のCLIベースDeployGateAPIクライアント
資料
形式手法について調べてみた
スピーカー
- altitude3190
内容
- 仕様の不備に開発フェーズの速い段階で気づきたい
- あとになればなるほど手戻り工数が大きい
- 改善はしていたが場当たり的な対応、工数・スキル的に手に負えなかった
- 形式手法
- 仕様を明確に記述
- 機械的に検証する
- 数学に基づく科学的な裏付けがある
- 形式仕様記述
- VDM++/Event-B/Zなど
- 定理証明
- Coq
- モデル検査
- Alloy/Promela
- システムを有限個の状態を持つモデルで表現
- モデルの状態を機械的網羅的に検査
- Spinはモデル検査ツールでHomebrewでインストール可能
- 判例があればトレースとともに表示
- メリットデメリット
- 形式手法メリット
- 検査結果から仕様不備に気付ける
- デメリット
- 導入コスト
- モデル化が難しい場合がある
- 部分化導入がよい
- 現場にマッチした形式手法がどれになるか吟味する必要がある
資料
UI テストで楽するための技術
スピーカー
- ksfee
内容
- UIテストは大変
- 環境構築が大変
- 実行時間がかかる
- 実行結果の確認
- 依存が多い
- 実装が大変
- 楽する技術
資料
- 投稿日:2019-03-07T19:52:25+09:00
Android Test Night #6 内容まとめ(随時更新)
はじめに
Test NightコミュニティのAndroid Test Night 第6回目(2019/3/7)のまとめです。
本イベントはAndroidにおけるテスト周りに関する知識を共有することを目的としたものです。
テスト周りに関するものであれば何でもOKです。 例をあげるとすれば以下のようなものなどです。
テストをはじめてみた&ここで苦労した
このテスティングフレームワークはここがハマりどころ
テスティングフレームワークをこうやって使い分けている
こうやって工夫してテストしている
オレが考えるAndroidアプリにおけるテスタビリティの高い設計
弊社のCI/CD環境はこんな感じにしている
DroidKaigiで話せなかったネタをここでハッシュタグは#android_test_nightです!
LT
モバイルテスターの世界戦向けトレーニング - Curiosity and Knowledge
スピーカー
- mkwrd
内容
- SeleniumConf Tokyoの宣伝
- 日本初開催
- 4月18日、19日
- テスト自動化の標準となりつつあるSeleniumを学ぼう
- 160以上のCFPがきた。セッションの選定に時間かけた。
- 参加者は海を超えて
- 自分のレベルを図れる
- 「エンジニアの戦場は世界だ」
- モバイルテスターの世界向き要素
- モバイルアプリ開発は世界の潮流
- 世界にでるのにモバイルテスターほど適した資格はない
- モバイルテスターの資格
- ISTQB
- JSTQBは日本向けの加盟組織
- AGILE,CORE,SPECIALISTなどたくさんある
- ASTQB
- ISTQB以外の資格も扱う。
- MobileTesting資格を独自に実施
- CBTなので日本でも受験できる
資料
AzureDevOpsで始めるAndroidのCI/CD
スピーカー
- nakasho
内容
Azure DevOps
- 組織のOwnerを簡単に切り替えられる。お客さんへの納品が簡単
- 料金
- 3360円
- Free
- 5ユーザまで無料って?
- 無料枠利用者5人まで + VSサブスクライバーは人数制限なし + 編集権限なしの人も制限なし
- AzureRepos
- 容量無制限のGitリポジトリがもらえる
- PJに複数のリポジトリ作成が可能
- ブランチごとに権限設定可能
- AzurePipeline
- AzureReposGit,GitHub,Bitbucket、SVNなど使える
- ビルドテンプレートもいろいろ。今回はAndroidの説明
- AndroidSigningの設定すればビルドのあとに署名もしてくれる
- AppCenterTestやDistributeの設定もできる
- 毎月1800分まで無料
- 自前でAgentをインストールすることも可能。これつかえば時間の制限なし
資料
Make CI/CD More Comfortable Before/After Code Testing
スピーカー
- red_fat_daruma
内容
- モチベーション
- テストは開発プロセスで重要。
Definition
- 開発を加速させるもの
柔軟なCI
bad
- Lintが落ちたらテストが実行されないCI
- Lintはテストを止めるものではない
- 一つのタスクが失敗したらすべてが失敗する
プロダクションでは落とす必要のあるがステージングでは実行されてほしい
Gradletasksのontinueオプション
Jcneterのリトライロジックを入れる
Dangerはテストが失敗してもしなくても実行してほしい
ジョブの分け方
- VitalJob
- TestJob
- DeploymentJob
ビルドの中間情報はGithubイシューやHTMLを利用する
CIサービスのキャッシュの使いどころ
- PRでは使うべきではない
DeployGate
- CDN通してるのでダウンロード速度が速い
- CIでデプロイしたらレスポンスをカールで叩くことで人が初回ダウンロードするときにはキャッシュつかえる
- DistributionTracks
説明内容が濃くて理解が追いつきませんでした。。。後ほど加筆しますm(_ _)m
資料
形式手法について調べてみた
スピーカー
- altitude3190
内容
- 仕様の不備に開発フェーズの速い段階で気づきたい
- あとになればなるほど手戻り工数が大きい
- 改善はしていたが場当たり的な対応、工数・スキル的に手に負えなかった
- 形式手法
- 仕様を明確に記述
- 機械的に検証する
- 数学に基づく科学的な裏付けがある
- 形式仕様記述
- VDM++/Event-B/Zなど
- 定理証明
- Coq
- モデル検査
- Alloy/Promela
- システムを有限個の状態を持つモデルで表現
- モデルの状態を機械的網羅的に検査
- Spinはモデル検査ツールでHomebrewでインストール可能
- 判例があればトレースとともに表示
- メリットデメリット
- 形式手法メリット
- 検査結果から仕様不備に気付ける
- デメリット
- 導入コスト
- モデル化が難しい場合がある
- 部分化導入がよい
- 現場にマッチした形式手法がどれになるか吟味する必要がある
資料
UI テストで楽するための技術
スピーカー
- ksfee
内容
- UIテストは大変
- 環境構築が大変
- 実行時間がかかる
- 実行結果の確認
- 依存が多い
- 実装が大変
- 楽する技術
資料
- 投稿日:2019-03-07T18:38:21+09:00
background-sizeの記載には気を付けよう
androidで、非表示の要素を表示させたときに背景画像設定が消える現象が発生しました。
調べてみたところ、background-sizeの書き方に問題がありました。before
cssbackground: url(../../hogehoge.jpg) left top repeat-y; background-size: 100% auto;100% auto という記載に問題がありました。
after
cssbackground: url(../../hogehoge.jpg) left top repeat-y; background-size: 100%;これで直りました。
- 投稿日:2019-03-07T18:33:24+09:00
Haxeを用いたゲームエンジン一覧(随時更新)
『Haxe Game Engines』というYouTubeのビデオで、Haxeが使われているゲームエンジンが紹介されていました。意外とあるなぁ、というのと聞いたことないけど調べてみるのも良いなと思いメモのため公開しておきます。この記事に出ている以上のことはビデオ(英語ですが)の方をご覧ください。
Haxe Game Engines https://t.co/C5deQpoeva
— JavaCommons (@javacommons) 2019年3月7日ローレベルなものから本格的なゲームエンジンまでいろいろありますが、できれば全てのエンジンに実際に触れてみて分かったことなどを、この記事にも反映していければと思っています。
Lowest Level なもの
NME(Native Media Engine) ⇒ https://github.com/haxenme/nme
Lime ⇒ https://github.com/openfl/lime
Intermediate Level なもの
Kha ⇒ https://github.com/Kode/Kha
OpenFL ⇒ http://www.openfl.org/
High Level 2D なもの
HaxeFlixel ⇒ http://haxeflixel.com/
HaxePunk ⇒ http://haxepunk.com/
Stencyl ⇒ http://www.stencyl.com/
High Level 3D なもの
Away3D ⇒ https://github.com/openfl/away3d
Heaps ⇒ https://heaps.io/
Armory3D ⇒ https://armory3d.org/
対応プラットフォーム
ゲームエンジン 対応プラットフォーム NME iOS, Android, Windows, Mac, Linux Lime windows, mac, linux, neko, android, ios, html5, flash, air Kha HTML5 (WebGL 2, WebGL and canvas), Windows (Direct3D 12, Direct3D 11, Direct3D 9, Vulkan or OpenGL), Universal Windows Platform (Direct3D 12 or Direct3D 11), macOS (Metal or OpenGL), Linux (Vulkan or OpenGL), Android (via C++ or via Java), iOS (Metal or OpenGL), tvOS, Raspberry Pi, PlayStation 4, Xbox One, Nintendo Switch, Tizen, Flash, Unity 3D, Node.js (for automatically created server versions), Java and AWT, C# and Windows Presentation Foundation OpenFL Windows, macOS, Linux, iOS, Android, Flash, AIR, HTML5 HaxeFlixel Flash, Windows, Mac, Linux, Android, iOS, Neko, BlackBerry, WebOS HaxePunk Windows, Mac, Linux, HTML 5 (WebGL), iOS, Android Stencyl iOS (iPhone/iPad), Android, Windows, Mac, Linux, Flash, HTML5 Away3D Flash, HTML5, iOS, Android, Windows, Mac, Linux Heaps HTML5 (requires WebGL1, WebGL2 is supported as well) - Mobile (iOS, Android, and tvOS) - Desktop with OpenGL (Win/Linux/OSX) or DirectX (Windows only) - Consoles (Nintendo Switch, Sony PS4, XBox One - requires being a registered developer) - Flash Stage3D Armory3D HTML5, HTML5 (native), HTML5 Worker, Windows, Windows Universal, OS X, Linux, Android, Android (native), iOS, Raspberry Pi, Tizen, Flash, node.js, Unity, PlayStation Mobile, Java, WPF
- 投稿日:2019-03-07T17:22:45+09:00
CoordinatorLayoutに表示したSnackBarをスワイプで消せないようにする方法
知らなかったんですが、CoordinatorLayoutにSnackBarを表示すると、左から右へスワイプで消せるんですね。
でも、消したくない!ってこともあるので、消さないようにする方法を調べて実際に消せないようにしたまずは消している実装を調べた
Snackbarが継承している BaseTransientBottomBar で以下のようなコードがあります。
final void showView() { if (mView.getParent() == null) { final ViewGroup.LayoutParams lp = mView.getLayoutParams(); if (lp instanceof CoordinatorLayout.LayoutParams) { // If our LayoutParams are from a CoordinatorLayout, we'll setup our Behavior final CoordinatorLayout.LayoutParams clp = (CoordinatorLayout.LayoutParams) lp; final Behavior behavior = new Behavior(); behavior.setStartAlphaSwipeDistance(0.1f); behavior.setEndAlphaSwipeDistance(0.6f); behavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END); // 略 clp.setBehavior(behavior); // Also set the inset edge so that views can dodge the bar correctly clp.insetEdge = Gravity.BOTTOM; } mTargetParent.addView(mView); }Snackbarが使ってるViewのLayoutParamsがCoordinatorLayoutだったときだけ特別にBehaviorなるものを設定して、スワイプで消せるようにしてました。
消さないようにする方法
CoordinatorLayoutを使いながらも、スワイプで消さないようにするために、Behaviorを上書きするようにしました。
val snackBar = Snackbar.make(binding.coordinatorLayout, message.text, length).apply { addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() { override fun onShown(transientBottomBar: Snackbar) { super.onShown(transientBottomBar) val lp = transientBottomBar.view.layoutParams if (lp is CoordinatorLayout.LayoutParams) { lp.behavior = null } } }) } snackBar.show()addCallbackで、snackBarが表示出来た後にBehaviorを上書きしてます。
snackBar.show()の直後だと、BaseTransientBottomBar.showViw()でBehaviorが上書きされてしまうのでスワイプで消えてしまいます。
- 投稿日:2019-03-07T15:38:08+09:00
Android Studio TabbedActivity 拡張
各フラグメント表示後、上からリストビューが下りてくるTabbedActivityです。
ポイントはSectionsPagerAdapter#setPrimaryItem()でリストビューのY位置変更処理をする所です。
下記のコードの大部分はTabbedActivity を選択すると、AndroidStudio が自動的に生成してくれます。public static class PlaceholderFragment extends Fragment { private static final String ARG_SECTION_NUMBER = "section_number"; static ListView[] list = new ListView[7]; //リストビュー変数宣言 public PlaceholderFragment() { } public static PlaceholderFragment newInstance(int sectionNumber) { PlaceholderFragment fragment = new PlaceholderFragment(); Bundle args = new Bundle(); args.putInt(ARG_SECTION_NUMBER, sectionNumber); fragment.setArguments(args); return fragment; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_stage_select, container, false); int selector = getArguments().getInt(ARG_SECTION_NUMBER); ///////////////////////////////////////////////ここから追加 ImageView imageView = rootView.findViewById(R.id.imageView); list[selector] = rootView.findViewById(R.id.movingList); ArrayList messages = new ArrayList<String>(); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this.getActivity(), android.R.layout.simple_list_item_1, messages); list[selector].setAdapter(adapter); switch(selector){ case 0: messages.add("Stage0"); messages.add("Level1"); break; case 1: messages.add("Stage1"); break; case 2: messages.add("Stage2"); list[2].setY(-list[2].getHeight()); break; case 3: messages.add("Stage3"); messages.add("Level 250"); //imageView.setImageResource(R.drawable.stage3); break; case 4: messages.add("Level 2 室町用"); imageView.setImageResource(R.drawable.stage4); break; case 5: messages.add("How to play"); messages.add("Level 0"); imageView.setImageResource(R.drawable.stage5); break; case 6: messages.add("Stage6"); //imageView.setImageResource(R.drawable.stage6); break; } ///////////////////////////////////////////////ここまで追加 return rootView; } } public class SectionsPagerAdapter extends FragmentPagerAdapter { int page; public SectionsPagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { return PlaceholderFragment.newInstance(position); } @Override public int getCount() { return 7; //要素数設定 } ///////////////////////////////////////ここから追加 @Override public void setPrimaryItem(ViewGroup container, final int position, Object object){ super.setPrimaryItem(container, position, object); if(page == position)return; final float hide_y = -PlaceholderFragment.list[position].getHeight(); //前ページのList隠し処理 for(int i = 0; i < 7; i++){ if(PlaceholderFragment.list[i] != null) PlaceholderFragment.list[i].setY(-PlaceholderFragment.list[i].getHeight()); } page = position; new Thread(new Runnable(){ float y = hide_y; public void run(){ while(y < 0) { ListView list = PlaceholderFragment.list[position]; list.setY(y); Log.d("messaged", "position y: " + y); y += 5; //Y軸移動量 / 1ステップ try { Thread.sleep(10); //タイムインターバル } catch (Exception e) { e.printStackTrace(); } } } }).start(); Log.d("messaged", "setPrimaryItem position: " + position); } ////////////////////////////////////////////ここまで追加
- 投稿日:2019-03-07T15:24:35+09:00
androidx.appcompat.widget.Toolbar のタイトルとアイコンの色を変える方法
実装
activity_main.xml<androidx.constraintlayout.widget.ConstraintLayout ..> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" android:layout_width="0dp" android:layout_height="wrap_content" android:background="?attr/colorPrimary" app:theme="@style/ToolbarTheme" />styles.xml<resources> <style name="ToolbarTheme" parent="@style/ThemeOverlay.AppCompat.ActionBar"> <!-- ToolBarのタイトルテキスト色 --> <item name="android:textColorPrimary">@color/colorWhite</item> <!-- iconの色 --> <item name="colorControlNormal">@color/colorWhite</item> </style> </resources>参考
- 投稿日:2019-03-07T15:17:31+09:00
listViewに置かれたボタンからdialog fragmentを表示しようとして泥沼だった話
今回はかなり苦戦しました。(毎回ですけど)
タイトルの通りです。
どうにかこれを達成できたのでその手順を書きます。
まず全体の流れは
※④を訂正し、③を打ち消しました。(2019/3/7)
①listViewでクリックイベントの結果を取得
→②"listViewを表示しているfragment"に上述の結果を渡す
→③"上述のfragmentを表示しているactivity"に上述の結果を渡す
→④ ②のfragmentでdialog fragmentを表示する(訂正あり)このようなややこしい流れになりました。
コメント欄にてご指摘をいただきましたが、③は完全に勘違いで、activityからしかdialogFragmentを呼び出せないということはなく、fragmentからdialogFragmentを直接呼び出すことが可能でした。④にて後述します。
というわけで手順を①から見ていきます。
①listViewでクリックイベントの結果を取得
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // ここに処理を書く } });これは毎度おなじみですね。
R.id.buttonのbuttonの部分は、xmlファイルのコンポーネントのIDと同じにしてください。②"listViewを表示しているfragment"に上述の結果を渡す
これがなかなか見つからなかったコードです。SomeAdapter.javafindViewById(R.id.button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // ここに処理を書く ((ListView) parent).performItemClick(v, position, R.id.button); } });SomeFragment.javalistView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { switch (view.getId()) { case R.id.button: //buttonがclickされたらここに飛びます } } });以上の処理で、fragmentに飛べます。
parentってなんぞや。
→getViewの引数、ViewGroupです。positionってなんぞや。
→これもlistViewの引数getViewのint型の引数です※getViewはアダプターを作るときに呼ばれるメソッドです。検索したら出てきます。
④ ②のfragmentでdialog fragmentを表示する(訂正あり)
アクティビティにdisplayDialogメソッドを追加しますSomeFragment.javalistView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { switch (view.getId()) { case R.id.button: //buttonがclickされたらここに飛びます SomeDialogFragment dialog = new SomeDialogFragment(); dialog.show(getFragmentManager(), "tag"); } } });dialog fragmentをインスタンス化してshowメソッドを呼ぶだけで表示できたようでとても悲しいです。
あと、getFragmentManagerとgetSupportFragmentManagerの違いについてもご指摘をいただきました。
以下はコメント欄の引用です。バージョン違いではなく、継承元のクラスが何か、によって、getSupportFragmentManagerを使うべきなのかgetFragmentManagerを使うべきなのかが変わります。
android.app.Activityと、
android.app.DialogFragment
android.app.Fragment
なら、
Activity#getFragmentManagerを使うandroid.support.v7.app.AppCompatActivityと、
android.support.v4.app.Fragment
android.support.v4.app.DialogFragment
なら、
AppCompatActivity#getSupportFragmentManagerを使うこの組み合わせで使わないと、いろいろと不具合が起こります。
そして、API28(AndroidP)から、android.app.DialogFragmentとandroid.app.Fragmentは非推奨となったので、実質、後者の組み合わせで行くべきかと思います。どっちかが新しいんだろうなくらいの感覚でテキトーにoption + enterで追加していたので、きちんと組み合わせがあったらしくちょっと感動しました。
@kasa_le さん
この度はありがとうございました。
精進します!という感じでした。
- 投稿日:2019-03-07T14:37:21+09:00
[ANDROID]CPUとアーキテクチャとABIの関係
andoidアプリ開発中に、外からとってきたsoファイルを使いたいとする。(openCVとか)
大体、armeabi-v7a用とかx86用とか書いてあるけど気にせず全部jniLibs直下にコピーする。
実際、どの端末でどのsoファイルが動いてるかよく知らない。そもそもarmとかx86とか何?
そんな自分のための記事。ARM? X86? MIPS?
android端末のCPUに適用される命令セットアーキテクチャ(または、CPUそのもの)の名称を指す。
armであれば、arm社のarmアーキテクチャが
X86なら、Intel社のIntel 8086、またその後方互換のアーキテクチャ、
MIPSなら、ミップス・コンピュータシステムズ(現ミップス・テクノロジーズ)社のアーキテクチャがそれぞれ適用されているCPUであるということになる。命令セットアーキテクチャが違うと?
C/C++言語で書いたコードは、ターゲットとする命令セットアーキテクチャに対してコンパイルし、専用の実行バイナリファイルを作成する。
つまり、他の命令セットアーキテクチャのためにコンパイルされたバイナリファイルは動かない。なので、openCVなどのオープンソースはどのCPUを積んだ端末でも動くよう、それぞれの命令セットアーキテクチャに合わせたsoファイルを用意している。
OpenCV4Android SDK
androidにおいては、その命令セットアーキテクチャの違いをABIという定義で管理する。ABIとは?
Application Binary Interfaceの略
正式な定義は、
"ABI は、アプリケーションのマシンコードが実行時にシステムとやり取りする方法を詳細に定義したもの"
Android Developers->NDK ABI管理適当な覚え方は、
端末のCPUと命令セットに応じて定義するディレクトリの名称。soファイルを入れるところ。
端末Aでは動いたのに、端末Bでは"Fatal Exception: java.lang.UnsatisfiedLinkError"でアプリが落ちたみたいなとき、端末Bに対応するABIのsoファイルを入れてなかったりする。
端末ごとの調べ方(手元に調べたい端末がない時)
仮にNexus10でsoファイルを含むアプリを動かしたいとする。
1. Nexus10のCPUを調べる。
⇒https://ja.wikipedia.org/wiki/Nexus_10
⇒ARMのCoretex-A15ベースのExynos 52502.Coretex-A15のアーキテクチャを調べる。
⇒https://ja.wikipedia.org/wiki/ARM%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3
⇒v7-a
3.対応するABIを調べる。
⇒https://developer.android.com/ndk/guides/abis
⇒armeabi-v7a
なので、"armeabi-v7a"においたsoファイルが動いてることになる。
端末ごとの調べ方(手元に調べたい端末がある時)
"adb shell"でつないで、"$ getprop | grep cpu"を叩くだけ。
[ro.product.cpu.abi]: [armeabi-v7a]
ABIが表示されすぐにわかる。サポートしているABIのsoファイル入れてないのになぜか動いてるけど。。。
ARMであれば、armeabi → armeabi-v7a → arm64-v8aの順に新しい。
基本的に下位互換性があるので、Nexus10であれば、armeabi-v7aにsoファイルがなくても、armeabiのsoファイルでアプリは動く。参考記事
- 投稿日:2019-03-07T12:42:04+09:00
Android Studio を Applications にコピーするときに「一部の項目へのアクセス権がないため、操作を完了できません。」と表示されてもあきらめない
環境
- macOS High Sierra 10.13.6
- Android Studio 3.3.2
内容
Android Studio 3.3.2 をインストールするときに
こんなエラーが出た。
Applications へコピーしている最中、古いバージョンの Android Studio を起動しているのが原因かと思い終了させて再度チャレンジしてみたが変わらず。
(Android Studioをバージョンアップするとビルドが通らなくなったことがあるので、あえて Android Studio のアップデートではなくインストールしている。)ググると
- アクセス権が設定されていないのでは?
- ルートユーザーでログインしてる?
- HDD が壊れているかも?
といった内容が出てきたので確認してみたがどれも大丈夫そう。
(最後のは そんなハズないし壊れていたら困る… と思って確認していない)他に情報がないかググりつつ、エラーを無視して何度もコピーしつづけた。
んー
んーー
おや?
地味にプログレスバー伸びてる??これは伸びてるな!
いけたーーー!!
まとめ
あきらめずに想いを伝え続ければ、想いは伝わる。
(なぜ少しずつコピーできたのか、調べてみたがわからずだった。。)
- 投稿日:2019-03-07T05:00:24+09:00
あの素晴らしいlicense-tools-pluginをもう一度
はじめに
Qiita初投稿になります。6年程会社でプログラマとして仕事をし、今はフリーランスでAndroidアプリを中心にJava/Kotlinでゴリゴリ開発してます。今回、Androidアプリで使っているライブラリの表示をする必要がありまして、以前採用させていただいたクックパッドさんの
license-tools-pluginを使おうとしたのですが、プラグイン自体のアップデートやIDE(Android Studio)の更新もあり、すんなり導入することができませんでした。公式が クックパッド開発者ブログ で解説していますが、導入方法とハマったポイントを少し長いですが共有します。間違いや補足などがあればコメントにて教えてください。
開発環境
- PC: MacBook Air (11-inch, Early 2014)
- OS: macOS Mojave 10.14.3
- IDE: Android Studio 3.3.2
license-tools-pluginとは
クックパッドさんが開発したGradleの神プラグインです。プロジェクトで使用しているライブラリのライセンスをYAMLで管理、HTMLもしくはJSONに一覧を出力してくれます。ライセンスの追記漏れもチェックしてくれる優れものです。GitHubのリポジトリは こちら です。
導入
Gradleのプラグインなので、プロジェクトルート直下の
build.gradleに追記します。記事執筆時の最新バージョンは1.7.0でした。build.gradle[root]buildscript { ... dependencies { classpath 'com.android.tools.build:gradle:3.3.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.cookpad.android.licensetools:license-tools-plugin:1.7.0' // 追加 } }アプリ内で使用する為に、app直下の
build.gradleの冒頭に追記をします。build.gradle[app]apply plugin: 'com.cookpad.android.licensetools' // 追加動作する為に
JDK8以上が必須とのことなので、こちらもbuild.gradleに互換性を追記しておきます。JDK8はAndroid Studio 3.3.2であれば、ビルトインとしてあらかじめ用意されているはずです。build.gradle[app]android { ... // 追加 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } }全ての追記が終わったら
Sync Projectを行い、エラーが出ていなければOKです。
使い方
ライブラリの依存関係を確認
ライブラリの依存関係を確認します。今回はデモの為に、Androidアプリの開発でよく使うものを追加しました。
build.gradle[app]dependencies { // Kotlin implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" // Android Support Library implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support:recyclerview-v7:28.0.0' implementation 'com.android.support:cardview-v7:28.0.0' implementation 'com.android.support:design:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' // Square implementation 'com.squareup.moshi:moshi:1.8.0' implementation 'com.squareup.moshi:moshi-kotlin:1.8.0' implementation 'com.squareup:otto:1.3.8' implementation 'com.squareup.picasso:picasso:2.71828' implementation 'com.squareup.retrofit2:retrofit:2.5.0' // ReactiveX implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'io.reactivex.rxjava2:rxjava:2.2.7' }ライセンス情報の作成
依存関係の記述が終わったら、Android Studioのサイドバーにある
Gradle > [project-name] > :app > Tasks > verificationから、checkLicensesタスクを実行します。コンソールにライセンス情報の一覧が出力されるので、app直下にlicenses.ymlを作成して内容をコピペします。licenses.yml- artifact: android.arch.core:common:+ name: Android Arch-Common copyrightHolder: #COPYRIGHT_HOLDER# license: The Apache Software License, Version 2.0 licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt url: https://developer.android.com/topic/libraries/architecture/index.html - artifact: com.squareup.okhttp3:okhttp:+ name: OkHttp copyrightHolder: #COPYRIGHT_HOLDER# license: #LICENSE# - artifact: org.jetbrains.kotlin:kotlin-stdlib:+ name: org.jetbrains.kotlin:kotlin-stdlib copyrightHolder: #COPYRIGHT_HOLDER# license: The Apache License, Version 2.0 licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt url: https://kotlinlang.org/ - artifact: com.squareup.okio:okio:+ name: Okio copyrightHolder: #COPYRIGHT_HOLDER# license: #LICENSE# - artifact: com.android.support:support-annotations:+ name: Android Support Library Annotations copyrightHolder: #COPYRIGHT_HOLDER# license: The Apache Software License, Version 2.0 licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt url: http://developer.android.com/tools/extras/support-library.html - artifact: com.android.support:design:+ name: Material Components for Android copyrightHolder: #COPYRIGHT_HOLDER# license: The Apache Software License, Version 2.0 licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt url: http://developer.android.com/tools/extras/support-library.html - artifact: com.android.support:support-core-utils:+ name: Android Support Library core utils copyrightHolder: #COPYRIGHT_HOLDER# license: The Apache Software License, Version 2.0 licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt url: http://developer.android.com/tools/extras/support-library.html - artifact: com.android.support:interpolator:+ name: Android Support Library Interpolators copyrightHolder: #COPYRIGHT_HOLDER# license: The Apache Software License, Version 2.0 licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt url: http://developer.android.com/tools/extras/support-library.html # 以下、長いので省略ライセンス情報の編集
その後
licenses.yamlの情報を編集していきます。具体的には#COPYRIGHT_HOLDER#や#LICENSE#と言ったプレースホルダ部分を編集します。例えばlicenses.yaml- artifact: com.android.support:appcompat-v7:+ name: Android AppCompat Library v7 copyrightHolder: #COPYRIGHT_HOLDER# license: #LICENSE# - artifact: com.squareup.retrofit2:retrofit:+ name: Retrofit copyrightHolder: #COPYRIGHT_HOLDER# license: #LICENSE# - artifact: io.reactivex.rxjava2:rxjava:+ name: RxJava copyrightHolder: #COPYRIGHT_HOLDER# license: #LICENSE#のような一覧が出力された場合は
licenses.yaml- artifact: com.android.support:appcompat-v7:+ name: Android AppCompat Library v7 copyrightHolder: The Android Open Source Project license: The Apache License, Version 2.0 licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt url: https://developer.android.com/topic/libraries/support-library/ - artifact: com.squareup.retrofit2:retrofit:+ name: Retrofit copyrightHolder: Square, Inc. license: The Apache License, Version 2.0 licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt url: https://github.com/square/retrofit/ - artifact: io.reactivex.rxjava2:rxjava:+ name: RxJava copyrightHolder: RxJava Contributors license: The Apache License, Version 2.0 licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt url: https://github.com/reactivex/rxjava/となります。基本的にGitHubの情報を元に調べ編集していきます。Android Open Source Project(AOSP)は ここ にライセンスに関する内容が記載されています。全ての情報の編集が終わったら、再度
checkLicensesタスクを実行し、エラーが発生しなければOKです。ライセンス情報の表示
最後に
Gradle > [project-name] > :app > Tasks > otherから、generateLicensePageタスクを実行し、出力されたHTMLファイルをアプリ内のWebViewに読み込ませます。MainActivity.ktoverride fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) webView.loadUrl("file:///android_asset/licenses.html") }
ハマったこと
checkLicensesタスクが失敗する
checkLicensesタスクを実行した際にmissing libraries in licenses.ymlというエラーで失敗してしまう。初回実行時にはlicenses.yamlが存在しないので、コンソールに表示されたライセンスの情報一覧をコピペして作成すればいいのですが、2回目以降であれば不足している情報がコンソールに出力されていますので、YAMLファイルに追記します。また、使用しているライブラリが、内部的に別のライブラリに依存している場合(例えばappcompat-v7は内部的にsupport-annotationsやsupport-compatに依存している)、その関係性も監視対象になります。そういったライブラリはlicenses.yaml- artifact: com.android.support:appcompat-v7:+ name: Android AppCompat Library v7 copyrightHolder: The Android Open Source Project license: The Apache License, Version 2.0 - artifact: com.android.support:support-annotations:+ skip: true - artifact: com.android.support:support-compat:+ skip: trueと記述することで、一覧から除外することができます。また、グループ一式で除外する場合は
licenses.yaml- artifact: com.android.support:+:+ skip: trueの様にワイルドカードで指定したり、
build.gradle[app]licenseTools { ignoredGroups = ['com.android.support'] }の様に記述することで可能となります。
generateLicensePageタスクが失敗する
コンソールでエラーの内容を確認すると
checkLicensesタスクでコケていることがほとんどかと思います。 checkLicensesタスクが失敗する を参考に解決します。必要な項目が不足している
licenses.yamlに必要な項目が不足している場合にも、プラグインのタスクが失敗します。必須項目は以下の通りです。
- artifact
- name
- copyrightHolder/ author/ authors/ noticeのうちどれか1つ
オプションとして付与できる項目は
- year
- skip
- forceGenerate
などがあります。詳しくは公式の GitHubリポジトリ を参照してください。
最終的な着地点
ここまでのハマった箇所を踏まえて、デモ用のアプリとしてGitHubに demo-licenses をアップしておきます。参考になるかわかりませんが、躓いていらっしゃる方の助けに少しでもなれたらなと思います。表示させないライセンスについてですが、最終的には
ignoredGroupsとskipを併用することで解決しました。ignoredGroupsは、名前の通り使用しているライブラリのgroupIdのみにしか対応しておらずartifactIdでは除外できませんでした。groupId+artifactIdでもプラグインの監視対象から除外できれば良いのになぁ。
まとめ
結果的に調べたりなんなりで作業自体のコストはかかってしまいましたが、1から全て自前で実装するよりは遥かに良いです。今後、同様な実装が発生した場合にはこの記事を参考にスピーディに対応したいですね。また、先ほどの
groupId+artifactIdで除外するPull Requestなどを本家に出してみたいと思っています。ライセンスの表示は、Androidアプリを開発していく上で必要なことではありますが、開発が進むにつれて管理が億劫になったり、時間を割くことが難しいかもしれません。そんな痒いところに手が届くようなライブラリを開発してくださったクックパッド様には感謝しかないです
どうでも良い話
ライセンスの綴りとして license と licence があります。大きな違いとして、イギリスでは動詞の場合に『license』名詞の場合に『licence』を使い分けますが、アメリカでは特に区別をせず日常的に『license』が使われるみたいです。結構タイプミスするので気をつけたいです。
参考文献
- 投稿日:2019-03-07T02:16:52+09:00
【Android】画像をカルーセル表示するときにハマった話
RecyclerViewを使うと簡単に画像をカルーセル表示することができます。
ですが、ImageViewの扱いでハマった部分があったので、その際に調査した内容をまとめました。TL;DR
ImageViewの
AdjustViewBoundsにtrueをセットすることで、ImageViewのサイズを画像サイズに合わせて調整することができます。前提条件
カルーセル、画像のサイズおよび表示方法に関する条件は以下の通りです。
- カルーセルの高さは固定とする
- カルーセルに表示する各画像のサイズはすべて異なる可能性がある
- 各画像の高さはカルーセルの高さと一致するように拡大縮小する
- 各画像のアスペクト比は維持する
- 各画像はクロップしない
ハマった内容
以前作成したScaleTypeと表示画像の対応表を参考に、
ScaleTypeは一旦FIT_CENTER(デフォルト値のためxml上の指定なし)、またlayout_widthはwrap_content、layout_heightは100dpとしました。RecyclerViewの各要素に表示する画面のxmlは以下の通りです。
item_main.xml<?xml version="1.0" encoding="utf-8"?> <ImageView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="100dp" />正直どの
ScaleTypeも今回の条件を満たさないと感じつつも、layout_widthをwrap_contentにすることで、FIT_CENTERであれば画像の高さをカルーセルの高さと一致させ、それに合わせて画像全体が含まれるようにアスペクト比を維持しながら幅を調整してくれるのでは?というわずかな希望を抱きながら試してみました。結果は以下のようになりました。案の定、希望通りの表示になりませんでした。
これを見て、画像が小さいときの結果(左)は納得できるのですが、画像が大きいときの結果(右)に関しては何で左右に余白ができるのかが理解できませんでした。解決方法
冒頭でも触れましたが、ImageViewの
AdjustViewBoundsをtrueにすることで解決できました。これは文字通り「Viewの境界を調整する」ための属性です。
公式ドキュメントはこちらにあります。
AdjustViewBoundsをtrueにすると、結果は以下のようになりました。上が先ほどの結果(デフォルト値はfalse)で、下が今回の結果です。
これで、ImageViewのサイズを画像サイズに合わせて調整することができました。AdjustViewBoundsをセットしたときの内部処理
結果としてはこれで問題ないのですが、画像が大きいときの結果に関してはこれだけでは理解ができなかったので、ImageViewの処理を追ってみました。
ImageView.javaのソースコードはこちらにあります。Androidにおいて、Viewが表示されるまでの大まかな流れは以下の通りになります。今回の肝となるのは1.の
onMeasure()です。
- サイズを決める(
onMeasure())- 場所を決める(
onLayout())- 描画する(
onDraw())これに関してはこちらの記事を参考にさせていただきました。
では、ここからはImageViewのonMeasure()の処理を追っていきます。最初は各変数を宣言しているだけだったので割愛します。
以下の処理が1つ目のポイントになります。ImageView.java#LL.1088-1089final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
widthMeasureSpecとheightMeasureSpecはonMeasure()の引数で、そこからMeasureSpecのModeを取得しています。
ただ、widthMeasureSpecとheightMeasureSpecがどのように決定されるのかは正直よく分かりませんでした。
MeasureSpecは親Viewから子Viewに対して課される制約を表しており、以下の3種類のModeがあります。公式ドキュメントはこちらにあります。
MeasureSpec.Mode 制約条件 UNSPECIFIED 親Viewによって子Viewのサイズが決定されない EXACTLY 親Viewによって子Viewの正確なサイズが決定される AT_MOST 親Viewによって子Viewの最大のサイズが決定される したがって、
widthSpecModeはMeasureSpec.UNSPECIFIED、heightSpecModeはMeasureSpec.EXACTLYとなります。
そして、以下の処理が2つ目のポイントになります。
AdjustViewBoundsがtrueの場合のみ以下の処理が行われます。ImageView.java#LL.1104-1109if (mAdjustViewBounds) { resizeWidth = widthSpecMode != MeasureSpec.EXACTLY; resizeHeight = heightSpecMode != MeasureSpec.EXACTLY; desiredAspect = (float) w / (float) h; }
resizeWidthとresizeHeightは幅・高さのリサイズを行うか否かを制御する変数です。
desiredAspectは画像のアスペクト比(wは幅、hは高さ)を表す変数です。今回の場合、
widthSpecModeはMeasureSpec.UNSPECIFIED、heightSpecModeはMeasureSpec.EXACTLYなので、AdjustViewBoundsがtrueのときはresizeWidthのみがtrueとなります。
そして、以下の処理が3つ目のポイントになります。
長いので詳細は省略しますが、おおむね以下のような処理が行われています。
AdjustViewBoundsがtrueのとき
ImageViewのサイズを画像サイズに合わせて調整するAdjustViewBoundsがfalseのとき
ImageViewの幅を画像の幅と一致するよう調整するImageView.java#LL.1120-1190int widthSize; int heightSize; if (resizeWidth || resizeHeight) { // Get the max possible width given our constraints widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec); // Get the max possible height given our constraints heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec); if (desiredAspect != 0.0f) { // See what our actual aspect ratio is final float actualAspect = (float)(widthSize - pleft - pright) / (heightSize - ptop - pbottom); if (Math.abs(actualAspect - desiredAspect) > 0.0000001) { boolean done = false; // Try adjusting width to be proportional to height if (resizeWidth) { int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) + pleft + pright; // Allow the width to outgrow its original estimate if height is fixed. if (!resizeHeight && !sCompatAdjustViewBounds) { widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec); } if (newWidth <= widthSize) { widthSize = newWidth; done = true; } } // Try adjusting height to be proportional to width if (!done && resizeHeight) { ... } } } } else { w += pleft + pright; h += ptop + pbottom; w = Math.max(w, getSuggestedMinimumWidth()); h = Math.max(h, getSuggestedMinimumHeight()); widthSize = resolveSizeAndState(w, widthMeasureSpec, 0); heightSize = resolveSizeAndState(h, heightMeasureSpec, 0); } setMeasuredDimension(widthSize, heightSize);
ここで、else句の最後にあるresolveSizeAndState()がMeasureSpec.UNSPECIFIEDのときは引数に指定した画像サイズそのものを返していたので、ImageViewの幅が画像の幅と同じサイズで表示されるということが分かりました。スッキリ。
View.javaのソースコードはこちらにあります。View#LL.23463-23483public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { final int specMode = MeasureSpec.getMode(measureSpec); final int specSize = MeasureSpec.getSize(measureSpec); final int result; switch (specMode) { case MeasureSpec.AT_MOST: if (specSize < size) { result = specSize | MEASURED_STATE_TOO_SMALL; } else { result = size; } break; case MeasureSpec.EXACTLY: result = specSize; break; case MeasureSpec.UNSPECIFIED: default: result = size; } return result | (childMeasuredState & MEASURED_STATE_MASK); }サンプルアプリ
上記の画像キャプチャを撮影したアプリです。
GitHub - AdjustViewBoundsChecker
- 投稿日:2019-03-07T02:04:44+09:00
BottomNavigationViewをiOSのTabBarっぽくする
概要
MaterialDesignから提供されているBottomNavigationViewを用いることでiOSのTabBarのような下タブのナビゲーションを作成することが容易に可能になりました.
そこでiOSライクにAndroidでも下タブのアプリを開発してやるぜー!と思っていると選択範囲しかアイテム名が表示されなかったりとBottomNavigationView 独特のクセが目立ちました.
もちろんこれがMaterialDesignガイドラインが示している解だとはわかっているものの下タブといえばiOSのアレをどうしても想像してしまうためクセは気になります.
この記事ではそれらのクセを完全に断ち切って消してiOSライクな下タブを目指しますアイテムの色を選択状態によって変える
リソースファイルに
colorフォルダなどを作成し以下のファイルを追加します.
選択・非選択時の色は各自変えてください.
選択状態の色がプライマリーカラーにする場合は以下の操作は不要で一番下のcheckedにtrueをセットしてやるだけで可能です.nav_color.xml<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_checked="true" android:color="@color/colorPrimary" /> <item android:state_checked="false" android:color="@android:color/darker_gray"/> </selector>次に
BottomNavigationViewが記述されているレイアウトファイルに以下の文を追加しますactivity_main.xmlapp:itemIconTint="@color/nav_color" app:itemTextColor="@color/nav_color"最後にコード上から選択時に選択状態を直接変更すれば,選択に合わせてアイテムの色を変更できます.
MainActivity.javamenuItem.setChecked(true);アイテムの名前を常に表示させる
デフォルトの設定ではアイテム数が増えるとアイテムの名前は選択状態のものだけが表示されていて,常時表示にはなっていません.
これを常時表示にするにはBottomNavigationViewに以下のパラメータを指定することで解決できます.activity_main.xmlapp:labelVisibilityMode="labeled"選択時のアイコン拡大アニメーションを無効化
このアニメーションは選択・非選択の状態においてアイテム内のアイコン・テキストの大きさが変動するために起きています.そもそも標準のフォントサイズは若干大きいのでiOSと同じ
10spに統一します.
dimes.xmlに以下の文を追加します.dimes.xml<dimen name="design_bottom_navigation_text_size" tools:override="true">10sp</dimen> <dimen name="design_bottom_navigation_active_text_size" tools:override="true">10sp</dimen>これで選択・非選択でも文字の大きさが固定され選択中のアイテムが大きくなるアニメーションなどは起こらなくなります.
BottomNavigationViewが手前に来るのを防ぐ
どういうわけかデフォルトではBottomNavigationViewは画面下に固定されている状態ではなく,EditText時などにはFloatingActionButtonの様にNavigationViewごとキーボード上に浮上してきます.
それを防ぐのが以下のコードです.AndroidManifest.xml<activity android:name=".MainActivity" android:windowSoftInputMode="adjustPan"></activity>ManifestファイルのBottomNavigationViewが入っているActivityの部分をこのように書き換えることで,BottomViewNavigationが文章編集時でも所定の位置にいてくれます.

























