安卓实训项目:贪吃蛇V1.0

目录

一、功能要求

1、游戏规则

2、游戏设置

3、游戏排行榜

二、运行效果

1、启动界面

2、游戏主控界面

3、选择地图

4、设置速度

5、选择背景音乐

6、开始游戏

7、游戏结束

8、查看排行榜

9、查看帮助信息

10、关于作者

三、涉及知识点

1、布局

2、常用控件

3、线程

4、消息处理器

5、自定义视图

6、图形图像绘制

7、媒体播放器

8、集合类

四、项目结构图

五、项目实现

1、创建安卓项目:贪吃蛇V1.0

2、修改包名

3、创建子包entity、app与view

4、准备图片素材,拷贝到drawable-mdpi目录里

5、在res里创建raw文件夹,拷贝几首mp3音乐

6、在res里创建drawable目录,在里面创建按钮背景选择器

(1)开始游戏按钮背景选择器

(2)选择地图按钮背景选择器

(3)选择背景音乐按钮背景选择器

(4)设置速度按钮背景选择器

(5)排行榜按钮背景选择器

(6)关于作者按钮背景选择器

(7)帮助信息按钮背景选择器

(8)退出游戏按钮背景选择器

7、在app子包里创建应用程序常量接口AppConstants

8、在app子包里创建贪吃蛇应用程序类SnakeApplicaiton

9、在entity子包里创建坐标实体类Coordinate

10、在view子包里创建方砖视图TileView

(1)在values目录创建属性文件attrs.xml

(2)方砖视图类TileView

11、在view子包里创建贪吃蛇视图SnakeView

12、在ui子包里创建贪吃蛇界面类SnakeActivity

(1)贪吃蛇界面布局文件activity_snake.xml

(2)贪吃蛇界面类SnakeActivity

13、在ui子包里创建选择地图界面类SelectMapActivity

(1)在values里的styles.xml里增加单选按钮和按钮的样式定义

(2)选择地图界面布局文件activity_select_map.xml

(3)选择地图界面类SelectMapActivity

14、在ui子包里创建设置速度界面类SetSpeedActivity

(1)设置速度界面布局文件activity_set_speed.xml

(2)设置速度界面类SetSpeedActivity

15、在ui子包里创建选择背景音乐界面类SelectMusicActivity

(1)选择背景音乐界面布局文件activity_select_music.xml

(2)音乐列表项模板music_list_item.xml

(3)选择背景音乐界面类SelectMusicActivity

16、在ui子包里创建排行榜界面类TopListActivity

(1)排行榜界面布局文件activity_top_list.xml

(2)在字符串资源文件strings.xml里定义变量

(3)排行榜界面类TopListActivity

17、在ui子包里创建主界面类MainActivity

(1)主界面布局文件activity_main.xml

(2)字符串资源文件strings.xml里定义变量

(4)主界面类MainActivity

18、在ui子包里修改启动界面类SplashScreen

(1)启动界面布局文件activity_splash_screen.xml

(2)字符串资源文件strings.xml里定义变量

(3)启动界面类SplashScreenActivity

19、项目清单文件设置全部界面都是全屏显示


项目图片素材【百度网盘下载网址 密码:gcpt】

一、功能要求

1、游戏规则

玩家使用方向键或手势滑动操控一条长长的蛇不断吞苹果,同时蛇身随着吞下的苹果不断长,当蛇头撞到蛇身或障壁时游戏结束。

2、游戏设置

玩家可选择地图、设置背景音乐、选择蛇移动速度。

3、游戏排行榜

提供玩家排行榜,显示前三名分数(贪吃蛇吃掉苹果的数量)

二、运行效果

1、启动界面

2、游戏主控界面

3、选择地图

4、设置速度

5、选择背景音乐

6、开始游戏

7、游戏结束

8、查看排行榜

9、查看帮助信息

10、关于作者

三、涉及知识点

1、布局

2、常用控件

3、线程

4、消息处理器

5、自定义视图

6、图形图像绘制

7、媒体播放器

8、集合类

四、项目结构图

五、项目实现

1、创建安卓项目:贪吃蛇V1.0

2、修改包名

说明:net.hw.snake为根包,ui子包用于存放界面类。

3、创建子包entity、app与view

4、准备图片素材,拷贝到drawable-mdpi目录里

5、在res里创建raw文件夹,拷贝几首mp3音乐

6、在res里创建drawable目录,在里面创建按钮背景选择器

(1)开始游戏按钮背景选择器

(2)选择地图按钮背景选择器

(3)选择背景音乐按钮背景选择器

(4)设置速度按钮背景选择器

(5)排行榜按钮背景选择器

(6)关于作者按钮背景选择器

(7)帮助信息按钮背景选择器

(8)退出游戏按钮背景选择器

7、在app子包里创建应用程序常量接口AppConstants

/**
 * 功能:应用程序常量接口
 * 作者:华卫
 * 日期:2017年1月1日
 */
package net.hw.snake.app;

import android.app.Activity;

public interface AppConstants {
    /**
    * 应用程序标记
    */
    String TAG = "net.hw.snake";
    
    /**
    * 地图常量
    */
    int O_SHAPE_MAP = 0;
    int I_SHAPE_MAP = 1;
    int X_SHAPE_MAP = 2;
    int Z_SHAPE_MAP = 3;
    int U_SHAPE_MAP = 4;
    
    /**
    * 速度常量
    */
    int LOW_SPEED = 0;
    int MEDIUM_SPEED = 1;
    int HIGH_SPEED = 2;

    /**
    * 排行榜配置文件名
    */
    String TOP_LIST_FILE_NAME = "toplist";
    /**
    * 文件访问模式
    */
    int ACCESS_MODE = Activity.MODE_PRIVATE;

    /**
    * 游戏状态常量
    */
    int PAUSE = 0;
    int READY = 1;
    int RUNNING = 2;
    int LOSE = 3;

    /**
    * 移动方向常量
    */
    int NORTH = 1;
    int SOUTH = 2;
    int EAST = 3;
    int WEST = 4;

    /**
    * 图片常量
    */
    int RED_STAR = 1;
    int YELLOW_STAR = 2;
    int GREEN_STAR = 3;
    
    /**
    * 蛇两次移动之间的间隔
    */
    int SHORT_INTERVAL = 100;
    int MEDIUM_INTERVAL = 350;
    int LONG_INTERVAL = 600;    
    
    /**
    * 前三名分数
    */
    String SCORE_OF_FIRST = "score_of_first";
    String SCORE_OF_SECOND = "score_of_second";
    String SCORE_OF_THIRD = "score_of_third";
}

8、在app子包里创建贪吃蛇应用程序类SnakeApplicaiton

/**
 * 功能:贪吃蛇应用程序类
 * 作者:华卫
 * 日期:2017年1月1日
 */
package net.hw.snake.app;

import android.app.Application;
import android.media.MediaPlayer;

public class SnakeApplication extends Application {
    /**
    * 地图索引
    */
    private int mapIndex;
    /**
    * 背景音乐索引
    */
    private int musicIndex;
    /**
    * 速度索引
    */
    private int speedIndex;
    /**
    * 媒体播放器
    */
    private MediaPlayer mediaPlayer;

