ページ

2013/12/21

[Android]Toastをカスタマイズして表示時間を変更したりアニメーションを変更したり即時非表示にしたり消えたことを検知したりしたい

この記事は Android Advent Calendar 2013 の 12月21日の記事ではありませんし、
よく考えたら私は Android Advent Calendar 2013 に参加もしていませんでしたが、
Androider の役に立つかもしれない Toast をカスタマイズしたものを公開します。

経緯

Toast の表示時間を Toast#LENGTH_LONG(約4秒),
Toast#LENGTH_SHORT(約2秒)から変更したい!
じゃあ探してみるか、と見つけたのが以下の2つ。
琴線探査: AndroidでToastの表示時間を自由に設定したりアニメーションなしで即非表示したい!
Android Toastの表示時間を長く/短くする 【Android TIPS】
前者はサイズ指定とかが面倒だったのと Toast#makeText の気軽さがなかったので微妙でした。
後者は Toast#makeText 限定でカスタム View を表示できなかったので使いませんでした。

もっと Toast ライクで自由の効く拡張的な実装はないのか!と憤ってしまったので
Toast の実装を眺めて Toast の拡張である Toaster を作ってみました。
Toaster | wada811/AndroidLibrary-wada811

Toast と Toaster の比較

Toast Toaster
任意の表示時間の設定 ×
表示/非表示のアニメーション ×
一括キャンセル ×
非表示時のイベント通知 ×
表示時間にマイナスを設定すれば明示的にキャンセルするまで Toast を消さないようにすることもできます。
Web でよくあるチュートリアルで出てくるポップアップのように使えるかと思います。
アニメーションにこだわりたい開発者のためにアニメーションを設定できるようにしました。
アプリがバックグラウンドに回ったのに投稿ボタンを連打したせいで
「あれ?何かがおかしいようです。」「あれ?何か「あれ?「あれ?「あれ?何かがおかしいようです。」
のように Toast が残り続けることがないように一括キャンセルできるようにしました。
ToastOnDismiss の需要もありそうなのでリスナーをセットできるようにしました。

Toast と Toaster の対応

Toast Toaster
Toast 管理用 Service の起動・実行 システム Toaster
Toast 自体 Toast ToastBread
Toast の表示/非表示 TN Bread
ToastQueue の管理・表示/非表示のイベント通知 NotificationManagerService ToasterService
Toast はキュー管理の Service とその起動がシステム側に組み込まれているので
再現するには自分でキュー管理用の Service を実装する必要がありました。
また、その Service の起動も自分で行う必要があります。

Toaster の使い方

AndroidManifest.xml に Toast 管理用 Service を追加します。

<service android:name="at.wada811.widget.ToasterService" />

Toaster のインスタンス化

@Override
protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    mToaster = new Toaster(this);
}
自動的に Toast 管理用 Service が bind されます。
※ ServiceConnection が確立する僅かな時間の間に表示しようとした Toast は無視されます。

Toaster の電源を切る

@Override
protected void onPause(){
    super.onPause();
    mToaster.unplug();
}
自動的に Toast が全てキャンセルされ、Toast 管理用 Service が unbind されます。

動作サンプル

ToasterActivity.java | wada811/AndroidLibrary-wada811 を見てください。

Toaster ができるまで

Toast ってどうやって表示されているの?

システムに常駐している NotificationManagerService でキュー管理されています。
参考: Toast の表示時間を変更する | アンドロイドな日々
フレームワークのソースコードを見に行くと何を行っているかなんとなくわかります。
services/java/com/android/server/NotificationManagerService.java - platform/frameworks/base
OSS だとこういうのが簡単に確認できて素晴らしい。
Toast と Notification の処理が混ざっていたので Toast の部分を切り出したのがこちら。
ToasterService.java | wada811/AndroidLibrary-wada811

Toast は IPC ( Interprocess Communication: プロセス間通信) をしなければならないので
AIDL ( Android Interface Definition Language: Android インターフェイス定義言語 ) で
インターフェースを定義しています。
ITransientNotification.aidl - android-source-browsing - Android Source Browsing Test
Toaster では IPC をする必要がないので 普通にインターフェース ToasterCallback を定義しています。
Toaster$ToasterCallback | wada811/AndroidLibrary-wada811

