はじめに
渋谷オフィスから 今中 がお届けします。
ご無沙汰しております。
Google I/O 2015が開催されました。
私は六本木ヒルズ近くの映画館でサテライト上映されていたものを鑑賞するイベントに参加して、キーノートを拝見しておりました。
映画館のシアターをひとつ、一晩借り切るという非日常が楽しかったです。
さて、そんなGoogle I/Oで発表された話題から、「Chrome Custom Tabs」についてお届けしたいと思います。
Chrome Custom Tabs とは?
Chrome Custom Tabsは、今回のGoogle I/Oで発表された、アプリとChromeブラウザをより密に結びつけ、ブラウザ画面をよりネイティブアプリらしく表現することが出来る仕組みです。
これまで、アプリからURIを開くとき、開発者は2つの方法を取ることが出来ました。
この2つにはそれぞれ優れた点と悪い点があります。
- ブラウザを起動させる
- pros: 実装が極めて楽である(Intentを投げるだけ)
- cons: カスタマイズが不可能
- WebViewコンポーネントを使い、ネイティブアプリ内に実装する
- pros: カスタマイズが柔軟にできる(楽にできるとは言っていない)
- cons: ブラウザとデータを共有できない、WebViewロジックのメンテナンスコストが高い
この両者の良い所を取り入れることができれば、最高ではありませんか?
それが可能になるかもしれないのが、「Chrome Custom Tabs」です。
動作画面
まずは以下の動画を御覧ください。
こちらは、現在開発中のSeesaaブログアプリで「Chrome Custom Tabs」を利用してみた例です。
一見すると、「プライバシーポリシー」「利用規約」はWebViewで作ってあるように見えますが、これは実際にChromeで動いています!
注意!
この機能は、現時点において、Chrome 44 beta および Chrome 45 Dev で使用可能ですが、
chrome://flagsにおいて「enable hosted mode」を有効にする必要があります。
また、いくつかの実装(例えばExtraのキー名)は正式版のリリースに伴い、変更される可能性があります。
実装方法
追記(2015/08/18):
本日、Custom Tabs Support Libraryがリリースされました。
これにより、より楽にCustom Tabsを実装できるようになっております。
(但しCustom Tabs Support LibraryはminSdkVersionが16であることをご注意ください)
よって、以下の記述を用いるよりかは、サポートライブラリを使うことをお勧めします。
new CustomTabsIntent.Builder()
.setToolbarColor(color)
.setStartAnimations(activity, R.anim.enter_from_right, R.anim.exit_to_left)
.setExitAnimations(activity, R.anim.enter_from_left, R.anim.exit_to_right)
.setShowTitle(true)
.build()
.launchUrl(activity, Uri.parse(url));
下記コードは、現状動くであろうコードとして書きなおしてありますが、
前述のとおりBuilderを使うほうが良いと考えておりますので、私は実際に以下のコードを使っているわけではありません。・・・追記終わり
実装方法はChrome Custom Tabsの公式解説ページに記載されています。親切。
とはいえちょびっとハマった点があるので解説したいと思います。
Chrome Custom Tabsの有効化
動作画面で示したような、非常にシンプルな例の場合は以下の様なコードスニペットでらくらく実現できます。
private static final String EXTRA_CUSTOM_TABS_SESSION= "android.support.customtabs.extra.SESSION"; private static final String CATEGORY_CUSTOM_TABS = "android.intent.category.CUSTOM_TABS"; String url = "https://blog.seesaa.jp/"; Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); Bundle extras = new Bundle(); extras.putBinder(EXTRA_CUSTOM_TABS_SESSION, null); intent.putExtras(extras);
もしも対応バージョンのChromeがユーザーの端末にインストールされてなかったら? その場合、単なるACTION_VIEWなIntentとして扱われます。つまり、これまでどおりブラウザでリンクが開かれるということです。
また、対応バージョンのChromeがインストールされていて、デフォルトブラウザに指定されているなら、
Chromeは自動的にカスタマイズされたUIを処理して表示してくれます。
そして、渡したExtraを解釈できるブラウザであれば、Chrome以外のブラウザでもCustom Tabsの機能を提供することがあるかも知れません。
アドレスバーの色をカスタマイズする
デフォルトの色でも充分かもしれませんが、せっかくなのでアプリのカラーと統一させてみましょう。
// omnibox(アドレスバー)の背景色を変更するExtraを指定します。 // 与える値には Color ( http://developer.android.com/reference/android/graphics/Color.html ) で定義された色を指定します private static final String EXTRA_CUSTOM_TABS_TOOLBAR_COLOR = "android.support.customtabs.extra.TOOLBAR_COLOR"; int color = ActivityCompat.getColor(activity, R.color.General_ThemeColor); intent.putExtra(EXTRA_CUSTOM_TABS_TOOLBAR_COLOR, color);
General_ThemeColorはActionBar(ToolBar)に使用している色と同じにしておくと良い感じ。
画面のEnter/Exit アニメーションをカスタマイズする
デフォルト状態だと、Activityを起動させたような遷移エフェクトがかかります(Lollipopだと下から上に重なるようなエフェクト)。 これをカスタマイズすれば、アプリとChromeの境界が分かりづらくなり、アプリへの没入感が増すのではないか?と思います。
private static final String EXTRA_CUSTOM_TABS_EXIT_ANIMATION_BUNDLE = "android.support.customtabs.extra.EXIT_ANIMATION_BUNDLE"; // Chrome終了時:clientEnter -> ChromeExit Bundle finishBundle = ActivityOptionsCompat.makeCustomAnimation(context, R.anim.clientEnterAnimResId, R.anim.ChromeExitAnimResId).toBundle(); intent.putExtra(EXTRA_CUSTOM_TABS_EXIT_ANIMATION_BUNDLE, finishBundle); // Chrome起動時:ChromeEnter -> clientExit Bundle startBundle = ActivityOptionsCompat.makeCustomAnimation(context, R.anim.ChromeEnterAnimResId, R.anim.clientExitAnimResId).toBundle(); ActivityCompat.startActivity(context, intent, startBundle);
ここがミソですが、API16未満(たとえばAPI14)でも動作させることが出来るように、「ActivityCompat」や「ActivityOptionsCompat」を使用することをお勧めします。
ボタンを設置する
これまで、ブラウザ側にボタンを設置するなど不可能で、どうしても実現したくばWebViewで実装する必要がありました。 ボタン1つのためだけにWebViewを使った開発をするのはもう終わりです! (※なお、重ねて申し上げますが、この機能は現在Chrome 44 betaより新しいバージョンでのみ実装されています。実際にそのような夢が現実になるのはずいぶん先だと思います。。。)
// ActionButtonに指定するアイコンリソースをBitmap( http://developer.android.com/reference/android/graphics/Bitmap.html )で与えます // アイコンの大きさは一般に、 幅 24 〜 48 dp / 高さ 24 dp で作成してください private static final String KEY_CUSTOM_TABS_ICON = "android.support.customtabs.customaction.ICON"; // ボタンやメニュー項目を押した時に呼ばれるPendingIntentを指定します。 // Chromeはタップ時に PendingIntent#send() を呼びますが、そのときに url をデータとして送ります。 // このurlを取得したければ、アプリ側で Intent#getDataString() を実行して取り出してください。 private static final String KEY_CUSTOM_TABS_PENDING_INTENT = "android.support.customtabs.customaction.PENDING_INTENT"; // ActionButtonのパラメータであることを示すExtraキーです private static final String EXTRA_CUSTOM_TABS_ACTION_BUTTON_BUNDLE = "android.support.customtabs.extra.ACTION_BUNDLE_BUTTON"; Bundle actionButtonBundle = new Bundle(); actionButtonBundle.putParcelable(KEY_CUSTOM_TABS_ICON, icon); // BitmapFactory.decodeResource()を使ってきれいな画像を渡せないか検討してください actionButtonBundle.putParcelable(KEY_CUSTOM_TABS_PENDING_INTENT, pendingIntent); intent.putExtra(EXTRA_CUSTOM_TABS_ACTION_BUTTON_BUNDLE, actionButtonBundle);
また、メニューアイテムとして項目を登録することも出来ます。必要に応じて使い分けてください。
// カスタムメニューアイテムのタイトル private static final String KEY_CUSTOM_TABS_MENU_TITLE = "android.support.customtabs.customaction.MENU_ITEM_TITLE"; // ArrayListでメニューに関連するパラメータを指定します。 // これらはメニューアイテムごとにバラバラのBundleでなければなりません private static final String EXTRA_CUSTOM_TABS_MENU_ITEMS = "android.support.customtabs.extra.MENU_ITEMS"; ArrayList menuItemBundleList = new ArrayList<>(); // 例えばforで回すとか。。。 Bundle menuItem = new Bundle(); menuItem.putString(KEY_CUSTOM_TABS_MENU_TITLE, menuItemTitle); menuItem.putParcelable(KEY_CUSTOM_TABS_PENDING_INTENT, pendingIntent); // 「ボタンを設置する」のコードを参考にしてください menuItemBundleList.add(menuItem); // ここまで。。。 intent.putParcelableArrayList(EXTRA_CUSTOM_TABS_MENU_ITEMS, menuItemBundleList);
他にもいろいろできちゃうかも?
Warm-upというワードに心惹かれたり、無駄にaidlを使ってChromeのServiceと接続してみたい人は居ませんか? Chrome Custom Tabs - Google Chromeではアプリ側のServiceと連携して、 ページのロード速度を早くするための方法が記載されています。 また、 GoogleChrome/custom-tabs-client に、今回紹介した機能を含むコードのサンプルがあります。
あと、公式の解説ページでは、どのような場合にCustom Tabsを使うべきか、またはWebViewを使用した独自実装にすべきかが書かれております。 一度ご確認いただいた上で、利用判断をすると良さそうです。
まとめ
Chrome Custom Tabs は、非常に簡単な記述で、アプリの没入感を高めてくれます。 まだ開発途中の機能ではありますが、先行して取り入れても破壊されることはないのではないか、と思います。 これをいち早く取り入れて、より良いアプリ体験を届けることができればいいですねー!
Disclaimer
当記事に掲載されているコードや説明は、以下のサイトから CC-By 3.0 license に基づき利用しております。
当記事にて掲載しているコードは、現在試験運用中の機能を使用しているため、正常に動作しなくなる可能性があります。予めご了承ください。