    @Override
    public void onCreate() {
        super.onCreate();
    }

    public int getMapIndex() {
        return mapIndex;
    }

    public void setMapIndex(int mapIndex) {
        this.mapIndex = mapIndex;
    }

    public int getMusicIndex() {
        return musicIndex;
    }

    public void setMusicIndex(int musicIndex) {
        this.musicIndex = musicIndex;
    }

    public int getSpeedIndex() {
        return speedIndex;
    }

    public void setSpeedIndex(int speedIndex) {
        this.speedIndex = speedIndex;
    }

    public MediaPlayer getMediaPlayer() {
        return mediaPlayer;
    }

    public void setMediaPlayer(MediaPlayer mediaPlayer) {
        this.mediaPlayer = mediaPlayer;
    }
}

9、在entity子包里创建坐标实体类Coordinate

/**
 * 功能:坐标实体
 * 作者:华卫
 * 日期:2017年1月1日
 */
package net.hw.snake.entity;

public class Coordinate {
    /**
    * 横坐标
    */
    public int x;
    /**
    * 纵坐标
    */
    public int y;

    /**
    * 构造方法
    * 
    * @param x
    * @param y
    */
    public Coordinate(int x, int y) {
        this.x = x;
        this.y = y;
    }

    /**
    * 相等方法(判断两点是否重合)
    */
    public boolean equals(Object obj) {
        if (obj instanceof Coordinate) {
            Coordinate other = (Coordinate) obj;
            if (x == other.x && y == other.y) {
                return true;
            }
        }
        return false;
    }

    @Override
    public String toString() {
        return "Coordinate: [" + x + "," + y + "]";
    }
}

10、在view子包里创建方砖视图TileView

(1)在values目录创建属性文件attrs.xml

(2)方砖视图类TileView

/**
 * 功能:方砖视图
 * 作者:华卫
 * 日期:2017年1月1日
 */

/**
 * 控制方砖尺寸及其在视图里显示范围的参数,宽度与高度的单位是像素。 
 * 可绘制对象可以缩放来适合方砖尺寸,然后计算出横向与纵向的方砖数。 
*/
package net.hw.snake.view;

import net.hw.snake.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;

public class TileView extends View {
    /**
    * 方砖尺寸
    */
    protected static int mTileSize;
    /**
    * 横向方砖数量
    */
    protected static int mXTileCount;
    /**
    * 纵向方砖数量
    */
    protected static int mYTileCount; 
    /**
    * 横向偏移量
    */
    private static int mXOffset;
    /**
    * 纵向偏移量
    */
    private static int mYOffset;
    /**
    * 方砖数组
    */
    private Bitmap[] mTileArray;
    /**
    * 方砖网格
    */
    private int[][] mTileGrid;
    /**
    * 画笔
    */
    private final Paint mPaint = new Paint();

    /**
    * 构造方法
    * 
    * @param context
    * @param attrs
    * @param defStyle
    */
    public TileView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TileView);
        mTileSize = a.getInt(R.styleable.TileView_tileSize, 12);
        a.recycle();
    }

    /**
    * 构造方法
    * 
    * @param context
    * @param attrs
    */
    public TileView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TileView);
        mTileSize = a.getInt(R.styleable.TileView_tileSize, 12);
        a.recycle();
    }

    /**
    * 重置方砖
    * 
    * @param tilecount
    */

    public void resetTiles(int tileCount) {
        mTileArray = new Bitmap[tileCount];
    }

    /**
    * 屏幕尺寸发生变化时触发,重新计算横向与纵向方砖数以及横向与纵向的偏移量,设置方砖网格
    */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        // 横向方砖数
        mXTileCount = (int) Math.floor(w / mTileSize);
        // 纵向方砖数
        mYTileCount = (int) Math.floor(h / mTileSize);

        // 横向偏移量
        mXOffset = ((w - (mTileSize * mXTileCount)) / 2);
        // 纵向偏移量
        mYOffset = ((h - (mTileSize * mYTileCount)) / 2);

        // 方砖网格
        mTileGrid = new int[mXTileCount][mYTileCount];

        // 清除方砖
        clearTiles();
    }

    /**
    * 加载方砖
    * 
    * @param key
    * @param tile
    */
    public void loadTile(int key, Drawable tile) {
        Bitmap bitmap = Bitmap.createBitmap(mTileSize, mTileSize, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        tile.setBounds(0, 0, mTileSize, mTileSize);
        tile.draw(canvas);
        mTileArray[key] = bitmap;
    }

    /**
    * 清空方砖
    */
    public void clearTiles() {
        for (int x = 0; x < mXTileCount; x++) {
            for (int y = 0; y < mYTileCount; y++) {
                // 第一个参数为0,表明不画方砖
                setTile(0, x, y); 
            }
        }
    }

    /**
    * 设置方砖
    * 
    * @param tileIndex
    * @param x
    * @param y
    */
    public void setTile(int tileIndex, int x, int y) {
        // 在x行y列画指定的方砖
        mTileGrid[x][y] = tileIndex; 
    }

    /**
    * 绘制方法
    */
    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (int x = 0; x < mXTileCount; x += 1) {
            for (int y = 0; y < mYTileCount; y += 1) {
                if (mTileGrid[x][y] > 0) { 
                    canvas.drawBitmap(mTileArray[mTileGrid[x][y]], 
                          mXOffset + x * mTileSize, mYOffset + y * mTileSize, mPaint);
                }
            }
        }
    }
}

11、在view子包里创建贪吃蛇视图SnakeView

/**
 * 功能:贪吃蛇视图
 * 作者:华卫
 * 日期:2017年1月1日
 */
package net.hw.snake.view;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.TextView;
import net.hw.snake.R;
import net.hw.snake.app.AppConstants;
import net.hw.snake.app.SnakeApplication;
import net.hw.snake.entity.Coordinate;

public class SnakeView extends TileView implements AppConstants {
    /**
    * 共享参数
    */
    private SharedPreferences sp;
    /**
    * 编辑器
    */
    private Editor editor;
    /**
    * 名次信息
    */
    private String rankMsg;
    /**
    * 游戏状态变量
    */
    public int mMode = READY;

    /**
    * 蛇当前移动方向
    */
    public int mDirection = EAST;
    /**
    * 蛇下一个移动方向
    */
    public int mNextDirection = EAST;
    /**
    * 分数:蛇吃掉的苹果数量
    */
    private int mScore = 0;
    /**
    * 蛇移动的时间间隔
    */
    private int mMoveDelay = 600;
    /**
    * 蛇上次移动的时间
    */
    private long mLastMove;
    /**
    * 蛇身数组列表
    */
    private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordinate>();
    /**
    * 苹果数组列表
    */
    private ArrayList<Coordinate> mAppleList = new ArrayList<Coordinate>();
    /**
    * 墙壁数组列表
    */
    private ArrayList<Coordinate> mWallList = new ArrayList<Coordinate>();
    /**
    * 随机对象
    */
    private static final Random RNG = new Random();
    /**
    * 贪吃蛇应用程序
    */
    private SnakeApplication app;

