6

随着我对活动和自定义视图的使用越来越熟悉,我一直不得不决定与视图及其父 Activity 相关的代码应该放在哪里。添加需要两者访问的自定义对象,以及如何构造代码的选项是无穷无尽的。这是我的问题的具体情况:

相关类/文件:
GameActivity扩展了 Activity:它使用的布局包含一些自定义视图。

MapView extends View:这包含在 GameActivity 使用的布局中

World:定义自定义 World 对象的自定义类

预期结果:
GameActivity 拉起包含 MapView 的布局。MapView 的 onDraw() 函数使用来自 World 对象的信息在画布上绘制地图。

问题:
MapView 需要的 World 对象是从以前保存的文件中加载的。我可以通过多种不同的方式将该对象获取到 MapView,这就是我优柔寡断的地方。我经历了以下迭代。(注意,它们都有效。我正在寻找的是使用一种方式而不是另一种方式的理由。)

版本 1 :
GameActivity:它所做的只是 setContentLayout(layout_with_mapview_in_it.xml)

MapView:具有从文件加载 World 对象的所有代码,并使用它来引用所需的绘图参数

World:一个简单的自定义类,其构造函数中有 3 个参数

然后我决定从文件中加载 World 对象应该是 World 类本身的一个方法,而不是 MapView 的 onCreate() 方法中的某个方法。由于加载世界文件是没有世界对象的事情,所以它应该是类方法是有道理的。所以我在 World 类中创建了一个 loadWorld(String world_name) 方法。现在文件看起来像这样:

版本2 :
GameActivity:它所做的只是 setContentLayout(layout_with_mapview_in_it.xml)

MapView:使用构造函数创建一个新的 World 对象,然后调用它的 loadWorld() 方法以使用文件信息更新它

World:一个简单的自定义类,在它的构造函数中带有 3 个参数,以及一个 loadWorld() 方法

最后,我决定只有绘图活动应该在 MapView 中。拥有 World 对象的全部意义在于能够传递它,对吗?因此,我将 World 构建/加载从 View 移到了 Activity 中。这导致必须在 MapView 中创建一个 setter 方法,以便能够从创建它的父活动传递 World 对象。

Version3:
GameActivity : 设置布局,创建一个 World 对象并调用它的 loadWorld() 方法从文件中加载它。通过 Id 引用 MapView,然后调用 MapView 的 setWorld() 方法将 World 对象的实例传递过来

MapView:世界对象是从外部设置的。使用此对象的信息绘制地图

World:一个简单的自定义类,在它的构造函数中带有 3 个参数,以及一个 loadWorld() 方法。

好的,这就是我目前所在的位置。我的问题是,虽然我喜欢我选择的约定,即视图仅包含绘图相关代码,并将与类相关的方法保留在自己的类中 - 似乎当我切换到该方法时,我突然更多地创建了临时对象经常将对象从活动传递到活动以查看等等。这似乎是更多的开销,但同时这就是抽象的全部意义,对吧?抽象出一个类,以便您可以从中实例化一个对象并传递它。然而不知何故,在我看来,我越抽象事物,处理对象就越复杂。

我想我想在这里问的是,我是否通过不从 MapView 本身的文件中加载世界而使事情变得更加复杂?我是否只是固执地不想拥有涉及从 View 类中的文件中读取对象的代码?将它放在自己的班级或活动中会更好吗?在做出这些决定时我需要考虑什么?有没有我什至不知道的困境的解决方案?

我认为答案将取决于个人喜好,但我想知道是否有惯例可以以一种或另一种方式做到这一点,或者是否有充分的理由来构建某种方式。我一直在尝试寻找这个问题的答案,但我认为我使用了错误的搜索词。我经常遇到描述如何构建活动/视图结构的东西,但对里面的代码一无所知。当有代码时,不可避免地有人教如何在活动之间传递数据,或者在活动和视图之间传递数据等。我知道所有这些方法。我只是不知道该用哪一个。

我一直在查看的一些链接:
具有多个视图的 Android 应用程序 - 最佳实践?
Android - 活动与视图
Android 活动和视图
Android:哪个更好 - 多个活动或手动切换视图?

编辑:包含有关应用程序结构和代码示例的更多详细信息

游戏活动:

/*IMPORT STATEMENTS REMOVED*/

public class GameActivity extends Activity implements OnTouchListener {

@Override
protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    setContentView(R.layout.game);  
    Log.d("LOGCAT", "GameActivity Started");

    //get the world_name from MenuActivity
    Intent intent = getIntent();
    String world_name = intent.getStringExtra(MenuActivity.EXTRA_MESSAGE);

    //load the world
    World world = loadWorld(world_name);

