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