    /**
    * 负责重绘的消息处理器
    */
    private class RefreshHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            // 更新贪吃蛇视图
            SnakeView.this.update();
            // 重绘贪吃蛇视图
            SnakeView.this.invalidate();
        }

        public void sleep(long delayMillis) {
            // 清空消息队列
            this.removeMessages(0);
            // 延迟发送消息
            sendMessageDelayed(obtainMessage(0), delayMillis);
        }
    }

    /**
    * 声明负责重绘的消息处理器
    */
    private RefreshHandler mRedrawHandler = new RefreshHandler();

    /**
    * 贪吃蛇视图构造方法
    * 
    * @param context
    * @param attrs
    */
    public SnakeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 获取贪吃蛇应用程序对象
        app = (SnakeApplication) ((Activity) context).getApplication();
        // 初始化贪吃蛇视图
        initSnakeView();
    }

    /**
    * 构造贪吃蛇视图
    * 
    * @param context
    * @param attrs
    * @param defStyle
    */
    public SnakeView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // 获取贪吃蛇应用程序对象
        app = (SnakeApplication) ((Activity) context).getApplication();
        // 初始化贪吃蛇视图
        initSnakeView();

    }

    /**
    * 初始化贪吃蛇视图
    */
    private void initSnakeView() {
        // 设置为可获得焦点
        setFocusable(true);
        // 重置方砖(四种方砖)
        resetTiles(4);
        // 加载红星方砖
        loadTile(RED_STAR, getContext().getResources().getDrawable(R.drawable.redstar));
        // 加载黄星方砖
        loadTile(YELLOW_STAR, getContext().getResources().getDrawable(R.drawable.yellowstar));
        // 加载绿星方砖
        loadTile(GREEN_STAR, getContext().getResources().getDrawable(R.drawable.greenstar));

        // 加载音乐
        switch (app.getMusicIndex()) {
        case 0:
            // 音乐:梦中的婚礼
            app.setMediaPlayer(MediaPlayer.create(getContext(), R.raw.dream));
            break;
        case 1:
            // 音乐:秋日私语
            app.setMediaPlayer(MediaPlayer.create(getContext(), R.raw.autumn));
            break;
        case 2:
            // 音乐:天空之城
            app.setMediaPlayer(MediaPlayer.create(getContext(), R.raw.sky));
            break;
        case 3:
            // 音乐:致爱丽丝
            app.setMediaPlayer(MediaPlayer.create(getContext(), R.raw.alice));
            break;
        case 4:
            // 歌曲:贝尔加湖畔
            app.setMediaPlayer(MediaPlayer.create(getContext(), R.raw.lake));
        case 5:
            // 歌曲:暗香
            app.setMediaPlayer(MediaPlayer.create(getContext(), R.raw.fragrance));
        }
        // 设置为循环播放
        app.getMediaPlayer().setLooping(true);

        // 创建共享参数对象
        sp = getContext().getSharedPreferences(TOP_LIST_FILE_NAME, ACCESS_MODE);
        // 获得编辑器
        editor = sp.edit();
    }

    /**
    * 初始化新游戏
    */
    public void initNewGame() {
        // 清除蛇
        mSnakeTrail.clear();
        // 清除所有苹果
        mAppleList.clear();

        // 初始蛇,三节,朝东移动
        mSnakeTrail.add(new Coordinate(7, 7));
        mSnakeTrail.add(new Coordinate(6, 7));
        mSnakeTrail.add(new Coordinate(5, 7));
        mNextDirection = EAST;

        // 更新墙壁
        updateWalls();

        // 随机添加两个苹果
        addRandomApple();
        addRandomApple();

        // 根据用户设置速度修改初始的蛇两次移动时间间隔初始值
        switch (app.getSpeedIndex()) {
        case LOW_SPEED:
            mMoveDelay = LONG_INTERVAL;
            break;
        case MEDIUM_SPEED:
            mMoveDelay = MEDIUM_INTERVAL;
            break;
        case HIGH_SPEED:
            mMoveDelay = SHORT_INTERVAL;
            break;
        }

        // 初始化游戏得分
        mScore = 0;
    }

    /**
    * 将数组列表转换成数组
    * 
    * @param cvec
    *            坐标对象的数组列表
    * @return 包含坐标值的简单数组,如 [x1,y1,x2,y2,x3,y3...]
    */
    private int[] coordArrayListToArray(ArrayList<Coordinate> cvec) {
        int count = cvec.size();
        int[] rawArray = new int[count * 2];
        for (int index = 0; index < count; index++) {
            Coordinate c = cvec.get(index);
            rawArray[2 * index] = c.x;
            rawArray[2 * index + 1] = c.y;
        }
        return rawArray;
    }

    /**
    * 保存游戏状态,那么在后台游戏进程被杀掉也不会丢失任何数据
    * 
    * @return 保存视图状态的数据包
    */
    public Bundle saveState() {
        Bundle map = new Bundle();

        map.putIntArray("mAppleList", coordArrayListToArray(mAppleList));
        map.putInt("mDirection", Integer.valueOf(mDirection));
        map.putInt("mNextDirection", Integer.valueOf(mNextDirection));
        map.putLong("mMoveDelay", Integer.valueOf(mMoveDelay));
        map.putLong("mScore", Integer.valueOf(mScore));
        map.putIntArray("mSnakeTrail", coordArrayListToArray(mSnakeTrail));

        return map;
    }

    /**
    * 恢复游戏状态
    * 
    * @param data
    *            包含游戏状态的数据包
    */
    public void restoreState(Bundle data) {
        // 设置游戏模式为暂停
        setMode(PAUSE);

        mAppleList = coordArrayToArrayList(data.getIntArray("mAppleList"));
        mDirection = data.getInt("mDirection");
        mNextDirection = data.getInt("mNextDirection");
        mMoveDelay = data.getInt("mMoveDelay");
        mScore = data.getInt("mScore");
        mSnakeTrail = coordArrayToArrayList(data.getIntArray("mSnakeTrail"));
    }

    /**
    * 将坐标对构成的数组转换成坐标对象的数组列表
    * 
    * @param rawArray
    *            : [x1,y1,x2,y2,...]
    * @return 坐标对象的数组列表
    */
    private ArrayList<Coordinate> coordArrayToArrayList(int[] rawArray) {
        ArrayList<Coordinate> coordArrayList = new ArrayList<Coordinate>();

        int coordCount = rawArray.length;
        for (int index = 0; index < coordCount; index += 2) {
            Coordinate c = new Coordinate(rawArray[index], rawArray[index + 1]);
            coordArrayList.add(c);
        }
        return coordArrayList;
    }

    /*
    * 按键事件处理
    */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent msg) {
        // 按上方向键
        if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
            if (mMode == READY | mMode == LOSE) {
                // 开始新游戏
                initNewGame();
                // 设置为运行模式
                setMode(RUNNING);
                // 播放音乐
                app.getMediaPlayer().start();
                // 更新游戏界面
                update();
                return (true);
            }

            if (mMode == PAUSE) {
                setMode(RUNNING);
                update();
                return (true);
            }

            if (mDirection != SOUTH) {
                mNextDirection = NORTH;
            }
            return (true);
        }

        // 按下方向键
        if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
            if (mDirection != NORTH) {
                mNextDirection = SOUTH;
            }
            return (true);
        }

        // 按左方向键
        if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
            if (mDirection != EAST) {
                mNextDirection = WEST;
            }
            return (true);
        }

        // 按右方向键
        if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
            if (mDirection != WEST) {
                mNextDirection = EAST;
            }
            return (true);
        }

        return super.onKeyDown(keyCode, msg);
    }

    /**
    * 设置游戏模式
    * 
    * @param newMode
    */
    public void setMode(int newMode) {
        int oldMode = mMode;
        mMode = newMode;

        if (newMode == RUNNING & oldMode != RUNNING) {
            update();
            app.getMediaPlayer().start();
            return;
        }

        if (newMode == LOSE) {
            app.getMediaPlayer().stop();
            try {
                app.getMediaPlayer().prepare();
                app.getMediaPlayer().seekTo(0);
            } catch (IllegalStateException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
    * 随机添加苹果
    */
    private void addRandomApple() {
        Coordinate newCoord = null;
        boolean found = false;
        while (!found) {
            // 为苹果选择新位置
            int newX = 1 + RNG.nextInt(mXTileCount - 2);
            int newY = 1 + RNG.nextInt(mYTileCount - 2);

            newCoord = new Coordinate(newX, newY);

            boolean collision = false;
            // 判断新产生的苹果有没有与蛇或者墙碰撞
            if (mSnakeTrail.contains(newCoord) || mWallList.contains(newCoord)) {
                collision = true;
            }

            // 如果碰撞了,就表明没找到合适位置,还要继续找
            found = !collision;
        }

        if (newCoord == null) {
            Log.e(TAG, "新坐标为空!");
        }

        mAppleList.add(newCoord);
    }

    /**
    * 更新方法:检查是否处于运行状态,决定是否该移动,更新蛇的位置
    */
    public void update() {
        if (mMode == RUNNING) {
            long now = System.currentTimeMillis();

            if (now - mLastMove > mMoveDelay) {
                clearTiles();
                updateWalls();
                updateSnake();
                updateApples();
                mLastMove = now;
            }
            mRedrawHandler.sleep(mMoveDelay);
        }
    }

    /**
    * 更新墙壁
    * 
    */
    private void updateWalls() {
        for (int x = 0; x < mXTileCount; x++) {
            setTile(GREEN_STAR, x, 0);
            setTile(GREEN_STAR, x, mYTileCount - 1);
            mWallList.add(new Coordinate(x, 0));
            mWallList.add(new Coordinate(x, mYTileCount - 1));
        }
        for (int y = 1; y < mYTileCount - 1; y++) {
            setTile(GREEN_STAR, 0, y);
            setTile(GREEN_STAR, mXTileCount - 1, y);
            mWallList.add(new Coordinate(0, y));
            mWallList.add(new Coordinate(mXTileCount - 1, y));
        }

        switch (app.getMapIndex()) {
        case I_SHAPE_MAP: // 工字型地图
            for (int x = 6; x < mXTileCount - 6; x++) {
                setTile(GREEN_STAR, x, 20);
                mWallList.add(new Coordinate(x, 20));
            }
            for (int y = 20; y <= mYTileCount - 20; y++) {
                setTile(GREEN_STAR, mXTileCount / 2, y);
                mWallList.add(new Coordinate(mXTileCount / 2, y));
            }
            for (int x = 6; x < mXTileCount - 6; x++) {
                setTile(GREEN_STAR, x, mYTileCount - 20);
                mWallList.add(new Coordinate(x, mYTileCount - 20));
            }
            break;
        case X_SHAPE_MAP: // X字型地图
            for (int x = 10, y = 20; x < mXTileCount - 10 && y < mYTileCount - 20; x++, y++) {
                setTile(GREEN_STAR, x, y);
                mWallList.add(new Coordinate(x, y));
            }
            for (int x = mXTileCount - 10, y = 20; x >= 10 && y < mYTileCount - 20; x--, y++) {
                setTile(GREEN_STAR, x, y);
                mWallList.add(new Coordinate(x, y));
            }
            break;
        case Z_SHAPE_MAP: // Z字型地图
            for (int x = 6; x < mXTileCount - 6; x++) {
                setTile(GREEN_STAR, x, 20);
                mWallList.add(new Coordinate(x, 20));
            }
            for (int x = mXTileCount - 6, y = 20; y <= 35; y++) {
                setTile(GREEN_STAR, x, y);
                mWallList.add(new Coordinate(x, y));
            }

            for (int x = 6; x < mXTileCount - 6; x++) {
                setTile(GREEN_STAR, x, 35);
                mWallList.add(new Coordinate(x, 35));
            }

            for (int x = 6, y = 35; y <= mYTileCount - 20; y++) {
                setTile(GREEN_STAR, x, y);
                mWallList.add(new Coordinate(x, y));
            }

            for (int x = 6; x < mXTileCount - 6; x++) {
                setTile(GREEN_STAR, x, mYTileCount - 20);
                mWallList.add(new Coordinate(x, mYTileCount - 20));
            }
            break;
        case U_SHAPE_MAP: // U字型地图
            for (int x = 6, y = 20; y < mYTileCount - 20; y++) {
                setTile(GREEN_STAR, x, y);
                mWallList.add(new Coordinate(x, y));
            }
            for (int x = mXTileCount - 7, y = 20; y < mYTileCount - 20; y++) {
                setTile(GREEN_STAR, x, y);
                mWallList.add(new Coordinate(x, y));
            }
            for (int x = 6; x < mXTileCount - 6; x++) {
                setTile(GREEN_STAR, x, mYTileCount - 20);
                mWallList.add(new Coordinate(x, mYTileCount - 20));
            }
            break;
        }
    }

    /**
    * 更新苹果
    */
    private void updateApples() {
        for (Coordinate c : mAppleList) {
            setTile(YELLOW_STAR, c.x, c.y);
        }
    }

    /**
    * 更新蛇
    */
    private void updateSnake() {
        // 设置蛇生长状态
        boolean growSnake = false;

        // 通过蛇头来抓住蛇
        Coordinate head = mSnakeTrail.get(0);
        Coordinate newHead = new Coordinate(1, 1);

        mDirection = mNextDirection;

        // 根据蛇移动方向决定蛇头坐标
        switch (mDirection) {
        case EAST:
            newHead = new Coordinate(head.x + 1, head.y);
            break;
        case WEST:
            newHead = new Coordinate(head.x - 1, head.y);
            break;
        case NORTH:
            newHead = new Coordinate(head.x, head.y - 1);
            break;
        case SOUTH:
            newHead = new Coordinate(head.x, head.y + 1);
            break;
        }

        // 判断是否撞墙
        if (mWallList.contains(newHead)) {
            // 游戏失败
            setMode(LOSE);
            // 更新排行榜
            updateTopList();
            // 停止音乐播放
            app.getMediaPlayer().stop();
            try {
                app.getMediaPlayer().prepare();
                app.getMediaPlayer().seekTo(0);
            } catch (IllegalStateException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

            AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
            builder.setIcon(R.drawable.ic_launcher);
            builder.setTitle("游戏结束");
            if (mScore > 0) {
                builder.setMessage("呵呵,你吃掉了" + mScore + "个苹果!" + rankMsg);
            } else {
                builder.setMessage("遗憾,你一个苹果也没有吃掉!");
            }
            builder.setPositiveButton("返回主界面", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    // 设置游戏模式为准备就绪
                    setMode(READY);
                    // 关闭当前界面
                    ((Activity) getContext()).finish();
                }
            });
            builder.setNegativeButton("再来玩一次", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    // 初始化新游戏
                    initNewGame();
                    // 设置游戏模式为运行
                    setMode(RUNNING);
                    // 播放音乐
                    app.getMediaPlayer().start(); 
                    // 更新游戏
                    update();
                }
            });
            builder.show();
        }

        // 判断是否撞到自己
        if (mSnakeTrail.contains(newHead)) {
            // 游戏失败
            setMode(LOSE);
            // 更新排行榜
            updateTopList();
            // 停止音乐播放
            app.getMediaPlayer().stop();
            try {
                app.getMediaPlayer().prepare();
                app.getMediaPlayer().seekTo(0);
            } catch (IllegalStateException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

            AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
            builder.setIcon(R.drawable.ic_launcher);
            builder.setTitle("游戏结束");
            if (mScore > 0) {
                builder.setMessage("呵呵,你吃掉了" + mScore + "个苹果!" + rankMsg);
            } else {
                builder.setMessage("遗憾,你一个苹果也没有吃掉!");
            }
            builder.setPositiveButton("返回主界面", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    setMode(READY);
                    ((Activity) getContext()).finish();
                }
            });
            builder.setNegativeButton("再来玩一次", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    // 初始化新游戏
                    initNewGame();
                    // 设置游戏模式为运行
                    setMode(RUNNING);
                    // 播放音乐
                    app.getMediaPlayer().start();
                    // 更新游戏
                    update();
                }
            });
            builder.show();
        }

        // 检测是否碰撞了苹果
        int applecount = mAppleList.size();
        for (int appleindex = 0; appleindex < applecount; appleindex++) {
            Coordinate c = mAppleList.get(appleindex);
            if (c.equals(newHead)) {
                mAppleList.remove(c);
                addRandomApple();
                // 得一分
                mScore++;
                switch (app.getSpeedIndex()) {
                case LOW_SPEED:
                    mMoveDelay *= 0.9;
                    break;
                case MEDIUM_SPEED:
                    mMoveDelay *= 0.8;
                    break;
                case HIGH_SPEED:
                    mMoveDelay *= 0.7;
                }
                // 蛇生长状态设置为真
                growSnake = true;
            }
        }

        // 在蛇头添加新元素
        mSnakeTrail.add(0, newHead);
        // 如果蛇不生长,那么去掉蛇尾一个元素
        if (!growSnake) {
            mSnakeTrail.remove(mSnakeTrail.size() - 1);
        }

        // 重新绘制蛇(蛇头设置为黄砖,蛇身设置为红砖)
        int index = 0;
        for (Coordinate c : mSnakeTrail) {
            if (index == 0) {
                setTile(YELLOW_STAR, c.x, c.y);
            } else {
                setTile(RED_STAR, c.x, c.y);
            }
            index++;
        }
    }

    /**
    * 更新排行榜
    */
    private void updateTopList() {      
        // 获取前三名分数
        int scoreOfFirst = sp.getInt(SCORE_OF_FIRST, 0);
        int scoreOfSecond = sp.getInt(SCORE_OF_SECOND, 0);
        int scoreOfThird = sp.getInt(SCORE_OF_THIRD, 0);
        if (mScore > scoreOfFirst) {
            rankMsg = "恭喜你获得第一名!";
            editor.putInt(SCORE_OF_FIRST, (int) mScore);
            editor.putInt(SCORE_OF_SECOND, scoreOfSecond);
            editor.putInt(SCORE_OF_THIRD, scoreOfThird);
        } else if (mScore > scoreOfSecond) {
            rankMsg = "恭喜你获得第二名!";
            editor.putInt(SCORE_OF_SECOND, (int) mScore);
            editor.putInt(SCORE_OF_THIRD, scoreOfSecond);
        } else if (mScore > scoreOfThird) {
            rankMsg = "恭喜你获得第三名!";
            editor.putInt(SCORE_OF_THIRD, (int) mScore);
        } else {
            rankMsg = "加油啊,争取进入排行榜!";
        }
        // 提交数据
        editor.commit();
    }

    @Override
    public void onDraw(Canvas canvas) {

        super.onDraw(canvas);
        if (mMode == READY) {
            initNewGame();
            setMode(RUNNING);
            app.getMediaPlayer().start();
        }

        if (mMode == LOSE) {
            // 绘制背景图像
            Paint paint = new Paint();
            Bitmap background = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.lose);
            canvas.drawBitmap(background, 0, 0, paint);
        }
    }
}

