【问题标题】:Random activity when button pressed按下按钮时的随机活动
【发布时间】:2020-08-16 21:09:11
【问题描述】:

我想做一个游戏,如果我按下播放按钮,随机关卡(活动)将打开。我得到了这个代码:https://stackoverflow.com/a/29579373/13101103 这是有效的,但我想编辑,例如,所有级别都有 2 个不同的答案,answer1 失败,answer2 通过级别,如果用户通过 level1,并且在 level2 失败,则返回 mainactivity,如果重新开始,则通过的关卡将不再显示。

示例: 有5个关卡,用户启动随机关卡,例如level3,它通过了,进入下一个随机关卡,例如level2,它通过,进入下一个... level4,它失败了,回到mainactivity,用户重新开始,但是已经通过的关卡不会显示,只会显示未通过的...示例开始level3...如果通过则转到level1....

如何为我的解决方案编辑此代码?有人可以给我一些提示吗?因为在这种情况下,如果我回到 mainactivity 并重新开始,那么它会从所有级别开始......我试图编辑,但我被卡住并且无法工作......

另外我想在用户离开应用程序时保存进度。在 sharedpreferences 中如何保存传递的级别(arraylist)......?

主活动:

enter code here

Button level1Button = findViewById(R.id.level1Button);
    level1Button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // We are creating a list, which will store the activities that haven't been opened yet
            ArrayList<Class> activityList = new ArrayList<>();
            activityList.add(Level1Activity.class);
            activityList.add(Level2Activity.class);
            activityList.add(Level3Activity.class);
            activityList.add(Level4Activity.class);
            activityList.add(Level5Activity.class);

            Random generator = new Random();
            int number = generator.nextInt(5) + 1;

            Class activity = null;

            // Here, we are checking to see what the output of the random was
            switch(number) {
                case 1:
                    activity = Level1Activity.class;
                    // We are adding the number of the activity to the list
                    activityList.remove(Level1Activity.class);
                    break;
                case 2:
                    activity = Level2Activity.class;
                    activityList.remove(Level2Activity.class);
                    break;
                case 3:
                    activity = Level3Activity.class;
                    activityList.remove(Level3Activity.class);
                    break;
                case 4:
                    activity = Level4Activity.class;
                    activityList.remove(Level4Activity.class);
                    break;
                default:
                    activity = Level5Activity.class;
                    activityList.remove(Level5Activity.class);
                    break;
            }
            // We use intents to start activities
            Intent intent = new Intent(getBaseContext(), activity);
            // `intent.putExtra(...)` is used to pass on extra information to the next activity
            intent.putExtra("ACTIVITY_LIST", activityList);
            startActivity(intent);
        }
    });

Level1Activity:

enter code here
failbutton1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v){
            ArrayList<Class> activityList = new ArrayList<>();
            activityList.add(Level1Activity.class);
            Bundle extras = getIntent().getExtras();
            activityList = (ArrayList<Class>) extras.get("ACTIVITY_LIST");


            //Class activity = null;


            Intent intent = new Intent(Level1Activity.this, Main2Activity.class);
            intent.putExtra("ACTIVITY_LIST", activityList);
            startActivity(intent);
        }
    });

    buttonlevel1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            ArrayList<Class> activityList = new ArrayList<>();
            Bundle extras = getIntent().getExtras();
            activityList = (ArrayList<Class>) extras.get("ACTIVITY_LIST");

            if(activityList.size() == 0) {
                // Do something when after all activities have been opened
                //startActivity(new Intent(Level1Activity.this, Main2Activity.class));

                //Intent intent = new Intent(Level1Activity.this, Main2Activity.class);
                //intent.putExtra("ACTIVITY_LIST", activityList);
                //startActivity(intent);
            } else {
                // Now, the random number is generated between 1 and however many
                // activities we have remaining
                Random generator = new Random();
                int number = generator.nextInt(activityList.size()) + 1;

                Class activity = null;

                // Here, we are checking to see what the output of the random was
                switch(number) {
                    case 1:
                        // We will open the first remaining activity of the list
                        activity = activityList.get(0);
                        // We will now remove that activity from the list
                        activityList.remove(0);
                        break;
                    case 2:
                        // We will open the second remaining activity of the list
                        activity = activityList.get(1);
                        activityList.remove(1);
                        break;
                    case 3:
                        // We will open the third remaining activity of the list
                        activity = activityList.get(2);
                        activityList.remove(2);
                        break;
                    case 4:
                        // We will open the fourth remaining activity of the list
                        activity = activityList.get(3);
                        activityList.remove(3);
                        break;
                    default:
                        // We will open the fifth remaining activity of the list
                        activity = activityList.get(4);
                        activityList.remove(4);
                        break;
                }

                // Note: in the above, we might not have 3 remaining activities, for example,
                // but it doesn't matter because that case wouldn't be called anyway,
                // as we have already decided that the number would be between 1 and the number of
                // activities left.


                // Starting the activity, and passing on the remaining number of activities
                // to the next one that is opened
                Intent intent = new Intent(getBaseContext(), activity);
                intent.putExtra("ACTIVITY_LIST", activityList);
                startActivity(intent);
            }
        }
    });

