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的调用关系:


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