12、在ui子包里创建贪吃蛇界面类SnakeActivity

(1)贪吃蛇界面布局文件activity_snake.xml

(2)贪吃蛇界面类SnakeActivity

/**
 * 功能:贪吃蛇界面
 * 作者:华卫
 * 日期:2017年1月1日
 */

/**
 * 贪吃蛇: 人人都可以享受的简单游戏。 
 * 这是经典游戏“贪吃蛇”的一个实现。你控制一条在花园里游动着寻找苹果的蛇。 
 * 当心,蛇吃了苹果,不仅要变长,而且移动速度会更快,只要蛇撞墙就结束游戏。
 */
package net.hw.snake.ui;

import android.app.Activity;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.widget.TextView;
import net.hw.snake.R;
import net.hw.snake.app.AppConstants;
import net.hw.snake.app.SnakeApplication;
import net.hw.snake.view.SnakeView;

public class SnakeActivity extends Activity implements OnGestureListener, AppConstants {
    /**
    * 贪吃蛇视图
    */
    private SnakeView mSnakeView;
    /**
    * 手势侦测器
    */
    private GestureDetector detector;
    /**
    * 贪吃蛇应用程序
    */
    private SnakeApplication app;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 利用布局资源文件设置用户界面
        setContentView(R.layout.activity_snake);

