AndroidのView表示アニメーション
半透明のActivityを下からスライドさせて開く、というのをやりたくなった。
Activityのアニメーションで検索すると、大体ActivityのテーマのwindowAnimationStyleを設定する方法が出てくるようだ。
<?xml version="1.0" encoding="utf-8"?> <resources> <style name="MyTheme" parent="android:Theme"> <item name="android:windowAnimationStyle">@style/Animation.MyAnimation</item> </style> </resources>
しかし、この方法はランチャーから起動する時など無視されることが多々ある。自分はランチャーから起動したときも下からせりあがってきて欲しいので、今回の件には使えない。
幸い、現在開発中のアプリは透明のActivityに半透明のviewを貼りつける構成になっているので、以下の方針を立てた。
- Activityが完全に透明であれば、windowAnimationStyleの影響を受けない。
- ルートのviewを最初は非表示にしておき、Activityの起動後にアニメーション表示させる。
やってみた
あらかじめルートviewのvisibilityをinvisibleに設定しておき非表示にする。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/main" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:visibility="invisible" > </layout>
そしてアニメーションを定義。
anim/open_main_screen.xml
<translate xmlns:android="http://schemas.android.com/apk/res/android" android:fromYDelta="100%" android:toYDelta="0%" android:duration="200" android:fillAfter="true" > </translate>
translateは直線移動のアニメーション。fromYDelta、toYDeltaで開始/終了Y座標(絶対座標でも%でも可)を、durationでアニメーションにかける時間(msec)を設定する。さらにfillAfterでアニメーション終了後の状態を維持する。
仕上げにjavaのコードから呼び出し。
Animation animation = AnimationUtils.loadAnimation(activity,R.anim.open_main_screen); View rootView = activity.findViewById(R.id.main); rootView.startAnimation(animation);
思った通りに動作した。Acitivtyが閉じるときにルートviewを非表示にしておけば完璧。
おまけ
XMLを使いたくなければ全てJavaで記述することもできる。
TranslateAnimation animation = new TranslateAnimation( Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 1.0f, Animation.RELATIVE_TO_PARENT, 0.0f); animation.setDuration(200); animation.setFillAfter(true); rootView.startAnimation(animation);
Animation.RELATIVE_TO_PARENTを指定すればXMLのときと同じく%で指定できる。(1.0 == 100%)
アプリの再インストールを検知する方法
アプリのインストール情報を検知するためandroid.manufestに以下の定義をする。
<receiver android:name=".MyReciever"> <intent-filter> <action android:name="android.intent.action.PACKAGE_ADDED" /> <action android:name="android.intent.action.PACKAGE_CHANGED" /> <action android:name="android.intent.action.PACKAGE_DATA_CLEARED" /> <action android:name="android.intent.action.PACKAGE_INSTALL" /> <action android:name="android.intent.action.PACKAGE_REMOVED" /> <action android:name="android.intent.action.PACKAGE_REPLACED" /> <action android:name="android.intent.action.PACKAGE_RESTARTED" /> </intent-filter> </receiver>
そしてjavaのコードでレシーバーを定義する。
public class MyrReciever extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); Log.d("MyrReciever" ,action); } }
しかしこれだけではインテントが飛んでこない。
調べてみるとandroid deveopersに回答があった。
どうやらインテントフィルターにschemeを追加する必要があるらしい。
つまり、android.manufestを以下のように書き換える。
<receiver android:name=".MyReciever"> <intent-filter> <action android:name="android.intent.action.PACKAGE_ADDED" /> <action android:name="android.intent.action.PACKAGE_CHANGED" /> <action android:name="android.intent.action.PACKAGE_DATA_CLEARED" /> <action android:name="android.intent.action.PACKAGE_INSTALL" /> <action android:name="android.intent.action.PACKAGE_REMOVED" /> <action android:name="android.intent.action.PACKAGE_REPLACED" /> <action android:name="android.intent.action.PACKAGE_RESTARTED" /> <data android:scheme="package"/> </intent-filter> </receiver>
これでパッケージの状態が変化するとブロードキャストレシーバーにインテントが飛んでくるようになる。
ちなみにdataタグにpathを指定することで変更を通知するパッケージを絞ることができるようだ。
2012/2/9
通知するパッケージを絞れるというのは勘違いのようでした。
これらのブロードキャストのUriは"package:com.example.myapp"というURIだか何なんだかよく分からない形式になっていて、どうもintent-filterで絞りをかける手段はないようです。
<data android:scheme="package" android:path="com.example.myapp" />
参考:
Google グループ
Activityの連続起動を検知する
事の発端
自作のアプリケーションランチャーでHomeボタンの押下を検知したくなった。
Homeボタンを一回押したらランチャーを表示し、ランチャーが表示されている状態でHomeボタンを押したら別の外部のHomeアプリを起動したい(PreHomeみたいな動作)。
というわけでwebで調査していたところ、日本androidの会にActivity#onUserLeaveHintをオーバーライドすればHomeボタンの検知ができるとの情報があった。
しかしAPIリファレンスを読んでも、記述があいまいと言うか、いまいち要領を得ない。
試してみた
ActivityLifeCycleというテストアクティビティをつくってlogをトレースしてみる。
(1)Homeボタン押下(HomeApp起動)
ActivityLifeCycle(10487): onUserInteraction ActivityLifeCycle(10487): onUserLeaveHint ActivityLifeCycle(10487): onPause ActivityManager(120): Displayed activity com.example.android.home/.HomeApp: 285 ms (total 285 ms)
(2)Homeボタン長押し (アプリ履歴表示)
dalvikvm(204): GC_EXPLICIT freed 3053 objects / 178712 bytes in 79ms dalvikvm(120): GC_EXTERNAL_ALLOC freed 21870 objects / 973168 bytes in 105ms StatusBar(120): DISABLE_EXPAND: yes
- > 自Activity(ActivityLifeCycle )起動
ActivityManager(120): Starting activity: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10100000 cmp=com.amle.android.mysample/.ActivityLifeCycle }
(3)Homeボタン長押し(アプリ履歴表示)
dalvikvm(120): GC_EXTERNAL_ALLOC freed 19860 objects / 988712 bytes in 104ms StatusBar(120): DISABLE_EXPAND: yes
-> 他アプリ(smartbar/.GridFavouristApps)起動
ActivityManager(120): Starting activity: Intent { flg=0x10100000 pkg=com.aps.smartbar cmp=com.aps.smartbar/.GridFavouristApps }
ActivityLifeCycle(10487): onUserInteraction
ActivityLifeCycle(10487): onUserLeaveHint
ActivityLifeCycle(10487): onPause
AudioFlinger(90): write blocked for 163 msecs, 2069 delayed writes, thread 0xdae0
ActivityManager(120): Displayed activity com.aps.smartbar/.GridFavouristApps: 421 ms (total 421 ms)
(4)検索ボタン長押し(他アプリ(smartbar/.GridFavouristApps)起動)
ActivityLifeCycle(10257): onUserInteraction ActivityLifeCycle(10257): onUserInteraction ActivityManager(120): Starting activity: Intent { act=android.intent.action.SEARCH_LONG_PRESS flg=0x10000000 cmp=com.aps.smartbar/.SearchButtonClickedReceiver } ActivityLifeCycle(10257): onUserInteraction ActivityLifeCycle(10257): onUserLeaveHint ActivityLifeCycle(10257): onPause ActivityLifeCycle(10257): onUserInteraction InputManagerService(120): Window already focused, ignoring focus gain of: com.android.internal.view.IInputMethodClient$Stub$Proxy@445bd798 ActivityLifeCycle(10257): onUserInteraction ActivityManager(120): Starting activity: Intent { flg=0x10000000 pkg=com.aps.smartbar cmp=com.aps.smartbar/.GridFavouristApps }
(5)ステータスバーを開く
(反応なし)
- >他アプリ(MobileRTM/.Activities.RTMReminderActivity)起動
ActivityManager(120): Starting activity: Intent { flg=0x10000000 cmp=com.rememberthemilk.MobileRTM/.Activities.RTMReminderActivity bnds=[0,568][480,664] (has extras) }
ActivityLifeCycle(10487): onUserInteraction
ActivityLifeCycle(10487): onUserLeaveHint
ActivityLifeCycle(10487): onPause
AudioFlinger(90): write blocked for 174 msecs, 2072 delayed writes, thread 0xdae0
ActivityManager(120): Displayed activity com.rememberthemilk.MobileRTM/.Activities.RTMReminderActivity: 186 ms (total 186 ms)
(6)自Activityの表示中に外部のアラームアプリを使ってAlarmManager経由で他アプリ(myalarm/.TimeupActivity)を起動させる。
ActivityManager(52): Starting activity: Intent { act=android.intent.action.MAIN flg=0x10000000 cmp=com.amle.android.myalarm/.TimeupActivity (has extras) } ActivityLifeCycle(1183): onUserInteraction ActivityLifeCycle(1183): onUserLeaveHint ActivityLifeCycle(1183): onPause AlarmService(353): onStart
(7)自Activityで別スレッドを作り、10秒後にブラウザを起動。
ActivityManager(52): Starting activity: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.android.browser/.BrowserActivity } ActivityLifeCycle(1146): onUserInteraction ActivityLifeCycle(1146): onUserLeaveHint ActivityLifeCycle(1146): onPause
(8)自Activityで別スレッドを作り、10秒後に自Activityをfinish()する。
ActivityLifeCycle(1240): onPause
(9)自Activityで別スレッドを作り、10秒後に自アプリの別activity(MySample)を起動する。
dalvikvm(204): GC freed 117 objects / 5256 bytes in 66ms ActivityManager(52): Starting activity: Intent { cmp=com.amle.android.mysample/.MySample } ActivityLifeCycle(626): onUserInteraction ActivityLifeCycle(626): onUserLeaveHint ActivityLifeCycle(626): onPause
(10)自Activityで別スレッドを作り、10秒後に自アプリの別activity(MySample)を起動し、その後自分をfinish()する。
dalvikvm(544): GC freed 2963 objects / 314176 bytes in 87ms ActivityManager(52): Starting activity: Intent { cmp=com.amle.android.mysample/.MySample } ActivityLifeCycle(662): onUserInteraction ActivityLifeCycle(662): onUserLeaveHint ActivityLifeCycle(662): onPause
いろいろなパターンを試してみたが、まとめると
- 他のActivityを起動させるとonUserLeaveHint()が呼ばれる。(Homeキー、検索キー長押し、バックグラウンドスレッドからのstartActivity()やAlarmManagerからのブロードキャストなど手段を問わない。)
- 自アプリ内の他のactivityを起動させても呼ばれる。
- 自Activityを起動させても呼ばれない。
- 自Activityをfinish()させても呼ばれない。(ただし他のActivityを起動した後にfinish()してもonUserLeaveHint()は呼ばれる。)
- ステータスバーの開閉、標準のアプリ履歴の表示では呼ばれない。(そこから別のActivityを起動すると呼ばれる。)
…となる。
実際のところonUserLeaveHint()は、他activityが起動される時には、一部の例外(着呼のときには呼ばれない)を除いてほとんど常に呼ばれると思ったほうが良さそうだ。
結論:
onUserLeaveHint()は、他のactivityが起動された(startActivity()された)ことを検知するためのメソッドであると考えられる。
最初はonPause()との使い分けがわからなかったが、「電話の着信では呼ばれない」、「自activityをfinish()しても呼ばれない」という2点でonPause()と異なる。
結局のところHomeボタンの検知に使えるわけではなさそうだが2重起動の検知には使えそうだ。
つまり、onUserLeaveHint()を挟むことなく連続でonResume()が呼び出されたら、それは連続起動(ランチャーActivityの表示中にさらにランチャーActivityが呼び出された)である。
着呼の時にはonUserLeaveHint()が呼び出されないが、この時はActivityが終了するようなので連続起動扱いにはならない。
つまり、着呼の後には常にランチャーが表示されることになる。
この動作が正しいかどうかはアプリの仕様次第だろう。
開設
とりあえず作っておきます。
プログラム関係の話題を書く予定。
どれだけの更新頻度になるかはわかりません。