level2, level3.... 相同只是 id-s 不同

【问题讨论】:

    标签: java android random android-activity


    【解决方案1】:

    我建议使用Singleton pattern 来处理活动之间的数据传递。

    您可以通过意图的putExtra()SharedPreferences 传递列表,但使用Singleton 类,操作数据看起来更好、更容易,因为它们是封装的。在您想要保存关卡状态的情况下(例如,当它们已经完成时)。

    但是,如果您真的坚持使用SharedPreferences 来保存列表,那么我建议您使用Gson 将其转换为Json。 (请查看下面我的回答,了解如何实现。)

    正如我所说,我会使用Singleton 模式来避免创建不必要的样板代码并封装关卡的状态。

    LevelManager 类 (单例)

    final class LevelManager {
    
        // constants
        private static final String LEVELS_SHARED_PREFERENCES_NAME = "app_name.LEVELS";
    
        // variables
        private static LevelManager instance;
        private List<Class> levels;
        private SharedPreferences sharedPreferences;
    
        private LevelManager(Context context) {
            sharedPreferences =
                    context.getSharedPreferences(LEVELS_SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
            levels = new ArrayList<>();
            initializeList();
        }
    
        private void initializeList() {
            // Initialize levels, ie. add levels that are not yet completed/passed
            // Check in SharedPreferences if level has already been completed
            boolean alreadyPassed;
    
            alreadyPassed = sharedPreferences.getBoolean(Level1Activity.class.getSimpleName(), false);
            if (!alreadyPassed) levels.add(Level1Activity.class);
    
            alreadyPassed = sharedPreferences.getBoolean(Level2Activity.class.getSimpleName(), false);
            if (!alreadyPassed) levels.add(Level2Activity.class);
    
            alreadyPassed = sharedPreferences.getBoolean(Level3Activity.class.getSimpleName(), false);
            if (!alreadyPassed) levels.add(Level3Activity.class);
    
            alreadyPassed = sharedPreferences.getBoolean(Level4Activity.class.getSimpleName(), false);
            if (!alreadyPassed) levels.add(Level4Activity.class);
    
            alreadyPassed = sharedPreferences.getBoolean(Level5Activity.class.getSimpleName(), false);
            if (!alreadyPassed) levels.add(Level5Activity.class);
        }
    
        static LevelManager getInstance(Context context) {
            if (instance == null) {
                instance = new LevelManager(context);
            }
            return instance;
        }
    
        Class getRandomLevel() {
            if (levels.isEmpty()) {
                return null; // Return null if all levels are already completed
            }
            Collections.shuffle(levels);
            return levels.get(0);
        }
    
        void saveLevelState(Class levelClass, boolean passed) {
            sharedPreferences.edit().putBoolean(levelClass.getSimpleName(), passed).apply();
            if (passed) {
                // Remove level from list if user passed it so that it won't
                // be included in next levels
                levels.remove(levelClass);
            }
        }
    
        void reset() {
            // Clears all entries in SharedPreferences and re-initialize list
            sharedPreferences.edit().clear().apply();
            initializeList();
        }
    
    }
    


    MainActivity 中的 onCreate 内部

    // Get LevelManager singleton instance
    final LevelManager levelManager = LevelManager.getInstance(this);
    
    Button startButton = findViewById(R.id.startButton);
    startButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            // Get next random level
            Class levelToStart = levelManager.getRandomLevel();
    
            // If all levels are already completed
            if (levelToStart == null) {
                Toast.makeText(MainActivity.this, "All levels are completed!",
                        Toast.LENGTH_LONG).show();
                return;
            }
    
            Intent intent = new Intent(MainActivity.this, levelToStart);
            startActivity(intent);
        }
    });
    
    // I added a new button to reset all levels
    Button resetButton = findViewById(R.id.resetButton);
    resetButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            // Use the method reset() from LevelManager to restart everything
            levelManager.reset();
            Toast.makeText(MainActivity.this, "All levels have been reset!",
                    Toast.LENGTH_LONG).show();
        }
    });
    


    每个关卡活动的 onCreate 内部

    // Get LevelManager
    final LevelManager levelManager = LevelManager.getInstance(this);
    
    // I created two buttons to simulate pass and fail
    Button pass = findViewById(R.id.passButton);
    Button fail = findViewById(R.id.failButton);
    
    pass.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            // Save state (Don't forget to change 'N' below)
            levelManager.saveLevelState(LevelNActivity.class, true);
    
            // Get next level
            Class levelToStart = levelManager.getRandomLevel();
    
            // Check if all are levels already completed
            if (levelToStart == null) {
                Toast.makeText(LevelNActivity.this, "Completed all levels",
                        Toast.LENGTH_LONG).show();
                finish(); // Must implement to avoid going back to previous level (ie. Activity)
                return;
            }
    
            Intent intent = new Intent(LevelNActivity.this, levelToStart);
            startActivity(intent);
            finish();
        }
    });
    
    fail.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            finish();
        }
    });
    


    如您所见,如果用户未通过该级别,您可以简单地使用finish() 方法,而您使用下面的代码进入下一个级别:

    // Get LevelManager
    LevelManager levelManager = LevelManager.getInstance(this);
    
    // Set that the user passed this level (Change 'N' to the current level we are in)
    levelManager.saveLevelState(LevelNActivity.class, true);
    
    // Get next level
    Class nextLevel = levelManager.getRandomLevel();
    
    // If all levels are completed then 'nextLevel' will be null
    if (nextLevel == null) {
        // ...
    }
    
    // Start next level and finish current
    Intent intent = new Intent(this, nextLevel);
    startActivity(intent);
    finish();
    

    注意:为避免在开始下一个关卡时显式调用finish(),您可以将android:noHistory="true" 放在清单文件中关卡的活动标签中。



    如何通过使用 Gson 将列表转换为 Json 来将列表保存到 SharedPreferences

    要实际使用 Gson,您必须在应用的 gradle 依赖项中添加 implementation 'com.google.code.gson:gson:2.8.6'

    另外,在将Class 对象解析为Json 时,Gson 出现问题:您需要为这些对象创建自己的 serializerdeserializer将其注册到您的GsonBuilder

    ClassAdapter 类 (这是我们为 Class 对象创建自己的自定义序列化器和反序列化器的地方)

    public class ClassAdapter implements JsonSerializer<Class>, JsonDeserializer<Class> {
    
        @Override
        public JsonElement serialize(Class src, Type typeOfSrc, JsonSerializationContext context) {
            // Get our class 'src' name
            return new JsonPrimitive(src.getName());
        }
    
        @Override
        public Class deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            try {
                // Get class
                return Class.forName(json.getAsString());
            } catch (ClassNotFoundException e) {
                // If class could not be found or did not exists, handle error here...
                e.printStackTrace();
            }
            return null;
        }
    
    }
    

    下面是使用Json 使用Gson 将列表保存到SharedPreferences 的示例:

    // Create new GsonBuilder and register our adapter for Class objects
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(Class.class, new ClassAdapter());
    
    // Initialize our list of levels (ie. classes)
    List<Class> classes = new ArrayList<>();
    classes.add(Level1Activity.class);
    classes.add(Level2Activity.class);
    classes.add(Level3Activity.class);
    classes.add(Level4Activity.class);
    classes.add(Level5Activity.class);
    
    // Create Gson from GsonBuilder and convert list to json
    Gson gson = gsonBuilder.create();
    String json = gson.toJson(classes);
    
    // Save json to SharedPreferences
    SharedPreferences sharedPreferences = getSharedPreferences("app_name", MODE_PRIVATE);
    sharedPreferences.edit().putString("levels", json).apply();
    

    然后检索列表:

    // Retrieve json from SharedPreferences
    SharedPreferences sharedPreferences = getSharedPreferences("app_name", MODE_PRIVATE);
    String json = sharedPreferences.getString("levels", null);
    
    // Handle here if json doesn't exist yet
    if (json == null) {
        // ...
    }
    
    // Create new GsonBuilder and register our adapter for Class objects
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(Class.class, new ClassAdapter());
    
    // Create Gson from GsonBuilder and specify type of list
    Gson gson = gsonBuilder.create();
    Type type = new TypeToken<ArrayList<Class>>(){}.getType();
    
    // Convert json to list
    List<Class> classes = gson.fromJson(json, type);
    

    我希望您获得了解决此问题的宝贵建议!一如既往,祝您编码愉快!

    【讨论】:

    • 谢谢,我会努力的
    • 好的,我测试了它,运行良好,谢谢。顺便说一句,它保存了关卡、完成的关卡等,但没有实现“如何通过使用 Gson 将其转换为 Json 来将列表保存到 SharedPreferences”部分。没有 Gson 等...谢谢
    • 如果您想转换您的列表,我包含了 “保存列表到共享首选项” 部分(因为这似乎是您最初的计划)。无论如何,我真的很高兴它对你有用。
    • 谢谢,我也试试这个。你帮了我很多。另外我学到了很多:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-08
    • 1970-01-01
    • 2011-03-18
    • 1970-01-01
    • 2011-03-17
    • 1970-01-01
    相关资源
    最近更新 更多