    //create a tilemap and get the tile translator array from it 
            //need to convert this to a static Map
    TileMap tileMap = new TileMap(this);    
    Map<Integer,Bitmap> tileTranslator = tileMap.getTileTranslator();

    //Create a reference to the MapView object and set the translator
    MapView mapView = (MapView) findViewById(R.id.map_view);
    mapView.setArgs(world, tileTranslator);

    //implement the OnTouchSwipeListener
    mapView.setOnTouchListener(new OnSwipeTouchListener() {

            /*CODE REMOVED*/
    });

}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
    case android.R.id.home:
        NavUtils.navigateUpFromSameTask(this);
        return true;
    }
    return super.onOptionsItemSelected(item);
}

public World loadWorld(String world_name) {

//Create a dummy world to load into - why?!
    World dummy_world = new World();

    //load the world 
    Log.d("LOGCAT", "Loading the World");
    try {
        World world = dummy_world.loadWorld(this, world_name);
        return world;
    } catch (IOException e) {
    //do nothing!
    } catch (ClassNotFoundException f) {
    //do nothing!
    }

    return dummy_world; //if world load fails, send back the default world
                        // NOTE: it's not saved!!!

}

}

地图视图:

/*IMPORT STATEMENTS REMOVED*/

public class MapView extends View implements OnClickListener {

protected Context context;
public World world;
public Map<Integer,Bitmap> tileTranslator;

    //hardcoded variables for testing
private int tile_width = 50;
private int tile_height = 50;
public int screen_width = 12;
public int screen_height = 6;
public int playerX = 4;
public int playerY = 7;


public MapView(Context context, AttributeSet attrs) {

    super(context, attrs);
    this.context = context;
    Log.d("LOGCAT", "MapView created"); 

    setOnClickListener(this);

}

@Override
public void onDraw(Canvas canvas) {

        /*CODE REMOVED*/
    }

//ugly method, need to break it out into individual setters
public void setArgs(World world, Map<Integer,Bitmap> tileTranslator){

    this.world = world;
    this.tileTranslator = tileTranslator;

}

}

世界:

/*IMPORT STATEMENTS REMOVED*/

public class World implements Serializable {

public String world_name;
public int world_width;
public int world_height;
public int[][] world_map;

public World() { //default world - I don't even want this constructor here!

    world_name = "default_world";
    world_width = 1;
    world_height = 1;

    world_map = createWorld(world_width, world_height);

}

public World(String world_name, int world_width, int world_height) {

    //set the world attributes
    this.world_name = world_name;
    this.world_width = world_width;
    this.world_height = world_height;

    //generate the map
    world_map = createWorld(world_width, world_height);

}

private int[][] createWorld(int world_width, int world_height) {

    //create a local tile map 
    int[][] world_map = new int[world_width][world_height];

    //get a randomizer to fill the array with - {temporary solution}
    Random rand = new Random();

    //fill the tile map array with random numbers between 0 and 2
    for(int row = 0; row < world_map.length; row++) {
        for (int col = 0; col < world_map[row].length; col++) {
            world_map[row][col] = rand.nextInt(3);  //static number, needs variable!
                                                    //3 is the number of tile types
        }
    }

    return world_map;

}   

public void saveWorld(Context context, String world_name, World world) throws IOException {

    FileOutputStream fos = context.openFileOutput(world_name, Context.MODE_PRIVATE);
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    oos.writeObject(world);
    oos.close();

}

public World loadWorld(Context context, String world_name) throws IOException, ClassNotFoundException {

    FileInputStream fis = context.openFileInput(world_name);
    ObjectInputStream ois = new ObjectInputStream(fis);
    World world = (World)ois.readObject();

    /*this.world_name = world.world_name;
    this.world_width = world.world_width;
    this.world_height = world.world_height;
    this.world_map = world.world_map;*/  //why doesn't this work?
    return world;

}

}

删除了一些代码以节省空间。让我知道是否有任何已删除的代码或其他活动对查看有用。

有关幕后发生的事情的更多详细信息:

该应用程序从一个 MenuActivity 开始,它有 2 个按钮,每个按钮都指向另一个 Activity:WorldGenerationActivity 或 WorldSelectionActivity。

在 WorldGenerationActivity 中,向用户显示了一个 TextEdit 屏幕和一个按钮。他们为想要生成的世界输入参数(world_name、world_width、world_height)。一旦他们单击按钮,就会创建一个 World 对象并将其保存到使用给定 world_name 作为文件名的文件中。文件保存是通过 World 类中可用的 saveWorld(String world_name) 方法完成的。新创建的 World 对象的实例调用了它的 saveWorld() 方法,文件被保存,并且用户通过调用 finish() 被踢回父 MenuActivity。

