Android:确定代码是否应驻留在活动或自定义视图中

clu*_*lux 6 android

由于我已经熟悉使用活动和自定义视图,我一直不得不决定哪个代码与View和它的父Activity相关.添加两者都需要访问的自定义对象,并且有关如何构造代码的选项是无穷无尽的.这是我的问题的具体细节:

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

MapView扩展了View:它包含在GameActivity使用的布局中

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

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

问题:
MapView需要的World对象是从以前保存的文件加载的.我可以通过多种不同的方式将该对象传递给MapView,这就是我变得优柔寡断的地方.我经历了以下迭代.(注意,它们都有效.我正在寻找的是单向使用另一种方式的原因.)

Version1:
GameActivity:它所做的就是setContentLayout(layout_with_mapview_in_it.xml)

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

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

然后我决定从文件加载World对象应该是World类本身的一个方法,而不是在MapView的onCreate()方法中完成的方法.因为加载一个世界文件是你永远不会没有World对象的东西,所以它应该是class方法是有意义的.所以我在World类中创建了一个loadWorld(String world_name)方法.现在文件看起来像这样:

Version2:
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对象是从外部设置的.使用此对象的信息绘制地图

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!!!

}

}
Run Code Online (Sandbox Code Playgroud)

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;

}

}
Run Code Online (Sandbox Code Playgroud)

世界:

/*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;

}

}
Run Code Online (Sandbox Code Playgroud)

删除了一些代码以节省空间.如果有任何已删除的代码或其他活动对我们有用,请与我们联系.

关于幕后发生的事情的更多细节:

该应用程序以MenuActivity开头,它有2个按钮,每个按钮都指向另一个活动:WorldGenerationActivity或WorldSelectionActivity.

在WorldGenerationActivity中,向用户显示TextEdit屏幕和按钮.他们输入他们想要生成的世界的参数(world_name,world_width,world_height).单击按钮后,将使用给定的world_name作为文件名创建World对象并将其保存到文件中.文件保存是通过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对象.我想知道是否有一种更清洁的方法可以解决这个问题,并且仍然保留在有意义的课程中.

ben*_*n75 4

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

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

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;
    }    
}
Run Code Online (Sandbox Code Playgroud)

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

World world = WorldBuilder.fromFile(worldFile)
        .withName(p1)
        .withWidth(p2)
        .withHeight(p3)
        .build();
Run Code Online (Sandbox Code Playgroud)

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

World world = WorldBuilder.fromFile(null).build();
Run Code Online (Sandbox Code Playgroud)

编辑

代码写在哪里?

所有仅依赖于数据的计算代码都World可以编写在World类中。切勿MapView将作为方法的参数传递World(保持模型独立于视图)。

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