想把J2ME移植到Android平台上主要需要注意以下几个Android的新类,类的名字和J2ME下有点容易混淆。
1. Activity 相当于 J2ME 中的MIDlet
eg. public class Game extends Activity 相当于J2ME下面的 public class Game extends MIDlet
2. SurfaceView 相当于J2ME中的Canvas/GameCanvas
3. Canvas 相当于J2ME中的Graphics,所以在Android中,要在SurfaceView上画图,需要先取得Canvas的对象,这就好比在J2ME中先取得Graphics g然后把参数传给具体的画图函数画到Canvas上面
以下是Android的资源架构
Directory | Resource Types |
---|---|
res/anim/ | XML files that are compiled into frame by frame animation or tweened animation objects |
res/drawable/ | .png, .9.png, .jpg files that are compiled into the following Drawable resource subtypes: To get a resource of this type, use Note: Image resources placed in here may be automatically optimized with lossless image compression by the aapt tool. For example, a true-color PNG that does not require more than 256 colors may be converted to an 8-bit PNG with a color palette. This will result in an image of equal quality but which requires less memory. So be aware that the image binaries placed in this directory can change during the build. If you plan on reading an image as a bit stream in order to convert it to a bitmap, put your images in the |
res/layout/ | XML files that are compiled into screen layouts (or part of a screen). See Declaring Layout . |
res/values/ | XML files that can be compiled into many kinds of resource. Note: Unlike the other res/ folders, this one can hold any number of files that hold descriptions of resources to create rather than the resources themselves. The XML element types control where these resources are placed under the R class. While the files can be named anything, these are the typical files in this folder (the convention is to name the file after the type of elements defined within):
|
res/xml/ | Arbitrary XML files that are compiled and can be read at run time by calling Resources.getXML() . |
res/raw/ | Arbitrary files to copy directly to the device. They are added uncompiled to the compressed file that your application build produces. To use these resources in your application, call Resources.openRawResource() with the resource ID, which is R.raw.somefilename . |
Resources are compiled into the final APK file. Android creates a wrapper class, called R, that you can use to refer to these resources in your code. R contains subclasses named according to the path and file name of the source file
Note: Image resources placed in res/drawable/ may be automatically optimized with lossless image compression by the aapt tool. For example, a true-color PNG that does not require more than 256 colors may be converted to an 8-bit PNG with a color palette. This will result in an image of equal quality but which requires less memory. So be aware that the image binaries placed in this directory can change during the build. If you plan on reading an image as a bit stream in order to convert it to a bitmap, put your images in the res/raw/ folder instead, where they will not be optimized.
--------------转帖--------------------
To become familiar with mobile programming plattforms I've implemented a little game (a minesweeper clone) in J2ME. After having finished this I ported this game to Android.
While porting to Android I explored the following features:
- Rendering images via View.onDraw()
- Working with multiple Activities (Main Activity, Options Activity)
- Passing data from one Activity to another
- Handling Activity states (freeze, restore)
- Handling custom menues (onCreateOptionsMenu)
- etc.
It turned out that - as expected ;-) - separating program logic is a good thing. I used the same classes for the game logic.
Disclaimer: Please note, that my intention was to explore some features of the plattforms. There is still room for improvement as I've not implemented a timer, a highscore table etc..
Attached you can find the Eclipse (3.4) projects of both implementations:
The following sources are extracts from the Android implementation:
TrapMain.java
package at.lacherstorfer.trap.android;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
public class TrapMain extends Activity {
private TrapView trapView;
private static int EDIT_OPTIONS = 1;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.trap_main_layout);
trapView = (TrapView) findViewById(R.id.trap);
if (icicle != null) {
// We are being restored
Bundle map = icicle.getBundle("trapView");
if (map != null) {
trapView.restoreState(map);
}
}
trapView.doStart();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
// store game state
outState.putBundle("trapView", trapView.saveState());
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuItem menuItem = menu.add(0, 0, 0, "Start");
menuItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
trapView.doStart();
return true;
}
});
menuItem = menu.add(0, 0, 0, "Options");
menuItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
Intent optionsIntent = new Intent(TrapMain.this, TrapOptions.class);
Bundle extras = new Bundle();
extras.putInt("numberRows", trapView.getNumberRows());
extras.putInt("numberCols", trapView.getNumberCols());
extras.putInt("numberTraps", trapView.getNumberTraps());
optionsIntent.putExtras(extras);
startActivityForResult(optionsIntent, EDIT_OPTIONS);
return true;
}
});
return true;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
trapView.doStart(data.getIntExtra("numberRows", 8),
data.getIntExtra("numberCols", 8), data.getIntExtra("numberTraps", 8));
}
}
}
TrapView.java
package at.lacherstorfer.trap.android;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import at.lacherstorfer.trap.shared.Cell;
import at.lacherstorfer.trap.shared.Field;
public class TrapView extends View {
private Field field;
private int numberRows = 8;
private int numberCols = 8;
private int numberTraps = 8;
private int cursorX;
private int cursorY;
private Drawable cellClosedImage;
private Drawable cell0Image;
private Drawable cell1Image;
private Drawable cell2Image;
private Drawable cell3Image;
private Drawable cell4Image;
private Drawable cellBombImage;
private Drawable cellExplodedImage;
private Drawable cellFlaggedImage;
private Drawable cursorImage;
public TrapView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
loadImages(context);
}
public TrapView(Context context, AttributeSet attrs) {
super(context, attrs);
loadImages(context);
}
public TrapView(Context context) {
super(context);
loadImages(context);
}
public void loadImages(Context context) {
cellClosedImage = context.getResources().getDrawable(
R.drawable.cellclosed);
cell0Image = context.getResources().getDrawable(R.drawable.cell0);
cell1Image = context.getResources().getDrawable(R.drawable.cell1);
cell2Image = context.getResources().getDrawable(R.drawable.cell2);
cell3Image = context.getResources().getDrawable(R.drawable.cell3);
cell4Image = context.getResources().getDrawable(R.drawable.cell4);
cellBombImage = context.getResources().getDrawable(R.drawable.cellbomb);
cellExplodedImage = context.getResources().getDrawable(
R.drawable.cellexploded);
cellFlaggedImage = context.getResources().getDrawable(
R.drawable.cellflagged);
cursorImage = context.getResources().getDrawable(R.drawable.cursor);
setFocusable(true);
}
public Bundle saveState() {
Bundle map = new Bundle();
map.putInt("numberRows", Integer.valueOf(numberRows));
map.putInt("numberCols", Integer.valueOf(numberCols));
map.putInt("numberTraps", Integer.valueOf(numberTraps));
map.putInt("cursorX", Integer.valueOf(cursorX));
map.putInt("cursorY", Integer.valueOf(cursorY));
int[] fieldState = field.getState();
for (int i = 0; i < fieldState.length; i++) {
map.putInt("field-" + i, fieldState[i]);
}
return map;
}
public void restoreState(Bundle icicle) {
numberRows = icicle.getInt("numberRows");
numberCols = icicle.getInt("numberCols");
numberTraps = icicle.getInt("numberTraps");
cursorX = icicle.getInt("cursorX");
cursorY = icicle.getInt("cursorY");
int[] fieldState = new int[icicle.size() - 5];
for (int i = 0; i < fieldState.length; i++) {
fieldState[i] = icicle.getInt("field-" + i);
}
field = new Field(fieldState);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
Log.i("TrapView", "pressed key=" + keyCode);
boolean handled = false;
if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
cursorX -= cursorX > 0 ? 1 : 0;
handled = true;
} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
cursorX += cursorX < numberCols - 1 ? 1 : 0;
handled = true;
} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
cursorY -= cursorY > 0 ? 1 : 0;
handled = true;
} else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
cursorY += cursorY < numberRows - 1 ? 1 : 0;
handled = true;
} else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
|| keyCode == KeyEvent.KEYCODE_ENTER) {
fire();
handled = true;
} else if (keyCode == KeyEvent.KEYCODE_SPACE) {
flag();
handled = true;
}
if (handled) {
postInvalidate();
}
return handled;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean handled = false;
if (event.getAction() == MotionEvent.ACTION_DOWN) {
cursorX = (int) (event.getX() / 15);
cursorX = cursorX > numberCols ? numberCols - 1 : cursorX;
cursorY = (int) (event.getY() / 15);
cursorY = cursorY > numberRows ? numberRows - 1 : cursorY;
handled = true;
}
postInvalidate();
return handled;
}
private void fire() {
if (field == null)
return; // game not started yet
int cellValue = field.fireAt(cursorY, cursorX);
if (cellValue == Cell.TRAP) {
field.reveal();
Builder b = new AlertDialog.Builder(this.getContext());
b.setTitle("Trap");
b.setIcon(0);
b.setMessage("Game Over");
b.show();
} else if (field.isSolved()) {
Builder b = new AlertDialog.Builder(this.getContext());
b.setTitle("Trap");
b.setIcon(0);
b.setMessage("Congratulation, You Win!");
b.show();
}
}
private void flag() {
if (field == null)
return; // game not started yet
field.flagAt(cursorY, cursorX);
if (field.isSolved()) {
Builder b = new AlertDialog.Builder(this.getContext());
b.setTitle("Trap");
b.setIcon(0);
b.setMessage("Congratulation, You Win!");
b.show();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (field == null)
return; // game not started yet
// draw the field
for (int row = 0; row < numberRows; row++) {
for (int col = 0; col < numberCols; col++) {
Cell cell = field.getCellAt(row, col);
Drawable cellImage = getDrawable(cell);
cellImage.setBounds(/* left */col * 15, /* top */row * 15, /* right */
col * 15 + 15, /* bottom */row * 15 + 15);
cellImage.draw(canvas);
}
}
// draw the cursor
cursorImage.setBounds(cursorX * 15, cursorY * 15, cursorX * 15 + 15,
cursorY * 15 + 15);
cursorImage.draw(canvas);
}
public void doStart(int numberRows, int numberCols, int numberTraps) {
this.numberRows = numberRows;
this.numberCols = numberCols;
this.numberTraps = numberTraps;
field = new Field(numberRows, numberCols, numberTraps);
postInvalidate();
}
public void doStart() {
if (field == null) {
field = new Field(numberRows, numberCols, numberTraps);
} else {
field.restart();
}
postInvalidate();
}
private Drawable getDrawable(Cell cell) {
if (cell.isOpen()) {
switch (cell.getValue()) {
case Cell.TRAP:
return cellBombImage;
case 0:
return cell0Image;
case 1:
return cell1Image;
case 2:
return cell2Image;
case 3:
return cell3Image;
case 4:
return cell4Image;
}
} else if (cell.isClosed()) {
return cellClosedImage;
} else if (cell.isFlagged()) {
return cellFlaggedImage;
} else if (cell.isExploded()) {
return cellExplodedImage;
} else {
throw new RuntimeException("Internal Error");
}
return null;
}
public int getNumberRows() {
return numberRows;
}
public int getNumberCols() {
return numberCols;
}
public int getNumberTraps() {
return numberTraps;
}
}
TrapOptions.java
package at.lacherstorfer.trap.android;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.EditText;
public class TrapOptions extends Activity {
private EditText etNumberCols;
private EditText etNumberRows;
private EditText etNumberTraps;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.trap_options_layout);
etNumberRows = (EditText) findViewById(R.id.number_rows);
etNumberCols = (EditText) findViewById(R.id.number_cols);
etNumberTraps = (EditText) findViewById(R.id.number_traps);
Bundle extras = getIntent().getExtras();
etNumberRows.setText(""+extras.getInt("numberRows"));
etNumberCols.setText(""+extras.getInt("numberCols"));
etNumberTraps.setText(""+extras.getInt("numberTraps"));
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuItem menuItem = menu.add(0, 0, 0, "Ok");
menuItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
int numberRows = Integer.parseInt(etNumberRows.getText().toString());
int numberCols = Integer.parseInt(etNumberCols.getText().toString());
int numberTraps = Integer.parseInt(etNumberTraps.getText().toString());
Bundle b = new Bundle();
b.putInt("numberRows", numberRows);
b.putInt("numberCols", numberCols);
b.putInt("numberTraps", numberTraps);
Intent resultIntent = new Intent();
resultIntent.putExtras(b);
setResult(RESULT_OK, resultIntent);
finish();
return true;
}
});
menuItem = menu.add(0, 0, 0, "Cancel");
menuItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
setResult(RESULT_CANCELED);
finish();
return true;
}
});
return true;
}
}
trap_main_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<at.lacherstorfer.trap.android.TrapView
android:id="@+id/trap"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</FrameLayout>
trap_options_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TableRow>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Number Columns"/>
<EditText android:id="@+id/number_cols"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:maxLength="2" />
</TableRow>
<TableRow>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Number Rows"/>
<EditText android:id="@+id/number_rows"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLength="2" />
</TableRow>
<TableRow>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Number Traps"/>
<EditText android:id="@+id/number_traps"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:maxLength="2"/>
</TableRow>
</TableLayout>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="at.lacherstorfer.trap.android">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name="TrapMain" android:label="@string/app_name">
<intent-filter>
<action android:value="android.intent.action.MAIN"
android:name="android.intent.action.MAIN"/>
<category android:value="android.intent.category.LAUNCHER"
android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name="TrapOptions"
android:label="@string/app_name"/>
</application>
</manifest>