Dialogによる通知
Androidで通知などするとき、トーストよりもユーザーにアプローチしたいときはActivityやダイアログを用いると思います。
(あまりAndroidの思想的には良ろしくない等はさておき。。)
Dialogを表示する、と言っても実質的にActivityの表示と変わらないですが。。
自分のメモとして、Dialogを表示する方法を整理しました。
結論
はじめに、もっとも推奨する方法はテーマや実装を工夫したActivityでDialogFragmentを表示する方法です。
ServiceからIntentでIntent.FLAG_NEW_TASKを追加してActivityを起動する方法がいいかと思います。
ロック画面でも表示する場合はこちらの記事も参考にしてください。
ロック画面でのDialog表示する
方法
方法はいくつかあります。
- ActivityのテーマをDialogにして、ServiceからActivityを起動
- 透明なActivityを起動してDialogFragmentを表示
- WindowManagerに直にViewをaddする
それぞれのメリット・デメリットを考えてみます。
1. ActivityのテーマをDialogにして、ServiceからActivityを起動
まずはもっとも簡単な、ActivityのThemeをDialogを継承したものにする方法です。
<activity
android:name=".MyActivity"
// Dialogテーマを継承したテーマの設定
android:theme="@style/Theme.AppCompat.Dialog">
</activity>
もちろん自分でカスタムテーマをつくることができます。
メリット
- 普通のActivityを実装すればいい
- Activityだけなのでライフサイクルが単純になる
デメリット
- 標準のDialogの仕組みを使えないので、見た目・機能を実装する必要がある。
- Manifestに記入する必要がある
- 複数Dialogの切り替えができない。(それぞれ実装する必要がある)
- 公式推奨でない古い方法な気がする(DialogFragmentが公式推奨なので)
この方法の選択理由はFragmentなどを実装しなくて済む、簡単なことでしょうか。
デメリットも、表示にカスタムダイアログを使い、標準のAlertDialogなどを使わなければ、特にありません。
標準のDialogのUIを再現するにはライブラリやその他の工夫が必要と思います。
ただ、最近のAndroidの方針としてはFragmentを使った実装にしたほうがいいようです。
あとは色々な表示を気にすると、テーマをいじる必要が出てきそうです。
2. 透明なActivityを起動してDialogFragmentを表示
次に、あまり変わりませんがFragmentとActivityを用いた方法です。
普通にActivityにDialogFragmentをAttachするのですが、Activityのテーマに背景が透明なPanelなものを設定します。
<activity
android:name=".MyActivity"
// Dialogテーマを継承したテーマの設定
android:theme="@android:style/Theme.Panel">
</activity>
AppCompatにはPanelテーマはないので自分でつくる必要がありそうです。
厳密にPanelテーマと同等にする必要はありませんが、一応例を載せておきます。
<style name="AppPanelTheme" parent="style/Theme.AppCompat">
// android:style/Theme.Panel 等を参考に。。
<item name="android:windowBackground">@color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowFrame">@null</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowAnimationStyle">@null</item>
// こちらはTheme.Panelだとtrueですが、falseでもいいです。
// trueだとロック画面に表示できません(不要かもしれませんが)
<!--<item name="android:windowIsFloating">true</item>-->
<item name="android:windowIsFloating">false</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
// これは書かれていませんが、追加してもいいかもしれません
<item name="android:windowDisablePreview">true</item>
<item name="android:windowNoDisplay">false</item>
</style>
メリット
- Dialog一枚なら、Activityだけを実装するのとあまり手間は変わらない
- ライフサイクルもDialogFragmentに慣れていればそこまで難しくない
- 複数DialogもFragment切り替えで実装できる。
- 標準のAlertDialogなどのUIも使える
デメリット
- Activityのみよりやはり少し煩雑
- Fragmentの罠がある
こちらが現在はAndroidの方針に沿った方法でしょうか。
Activityがたくさんある代わりにFragmentがたくさんになった、と考えれば煩雑さもあまり1つめと変わらないかと思います。
デメリットもあえてあげたレベルのもので特になし、と言えます。
特にFragmentアレルギーとかでない限りはこの方法がいいと思います。
3. WindowManagerに直にViewをaddする
Activityのライフサイクルに引っ張られたくない、Homeボタン等でダイアログを閉じたくない、
など特殊な事情がある場合はWindowManager::addViewを使ってViewを追加する方法もあります。
この方法を使うにはまずaddViewを可能にする権限を追加します。
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
次にServiceかなにかでViewを生成してWindowManagerを使って追加します。
public void show(){
// Viewをinflateする。
mView = LayoutInflater.from(mContext).inflate(R.layout.view, null);
// 適宜Viewをカスタマイズする
// Windowに追加するときのLayoutParamsをつくる
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.MATCH_PARENT;
params.gravity = Gravity.CENTER;
// どの層にaddするか。追加できない層もたくさんある。
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
params.format = PixelFormat.TRANSLUCENT;
// Dialogのアニメーションにするとそれっぽいです。
params.windowAnimations = android.R.style.Animation_Dialog;
// 必要に応じていろんなflagを設定します。入らなければ削ります。
params.flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_DIM_BEHIND
| WindowManager.LayoutParams.FLAG_FULLSCREEN;
// 背景に影を表示してそれっぽくします。
params.dimAmount = 0.6f;
// WindowManagerを使ってViewを追加する
mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
mWindowManager.addView(mView, params);
}
public void dismiss(){
// removeでViewの表示をけす。
mWindowManager.removeView(mView);
}
よくある、Serviceから表示したい、という要望の実装です。
詳しいことは他の記事を参照してください。
画面上にアプリの情報を常時表示する
【Android】他のアプリケーションの上にViewを表示する
メリット
- Activityを実装しなくていい
- 起動中のアプリケーションを停止せずに表示できる
- 閉じられない表示もできてしまう
デメリット
- 実装が煩雑
- ライフサイクルがActivity等と異なるため注意すべき事項が多い。というか自分で実装しなければいけない
- ユーザフレンドリーでない実装ができてしまう
- 余計なPermissionを追加しなければいけない
技術的に可能、という意味で紹介しています。
様々なデメリットから要件がない限りは特に実装すべきでないと思います。
とくにPermissionが追加されるのが問題かと思います。
(Playストアで一番最初の一覧に表示されなくなったのは大きな改悪である、と思います。。)
実装方法次第で、場合によっては悪質なマルウェアも作れてしまいそうです。
ただ、WindowManagerに追加するときのLayoutParamsをいじればおよそDialogに見える実装が可能なので紹介しました。
まとめ
色々紹介しましたが、まとめとしては結論と同じく、2番目の方法、ActivityとDialogFragmentの併用かと思います。
特別な理由がない場合はこちらで実装すべきですし、、背景が気になったりロック画面でも表示したい場合はFlagやテーマを工夫してください。
この記事は3番目の方法をメモしておく目的で書きましたが、あまり使いたくありませんね。