        // 通过资源标识获得控件实例
        mSnakeView = (SnakeView) findViewById(R.id.snake);

        // 获取贪吃蛇应用程序对象
        app = (SnakeApplication) getApplication();
        // 初始化手势侦测器
        detector = new GestureDetector(this, this);
        // 贪吃蛇视图获得焦点
        mSnakeView.requestFocus();

        // 判断是否保存过游戏示例状态数据
        if (savedInstanceState == null) {
            // 刚刚启动———开启一次新游戏
            mSnakeView.setMode(READY);
        } else {
            // 恢复贪吃蛇游戏状态数据
            Bundle map = savedInstanceState.getBundle(TAG);
            if (map != null) {
                mSnakeView.restoreState(map);
            } else {
                // 设置贪吃蛇视图为暂停模式
                mSnakeView.setMode(PAUSE);
            }
        }
    }

    /**
    * 暂停回调方法
    */
    @Override
    protected void onPause() {
        super.onPause();
        // 随窗口暂停而将贪吃蛇游戏视图设置为暂停模式
        mSnakeView.setMode(PAUSE);
        // 暂停背景音乐
        app.getMediaPlayer().pause();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        // 存储游戏状态数据
        outState.putBundle(TAG, mSnakeView.saveState());
    }

    /**
    * 销毁回调方法
    */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 停止背景音乐
        app.getMediaPlayer().stop();
        // 释放媒体播放器资源
        app.setMediaPlayer(null);
    }

    // 将窗口的触碰事件交给手势侦测器来处理
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return detector.onTouchEvent(event);
    }

    @Override
    public boolean onDown(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        // 手势向上滑动
        if (e2.getY() < e1.getY() - 20) {
            if (mSnakeView.mMode == READY | mSnakeView.mMode == LOSE) {
                // 开始新游戏
                mSnakeView.initNewGame();
                // 设置为运行模式
                mSnakeView.setMode(RUNNING);
                // 播放音乐
                app.getMediaPlayer().start();
                // 更新游戏界面
                mSnakeView.update();
            }

            if (mSnakeView.mMode == PAUSE) {
                mSnakeView.setMode(RUNNING);
                mSnakeView.update();
            }

            if (mSnakeView.mDirection != SOUTH) {
                mSnakeView.mNextDirection = NORTH;
            }
        }

        // 手势向下滑动
        if (e2.getY() > e1.getY() + 20) {
            if (mSnakeView.mDirection != NORTH) {
                mSnakeView.mNextDirection = SOUTH;
            }

        }

        // 手势向左滑动
        if (e2.getX() < e1.getX() - 20) {
            if (mSnakeView.mDirection != EAST) {
                mSnakeView.mNextDirection = WEST;
            }
        }

        // 手势向右滑动
        if (e2.getX() > e1.getX() + 20) {
            if (mSnakeView.mDirection != WEST) {
                mSnakeView.mNextDirection = EAST;
            }
        }

        return true;
    }

    @Override
    public void onLongPress(MotionEvent e) {
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        return false;
    }

    @Override
    public void onShowPress(MotionEvent e) {
    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }
}

