Replica Island 學習筆記 03 - Activity分析

MainMenuActivity

Activity界面

MainMenuActivity啓動的時候,會用fade in動畫逐個顯示菜單按鈕,並在屏幕最下方顯示一個Ticker:

   

點擊任一菜單按鈕後,被點擊的按鈕會flick幾下,然後執行期望的切換:

   

EXTRAS 菜單中,LINEAR MODE 和 LEVEL SELECT 兩個菜單一開始是 locked 的,並且紅色 LOCKED 水印會不斷閃爍,估計玩到後期會解鎖吧:

點擊任一菜單按鈕,背景也會fade out,我們可以通過點擊 OPTIONS 來看看效果:

 

代碼分析

public class MainMenuActivity extends Activity {

    private boolean mPaused;
    private View mStartButton;
    private View mOptionsButton;
    private View mExtrasButton;
    private View mBackground;
    private View mTicker;
    private Animation mButtonFlickerAnimation;
    private Animation mFadeOutAnimation;
    private Animation mAlternateFadeOutAnimation;
    private Animation mFadeInAnimation;
    private boolean mJustCreated;
    private String mSelectedControlsString;

    private final static int WHATS_NEW_DIALOG = 0;
    private final static int TILT_TO_SCREEN_CONTROLS_DIALOG = 1;
    private final static int CONTROL_SETUP_DIALOG = 2;

    // Create an anonymous implementation of OnClickListener
    private View.OnClickListener sContinueButtonListener = new View.OnClickListener() {

        public void onClick(View v) {
            if (!mPaused) {
                Intent i = new Intent(getBaseContext(), AndouKun.class);
                v.startAnimation(mButtonFlickerAnimation);
                mFadeOutAnimation.setAnimationListener(new StartActivityAfterAnimation(i));
                mBackground.startAnimation(mFadeOutAnimation);
                mOptionsButton.startAnimation(mAlternateFadeOutAnimation);
                mExtrasButton.startAnimation(mAlternateFadeOutAnimation);
                mTicker.startAnimation(mAlternateFadeOutAnimation);
                mPaused = true;
            }
        }
    };

    private View.OnClickListener sOptionButtonListener = new View.OnClickListener() {

        public void onClick(View v) {
            if (!mPaused) {
                Intent i = new Intent(getBaseContext(), SetPreferencesActivity.class);
                v.startAnimation(mButtonFlickerAnimation);
                mFadeOutAnimation.setAnimationListener(new StartActivityAfterAnimation(i));
                mBackground.startAnimation(mFadeOutAnimation);
                mStartButton.startAnimation(mAlternateFadeOutAnimation);
                mExtrasButton.startAnimation(mAlternateFadeOutAnimation);
                mTicker.startAnimation(mAlternateFadeOutAnimation);
                mPaused = true;
            }
        }
    };

    private View.OnClickListener sExtrasButtonListener = new View.OnClickListener() {

        public void onClick(View v) {
            if (!mPaused) {
                Intent i = new Intent(getBaseContext(), ExtrasMenuActivity.class);
                v.startAnimation(mButtonFlickerAnimation);
                mButtonFlickerAnimation.setAnimationListener(new StartActivityAfterAnimation(i));
                mPaused = true;
            }
        }
    };

    private View.OnClickListener sStartButtonListener = new View.OnClickListener() {

        public void onClick(View v) {
            if (!mPaused) {
                Intent i = new Intent(getBaseContext(), DifficultyMenuActivity.class);
                i.putExtra("newGame", true);
                v.startAnimation(mButtonFlickerAnimation);
                mButtonFlickerAnimation.setAnimationListener(new StartActivityAfterAnimation(i));
                mPaused = true;
            }
        }
    };

以上是幾個菜單按鈕的響應方法,都利用變量mPaused防止快速連擊,並通過 mButtonFlickerAnimation 來閃爍點擊的按鈕菜單。其中前兩個listener將背景和其它按鈕用fade out動畫隱去,而後面兩個listener只是把點擊的按鈕用fade out動畫隱去。fade out動畫結束後,跳轉到目標Activity。

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mainmenu);
        mPaused = true;

onPause方法裏將mPaused設置爲true,onResume方法裏將mPaused設置爲false,這都好理解。但onCreate方法裏也將mPaused設置爲true,就有點奇怪了。查看了下Android的文檔,才回憶起Activity的幾個回調方法的順序:onCreate -> onStart -> onResume。所以在onCreate將mPaused設置爲true也很合理了。

        mStartButton = findViewById(R.id.startButton);
        mOptionsButton = findViewById(R.id.optionButton);
        mBackground = findViewById(R.id.mainMenuBackground);

        if (mOptionsButton != null) {
            mOptionsButton.setOnClickListener(sOptionButtonListener);
        }

        mExtrasButton = findViewById(R.id.extrasButton);
        mExtrasButton.setOnClickListener(sExtrasButtonListener);

