Android:确定代码是否应该驻留在活动或自定义视图中
正如我熟悉使用活动和自定义视图一样,我一直在不断地决定哪些代码与View和它的父Activity相关。 添加需要由两者访问的自定义对象,并且关于如何构造代码的选项是无止境的。 以下是我的问题的具体内容:
相关的类/文件:
GameActivity扩展活动:它使用的布局包含一些自定义视图。
MapView扩展视图:它包含在GameActivity使用的布局中
世界:定义自定义世界对象的自定义类
预期结果:
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中。 有一个世界对象的整个观点是能够传递它,对吗? 因此,我将世界建筑/装载从视图移到了活动中。 这导致必须在MapView中创建一个setter方法,以便能够从创建它的父活动中传递World对象。
版本3:
GameActivity:设置布局,创建一个World对象,并调用它的loadWorld()方法从文件中加载它。 通过Id引用MapView,然后调用MapView的setWorld()方法来传递World对象的实例
MapView:World对象从外部设置。 使用此对象的信息绘制地图
World:一个简单的自定义类,在它的构造函数中有3个参数,以及一个loadWorld()方法。
好的,这就是我目前所处的位置。 我的问题是,尽管我喜欢我在视图中只包含与绘制相关的代码并将类相关的方法保存在其自己的类中的约定,但似乎当我切换到该方法时,突然创建了更多临时对象经常和从活动到活动传递对象来查看等等。 这似乎有更多的开销,但在同一时间,这是抽象的整个对吧? 抽象出一个类,这样你就可以从中实例化一个对象并传递它。 然而,在我看来,抽象出来的东西越多,处理对象就越复杂。
我想我在这里要问的是,我是不是通过从MapView本身的文件中加载World来让事情变得更加复杂? 我只是固执地不想让代码涉及从View类中的文件读取对象? 在自己的班级或活动中使用它会更好吗? 做出这些决定时我需要考虑什么? 我没有意识到我的困境是否有解决方案?
我认为答案将会是个人偏好,但我想知道是否有惯例可以这样或那样的方式进行,或者如果有确定的理由来构建某种特定的方式。 我一直在试图寻找这个问题的答案,但我认为我使用了错误的搜索条件。 我一直在讨论如何构造活动/视图结构的东西,但没有涉及到代码。 当有代码时,不可避免地会有人教导如何在活动之间或活动与视图之间传递数据等。我知道所有这些方法。 我只是不知道使用哪一个。
我一直在看的一些链接:
具有多个视图的Android应用程序 - 最佳实践?
Android - 活动与视图
Android活动和观看
Android:有什么更好 - 多个活动或手动切换视图?
编辑:关于应用程序结构和代码示例的更多细节
GameActivity:
/*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!!!
}
}
MapView类:
/*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开始,该按钮有两个按钮,每个按钮可导致另一个活动: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对象。 我想知道是否有更清晰的方法去解决这个问题,并且仍然把事情放在有意义的类中。
我认为你的版本3比其他2个更好:你的模型(即World
)独立于你的视图( MapView
)和你的控制器( GameActivity
)将它们绑定在一起。
我认为你可以改进使用Factory模式创建World
对象的方式,以便创建它的作业位于单独的类中。 让我告诉你我的意思:
public class WorldFactory {
private File worldFile;
private String name = "default_world";
private int width = 1;
private int height = 1;
public static WorldFactory fromFile(File worldFile){
WorldFactory worldFactory = new WorldFactory();
worldFactory.worldFile = worldFile;
return worldFactory;
}
public WorldFactory withName(String name){
this.name= name;
return this;
}
public WorldFactory withWidth(int width){
this.parameter2 = param2;
return this;
}
public WorldFactory 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 = WorldFactory.fromFile(worldFile)
.withName(p1)
.withWidth(p2)
.withHeight(p3)
.build();
如果你需要用默认参数创建一个世界,你可以简单地写:
World world = WorldFactory.fromFile(null).build();
编辑
在编写代码的地方?
所有的计算代码都只能依赖World
数据才能写入World
类。 切勿将MapView
作为World
方法的参数传递(保持模型与视图无关)。
尽可能尝试以这种方式组织代码,以免在MapView
进行计算。 MapView
只能包含与显示直接相关的代码。
让你的世界级的单身人士与几个静态方法,将检索必要的信息。
这里有一个疯狂的例子:
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将重新使用该对象。 所以,基本上,您可以使用这个框架并根据需要添加尽可能多的模式“公共静态”方法。
链接地址: http://www.djcxy.com/p/11655.html上一篇: Android: determining if code should reside in activity or custom view