13、在ui子包里创建选择地图界面类SelectMapActivity

(1)在values里的styles.xml里增加单选按钮和按钮的样式定义

(2)选择地图界面布局文件activity_select_map.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/mapback"
    android:gravity="center"
    android:orientation="vertical" >

    <RadioGroup
        android:id="@+id/rg_map"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="80dp" >

        <RadioButton
            android:id="@+id/rb_o_map"
            style="@style/rb_style"
            android:checked="true"
            android:text="@string/o_map" />

        <RadioButton
            android:id="@+id/rb_i_map"
            style="@style/rb_style"
            android:text="@string/i_map" />

        <RadioButton
            android:id="@+id/rb_x_map"
            style="@style/rb_style"
            android:text="@string/x_map" />

        <RadioButton
            android:id="@+id/rb_z_map"
            style="@style/rb_style"
            android:text="@string/z_map" />

        <RadioButton
            android:id="@+id/rb_u_map"
            style="@style/rb_style"
            android:text="@string/u_map" />
    </RadioGroup>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:gravity="center_horizontal" >

        <Button
            android:id="@+id/btn_ok"
            style="@style/btn_style"
            android:onClick="doOK"
            android:text="@string/ok" />

        <Button
            android:id="@+id/btn_cancel"
            style="@style/btn_style"
            android:onClick="doCancel"
            android:text="@string/cancel" />
    </LinearLayout>

</LinearLayout>

(3)选择地图界面类SelectMapActivity

/**
 * 功能:选择地图
 * 作者:华卫
 * 日期:2017年1月1日
 */
package net.hw.snake.ui;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.RadioButton;
import net.hw.snake.R;
import net.hw.snake.app.AppConstants;
import net.hw.snake.app.SnakeApplication;

public class SelectMapActivity extends Activity implements AppConstants {
    /**
    * 口字型地图的单选按钮
    */
    private RadioButton rbOMap;
    /**
    * 工字型地图的单选按钮
    */
    private RadioButton rbIMap;
    /**
    * X型地图的单选按钮
    */
    private RadioButton rbXMap;
    /**
    * Z型地图的单选按钮
    */
    private RadioButton rbZMap;
    /**
    * U型地图的单选按钮
    */
    private RadioButton rbUMap;
    /**
    * 贪吃蛇应用程序
    */
    private SnakeApplication app;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 利用布局资源文件设置用户界面
        setContentView(R.layout.activity_select_map);

        // 通过资源标识获得界面控件实例
        rbOMap = (RadioButton) findViewById(R.id.rb_o_map);
        rbIMap = (RadioButton) findViewById(R.id.rb_i_map);
        rbXMap = (RadioButton) findViewById(R.id.rb_x_map);
        rbZMap = (RadioButton) findViewById(R.id.rb_z_map);
        rbUMap = (RadioButton) findViewById(R.id.rb_u_map);
        
        // 获取贪吃蛇应用程序对象
        app = (SnakeApplication) getApplication();

        // 设置地图单选按钮初始状态
        switch (app.getMapIndex()) {
        case O_SHAPE_MAP: // 口字型地图
            rbOMap.setChecked(true);
            break;
        case I_SHAPE_MAP: // I字型地图
            rbIMap.setChecked(true);
            break;
        case X_SHAPE_MAP: // X字型地图
            rbXMap.setChecked(true);
            break;
        case Z_SHAPE_MAP: // Z字型地图
            rbZMap.setChecked(true);
            break;
        case U_SHAPE_MAP: // U字型地图
            rbUMap.setChecked(true);
            break;
        }       
    }
    
    /**
    * 确定按钮单击事件处理方法
    * 
    * @param view
    */
    public void doOK(View view) {
        // 根据单选按钮选中状态设置地图索引
        if (rbOMap.isChecked()) {
            // 设置口字型地图
            app.setMapIndex(O_SHAPE_MAP);
        } else if (rbIMap.isChecked()) {
            // 设置工字型地图
            app.setMapIndex(I_SHAPE_MAP);
        } else if (rbXMap.isChecked()) {
            // 设置X字型地图
            app.setMapIndex(X_SHAPE_MAP);
        } else if (rbZMap.isChecked()) {
            // 设置Z字型地图
            app.setMapIndex(Z_SHAPE_MAP);
        } else if (rbUMap.isChecked()) {
            // 设置U字型地图
            app.setMapIndex(U_SHAPE_MAP);
        }
        
        // 关闭当前界面
        finish();
    }
    
    /**
    * 取消按钮单击事件处理方法
    * 
    * @param view
    */
    public void doCancel(View view) {
        // 关闭当前界面
        finish();
    }
}

14、在ui子包里创建设置速度界面类SetSpeedActivity

(1)设置速度界面布局文件activity_set_speed.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/speedback"
    android:gravity="center"
    android:orientation="vertical" >

    <RadioGroup
        android:id="@+id/rg_speed"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" >

        <RadioButton
            android:id="@+id/rb_low_speed"
            style="@style/rb_style"
            android:checked="true"
            android:text="@string/low_speed" />

        <RadioButton
            android:id="@+id/rb_medium_speed"
            style="@style/rb_style"
            android:text="@string/medium_speed" />

        <RadioButton
            android:id="@+id/rb_high_speed"
            style="@style/rb_style"
            android:text="@string/high_speed" />
    </RadioGroup>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:gravity="center_horizontal" >

        <Button
            android:id="@+id/btn_ok"
            style="@style/btn_style"
            android:text="@string/ok"
            android:onClick="doOK" />

        <Button
            android:id="@+id/btn_cancel"
            style="@style/btn_style"
            android:text="@string/cancel" 
            android:onClick="doCancel"/>
    </LinearLayout>

