package com.android_mvc.framework.activities; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.android_mvc.framework.common.BaseUtil; import com.android_mvc.framework.common.FWUtil; import com.android_mvc.framework.controller.action.ActionResult; import com.android_mvc.framework.controller.routing.Router; import com.android_mvc.framework.controller.validation.ValidationResult; import com.android_mvc.framework.task.AsyncTasksRunner; import com.android_mvc.framework.task.RunnerFollower; import com.android_mvc.framework.task.SequentialAsyncTask; import com.android_mvc.framework.ui.menu.OptionMenuBuilder; import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; /** * Map系+非Map系のActivityの共通処理を詰め込むクラス * @author id:language_and_engineering * */ public class CommonActivityUtil<T extends IBaseActivity> { // Activity中では,$という変数名で利用可能。 // NOTE: 本クラス中では,T→Activity のキャストを許可する。 // 疑似的な多重継承によってトラブル回避しているため。 private Activity activity; // 前画面から受け取ったIntent内の情報 private Bundle extras; private ValidationResult vres; private ActionResult ares; // オプションメニュー構築に関する情報 private OptionMenuBuilder optionMenuBuilder; // オプションメニューの構築処理をすでに実行済みか private boolean menuBuiltFlag = false; // ---------------- Activityの初期化関連 ------------------ /** * ActivityのonCreate時に呼び出される共通処理 */ public void onActivityCreated(T activity) { FWUtil.d("onActivityCreated開始"); this.activity = (Activity)activity; // 前画面から運搬されたデータを引き継ぎ carryDataFromPreviousPage(activity); // UI描画の事前処理と本処理 if( activity.requireProcBeforeUI() ) { // 非同期タスクで処理を行ってからUI描画 executeProcBeforeUIAndRender(activity); } else { // すぐに同期的にUI描画 renderUI(activity); } FWUtil.d("onActivityCreated終了"); } /** * 前画面からIntent経由で運搬されたデータを引き継ぎ,この画面で利用可能にする。 */ private void carryDataFromPreviousPage(T activity) { // Intentからデータ取得 extras = ((Activity)activity).getIntent().getExtras(); // なければ終了 if( extras == null ) return; // バリデーション結果があれば格納 if( extras.containsKey(Router.EXTRA_KEY_VALIDATION_RESULT)) { vres = (ValidationResult) extras.getSerializable(Router.EXTRA_KEY_VALIDATION_RESULT); } // アクション実行結果があれば格納 if( extras.containsKey(Router.EXTRA_KEY_ACTION_RESULT)) { ares = (ActionResult) extras.getSerializable(Router.EXTRA_KEY_ACTION_RESULT); } } /** * UI構築前に必要な非同期処理を済ませてから,UIを構築する。 */ private void executeProcBeforeUIAndRender(final T activity) { new AsyncTasksRunner( new SequentialAsyncTask[]{ new SequentialAsyncTask(){ @Override protected boolean main() { // 事前処理を実行 activity.procAsyncBeforeUI(); return CONTINUE_TASKS; } } }) .withSimpleDialog("読み込み中・・・", (Activity)activity) .whenAllTasksCompleted(new RunnerFollower(){ @Override protected void exec() { // UIスレッド上でUIを構築 renderUI(activity); }}) .begin(); } /** * UIを構築 */ private void renderUI(T activity) { // XMLが存在すれば,レイアウトを描画 renderByXmlIfExists( (Activity) activity ); // UI部品を定義 activity.defineContentView(); // メニューを定義 this.optionMenuBuilder = activity.defineMenu(); // 終わったらその時用の処理を呼び出し activity.afterViewDefined(); } /** * 該当アクティビティに対応するレイアウトXMLを検知して描画する */ private void renderByXmlIfExists( Activity activity ) { // NOTE: 残念ながら,layoutフォルダの内部は階層化できない。 // ソート時に把握しやすくなるようなネーミングを心がけること。 // http://ameblo.jp/m-ext/entry-10872776112.html // クラス名の末尾の「Activity」を除去 String activity_class_name = activity.getClass().getSimpleName(); Pattern reg_pattern = Pattern.compile( "Activity$" ); Matcher reg_matcher = reg_pattern.matcher( activity_class_name ); String activity_basic_name = reg_matcher.replaceFirst(""); // クラス名の基本部分をパスカル形式(PascalCase)からスネーク形式(snake_case)に変換 StringBuilder sb = new StringBuilder(); int class_name_length = activity_basic_name.length(); boolean previous_char_was_upper = false; // 1つ前の文字が大文字だったかどうか for( int i = 0; i < class_name_length; i ++ ) { Character c = activity_basic_name.charAt(i); // 大文字か if( Character.isUpperCase(c)) { // 直前が大文字でなければ,アンダーバーを追記 if( ( i > 0 ) && ( ! previous_char_was_upper ) ) { sb.append("_"); } // 小文字に変換 c = Character.toLowerCase(c); previous_char_was_upper = true; } else { previous_char_was_upper = false; } // 追記 sb.append(c); } // レイアウトXMLのフィールド名が完成 String xml_base_name = sb.toString(); BaseUtil.d( "xml name is " + xml_base_name); // この名称のレイアウトXMLのリソースIDを取得 int xml_resource_id = activity .getResources() .getIdentifier( xml_base_name, "layout", activity.getPackageName() ); // このXMLが存在すればレイアウトを描画 if( xml_resource_id != 0 ) { activity.setContentView( xml_resource_id ); } else { // NOTE: アクティビティに対応したXMLを作らない場合もある BaseUtil.w("xml not found!"); } return; } // ---------------- 初期化後の画面内操作関連 ------------------ /** * 前画面から受け取ったIntent内のデータを返す。 * 何もデータがなければnullを返す。 */ public Bundle extras() { return extras; // NOTE: AC側ではBundleの持つアクセッサをそのまま流用できる。 // TODO: RoutingTableもそうだが,Intentで渡される値に静的制約を設けたほうがよいか? } /** * バリデーション実行結果を返す。 * 何もデータがなければnullを返す。 */ public ValidationResult getValidationResult() { return vres; } /** * アクション実行結果を返す。 * 何もデータがなければnullを返す。 */ public ActionResult getActionResult() { return ares; } /** * バリデーション実行結果が存在するかどうか。 */ public boolean hasValidationResult() { return (vres != null); } /** * アクション実行結果が存在するかどうか。 */ public boolean hasActionResult() { return (ares != null); } /** * アクション実行結果に特定のキーが存在するかどうか。 */ public boolean actionResultHasKey(String key) { if( ! hasActionResult() ) return false; return ( ares.get(key) != null ); } /** * 受け取ったIntentが特定のキーを含んでいるかどうかを返す。 */ public boolean intentHasKey(String key) { if( extras() != null ) { return extras().containsKey( key ); } else { return false; } } /** * getTextのように,文字列リソースを取得する。 */ public String _(int target_string_id) { // リソースから取得 return FWUtil._(activity, target_string_id); } // ---------------- メニュー関連 ------------------ /** * オプションメニューの描画 */ public Menu renderOptionMenuAsDescribed(Menu menu) { // 初回であれば if( ! menuBuiltFlag ) { // ユーザがメニュー構築を指示していれば if( optionMenuBuilder != null ) { menu = optionMenuBuilder.registerItemsInMenu(menu); } } // 次回からは実行させない menuBuiltFlag = true; return menu; } /** * オプションメニュー内の特定の項目が押されたイベントを処理 */ public void onOptionItemSelected(MenuItem item) { if( optionMenuBuilder != null ) { optionMenuBuilder.onItemSelected(item); } } }