在 WorldSelectionActivity 中,向用户显示了一个插入 ArrayAdapter 的 ListView。从世界保存目录中包含的文件创建文件名数组,适配器使列表视图能够在列表中显示这些文件名。用户选择一个,然后通过intent.putExtra() 将选择作为字符串发送回parentMenuActivity。WorldSelectionActivity 为结果而启动,所以它结束,我们回到 MenuActivity。

一旦 MenuActivity 从 WorldSelectionActivity 获得结果,它会将 putExtra() 消息存储在一个参数中,然后调用 GameActivity。它通过另一个 putExtra() 将消息发送到 GameActivity。

GameActivity 接收消息并将其存储在名为 world_name 的变量中。然后它创建一个 World 对象并将 world_name 字符串传递给 World 类的 loadWorld(String world_name) 方法,该方法会加载用户之前从以前的文件保存中请求的特定 World。在我之前的解释中,我有点掩饰了这是如何处理的。由于我需要一个 World 对象来加载世界,我不得不首先在 GameActivity 中创建一个虚拟的 World 对象,然后调用它的 loadWorld 方法,并将结果传递给另一个新创建的 World 对象。这导致我不得不在我不想要的 World 类中包含一个没有参数的构造函数。我不确定为什么不先创建一个虚拟世界就无法让它工作。我尝试将文件读取逻辑放入无参数构造函数中,但这似乎也不起作用。我想我在这里遗漏了一些东西,但这不是我目前最关心的问题。

MapView 是 GameView 中包含的视图之一。其 onDraw() 方法中的登录需要来自 World 对象的信息。我曾经在这里完成所有世界的加载和构建,那时我只需要创建一个世界,然后用它做任何我想做的事情。一旦我将 loadWorld() 方法从 MapView 移到 World 类本身,并将该方法的调用从 MapView 移到 GameActivity 中,我似乎突然在各处创建了临时 World 对象。我想知道是否有一种更清洁的方法来解决这个问题,并且仍然将事情保留在有意义的类中。

4

2 回答 2

4

我认为您的版本 3确实比其他版本 2 更好:您的模型(即World)独立于您的视图(MapView)并且您的控制器(GameActivity)将它们绑定在一起。

我认为您可以改进World使用 Builder 模式创建对象的方式,以便创建它的工作在一个单独的类中。让我告诉你我的意思:

public class WorldBuilder {

    private File worldFile;
    private String name = "default_world";
    private int width = 1;
    private int height = 1;

    public static WorldBuilder fromFile(File worldFile){
        WorldBuilder worldBuilder = new WorldBuilder();
        worldBuilder.worldFile = worldFile;
        return worldBuilder;
    }

    public WorldBuilder withName(String name){
        this.name= name;
        return this;
    }

    public WorldBuilder withWidth(int width){
        this.parameter2 = param2;
        return this;
    }

    public WorldBuilder withHeight(int height){
        this.height = height;
        return this;
    }

    public World build(){
        World world = new World(name,width,height);
        if(worldFile!=null)
            world.loadWorld(worldFile);
        return world;
    }    
}

在 GameActivity 中,您可以使用这行代码创建世界:

World world = WorldBuilder.fromFile(worldFile)
        .withName(p1)
        .withWidth(p2)
        .withHeight(p3)
        .build();

如果您需要使用默认参数创建一个世界,您可以简单地编写:

World world = WorldBuilder.fromFile(null).build();

编辑

在哪里写代码?

除了只依赖于数据的所有计算代码World都可以写在World类中。永远不要将其MapView作为World方法的参数传递(保持模型独立于视图)。

尽可能尝试以不使用MapView. MapView必须只包含与显示直接相关的代码。

于 2013-01-03T09:42:17.390 回答
-1

使用几个静态方法来检索必要的信息,让你的世界级成为一个单例。

这是一些疯狂的例子:

public class World {
    private Rect mWorldAABB;
    private static World sInstance = null;
    private World() {
    // Put your code to read whatever you want from file
    };

    private static World getInstance() {
        if (sInstance == null) {
             sInstance = new World();
        }
         return sInstance;
    }

    private Rect getWorldRect() {
        return mWorldAABB;
    }
    public static Rect getWorldDimensions() {
        return getInstance().getWorldRect();
}

在这个实现中,您可以通过静态方法调用 a-la World.getWorldDimensions() 从 Activity 和 View 访问您的“世界”。世界实例是私有的和静态的。此外,由于延迟初始化,它只会在您第一次请求有关“世界”的任何内容时创建和初始化。在此示例中,一旦调用 World.getWorldDimensions(),getInstance() 方法将创建 World 类的对象,随后对 getWorldDimensions 的任何调用都将重新使用该对象。因此,基本上,您可以使用此框架并添加任意数量的模式“公共静态”方法。

于 2013-01-03T09:10:13.123 回答