6

アクティビティとカスタム ビューの操作に慣れてきたので、ビューとその親アクティビティの両方に関連するコードをどこに配置するかを常に決めなければなりませんでした。両方からアクセスする必要があるカスタム オブジェクトを追加すると、コードを構築する方法のオプションは無限にあります。私の問題の詳細は次のとおりです。

関連するクラス/ファイル:
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 オブジェクトのロードは、MapView の onCreate() メソッドで行うのではなく、World クラス自体のメソッドにする必要があると判断しました。ワールド ファイルのロードは World オブジェクトなしでは決してできないことなので、それがクラス メソッドであることは理にかなっています。そこで、World クラスに loadWorld(String world_name) メソッドを作成しました。ファイルは次のようになりました。

Version2:
GameActivity : setContentLayout(layout_with_mapview_in_it.xml) だけです

MapView : コンストラクターを使用して新しい World オブジェクトを作成し、その loadWorld() メソッドを呼び出してファイル情報で更新します

World : コンストラクターに 3 つのパラメーターを持つ単純なカスタム クラスと、loadWorld() メソッド

最後に、MapView には描画アクティビティのみを含めることにしました。World オブジェクトを持つことの全体的なポイントは、それを渡すことができるということですよね? そこで、ワールドの構築/読み込みをビューからアクティビティに移動しました。このため、MapView でセッター メソッドを作成して、World オブジェクトが作成される親アクティビティから World オブジェクトを渡すことができるようにする必要がありました。

バージョン 3:
GameActivity : レイアウトを設定し、World オブジェクトを作成し、その loadWorld() メソッドを呼び出してファイルからロードします。Id で MapView を参照し、MapView の setWorld() メソッドを呼び出して、World オブジェクトのインスタンスを渡します。

MapView : ワールド オブジェクトは外部から設定されます。このオブジェクトからの情報を使用してマップを描画します

World : コンストラクターに 3 つのパラメーターを持つ単純なカスタム クラスと、loadWorld() メソッド。

わかりました、それが私が現在いるところです。私の問題は、ビューに描画関連のコードのみを含め、クラス関連のメソッドを独自のクラスに保持するという点で選択した規則が好きですが、そのメソッドに切り替えると、突然一時オブジェクトをさらに作成しているようです多くの場合、アクティビティからアクティビティ、ビューなどにオブジェクトを渡します。これはより多くのオーバーヘッドのように見えますが、同時にそれが抽象化の要点ですよね? クラスを抽象化して、そこからオブジェクトをインスタンス化し、それを渡すことができるようにします。しかし、どういうわけか、物事を抽象化すればするほど、オブジェクトの処理がより複雑になるように思えます。

私がここで尋ねようとしているのは、MapView 自体のファイルから World をロードしないだけで、物事をさらに複雑にしているのでしょうか? ビュークラスのファイルからオブジェクトを読み取るコードを持ちたくないという頑固なところですか? 独自のクラスまたはアクティビティに含める方が良いですか? これらの決定を行う際、何を考慮する必要がありますか? 私が気づいていないジレンマの解決策はありますか?

答えは個人的な好みになると思いますが、何らかの方法でそれを行う慣習があるかどうか、または特定の方法で構造化する確固たる理由があるかどうかを知りたいと思います. この質問に対する回答を検索しようとしましたが、間違った検索用語を使用しているようです。アクティビティ/ビュー構造を構築する方法を説明するものに出くわしますが、内部のコードについては何もありません。コードがある場合、それは必然的に、アクティビティ間、またはアクティビティとビューの間などでデータを渡す方法を誰かが教えていることになります。私はそれらすべての方法を知っています。ただ、どれを使えばいいのかわからない。

私が見ているリンクのいくつか:
複数のビューを持つ 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 で始まります。この MenuActivity には、それぞれ別のアクティビティ (WorldGenerationActivity または WorldSelectionActivity) につながる 2 つのボタンがあります。

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 に含まれるビューの 1 つです。その onDraw() メソッドでのログインには、World オブジェクトからの情報が必要です。以前はすべての World の読み込みと構築をここで行っていましたが、当時は、World を 1 回作成するだけで、それでやりたいことが何でもできました。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 では、次の 1 行のコードでワールドを作成できます。

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

そして、デフォルトのパラメーターでワールドを作成する必要がある場合は、次のように簡単に記述できます。

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

編集

コードを書く場所は?

クラスには、データのみに依存するすべての計算コードWorldを記述できますWorld。をメソッドの引数として渡さないでください(モデルをビューから独立させてください)。MapViewWorld

で計算が行われないようにコードを編成するように、できる限り試みてください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();
}

この実装では、World.getWorldDimensions() などの静的メソッド呼び出しを介して、Activity と View の両方から「世界」にアクセスできます。ワールド インスタンスはプライベートで静的です。また、遅延初期化により、「世界」について何かを要求したときに初めて作成および初期化されます。この例では、World.getWorldDimensions() を呼び出すと、getInstance() メソッドが World クラスのオブジェクトを作成し、その後の getWorldDimensions の呼び出しでオブジェクトが再利用されます。したがって、基本的には、このスケルトンを使用して、モードの "public static" メソッドを好きなだけ追加できます。

于 2013-01-03T09:10:13.123 に答える