Toast#makeText のデザインで表示したい

com.android.internal.R.layout.transient_notification を使っているようなので再現しましょう。
Cross Reference: /frameworks/base/core/res/res/layout/transient_notification.xml
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/layout/transient_notification.xml
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="?android:attr/toastFrameBackground">
    <TextView
        android:id="@android:id/message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:layout_gravity="center_horizontal"
        android:textAppearance="@style/TextAppearance.Toast"
        android:textColor="@color/bright_foreground_dark"
        android:shadowColor="#BB000000"
        android:shadowRadius="2.75"
        />
</LinearLayout>
上からAndroidソースコード検索サービス - Developer Collaboration Projectで検索していきましょう。
Search android:attr/toastFrameBackground
/frameworks/base/core/res/res/values/
themes.xml  208 <item name="toastFrameBackground">@android:drawable/toast_frame</item>
@android:drawable/toast_frame を SDK から画像リソースをコピーしてきます。
/{AndroidHome}/platforms/android-x/data/res/drawable-x/toast_frame.9.png
以下のページを TextAppearance.Toast で検索。
Diff - 1962e26^^2..1962e26^ - platform/frameworks/base - Git at Google
/core/res/res/values/styles.xml
+    <style name="TextAppearance.Toast">
+        <item name="android:fontFamily">sans-serif-condensed</item>
+    </style>
Search @color/bright_foreground_dark
/packages/apps/Stk/res/values/
colors.xml  19 <color name="bright_foreground_dark">#ffffffff</color>

そこそこ大変でしたがここまで調べてまとめたのが以下のファイルです。
AndroidLibrary@wada811/res/layout/toast.xml | wada811/AndroidLibrary-wada811
Toaster では makeText で Toast 標準のデザインと変わらない表示を出すことができるようになっています。

表示/非表示のアニメーションを Toast 標準に合わせたい

プログラムから使うだけなら android.R.style.Animation_Toast を使えばいいです。
改造するなら以下を調査・変更する必要があります。
core/res/res/values/styles.xml - platform/frameworks/base を Animation.Toast で検索。
<style name="Animation.Toast">
    <item name="windowEnterAnimation">@anim/toast_enter</item>
    <item name="windowExitAnimation">@anim/toast_exit</item>
</style>
toast_enter.xml, toast_exit.xml は /{AndroidHome}/platforms/android-x/data/res/anim/ にあります。
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/anim/fade_in.xml
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License"); 
** you may not use this file except in compliance with the License. 
** You may obtain a copy of the License at 
**
**     http://www.apache.org/licenses/LICENSE-2.0 
**
** Unless required by applicable law or agreed to in writing, software 
** distributed under the License is distributed on an "AS IS" BASIS, 
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
** See the License for the specific language governing permissions and 
** limitations under the License.
*/
-->

<alpha xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@interpolator/decelerate_quad"
        android:fromAlpha="0.0" android:toAlpha="1.0"
        android:duration="@android:integer/config_longAnimTime" />
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/anim/fade_out.xml
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License"); 
** you may not use this file except in compliance with the License. 
** You may obtain a copy of the License at 
**
**     http://www.apache.org/licenses/LICENSE-2.0 
**
** Unless required by applicable law or agreed to in writing, software 
** distributed under the License is distributed on an "AS IS" BASIS, 
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
** See the License for the specific language governing permissions and 
** limitations under the License.
*/
-->

<alpha xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@interpolator/accelerate_quad" 
        android:fromAlpha="1.0" android:toAlpha="0.0"
        android:duration="@android:integer/config_longAnimTime" 
/>

Toast 管理用 Service を作りたい

以下のサイトが参考になりました。
Androidでの常駐型Serviceを使う方法(LocalServiceによる常駐アプリ) | Tech Booster
Androidのバックグラウンド処理に使うServiceのまとめメモ - リア充爆発日記
Service を使ったことがほとんどなかったので使いこなせるようになりたいですが
いまいちよくわからないので詳しく色々解説している書籍など欲しいです。

その他

基本全部コピペでリネーム、Toaster 独自機能の追加でなんとかなりました。

次の人?

Android Advent Calendar 2013じゃないのでいませんでした。。。
もし使ってみてもらって問題があったり、微妙なところがあったら PR してください。
よろしくお願いします。