随着我对活动和自定义视图的使用越来越熟悉,我一直不得不决定与视图及其父 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 对象。我想知道是否有一种更清洁的方法来解决这个问题,并且仍然将事情保留在有意义的类中。