初始化各個菜單按鈕。

        mButtonFlickerAnimation = AnimationUtils.loadAnimation(this, R.anim.button_flicker);
        mFadeOutAnimation = AnimationUtils.loadAnimation(this, R.anim.fade_out);
        mAlternateFadeOutAnimation = AnimationUtils.loadAnimation(this, R.anim.fade_out);
        mFadeInAnimation = AnimationUtils.loadAnimation(this, R.anim.fade_in);

加載fade in/out動畫。這裏,mFadeOutAnimation和mAlternateFadeOutAnimation是一個動畫,也可以加載不同的fade out動畫。AnimationUtils.loadAnimation(Context, int)這個方法是Android SDK的android.view.animation包提供的。

        SharedPreferences prefs =
                getSharedPreferences(PreferenceConstants.PREFERENCE_NAME, MODE_PRIVATE);
        final int row = prefs.getInt(PreferenceConstants.PREFERENCE_LEVEL_ROW, 0);
        final int index = prefs.getInt(PreferenceConstants.PREFERENCE_LEVEL_INDEX, 0);
        int levelTreeResource = R.xml.level_tree;
        if (row != 0 || index != 0) {
            final int linear = prefs.getInt(PreferenceConstants.PREFERENCE_LINEAR_MODE, 0);
            if (linear != 0) {
                levelTreeResource = R.xml.linear_level_tree;
            }
        }

        if (!LevelTree.isLoaded(levelTreeResource)) {
            LevelTree.loadLevelTree(levelTreeResource, this);
            LevelTree.loadAllDialog(this);
        }

加載level數據,其實就是NPC對話數據。這裏涉及到一大堆文件,先mark一下,後面再專門研究。

        mTicker = findViewById(R.id.ticker);
        if (mTicker != null) {
            mTicker.setFocusable(true);
            mTicker.requestFocus();
            mTicker.setSelected(true);
        }

初始化Ticker。

        mJustCreated = true;

        // Keep the volume control type consistent across all activities.
        setVolumeControlStream(AudioManager.STREAM_MUSIC);

        // MediaPlayer mp = MediaPlayer.create(this, R.raw.bwv_115);
        // mp.start();

