Context メモ
Android にはいくつかの Context がある。
Activity から参照可能なのは:
- this : Acrivity
- getBaseContext() : ContextImpl
- getApplicationContext() : Applicaiton
となる。
Activity を Context として持ちまわるのは、Activity のリークにつながるので、getApplicationContext() で取得した Application を持ちまわる、というのが慣習になっていると思っていたが、Application だと問題が発生することがある。
具体的に遭遇した問題は:
View view = inflate(context, R.layout.sample, null);
addView(view);
という具合に、inflate を用いて View を生成する際に使用する Context が Application だった場合、生成された View にアプリのテーマが適切に設定されず、EditText の背景が不正になることがあった。
getBaseContext() で取得した ContextImpl を使用した場合は適切なテーマが設定されたため、これがワークアラウンドなんだと思う。
FrameLayout メモ
FrameLayout は z index を簡単に調整することができる便利なレイアウトなんだけど、View が Button だった場合、z index を無視して最上位に表示されるようだ。
これが ImageButton なら期待した通りの z index になるようだ。
GridView メモ
備忘録。
GridView に表示する View に、Button が存在すると、そのボタンをタップしたかどうかにかかわらず、タッチイベントがボタンに取られてしまい、GridView の OnItemClickListener が通知されなくなる。
ViewPager メモ
備忘録。
Android の ViewPager は、単純な使い方ならかなり簡単でかつ便利なんだけど、ちょっと複雑なことをしようとすると、とたんにトラップが牙を剥く。
1.一度表示させた Fragment を取り替えることができない
例えば、3 ページ持つ ViewPager があったとする。
1 ページ目の入力で 2 ページ目に表示させる Fragment を動的に変える、というようなことが難しい。
表示を変えることはできるが、一度表示させた Fragment は ViewPager 内でキャッシュされ、またキャッシュされた Fragment を開放する方法を ViewPager が提供していないためである。
2.スワイプさせて表示した Fragment 内の View#onMeasure(int, int) が呼び出されない
ViewPager は初期状態で、setCurrentItem(int) で設定されたページと左右両隣のページの Fragment を要求し、このタイミングで生成された Fragment 内の View#onMeasure(int, int) は適切に呼び出される。ただし、指でスワイプしたり、後から setCurrentItem(int) などで違う Fragment を表示させる場合において、何故か View#onMeasure(int, int) が呼び出されない。
View#onMeasure(int, int) は、カスタム View を作る際にお世話になるメソッドで、場合によっては必要不可欠だったりするんだけど、ViewPager では機能しないため、対策が必要になる。
最もわかりやすい対策は、初期表示でなら適切にレイアウトされるので、まずすべてのページを初期状態で表示させ、その際使用した View をキャッシュしておき、改めて本番でそれらを使う、というものだ。キャッシュをどのように行うかがキモなんだけど、親 Activity などに持たせるのが賢明なんだと思う。
Animation メモ
FrameLayout に addView(View) をするタイミングで TranslateAnimatoin でスライドインさせようとした場合に、以下のケースでチラツキが発生したので、その解決法のメモ。
FrameLayout に元からある View を removeView(View) でアニメーションを伴わせ削除しつつ、addView(View) でアニメーションしつつ追加する場合はチラツキは発生しない。
しかし、FrameLayout に View を残したまま新しい View をアニメーションを伴わせつつ追加すると、一瞬アニメーション最終状態で表示され、アニメーション開始されてしまう。いわゆるチラツキ。
これを回避するために、以下の様なコードを書く。
child.setVisibility(View.GONE); animation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { child.setVisibility(View.VISIBLE); } @Override public void onAnimationEnd(Animation animation) { } @Override public void onAnimationRepeat(Animation animation) { } }); child.startAnimation(animation); parent.addView(child);
こんな感じ。こちらで見つけた。
android - How to avoid blink of View when using animation? - Stack Overflow
要するに、ちらつく最初は GONE で非表示にさせておき、アニメーションが開始したら VISIBLE に戻す、というもの。
根本的な解決にはなっておらず、かつ実際のアニメーション期間が少し短くなるが、ちらつくよりは良いかと思う。
プロキシサーバを作りたい
Web 開発を行っていて、僕の環境だけ LiveReload が期待通りに機能せず、500 エラーが頻発するという現象に見舞われた。
原因は LiveReload で、HTML から LiveReload 関連の script タグをいくつか取り除くだけでエラーが出なくなるので、ソースツリーには手を加えずに script タグを取り除く、ということをしたい。
試したのは Chrome Extension で取り除く方法。
これは:
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) { if (tab.url.startsWith("http://ubuntu:8080/")) { chrome.tabs.sendMessage(tab.id, { command: "remove_livereload" }, function(msg) { if (msg !== undefined) { alert(msg); } }); } });
のような感じでコールバックを仕込んで:
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) { if (msg.command === "remove_livereload") { $("script").each(function(index, element) { if ($(element).attr("type") === "text/javascript") { if ($(element).text().indexOf('WEB_SOCKET_SWF_LOCATION = "/__rack/WebSocketMain.swf";') !== -1 || $(element).attr("src") === "/__rack/swfobject.js" || $(element).attr("src") === "/__rack/web_socket.js" || $(element).text().indexOf("RACK_LIVERELOAD_PORT = 35729;") !== -1 || $(element).attr("src") === "/__rack/livereload.js?host=0.0.0.0&port=35729") { $(element).remove(); } } }); } });
のような感じで DOM 操作することで、目的の script タグを除去することができる。
ただし、chrome.tabs.onUpdated.addListener というコールバック名からも分かる通り、HTML をパースして DOM ツリーが完成した後で呼び出されるので、このタイミングだと JavaScript は動いてしまっている。
Chrome API リファレンスをくまなく見てみたところ、以下のイベントコールバックを見つけた。
chrome.webNavigation.onDOMContentLoaded.addListener
これは DOM をパースし終えて、まだ JS の処理を行う前に呼び出されるコールバックなんだけど、このコールバックで通知されるのは、JS がアクセスしようとしている URL だったりして、またちょっと目的を果たせない。
このコールバックでちょっと遊んでみたんだけど、世の中は広告だらけだなぁ、と改めて実感した。Chrome で新規タブを開くだけで、Chrome が Google の広告サーバにアクセスしてるのがわかった。
さて、Chrome Extension の仕様として、コールバック内で DOM 操作などは行えず、サンプルのように sendMessage でメッセージを送り、onMessage コールバック内で行う必要がある。つまり非同期なので、いくら最適なタイミングでコールバックが呼び出されたとしても、実際の処理を行うタイミングで処理が進んでしまっているので、JS が行う通信を完全に防ぐことは Chrome Extension ではできない。
もしかしたら Firefox なら出来るのかもしれないけど、ブラウザ拡張でどうにかするのは諦めて、プロキシサーバを立てて見ようと思う。Nginx が簡単そうだけど、Haskell で汎用的に作りたいような気分。