RubotoのTutorials and examplesには「Tutorial: Add a menu」「Tutorial: Add a context menu」とあります。
ただ、ActionBar上のPopup Menuに関するチュートリアルがなかったため、試してみました。
■環境
Platform | JDK | ant | Ruby | ruboto | jruby-jars | Device | API level |
---|---|---|---|---|---|---|---|
Windows7 x64 | 1.7.0_25 | 1.9.1 | RubyInstaller 1.9.3-p448 | 0.13.0 | 1.7.4 | Nexus7 2012 | android-17 |
■注意
RubotoのIssue443にある通り、「onCreateOptionsMenu」が初回に起動しないことの影響で、ActionBar上のメニューが初回起動時には表示されません (二回目以降はきちんと起動します)。
onCreateOptionsMenu (and other callbacks) not called on first time startup · Issue #443 · ruboto/ruboto · GitHub
■アプリの動き
- ActionBarの「Action」メニューをタップ
- Popupメニューが表示されるので、メニューをタップ
- toastの場合、toastが表示される
- intentの場合、Chromeが起動して、RubotoのWebページが表示される
■アプリの生成
アプリにJRubyを含めて生成します。
ruboto gen app --package com.example.popupmenu --target android-17 --with-jruby
Windowsなので、メモリサイズを変更します。
- popupmenu\rakelib\ruboto.rake の52行目あたり
# MINIMUM_DX_HEAP_SIZE = 1600 MINIMUM_DX_HEAP_SIZE = 768
- \androidsdk\build-tools\17.0.0\dx.bat の53行目あたり
rem defaultXmx=-Xmx1600M set defaultXmx=-Xmx768M
アプリをインストールして、起動します。
cd popupmenu rake install start
なお、エミュレータを使う場合は、別のコマンドプロンプトを起動し、以下のコマンドでエミュレータを起動しておきます。
cd popupmenu ruboto emulator -t android-17
エミュレータでインストールがうまくいかない場合は、再インストールやスクリプトのインストールにします。
rake reinstall start rake update_scripts:restart
■レイアウト用xmlの作成
今回はレイアウトをRubyのコードではなく、xmlにて指定するようにしました。
xmlによる方法は、RubotoのSpinnerのサンプルを参考にしました。
Example: spinner · ruboto/ruboto Wiki · GitHub
メイン画面のレイアウト
- popupmenu\res\layout\main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:id="@+id/Popup" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Popup Sample" /> </LinearLayout>
ActionBarに表示するメニューのレイアウト
- popupmenu\res\layout\action_bar_menu.xml
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/action_bar" android:title="action" android:showAsAction="ifRoom|withText" /> </menu>
ActionBarのメニューをタッチした時に表示されるPopupMenuのレイアウト
- popupmenu\res\layout\popup_menu.xml
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/toast" android:title="toast" /> <item android:id="@+id/intent" android:title="intent" /> </menu>
■Activityの修正
popupmenu\src\popupmenu_activity.rb を修正していきます。
java_importの記述
Javaのクラスを利用するために必要なものを java_import します。
「import」と「java_import」のどちらを使えばよいかで悩みましたが、以下の記事を見て「java_import」を使用することにしました。
- CallingJavaFromJRuby · jruby/jruby Wiki · GitHub
- ruby - JRuby: import vs include vs java_import vs include_class - Stack Overflow
なお、Android Debug Monitorにログを残したかったことから、android.util.Log も java_import しています。
on_xxx系の実装名について
JavaのonCreateOptionsMenuをRubyで書くとしたら、
- スネークケース: on_create_options_menu
- キャメルケース: onCreateOptionsMenu
どちらが良いのか分からなかったため、Rubotoのソースを読んだところ、どちらでも良さそうでした。
今回はRubyでよく目にする、スネークケースを使うことにしました。
参考 - Rubotoのソース:popupmenu\src\org\ruboto\RubotoActivity.java の256行目あたりから
public boolean onCreateOptionsMenu(android.view.Menu menu) { if (ScriptLoader.isCalledFromJRuby()) return super.onCreateOptionsMenu(menu); if (!JRubyAdapter.isInitialized()) { Log.i("Method called before JRuby runtime was initialized: RubotoActivity#onCreateOptionsMenu"); return super.onCreateOptionsMenu(menu); } String rubyClassName = scriptInfo.getRubyClassName(); if (rubyClassName == null) return super.onCreateOptionsMenu(menu); if ((Boolean)JRubyAdapter.runScriptlet(rubyClassName + ".instance_methods(false).any?{|m| m.to_sym == :on_create_options_menu}")) { return (Boolean) JRubyAdapter.runRubyMethod(Boolean.class, scriptInfo.getRubyInstance(), "on_create_options_menu", menu); } else { if ((Boolean)JRubyAdapter.runScriptlet(rubyClassName + ".instance_methods(false).any?{|m| m.to_sym == :onCreateOptionsMenu}")) { return (Boolean) JRubyAdapter.runRubyMethod(Boolean.class, scriptInfo.getRubyInstance(), "onCreateOptionsMenu", menu); } else { return super.onCreateOptionsMenu(menu); } } }
on_xxx系の実装について
以下の3つを実装しました。
MenuItemClickListenerクラスの作成
OnMenuItemClickListener のイベントリスナーとして登録したクラスを作成します。
なお、今回の作り方では、toast/intentに関する処理はこのクラスに実装しても動作しないため、Activityクラスにメソッド(tap_popup_menu)を実装しています。
popupmenu_activity.rbのソース
require 'ruboto/widget' require 'ruboto/activity' require 'ruboto/util/toast' # intent start用 java_import "android.content.Intent" java_import "android.net.Uri" # popup用 java_import 'android.widget.PopupMenu' # Log用 java_import 'android.util.Log' class PopupmenuActivity def on_create(bundle) super Log.v('POPUPMENU', 'on_create called') self.setContentView(Ruboto::R::layout::main) end def on_create_options_menu(menu) Log.v('POPUPMENU', 'on_create_options_menu called') self.getMenuInflater().inflate(Ruboto::R::layout::action_bar_menu, menu) true end def on_options_item_selected(item) Log.v('POPUPMENU', 'onOptionsItemSelected called') view = findViewById(item.getItemId()) popup = PopupMenu.new(self, view) popup.getMenuInflater().inflate(Ruboto::R::layout::popup_menu, popup.getMenu()) popup.setOnMenuItemClickListener(MenuItemClickListener.new(self)) popup.show() true end def tap_popup_menu(itemId) case itemId when Ruboto::R::id::toast toast 'select toast' when Ruboto::R::id::intent intent = Intent.new(Intent::ACTION_VIEW) intent.setData(Uri.parse("http://ruboto.org/")) startActivity(intent) else toast 'Popup Selected' + itemId.to_s end end end class MenuItemClickListener def initialize(activity) @activity = activity end def onMenuItemClick(item) @activity.tap_popup_menu(item.getItemId()) end end
■インストールと起動
あとは、アプリを再インストールして開始します。
rake reinstall start
初回起動時はActionBarのメニューが表示されないため、一度終了し、再度起動します。