</LinearLayout>

(2)设置速度界面类SetSpeedActivity

/**
 * 功能:设置速度
 * 作者:华卫
 * 日期:2017年1月1日
 */
package net.hw.snake.ui;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.RadioButton;
import net.hw.snake.R;
import net.hw.snake.app.AppConstants;
import net.hw.snake.app.SnakeApplication;

public class SetSpeedActivity extends Activity implements AppConstants{
    /**
    * 低速单选按钮
    */
    private RadioButton rbLowSpeed;
    /**
    * 中速单选按钮
    */
    private RadioButton rbMediumSpeed;
    /**
    * 高速单选按钮
    */
    private RadioButton rbHighSpeed;
    /**
    * 贪吃蛇应用程序
    */
    private SnakeApplication app;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 利用布局资源文件设置用户界面
        setContentView(R.layout.activity_set_speed);

        // 通过资源索引获得界面控件实例
        rbLowSpeed = (RadioButton) findViewById(R.id.rb_low_speed);
        rbMediumSpeed = (RadioButton) findViewById(R.id.rb_medium_speed);
        rbHighSpeed = (RadioButton) findViewById(R.id.rb_high_speed);

        // 获取贪吃蛇应用程序对象
        app = (SnakeApplication) getApplication();

        // 设置速度单选按钮的初始状态
        switch (app.getSpeedIndex()) {
        case LOW_SPEED: // 低速
            rbLowSpeed.setChecked(true);
            break;
        case MEDIUM_SPEED: // 中速
            rbMediumSpeed.setChecked(true);
            break;
        case HIGH_SPEED: // 高速
            rbHighSpeed.setChecked(true);
            break;
        }
    }
    
    /**
    * 确定按钮单击事件处理方法
    * 
    * @param view
    */
    public void doOK(View view) {
        // 设置速度索引
        if (rbLowSpeed.isChecked()) {
            // 设置低速
            app.setSpeedIndex(LOW_SPEED);
        } else if (rbMediumSpeed.isChecked()) {
            // 设置中速
            app.setSpeedIndex(MEDIUM_SPEED);
        } else if (rbHighSpeed.isChecked()) {
            // 设置高速
            app.setSpeedIndex(HIGH_SPEED);
        }
        // 关闭当前界面
        finish();
    }

    /**
    * 取消按钮单击事件处理方法
    * 
    * @param view
    */
    public void doCancel(View view) {
        // 关闭当前界面
        finish();
    }
}

15、在ui子包里创建选择背景音乐界面类SelectMusicActivity

(1)选择背景音乐界面布局文件activity_select_music.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/musicback"
    android:gravity="center"
    android:orientation="vertical"
    android:padding="20dp" >

    <ListView
        android:id="@+id/lv_music"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp" >
    </ListView>

</LinearLayout>

(2)音乐列表项模板music_list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp"
    android:gravity="center"
    android:textColor="#000000"
    android:textSize="20sp" />

(3)选择背景音乐界面类SelectMusicActivity

/**
 * 功能:选择背景音乐
 * 作者:华卫
 * 日期:2017年1月1日
 */
package net.hw.snake.ui;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import net.hw.snake.R;
import net.hw.snake.app.SnakeApplication;

public class SelectMusicActivity extends Activity {
    /**
    * 音乐数组
    */
    private String[] musics;
    /**
    * 数组适配器
    */
    private ArrayAdapter<String> adapter;
    /**
    * 音乐列表控件
    */
    private ListView lvMusic;
    /**
    * 贪吃蛇应用程序
    */
    private SnakeApplication app;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 利用布局资源文件设置用户界面
        setContentView(R.layout.activity_select_music);

        // 通过资源索引获得界面控件实例
        lvMusic = (ListView) findViewById(R.id.lv_music);

        // 获取贪吃蛇应用程序对象
        app = (SnakeApplication) getApplication();

        // 初始化音乐数组
        musics = new String[] { "梦中的婚礼", "秋日的私语", "天空之城", "致爱丽丝", "贝尔加湖畔", "暗香" };
        // 创建数组适配器
        adapter = new ArrayAdapter<String>(this, R.layout.music_list_item, musics);
        // 给列表视图对象设置适配器
        lvMusic.setAdapter(adapter);

        // 给音乐列表控件注册监听器
        lvMusic.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
                // 设置背景音乐索引
                app.setMusicIndex(position);
                // 弹出吐司提示用户
                Toast.makeText(SelectMusicActivity.this, "您选择了【" + musics[position] + "】作为背景音乐!", Toast.LENGTH_LONG)
                        .show();
                // 关闭当前界面
                finish();
            }
        });
    }
}

16、在ui子包里创建排行榜界面类TopListActivity

(1)排行榜界面布局文件activity_top_list.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/toplistback"
    android:gravity="center"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/tv_first"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="80dp"
        android:text="@string/first"
        android:textColor="#0000ff"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/tv_second"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dp"
        android:text="@string/second"
        android:textColor="#0000ff"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/tv_third"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dp"
        android:text="@string/third"
        android:textColor="#0000ff"
        android:textSize="20sp" />

    <Button
        android:id="@+id/btn_back"
        style="@style/btn_style"
        android:layout_marginTop="50dp"
        android:text="@string/back"
        android:onClick="doBack" />

</LinearLayout>

(2)在字符串资源文件strings.xml里定义变量

(3)排行榜界面类TopListActivity

/**
 * 功能:排行榜
 * 作者:华卫
 * 日期:2017年1月1日
 */
package net.hw.snake.ui;

import net.hw.snake.R;
import net.hw.snake.app.AppConstants;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class TopListActivity extends Activity implements AppConstants {
    /**
    * 第一名标签
    */
    private TextView tvFirst;
    /**
    * 第二名标签
    */
    private TextView tvSecond;
    /**
    * 第三名标签
    */
    private TextView tvThird;
    /**
    * 共享参数对象
    */
    private SharedPreferences sp;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 利用布局资源文件设置用户界面
        setContentView(R.layout.activity_top_list);

        // 通过资源索引获得界面控件实例
        tvFirst = (TextView) findViewById(R.id.tv_first);
        tvSecond = (TextView) findViewById(R.id.tv_second);
        tvThird = (TextView) findViewById(R.id.tv_third);

        // 获取共享参数对象
        sp = getSharedPreferences(TOP_LIST_FILE_NAME, ACCESS_MODE);

        // 在标签上显示前三名分数
        tvFirst.setText("第一名:" + sp.getInt(SCORE_OF_FIRST, 0));
        tvSecond.setText("第二名:" + sp.getInt(SCORE_OF_SECOND, 0));
        tvThird.setText("第三名:" + sp.getInt(SCORE_OF_THIRD, 0));     
    }
    
    /**
    * 返回按钮单击事件处理方法
    * 
    * @param view
    */
    public void doBack(View view) {
        // 关闭当前界面
        finish();
    }
}