初始化媒體播放器。遊戲中只播放了聲效,沒有背景音樂。這裏的MediaPlayer變量應該是留待以後擴展的。

    }

    @Override
    protected void onPause() {
        super.onPause();
        mPaused = true;
    }

    @Override
    protected void onResume() {
        super.onResume();
        mPaused = false;

        mButtonFlickerAnimation.setAnimationListener(null);

        if (mStartButton != null) {
            // Change "start" to "continue" if there's a saved game.
            SharedPreferences prefs =
                    getSharedPreferences(PreferenceConstants.PREFERENCE_NAME, MODE_PRIVATE);
            final int row = prefs.getInt(PreferenceConstants.PREFERENCE_LEVEL_ROW, 0);
            final int index = prefs.getInt(PreferenceConstants.PREFERENCE_LEVEL_INDEX, 0);
            if (row != 0 || index != 0) {
                ((ImageView) mStartButton).setImageDrawable(getResources().getDrawable(
                        R.drawable.ui_button_continue));
                mStartButton.setOnClickListener(sContinueButtonListener);
            } else {
                ((ImageView) mStartButton).setImageDrawable(getResources().getDrawable(
                        R.drawable.ui_button_start));
                mStartButton.setOnClickListener(sStartButtonListener);
            }

            TouchFilter touch;
            final int sdkVersion = Integer.parseInt(Build.VERSION.SDK);
            if (sdkVersion < Build.VERSION_CODES.ECLAIR) {
                touch = new SingleTouchFilter();
            } else {
                touch = new MultiTouchFilter();
            }

TouchFilter是Replica Island的一個抽象類,其繼承關係見《Replica Island 學習筆記 02 - 初步分析》。TouchFilter負責通過ObjectRegistry實例將觸摸屏的Touch事件傳遞給InputSystem實例。ObjectRegistry類只有一個全局靜態實例,且記錄於BaseObject類的成員變量sSystemRegistry。sSystemRegistry各成員的初始化在Game類的方法bootstrap裏。這裏,ObjectRegistry、InputSystem、BaseObject和Game都是指Replica Island內部實現的類。

            final int lastVersion = prefs.getInt(PreferenceConstants.PREFERENCE_LAST_VERSION, 0);
            if (lastVersion == 0) {
                // This is the first time the game has been run.
                // Pre-configure the control options to match the device.
                // The resource system can tell us what this device has.
                // TODO: is there a better way to do this? Seems like a kind of neat
                // way to do custom device profiles.
                final String navType = getString(R.string.nav_type);
                mSelectedControlsString = getString(R.string.control_setup_dialog_trackball);
                if (navType != null) {
                    if (navType.equalsIgnoreCase("DPad")) {
                        // Turn off the click-to-attack pref on devices that have a dpad.
                        SharedPreferences.Editor editor = prefs.edit();
                        editor.putBoolean(PreferenceConstants.PREFERENCE_CLICK_ATTACK, false);
                        editor.commit();
                        mSelectedControlsString = getString(R.string.control_setup_dialog_dpad);
                    } else if (navType.equalsIgnoreCase("None")) {
                        SharedPreferences.Editor editor = prefs.edit();

                        // This test relies on the PackageManager if api version >= 5.
                        if (touch.supportsMultitouch(this)) {
                            // Default to screen controls.
                            editor.putBoolean(PreferenceConstants.PREFERENCE_SCREEN_CONTROLS, true);
                            mSelectedControlsString =
                                    getString(R.string.control_setup_dialog_screen);
                        } else {
                            // Turn on tilt controls if there's nothing else.
                            editor.putBoolean(PreferenceConstants.PREFERENCE_TILT_CONTROLS, true);
                            mSelectedControlsString = getString(R.string.control_setup_dialog_tilt);
                        }
                        editor.commit();
                    }
                }
            }

            if (Math.abs(lastVersion) < Math.abs(AndouKun.VERSION)) {
                // This is a new install or an upgrade.

                // Check the safe mode option.
                // Useful reference: http://en.wikipedia.org/wiki/List_of_Android_devices
                if (Build.PRODUCT.contains("morrison") || // Motorola Cliq/Dext
                        Build.MODEL.contains("Pulse") || // Huawei Pulse
                        Build.MODEL.contains("U8220") || // Huawei Pulse
                        Build.MODEL.contains("U8230") || // Huawei U8230
                        Build.MODEL.contains("MB300") || // Motorola Backflip
                        Build.MODEL.contains("MB501") || // Motorola Quench / Cliq XT
                        Build.MODEL.contains("Behold+II")) { // Samsung Behold II
                    // These are all models that users have complained about. They likely use
                    // the same buggy QTC graphics driver. Turn on Safe Mode by default
                    // for these devices.
                    SharedPreferences.Editor editor = prefs.edit();
                    editor.putBoolean(PreferenceConstants.PREFERENCE_SAFE_MODE, true);
                    editor.commit();
                }

                SharedPreferences.Editor editor = prefs.edit();

                if (lastVersion > 0 && lastVersion < 14) {
                    // if the user has beat the game once, go ahead and unlock stuff for them.
                    if (prefs.getInt(PreferenceConstants.PREFERENCE_LAST_ENDING, -1) != -1) {
                        editor.putBoolean(PreferenceConstants.PREFERENCE_EXTRAS_UNLOCKED, true);
                    }
                }

解鎖EXTRAS菜單。

                // show what's new message
                editor.putInt(PreferenceConstants.PREFERENCE_LAST_VERSION, AndouKun.VERSION);
                editor.commit();

                showDialog(WHATS_NEW_DIALOG);

                // screen controls were added in version 14
                if (lastVersion > 0 && lastVersion < 14
                        && prefs.getBoolean(PreferenceConstants.PREFERENCE_TILT_CONTROLS, false)) {
                    if (touch.supportsMultitouch(this)) {
                        // show message about switching from tilt to screen controls
                        showDialog(TILT_TO_SCREEN_CONTROLS_DIALOG);
                    }
                } else if (lastVersion == 0) {
                    // show message about auto-selected control schemes.
                    showDialog(CONTROL_SETUP_DIALOG);
                }
            }
        }

        if (mBackground != null) {
            mBackground.clearAnimation();
        }

        if (mTicker != null) {
            mTicker.clearAnimation();
            mTicker.setAnimation(mFadeInAnimation);
        }

        if (mJustCreated) {
            if (mStartButton != null) {
                mStartButton
                        .startAnimation(AnimationUtils.loadAnimation(this, R.anim.button_slide));
            }
            if (mExtrasButton != null) {
                Animation anim = AnimationUtils.loadAnimation(this, R.anim.button_slide);
                anim.setStartOffset(500L);
                mExtrasButton.startAnimation(anim);
            }

            if (mOptionsButton != null) {
                Animation anim = AnimationUtils.loadAnimation(this, R.anim.button_slide);
                anim.setStartOffset(1000L);
                mOptionsButton.startAnimation(anim);
            }
            mJustCreated = false;

逐個顯示菜單按鈕。注意:mJustCreated決定了這個動作只會在MainMenuActivity創建時執行。

        } else {
            mStartButton.clearAnimation();
            mOptionsButton.clearAnimation();
            mExtrasButton.clearAnimation();
        }
    }

    @Override
    protected Dialog onCreateDialog(int id) {
        Dialog dialog;
        if (id == WHATS_NEW_DIALOG) {
            dialog =
                    new AlertDialog.Builder(this).setTitle(R.string.whats_new_dialog_title)
                            .setPositiveButton(R.string.whats_new_dialog_ok, null)
                            .setMessage(R.string.whats_new_dialog_message).create();

現實更新信息,即字符串資源R.string.whats_new_dialog_message,則任一新版本的更新都可以放到這個字符串資源中。

        } else if (id == TILT_TO_SCREEN_CONTROLS_DIALOG) {
            dialog =
                    new AlertDialog.Builder(this)
                            .setTitle(R.string.onscreen_tilt_dialog_title)
                            .setPositiveButton(R.string.onscreen_tilt_dialog_ok,
                                    new DialogInterface.OnClickListener() {

                                        public void onClick(DialogInterface dialog, int whichButton) {
                                            SharedPreferences prefs =
                                                    getSharedPreferences(
                                                            PreferenceConstants.PREFERENCE_NAME,
                                                            MODE_PRIVATE);
                                            SharedPreferences.Editor editor = prefs.edit();
                                            editor.putBoolean(
                                                    PreferenceConstants.PREFERENCE_SCREEN_CONTROLS,
                                                    true);
                                            editor.commit();
                                        }
                                    })
                            .setNegativeButton(R.string.onscreen_tilt_dialog_cancel, null)
                            .setMessage(R.string.onscreen_tilt_dialog_message).create();
        } else if (id == CONTROL_SETUP_DIALOG) {
            String messageFormat = getResources().getString(R.string.control_setup_dialog_message);
            String message = String.format(messageFormat, mSelectedControlsString);
            CharSequence sytledMessage = Html.fromHtml(message); // lame.
            dialog =
                    new AlertDialog.Builder(this)
                            .setTitle(R.string.control_setup_dialog_title)
                            .setPositiveButton(R.string.control_setup_dialog_ok, null)
                            .setNegativeButton(R.string.control_setup_dialog_change,
                                    new DialogInterface.OnClickListener() {

                                        public void onClick(DialogInterface dialog, int whichButton) {
                                            Intent i =
                                                    new Intent(getBaseContext(),
                                                            SetPreferencesActivity.class);
                                            i.putExtra("controlConfig", true);
                                            startActivity(i);
                                        }
                                    }).setMessage(sytledMessage).create();
        } else {
            dialog = super.onCreateDialog(id);
        }
        return dialog;
    }

    protected class StartActivityAfterAnimation implements Animation.AnimationListener {

        private Intent mIntent;

        StartActivityAfterAnimation(Intent intent) {
            mIntent = intent;
        }

        public void onAnimationEnd(Animation animation) {

            startActivity(mIntent);

            if (UIConstants.mOverridePendingTransition != null) {
                try {
                    UIConstants.mOverridePendingTransition.invoke(MainMenuActivity.this,
                            R.anim.activity_fade_in, R.anim.activity_fade_out);
                } catch (InvocationTargetException ite) {
                    DebugLog.d("Activity Transition", "Invocation Target Exception");
                } catch (IllegalAccessException ie) {
                    DebugLog.d("Activity Transition", "Illegal Access Exception");
                }
            }
        }

UIConstants類嘗試通過relect獲取Activity的overridePendingTransition方法,並記錄到成員變量mOverridePendingTransition中去,具體可以查看UIConstants類的代碼。

        public void onAnimationRepeat(Animation animation) {
            // TODO Auto-generated method stub
        }

        public void onAnimationStart(Animation animation) {
            // TODO Auto-generated method stub
        }
    }
}

AndouKun、DifficultyMenuActivity、ExtrasMenuActivity、SetPreferencesActivity

DifficultyMenuActivity、ExtrasMenuActivity的代碼結構與MainMenuActivity差不多,這裏就都不貼出來了。這些Activity裏的下列函數、子類的代碼都差不多,其實可以將它們提取到一個父類裏:

  • public boolean onKeyDown(int keyCode, KeyEvent event)
  • protected class StartActivityAfterAnimation implements Animation.AnimationListener

SetPreferencesActivity的代碼比較簡單,這裏也不貼出來了。

AndouKun這個Activity我會另起一篇來分析。

以下是這些Activity的調用關係:


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章