Android apps浅析01-Amazed:一个简单但令人上瘾的加速度为基础的大理石指导游戏。
这个例子中只有4个类,一个绘制大理石类Marble,一个绘制迷宫类Maze,一个Amazed视图类,一个Amazed活动类
1. 绘制大理石类Marble通过Canvas和Paint绘制,同时提供移动x轴和y轴坐标的方法,每个大理石都有一个状态值:活的/死的
/*
* Copyright (C) 2008 Jason Tomlinson.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.amazed;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.View;
/**
* Marble drawn in the maze.迷宫中的大理石绘制
*/
public class Marble {
// View controlling the marble.
private View mView;
// marble attributes
// x,y are private because we need boundary checking on any new values to
// make sure they are valid.
private int mX = 0;
private int mY = 0;
private int mRadius = 8;
private int mColor = Color.WHITE;
private int mLives = 5;
/**
* Marble constructor.
*
* @param view
* View controlling the marble
*/
public Marble(View view) {
this.mView = view;
init();
}
/**
* Setup marble starting co-ords.
*/
public void init() {
mX = mRadius * 6;
mY = mRadius * 6;
}
/**
* Draw the marble.
*
* @param canvas
* Canvas object to draw too.
* @param paint
* Paint object used to draw with.
*/
public void draw(Canvas canvas, Paint paint) {
paint.setColor(mColor);
canvas.drawCircle(mX, mY, mRadius, paint);
}
/**
* Attempt to update the marble with a new x value, boundary checking
* enabled to make sure the new co-ordinate is valid.
*
* @param newX
* Incremental value to add onto current x co-ordinate.
*/
public void updateX(float newX) {
mX += newX;
// boundary checking, don't want the marble rolling off-screen.
if (mX + mRadius >= mView.getWidth())
mX = mView.getWidth() - mRadius;
else if (mX - mRadius < 0)
mX = mRadius;
}
/**
* Attempt to update the marble with a new y value, boundary checking
* enabled to make sure the new co-ordinate is valid.
*
* @param newY
* Incremental value to add onto current y co-ordinate.
*/
public void updateY(float newY) {
mY -= newY;
// boundary checking, don't want the marble rolling off-screen.
if (mY + mRadius >= mView.getHeight())
mY = mView.getHeight() - mRadius;
else if (mY - mRadius < 0)
mY = mRadius;
}
/**
* Marble has died
*/
public void death() {
mLives--;
}
/**
* Set the number of lives for the marble
*
* @param Number
* of lives
*/
public void setLives(int val) {
mLives = val;
}
/**
* @return Number of lives left
*/
public int getLives() {
return mLives;
}
/**
* @return Current x co-ordinate.
*/
public int getX() {
return mX;
}
/**
* @return Current y co-ordinate.
*/
public int getY() {
return mY;
}
}
2. 绘制迷宫类Maze绘制一个20列26行的长方形,提供加载背景和数据以及等级的方法,同样通过Canvas和Paint绘制,另外需要提供(x,y)坐标上的大理石数据是活的还是死的
/*
* Copyright (C) 2008 Jason Tomlinson.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.amazed;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.Log;
/**
* Maze drawn on screen, each new level is loaded once the previous level has
* been completed.屏幕上迷宫绘制,一旦前一个级别完成后,每个新级别会被加载。
*/
public class Maze {
// maze tile size and dimension
private final static int TILE_SIZE = 16;
private final static int MAZE_COLS = 20;
private final static int MAZE_ROWS = 26;
// tile types
public final static int PATH_TILE = 0;
public final static int VOID_TILE = 1;
public final static int EXIT_TILE = 2;
// tile colors
private final static int VOID_COLOR = Color.BLACK;
// maze level data
private static int[] mMazeData;
// number of level
public final static int MAX_LEVELS = 10;
// current tile attributes
private Rect mRect = new Rect();
private int mRow;
private int mCol;
private int mX;
private int mY;
// tile bitmaps
private Bitmap mImgPath;
private Bitmap mImgExit;
/**
* Maze constructor.
*
* @param context
* Application context used to load images.
*/
Maze(Activity activity) {
// load bitmaps.
mImgPath = BitmapFactory.decodeResource(activity.getApplicationContext().getResources(),
R.drawable.path);
mImgExit = BitmapFactory.decodeResource(activity.getApplicationContext().getResources(),
R.drawable.exit);
}
/**
* Load specified maze level.
*
* @param activity
* Activity controlled the maze, we use this load the level data
* @param newLevel
* Maze level to be loaded.
*/
void load(Activity activity, int newLevel) {
// maze data is stored in the assets folder as level1.txt, level2.txt
// etc....
String mLevel = "level" + newLevel + ".txt";
InputStream is = null;
try {
// construct our maze data array.
mMazeData = new int[MAZE_ROWS * MAZE_COLS];
// attempt to load maze data.
is = activity.getAssets().open(mLevel);
// we need to loop through the input stream and load each tile for
// the current maze.
for (int i = 0; i < mMazeData.length; i++) {
// data is stored in unicode so we need to convert it.
mMazeData[i] = Character.getNumericValue(is.read());
// skip the "," and white space in our human readable file.
is.read();
is.read();
}
} catch (Exception e) {
Log.i("Maze", "load exception: " + e);
} finally {
closeStream(is);
}
}
/**
* Draw the maze.
*
* @param canvas
* Canvas object to draw too.
* @param paint
* Paint object used to draw with.
*/
public void draw(Canvas canvas, Paint paint) {
// loop through our maze and draw each tile individually.
for (int i = 0; i < mMazeData.length; i++) {
// calculate the row and column of the current tile.
mRow = i / MAZE_COLS;
mCol = i % MAZE_COLS;
// convert the row and column into actual x,y co-ordinates so we can
// draw it on screen.
mX = mCol * TILE_SIZE;
mY = mRow * TILE_SIZE;
// draw the actual tile based on type.
if (mMazeData[i] == PATH_TILE)
canvas.drawBitmap(mImgPath, mX, mY, paint);
else if (mMazeData[i] == EXIT_TILE)
canvas.drawBitmap(mImgExit, mX, mY, paint);
else if (mMazeData[i] == VOID_TILE) {
// since our "void" tile is purely black lets draw a rectangle
// instead of using an image.
// tile attributes we are going to paint.
mRect.left = mX;
mRect.top = mY;
mRect.right = mX + TILE_SIZE;
mRect.bottom = mY + TILE_SIZE;
paint.setColor(VOID_COLOR);
canvas.drawRect(mRect, paint);
}
}
}
/**
* Determine which cell the marble currently occupies.
*
* @param x
* Current x co-ordinate.
* @param y
* Current y co-ordinate.
* @return The actual cell occupied by the marble.
*/
public int getCellType(int x, int y) {
// convert the x,y co-ordinate into row and col values.
int mCellCol = x / TILE_SIZE;
int mCellRow = y / TILE_SIZE;
// location is the row,col coordinate converted so we know where in the
// maze array to look.
int mLocation = 0;
// if we are beyond the 1st row need to multiple by the number of
// columns.
if (mCellRow > 0)
mLocation = mCellRow * MAZE_COLS;
// add the column location.
mLocation += mCellCol;
return mMazeData[mLocation];
}
/**
* Closes the specified stream.
*
* @param stream
* The stream to close.
*/
private static void closeStream(Closeable stream) {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
// Ignore
}
}
}
}
3. Amazed视图类,自定义view用于绘制迷宫和大理石,响应加速度计的更新在屏幕上滚动的大理石,通过SensorListener和SensorManager传感器相关的类监听用户触摸的单元xyz坐标值,重载View的onDraw方法来调用游戏开始前,游戏开始中,游戏结束和游戏完成相应的绘制函数
/*
* Copyright (C) 2008 Jason Tomlinson.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.amazed;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.hardware.SensorListener;
import android.hardware.SensorManager;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
/**
* Custom view used to draw the maze and marble. Responds to accelerometer
* updates to roll the marble around the screen.自定义view用于绘制迷宫和大理石,响应加速度计的更新在屏幕上滚动的大理石。
*/
public class AmazedView extends View {
// Game objects
private Marble mMarble;
private Maze mMaze;
private Activity mActivity;
// canvas we paint to.
private Canvas mCanvas;
private Paint mPaint;
private Typeface mFont = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD);
private int mTextPadding = 10;
private int mHudTextY = 440;
// game states
private final static int NULL_STATE = -1;
private final static int GAME_INIT = 0;
private final static int GAME_RUNNING = 1;
private final static int GAME_OVER = 2;
private final static int GAME_COMPLETE = 3;
private final static int GAME_LANDSCAPE = 4;
// current state of the game
private static int mCurState = NULL_STATE;
// game strings
private final static int TXT_LIVES = 0;
private final static int TXT_LEVEL = 1;
private final static int TXT_TIME = 2;
private final static int TXT_TAP_SCREEN = 3;
private final static int TXT_GAME_COMPLETE = 4;
private final static int TXT_GAME_OVER = 5;
private final static int TXT_TOTAL_TIME = 6;
private final static int TXT_GAME_OVER_MSG_A = 7;
private final static int TXT_GAME_OVER_MSG_B = 8;
private final static int TXT_RESTART = 9;
private final static int TXT_LANDSCAPE_MODE = 10;
private static String mStrings[];
// this prevents the user from dying instantly when they start a level if
// the device is tilted.
private boolean mWarning = false;
// screen dimensions
private int mCanvasWidth = 0;
private int mCanvasHeight = 0;
private int mCanvasHalfWidth = 0;
private int mCanvasHalfHeight = 0;
// are we running in portrait mode.
private boolean mPortrait;
// current level
private int mlevel = 1;
// timing used for scoring.
private long mTotalTime = 0;
private long mStartTime = 0;
private long mEndTime = 0;
// sensor manager used to control the accelerometer sensor.
private SensorManager mSensorManager;
// accelerometer sensor values.
private float mAccelX = 0;
private float mAccelY = 0;
private float mAccelZ = 0; // this is never used but just in-case future
// versions make use of it.
// accelerometer buffer, currently set to 0 so even the slightest movement
// will roll the marble.
private float mSensorBuffer = 0;
// http://code.google.com/android/reference/android/hardware/SensorManager.html#SENSOR_ACCELEROMETER
// for an explanation on the values reported by SENSOR_ACCELEROMETER.
private final SensorListener mSensorAccelerometer = new SensorListener() {
// method called whenever new sensor values are reported.
public void onSensorChanged(int sensor, float[] values) {
// grab the values required to respond to user movement.
mAccelX = values[0];
mAccelY = values[1];
mAccelZ = values[2];
}
// reports when the accuracy of sensor has change
// SENSOR_STATUS_ACCURACY_HIGH = 3
// SENSOR_STATUS_ACCURACY_LOW = 1
// SENSOR_STATUS_ACCURACY_MEDIUM = 2
// SENSOR_STATUS_UNRELIABLE = 0 //calibration required.
public void onAccuracyChanged(int sensor, int accuracy) {
// currently not used
}
};
/**
* Custom view constructor.
*
* @param context
* Application context
* @param activity
* Activity controlling the view
*/
public AmazedView(Context context, Activity activity) {
super(context);
mActivity = activity;
// init paint and make is look "nice" with anti-aliasing.
mPaint = new Paint();
mPaint.setTextSize(14);
mPaint.setTypeface(mFont);
mPaint.setAntiAlias(true);
// setup accelerometer sensor manager.
mSensorManager = (SensorManager) activity.getSystemService(Context.SENSOR_SERVICE);
// register our accelerometer so we can receive values.
// SENSOR_DELAY_GAME is the recommended rate for games
mSensorManager.registerListener(mSensorAccelerometer, SensorManager.SENSOR_ACCELEROMETER,
SensorManager.SENSOR_DELAY_GAME);
// setup our maze and marble.
mMaze = new Maze(mActivity);
mMarble = new Marble(this);
// load array from /res/values/strings.xml
mStrings = getResources().getStringArray(R.array.gameStrings);
// set the starting state of the game.
switchGameState(GAME_INIT);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// get new screen dimensions.
mCanvasWidth = w;
mCanvasHeight = h;
mCanvasHalfWidth = w / 2;
mCanvasHalfHeight = h / 2;
// are we in portrait or landscape mode now?
// you could use bPortrait = !bPortrait however in the future who know's
// how many different ways a device screen may be rotated.
if (mCanvasHeight > mCanvasWidth)
mPortrait = true;
else {
mPortrait = false;
switchGameState(GAME_LANDSCAPE);
}
}
/**
* Called every cycle, used to process current game state.
*/
public void gameTick() {
// very basic state machine, makes a good foundation for a more complex
// game.
switch (mCurState) {
case GAME_INIT:
// prepare a new game for the user.
initNewGame();
switchGameState(GAME_RUNNING);
case GAME_RUNNING:
// update our marble.
if (!mWarning)
updateMarble();
break;
}
// redraw the screen once our tick function is complete.
invalidate();
}
/**
* Reset game variables in preparation for a new game.
*/
public void initNewGame() {
mMarble.setLives(5);
mTotalTime = 0;
mlevel = 0;
initLevel();
}
/**
* Initialize the next level.
*/
public void initLevel() {
if (mlevel < mMaze.MAX_LEVELS) {
// setup the next level.
mWarning = true;
mlevel++;
mMaze.load(mActivity, mlevel);
mMarble.init();
} else {
// user has finished the game, update state machine.
switchGameState(GAME_COMPLETE);
}
}
/**
* Called from gameTick(), update marble x,y based on latest values obtained
* from the Accelerometer sensor. AccelX and accelY are values received from
* the accelerometer, higher values represent the device tilted at a more
* acute angle.
*/
public void updateMarble() {
// we CAN give ourselves a buffer to stop the marble from rolling even
// though we think the device is "flat".
if (mAccelX > mSensorBuffer || mAccelX < -mSensorBuffer)
mMarble.updateX(mAccelX);
if (mAccelY > mSensorBuffer || mAccelY < -mSensorBuffer)
mMarble.updateY(mAccelY);
// check which cell the marble is currently occupying.
if (mMaze.getCellType(mMarble.getX(), mMarble.getY()) == mMaze.VOID_TILE) {
// user entered the "void".
if (mMarble.getLives() > 0) {
// user still has some lives remaining, restart the level.
mMarble.death();
mMarble.init();
mWarning = true;
} else {
// user has no more lives left, end of game.
mEndTime = System.currentTimeMillis();
mTotalTime += mEndTime - mStartTime;
switchGameState(GAME_OVER);
}
} else if (mMaze.getCellType(mMarble.getX(), mMarble.getY()) == mMaze.EXIT_TILE) {
// user has reached the exit tiles, prepare the next level.
mEndTime = System.currentTimeMillis();
mTotalTime += mEndTime - mStartTime;
initLevel();
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// we only want to handle down events .
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (mCurState == GAME_OVER || mCurState == GAME_COMPLETE) {
// re-start the game.
mCurState = GAME_INIT;
} else if (mCurState == GAME_RUNNING) {
// in-game, remove the pop-up text so user can play.
mWarning = false;
mStartTime = System.currentTimeMillis();
}
}
return true;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// quit application if user presses the back key.
if (keyCode == KeyEvent.KEYCODE_BACK)
cleanUp();
return true;
}
@Override
public void onDraw(Canvas canvas) {
// update our canvas reference.
mCanvas = canvas;
// clear the screen.
mPaint.setColor(Color.WHITE);
mCanvas.drawRect(0, 0, mCanvasWidth, mCanvasHeight, mPaint);
// simple state machine, draw screen depending on the current state.
switch (mCurState) {
case GAME_RUNNING:
// draw our maze first since everything else appears "on top" of it.
mMaze.draw(mCanvas, mPaint);
// draw our marble and hud.
mMarble.draw(mCanvas, mPaint);
// draw hud
drawHUD();
break;
case GAME_OVER:
drawGameOver();
break;
case GAME_COMPLETE:
drawGameComplete();
break;
case GAME_LANDSCAPE:
drawLandscapeMode();
break;
}
gameTick();
}
/**
* Called from onDraw(), draws the in-game HUD
*/
public void drawHUD() {
mPaint.setColor(Color.BLACK);
mPaint.setTextAlign(Paint.Align.LEFT);
mCanvas.drawText(mStrings[TXT_TIME] + ": " + (mTotalTime / 1000), mTextPadding, mHudTextY,
mPaint);
mPaint.setTextAlign(Paint.Align.CENTER);
mCanvas.drawText(mStrings[TXT_LEVEL] + ": " + mlevel, mCanvasHalfWidth, mHudTextY, mPaint);
mPaint.setTextAlign(Paint.Align.RIGHT);
mCanvas.drawText(mStrings[TXT_LIVES] + ": " + mMarble.getLives(), mCanvasWidth - mTextPadding,
mHudTextY, mPaint);
// do we need to display the warning message to save the user from
// possibly dying instantly.
if (mWarning) {
mPaint.setColor(Color.BLUE);
mCanvas
.drawRect(0, mCanvasHalfHeight - 15, mCanvasWidth, mCanvasHalfHeight + 5,
mPaint);
mPaint.setColor(Color.WHITE);
mPaint.setTextAlign(Paint.Align.CENTER);
mCanvas.drawText(mStrings[TXT_TAP_SCREEN], mCanvasHalfWidth, mCanvasHalfHeight, mPaint);
}
}
/**
* Called from onDraw(), draws the game over screen.
*/
public void drawGameOver() {
mPaint.setColor(Color.BLACK);
mPaint.setTextAlign(Paint.Align.CENTER);
mCanvas.drawText(mStrings[TXT_GAME_OVER], mCanvasHalfWidth, mCanvasHalfHeight, mPaint);
mCanvas.drawText(mStrings[TXT_TOTAL_TIME] + ": " + (mTotalTime / 1000) + "s",
mCanvasHalfWidth, mCanvasHalfHeight + mPaint.getFontSpacing(), mPaint);
mCanvas.drawText(mStrings[TXT_GAME_OVER_MSG_A] + " " + (mlevel - 1) + " "
+ mStrings[TXT_GAME_OVER_MSG_B], mCanvasHalfWidth, mCanvasHalfHeight
+ (mPaint.getFontSpacing() * 2), mPaint);
mCanvas.drawText(mStrings[TXT_RESTART], mCanvasHalfWidth, mCanvasHeight
- (mPaint.getFontSpacing() * 3), mPaint);
}
/**
* Called from onDraw(), draws the game complete screen.
*/
public void drawGameComplete() {
mPaint.setColor(Color.BLACK);
mPaint.setTextAlign(Paint.Align.CENTER);
mCanvas.drawText(mStrings[GAME_COMPLETE], mCanvasHalfWidth, mCanvasHalfHeight, mPaint);
mCanvas.drawText(mStrings[TXT_TOTAL_TIME] + ": " + (mTotalTime / 1000) + "s",
mCanvasHalfWidth, mCanvasHalfHeight + mPaint.getFontSpacing(), mPaint);
mCanvas.drawText(mStrings[TXT_RESTART], mCanvasHalfWidth, mCanvasHeight
- (mPaint.getFontSpacing() * 3), mPaint);
}
/**
* Called from onDraw(), displays a message asking the user to return the
* device back to portrait mode.
*/
public void drawLandscapeMode() {
mPaint.setColor(Color.WHITE);
mPaint.setTextAlign(Paint.Align.CENTER);
mCanvas.drawRect(0, 0, mCanvasWidth, mCanvasHeight, mPaint);
mPaint.setColor(Color.BLACK);
mCanvas.drawText(mStrings[TXT_LANDSCAPE_MODE], mCanvasHalfWidth, mCanvasHalfHeight, mPaint);
}
/**
* Updates the current game state with a new state. At the moment this is
* very basic however if the game was to get more complicated the code
* required for changing game states could grow quickly.
*
* @param newState
* New game state
*/
public void switchGameState(int newState) {
mCurState = newState;
}
/**
* Register the accelerometer sensor so we can use it in-game.
*/
public void registerListener() {
mSensorManager.registerListener(mSensorAccelerometer, SensorManager.SENSOR_ACCELEROMETER,
SensorManager.SENSOR_DELAY_GAME);
}
/**
* Unregister the accelerometer sensor otherwise it will continue to operate
* and report values.
*/
public void unregisterListener() {
mSensorManager.unregisterListener(mSensorAccelerometer);
}
/**
* Clean up the custom view and exit the application.
*/
public void cleanUp() {
mMarble = null;
mMaze = null;
mStrings = null;
unregisterListener();
mActivity.finish();
}
}
4. Amazed活动类,响应Activity来控制应用程序,定义和调用Amazed视图类,同时重载Activity的onResume方法和onSaveInstanceState方法分别进行Amazed类的注册和反注册
/*
* Copyright (C) 2008 Jason Tomlinson.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.amazed;
import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
/**
* Activity responsible for controlling the application.响应Activity来控制应用程序
*/
public class AmazedActivity extends Activity {
// custom view
private AmazedView mView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// remove title bar.
requestWindowFeature(Window.FEATURE_NO_TITLE);
// setup our view, give it focus and display.
mView = new AmazedView(getApplicationContext(), this);
mView.setFocusable(true);
setContentView(mView);
}
@Override
protected void onResume() {
super.onResume();
mView.registerListener();
}
@Override
public void onSaveInstanceState(Bundle icicle) {
super.onSaveInstanceState(icicle);
mView.unregisterListener();
}
}
源码来源:https://code.google.com/p/apps-for-android/
源码下载:http://download.csdn.net/user/yangzhenping
•Amazed: A simple but addictive accelerometer-based marble-guidance game.
•AndroidGlobalTime: a full representation of the Earth that you can spin around.
•AnyCut: A utility that lets users create Home screen shortcuts to nearly anything in the system.
•Clickin2DaBeat: A game that mashes up YouTube with custom rhythm-game logic.
•DivideAndConquer: a game in which you must isolate bouncing balls by creating walls around them.
•HeightMapProfiler: A simple 3D performance testing tool that renders a 3D height map.
•LOLcat Builder:
ho hai,i see in has cheese burger? I am in our phone, caption in our photos.
•Panoramio: An app that shows you nearby photos and points of interest.
•Photostream: An app that lets you view photostreams from online photo-hosting services.
•Radar: A radar-style relative location display view, used by Panoramio(Google照片分享服务) and others.
•RingsExtended: A utility that provides enhanced control over ringtones.
•Samples: Miscellaneous examples showing features of the Android platform (among which OpenGL ES).
•SpriteMethodTest: An application that compares the speed of various 2D sprite drawing methods.
•WebViewDemo: How Java and JavaScript can call each other inside a WebView.
•WikiNotes: A wiki note pad that uses intents to navigate to wiki words and other rich content stored in the notes.
•Amazed:一个简单但令人上瘾的加速度为基础的大理石指导游戏。
•AndroidGlobalTime:地球全表示,你可以不停地旋转。
•AnyCut:一种实用工具,可以让用户创建主屏幕快捷方式到系统中几乎任何东西。
•Clickin2DaBeat:一个游戏,捣烂了YouTube的自定义节奏的游戏逻辑。
•DivideAndConquer:一个游戏中,你必须隔离他们围绕创建墙壁弹跳球。
•HeightMapProfiler:一个简单的3D性能测试工具,它呈现一个三维高程图。
•LOLcat生成器:
何海,我看到有芝士汉堡?我在我们的电话,说明在我们的照片。
•Panoramio的:一个应用程序,显示你附近的照片和兴趣点。
•照片流:一个应用程序,让您从在线照片托管服务查看照片媒体。
•雷达:雷达式的相对位置显示视图,用于Panoramio的(谷歌照片分享服务)等。
•RingsExtended:提供增强的控制铃声的实用程序。
•Samples:显示Android平台(其中的OpenGL ES)的功能,其他的例子。
•SpriteMethodTest:用于比较各种2D精灵绘制方法速度的应用程序。
•WebViewDemo:如何Java与JavaScript可以调用对方的WebView里面。
•WikiNotes:使用意图导航到维基单词和存储在票据等丰富内容维基便条。
源码来源:https://code.google.com/p/apps-for-android/
源码下载:http://download.csdn.net/user/yangzhenping