17、在ui子包里创建主界面类MainActivity

(1)主界面布局文件activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/mainback"
    android:gravity="center"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/tv_version"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="10dp"
        android:layout_marginTop="100dp"
        android:text="@string/version"
        android:textColor="#ff0000"
        android:textSize="30sp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:gravity="center"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/btn_start_game"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/start_game_button_selector"
            android:onClick="doStartGame" />

        <Button
            android:id="@+id/btn_select_map"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/select_map_button_selector"
            android:onClick="doSelectMap" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/btn_set_speed"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/set_speed_button_selector"
            android:onClick="doSetSpeed" />

        <Button
            android:id="@+id/btn_select_music"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/select_music_button_selector"
            android:onClick="doSelectMusic" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/btn_top_list"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/top_list_button_selector"
            android:onClick="doTopList" />

        <Button
            android:id="@+id/btn_help"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/help_button_selector"
            android:onClick="doHelp" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/btn_about_author"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/about_author_button_selector"
            android:onClick="doAboutAuthor" />

        <Button
            android:id="@+id/btn_exit"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/exit_game_button_selector"
            android:onClick="doExit" />
    </LinearLayout>

</LinearLayout>

(2)字符串资源文件strings.xml里定义变量

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">贪吃蛇V1.0</string>
    <string name="version">Version1.0</string>
    <string name="o_map">口字型地图</string>
    <string name="i_map">工字型地图</string>
    <string name="x_map">X字型地图</string>
    <string name="u_map">U字型地图</string>
    <string name="z_map">Z字型地图</string>
    <string name="low_speed">蛇移动速度:低速</string>
    <string name="medium_speed">蛇移动速度:中速</string>
    <string name="high_speed">蛇移动速度:高速</string>
    <string name="ok">确定</string>
    <string name="cancel">取消</string>
    <string name="first">第一名:</string>
    <string name="second">第二名:</string>
    <string name="third">第三名:</string>
    <string name="back">返回</string> 
    <string name="author_info">单位:泸职院信息工程系\n作者:华卫\n电话:15892921170\n邮箱:[email protected]</string>
    <string name="game_help">贪吃蛇又名贪食蛇,是一款经典的小游戏。玩家使用方向键操控一条长长的蛇不断吞苹果,同时蛇身随着吞下的苹果不断长,当蛇头撞到蛇身或障壁时游戏结束。</string>
    <string name="action_settings">设置</string>

</resources>

(4)主界面类MainActivity

/**
 * 功能:贪吃蛇主控界面
 * 作者:华卫
 * 日期:2017年1月1日
 */
package net.hw.snake.ui;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import net.hw.snake.R;

public class MainActivity extends Activity {    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 利用布局资源文件设置用户界面
        setContentView(R.layout.activity_main);     
    }

    /**
    * 启动游戏
    * 
    * @param view
    */
    public void doStartGame(View view) {
        startActivity(new Intent(MainActivity.this, SnakeActivity.class));
    }

    /**
    * 选择地图
    * 
    * @param view
    */
    public void doSelectMap(View view) {
        startActivity(new Intent(MainActivity.this, SelectMapActivity.class));
    }

    /**
    * 设置速度
    * 
    * @param view
    */
    public void doSetSpeed(View view) {
        startActivity(new Intent(MainActivity.this, SetSpeedActivity.class));
    }

    /**
    * 选择背景音乐
    * 
    * @param view
    */
    public void doSelectMusic(View view) {
        startActivity(new Intent(MainActivity.this, SelectMusicActivity.class));
    }

    /**
    * 排行榜
    * 
    * @param view
    */
    public void doTopList(View view) {
        startActivity(new Intent(MainActivity.this, TopListActivity.class));
    }

    /**
    * 帮助信息
    * 
    * @param view
    */
    public void doHelp(View view) {
        AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
        builder.setIcon(R.drawable.ic_launcher);
        builder.setTitle("帮助信息");
        builder.setMessage(getResources().getString(R.string.game_help));
        builder.setPositiveButton("确定", null);
        builder.show();
    }

    /**
    * 关于作者
    * 
    * @param view
    */
    public void doAboutAuthor(View view) {
        AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
        builder.setIcon(R.drawable.ic_launcher);
        builder.setTitle("关于作者");
        builder.setMessage(getResources().getString(R.string.author_info));
        builder.setPositiveButton("确定", null);
        builder.show();
    }

    /**
    * 退出程序
    * 
    * @param view
    */
    public void doExit(View view) {
        AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
        builder.setIcon(R.drawable.snake);
        builder.setTitle("温馨提示");
        builder.setMessage("您是否要退出【贪吃蛇】游戏?");
        builder.setPositiveButton("是", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                System.exit(0);
            }
        });
        builder.setNegativeButton("否", null);
        builder.show();
    }
}

18、在ui子包里修改启动界面类SplashScreen

(1)启动界面布局文件activity_splash_screen.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/splashlayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/splashback"
    android:gravity="center_vertical"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/tv_version"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="30dp"
        android:layout_marginTop="170dp"
        android:text="@string/version"
        android:textColor="#ff00ff"
        android:textSize="50sp" />

    <TextView
        android:id="@+id/tv_author"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="30dp"
        android:layout_marginTop="100dp"
        android:text="@string/author"
        android:textColor="#0000ff"
        android:textSize="16sp" />

</LinearLayout>

(2)字符串资源文件strings.xml里定义变量

(3)启动界面类SplashScreenActivity

/**
 * 功能:启动界面
 * 作者:华卫
 * 日期:2017年1月1日
 */
package net.hw.snake.ui;

import net.hw.snake.R;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.widget.TextView;

public class SplashScreenActivity extends Activity {
    /**
    * 启动画面延迟时间
    */
    private final int DELAY_TIME = 3000;    

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 利用布局资源文件设置用户界面
        setContentView(R.layout.activity_splash_screen);        
        
        // 利用消息处理器延迟处理
        new Handler().postDelayed(new Runnable() {
            public void run() {
                // 跳转到主控界面
                startActivity(new Intent(SplashScreenActivity.this,
                        MainActivity.class));
                // 关闭启动界面
                finish(); 
            }
        }, DELAY_TIME);
    }
}

19、项目清单文件设置全部界面都是全屏显示

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="net.hw.snake"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="15" />

    <application
        android:name="net.hw.snake.app.SnakeApplication"
        android:icon="@drawable/snake"
        android:label="@string/app_name"
        android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
        <activity android:name="net.hw.snake.ui.SplashScreenActivity" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name="net.hw.snake.ui.MainActivity" />
        <activity android:name="net.hw.snake.ui.SnakeActivity" />
        <activity android:name="net.hw.snake.ui.SelectMapActivity" />
        <activity android:name="net.hw.snake.ui.SelectMusicActivity" />
        <activity android:name="net.hw.snake.ui.SelectSpeedActivity" />
        <activity android:name="net.hw.snake.ui.TopListActivity" >
        </activity>
    </application>

</manifest>

猜你喜欢

转载自blog.csdn.net/howard2005/article/details/85605971
今日推荐