編輯:關於Android編程
在去年的時候曾經寫了一個Android小游戲——2048,也在應用商店上線了,
當初設計的時候還不覺得什麼,最近在整理代碼時卻覺得當時代碼設計得很是糟糕,代碼混亂,界面也不好看。於是就趁著假期重寫了一遍,游戲運行界面如下
實現的功能有:
1.有4x4,5x5,6x6三種規則
2.記錄歷史最高分
3.使用純色塊
4.保存游戲
5.開啟音效
6.更換背景圖
開發工具用的是Android Studio
游戲的思路並不復雜,甚至可以說是挺簡單的。
首先要自定義一個View,作為可滑動的方塊(其實滑動效果是通過改變數字與顏色來模擬實現的),這個View要繼承於FrameLayout
每一種不同數值的方塊有不同的顏色,通過設置“setBackgroundColor”來實現。
public class Card extends FrameLayout {
private TextView label;
private int num = 0;
//用於判斷是否純色塊
public boolean flag;
public Card(Context context) {
super(context);
label = new TextView(context);
label.setGravity(Gravity.CENTER);
label.setTextSize(24);
label.setBackgroundColor(Color.parseColor("#77E8E2D8"));
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
lp.setMargins(5, 5, 0, 0);
addView(label, lp);
}
public void setNum(int num) {
this.num = num;
if (num == 0) {
label.setText("");
label.setBackgroundColor(Color.parseColor("#77E8E2D8"));
}else{
if(!flag){
label.setText(num + "");
}
changeCardColor();
}
}
public int getNum() {
return num;
}
public void changeCardColor() {
switch (num) {
case 2:
label.setBackgroundColor(Color.parseColor("#5DB8E8"));
break;
case 4:
label.setBackgroundColor(Color.parseColor("#A52812"));
break;
case 8:
label.setBackgroundColor(Color.parseColor("#0E7171"));
break;
case 16:
label.setBackgroundColor(Color.parseColor("#C0BB39"));
break;
case 32:
label.setBackgroundColor(Color.parseColor("#623889"));
break;
case 64:
label.setBackgroundColor(Color.parseColor("#5C7235"));
break;
case 128:
label.setBackgroundColor(Color.parseColor("#826FA3"));
break;
case 256:
label.setBackgroundColor(Color.parseColor("#355659"));
break;
case 512:
label.setBackgroundColor(Color.parseColor("#BB719B"));
break;
case 1024:
label.setBackgroundColor(Color.parseColor("#9B8B53"));
break;
case 2048:
label.setBackgroundColor(Color.parseColor("#196A5D"));
break;
default:
label.setBackgroundColor(Color.parseColor("#8A7760"));
}
}
public boolean equals(Card c) {
return this.getNum() == c.getNum();
}
}
此外,可以看到不管是4x4規則的或者5x5,6x6的,整個可滑動區域都是一個正方形,方塊平均分布,因此可以自定義一個View,繼承於GridLayout,為之添加多個Card 。
GameView 的重點在於方塊的滑動判斷以及實現滑動效果。
SoundPool 的使用方法在Android5.0之後發生了改變,所以需要在代碼中判斷當前系統版本,從而使用不同的初始化方法。
public class GameView extends GridLayout {
// 存儲所有方塊
private Card[][] Cards;
// 當前游戲的行數與列數
private int Row;
// 游戲記錄
private SharedPreferences gameRecord;
// 存儲游戲音效開關記錄
private SharedPreferences GameSettings;
private SharedPreferences.Editor grEditor;
public ScoreChangeListen scoreChangeListen=null;
private Context context;
//當前得分
private int Score;
public SoundPool soundPool;
// private HashMap soundID;
private int soundID;;
private boolean soundSwitch;
private class Point {
public Point(int x, int y) {
this.x = x;
this.y = y;
}
int x;
int y;
}
public GameView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.ViewSet);
Row = mTypedArray.getInt(R.styleable.ViewSet_Row, 5);
mTypedArray.recycle();
super.setColumnCount(Row);
init();
}
public GameView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.ViewSet);
Row = mTypedArray.getInt(R.styleable.ViewSet_Row, 5);
mTypedArray.recycle();
super.setColumnCount(Row);
init();
}
public GameView(Context context) {
super(context);
this.context = context;
init();
}
// 初始化
private void init() {
gameRecord = context.getSharedPreferences("GameRecord", Context.MODE_PRIVATE);
GameSettings = context.getSharedPreferences("GameSettings", Context.MODE_PRIVATE);
boolean flag=GameSettings.getBoolean("SolidColorSwitch",false);
soundSwitch=GameSettings.getBoolean("SoundSwitch",false);
//SoundPool的構建方法在5.0系統之後發生了變化
if (Build.VERSION.SDK_INT < 21) {
soundPool = new SoundPool(1,AudioManager.STREAM_MUSIC,0);
}else{
SoundPool.Builder builder = new SoundPool.Builder();
builder.setMaxStreams(1);
AudioAttributes.Builder attrBuilder = new AudioAttributes.Builder();
attrBuilder.setLegacyStreamType(AudioManager.STREAM_MUSIC);
builder.setAudioAttributes(attrBuilder.build());
soundPool = builder.build();
}
soundID=soundPool.load(context,R.raw.sound,1);
grEditor = gameRecord.edit();
Cards = new Card[Row][Row];
for (int y = 0; y < Row; y++) {
for (int x = 0; x < Row; x++) {
Cards[x][y] = new Card(context);
Cards[x][y].flag=flag;
}
}
// 添加兩個初始方塊
randomCard();
randomCard();
}
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 計算方塊的邊長
int cardWidth = (Math.min(w, h) - 5) / Row;
// 添加方塊
addCard(cardWidth);
}
// 計算分數
private void countScore(int num) {
Score = Score + num;
if(scoreChangeListen!=null){
scoreChangeListen.OnNowScoreChange(Score);
if(soundSwitch){
soundPool.play(soundID, 1, 1, 0, 0, 1);
}
}
}
// 添加方塊
private void addCard(int cardWidth) {
for (int y = 0; y < Row; y++) {
for (int x = 0; x < Row; x++) {
addView(Cards[x][y], cardWidth, cardWidth);
}
}
}
// 生成偽隨機方塊
private void randomCard() {
List points = new ArrayList<>();
for (int x = 0; x < Row; x++) {
for (int y = 0; y < Row; y++) {
// 如果還有空白方塊
if (Cards[x][y].getNum() == 0) {
points.add(new Point(x, y));
}
}
}
if (points.size() == 0) {
return;
}
int index = points.size() / 2;
Cards[points.get(index).x][points.get(index).y].setNum(2);
}
// 左移
private void moveLeftCard() {
allMoveLeft();
for (int y = 0; y < Row; y++) {
for (int x = 0; x < Row - 1; x++) {
if (Cards[x][y].getNum() != 0) {
if (Cards[x][y].equals(Cards[x + 1][y])) {
int num = Cards[x][y].getNum();
Cards[x][y].setNum(2 * num);
Cards[x + 1][y].setNum(0);
countScore(num);
allMoveLeft();
}
}
}
}
randomCard();
}
// 右移
private void moveRightCard() {
allMoveRight();
for (int y = 0; y < Row; y++) {
for (int x = Row - 1; x > 0; x--) {
if (Cards[x][y].getNum() != 0) {
if (Cards[x][y].equals(Cards[x - 1][y])) {
int num = Cards[x][y].getNum();
Cards[x][y].setNum(2 * num);
Cards[x - 1][y].setNum(0);
countScore(num);
allMoveRight();
}
}
}
}
randomCard();
}
// 上移
private void moveUpCard() {
allMoveUp();
for (int x = 0; x < Row; x++) {
for (int y = 0; y < Row - 1; y++) {
if (Cards[x][y].getNum() != 0) {
if (Cards[x][y].equals(Cards[x][y + 1])) {
int num = Cards[x][y].getNum();
Cards[x][y].setNum(2 * num);
Cards[x][y + 1].setNum(0);
countScore(num);
allMoveUp();
}
}
}
}
randomCard();
}
// 下移
private void moveDownCard() {
allMoveDown();
for (int x = 0; x < Row; x++) {
for (int y = Row - 1; y > 0; y--) {
if (Cards[x][y].getNum() != 0) {
if (Cards[x][y].equals(Cards[x][y - 1])) {
int num = Cards[x][y].getNum();
Cards[x][y].setNum(2 * num);
Cards[x][y - 1].setNum(0);
countScore(num);
allMoveDown();
}
}
}
}
randomCard();
}
// 全部左移
private void allMoveLeft() {
for (int y = 0; y < Row; y++) {
int i = 0;
for (int x = 0; x < Row; x++) {
if (Cards[x][y].getNum() != 0) {
int num = Cards[x][y].getNum();
Cards[x][y].setNum(0);
Cards[i++][y].setNum(num);
}
}
}
}
// 全部右移
private void allMoveRight() {
for (int y = 0; y < Row; y++) {
int i = Row - 1;
for (int x = Row - 1; x > -1; x--) {
if (Cards[x][y].getNum() != 0) {
int num = Cards[x][y].getNum();
Cards[x][y].setNum(0);
Cards[i--][y].setNum(num);
}
}
}
}
// 全部上移
private void allMoveUp() {
for (int x = 0; x < Row; x++) {
int i = 0;
for (int y = 0; y < Row; y++) {
if (Cards[x][y].getNum() != 0) {
int num = Cards[x][y].getNum();
Cards[x][y].setNum(0);
Cards[x][i++].setNum(num);
}
}
}
}
// 全部下移
private void allMoveDown() {
for (int x = 0; x < Row; x++) {
int i = Row - 1;
for (int y = Row - 1; y > -1; y--) {
if (Cards[x][y].getNum() != 0) {
int num = Cards[x][y].getNum();
Cards[x][y].setNum(0);
Cards[x][i--].setNum(num);
}
}
}
}
// 觸屏事件監聽
float X;
float Y;
float OffsetX;
float OffsetY;
int HintCount = 0;
public boolean isHalfway = true;
public boolean onTouchEvent(MotionEvent event) {
// 為了避免當游戲結束時消息多次提示
if (HintCount == 1) {
return true;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
X = event.getX();
Y = event.getY();
break;
case MotionEvent.ACTION_UP:
OffsetX = event.getX() - X;
OffsetY = event.getY() - Y;
if (Math.abs(OffsetX) > (Math.abs(OffsetY))) {
if (OffsetX < -5) {
moveLeftCard();
} else if (OffsetX > 5) {
moveRightCard();
}
} else {
if (OffsetY < -5) {
moveUpCard();
} else if (OffsetY > 5) {
moveDownCard();
}
}
HintMessage();
break;
}
return true;
}
// 判斷游戲是否結束
private boolean isOver() {
for (int y = 0; y < Row; y++) {
for (int x = 0; x < Row; x++) {
if ((Cards[x][y].getNum() == 0) || (x - 1 >= 0 && Cards[x - 1][y].equals(Cards[x][y]))
|| (x + 1 <= Row - 1 && Cards[x + 1][y].equals(Cards[x][y]))
|| (y - 1 >= 0 && Cards[x][y - 1].equals(Cards[x][y]))
|| (y + 1 <= Row - 1 && Cards[x][y + 1].equals(Cards[x][y]))) {
return false;
}
}
}
return true;
}
// 當游戲結束時提示信息
private void HintMessage() {
if (isOver()) {
Toast.makeText(getContext(), "游戲結束啦", Toast.LENGTH_SHORT).show();
HintCount=1;
}
}
//重新開始
public void restart(){
for (int y = 0; y < Row; y++) {
for (int x = 0; x < Row; x++) {
Cards[x][y].setNum(0);
}
}
Score=0;
HintCount=0;
// 添加兩個初始方塊
randomCard();
randomCard();
}
//保存游戲
public void saveGame(){
grEditor.clear();
grEditor.putInt("Row", Row);
grEditor.putInt("Score",Score);
int k = 0;
for (int i = 0; i < Row; i++) {
for (int j = 0; j < Row; j++) {
k++;
String str = k + "";
grEditor.putInt(str, Cards[i][j].getNum());
}
}
if( grEditor.commit()){
Toast.makeText(context, "保存成功", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(context, "保存失敗,請重試", Toast.LENGTH_SHORT).show();
}
}
// 恢復游戲
public void recoverGame() {
int k = 0;
for (int i = 0; i < Row; i++) {
for (int j = 0; j < Row; j++) {
k++;
String str = k + "";
int num = gameRecord.getInt(str, 0);
Cards[i][j].setNum(num);
}
}
Score=gameRecord.getInt("Score",0);
scoreChangeListen.OnNowScoreChange(Score);
}
}
要注意的是,在GameView的構造函數中,需要讀取GameView的一個自定義屬性“Row”,如果沒有指定則默認為5。該屬性的定義在values文件夾的attrs.xml文件中。
這樣,在布局文件中使用GameView時,先加上屬性聲明
xmlns:my="http://schemas.android.com/apk/res-auto"
然後就可以為GameView設置顯示行數了
整個游戲界面是由GameActivity呈現的,該Activity通過Bundle 攜帶的數據使用不同的布局文件。
public class GameActivity extends AppCompatActivity {
private GameView gameView;
private TextView text_nowScore;
private TextView text_highestScore;
private TextView text_restart;
private TextView text_saveGame;
private ScoreChangeListen scoreChangeListen;
// 游戲設置
private SharedPreferences gameSettings;
private SharedPreferences.Editor gsEditor;
// 歷史最高分
private int highestScore;
// 用於實現“在點擊一次返回鍵退出程序”的效果
private boolean isExit = false;
private boolean flag;
private int temp;
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
super.handleMessage(msg);
isExit = false;
}
};
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().hide();
Intent intent = getIntent();
Bundle bundle = intent.getExtras();
int row = bundle.getInt("Row", 4);
if (row == 4) {
setContentView(R.layout.activity_four);
} else if (row == 5) {
setContentView(R.layout.activity_five);
} else {
setContentView(R.layout.activity_six);
}
init();
//判斷是否需要恢復游戲記錄
if (bundle.getBoolean("RecoverGame", false)) {
gameView.recoverGame();
}
}
// 初始化
public void init() {
gameView = (GameView) findViewById(R.id.gameView_five);
text_nowScore = (TextView) findViewById(R.id.nowScore);
text_highestScore = (TextView) findViewById(R.id.highestScore);
text_restart = (TextView) findViewById(R.id.restart);
text_saveGame = (TextView) findViewById(R.id.save_game);
gameSettings = getSharedPreferences("GameSettings", Context.MODE_PRIVATE);
gsEditor = gameSettings.edit();
highestScore = gameSettings.getInt("HighestScore", 0);
text_nowScore.setText("當前得分\n" + 0);
text_highestScore.setText("最高得分\n" + highestScore);
flag = true;
LinearLayout rootLayout = (LinearLayout) findViewById(R.id.rootLayout);
int themeIndex = gameSettings.getInt("ThemeIndex", 1);
switch (themeIndex) {
case 1:
rootLayout.setBackgroundResource(R.drawable.back1);
break;
case 2:
rootLayout.setBackgroundResource(R.drawable.back2);
break;
case 3:
rootLayout.setBackgroundResource(R.drawable.back3);
break;
case 4:
rootLayout.setBackgroundResource(R.drawable.back4);
break;
case 5:
rootLayout.setBackgroundResource(R.drawable.back5);
break;
case 6:
rootLayout.setBackgroundResource(R.drawable.back6);
break;
}
//重新開始游戲
text_restart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(GameActivity.this);
builder.setMessage("確認重新開始游戲嗎?");
builder.setTitle("提示");
builder.setPositiveButton("確認", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
gameView.restart();
text_nowScore.setText("當前得分\n" + 0);
if (temp != 0) {
scoreChangeListen.OnHighestScoreChange(temp);
highestScore = temp;
flag = true;
}
}
});
builder.setNegativeButton("取消", null);
builder.create().show();
}
});
//保存游戲
text_saveGame.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(GameActivity.this);
builder.setMessage("確認保存游戲嗎?");
builder.setTitle("提示");
builder.setPositiveButton("確認", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
gameView.saveGame();
}
});
builder.setNegativeButton("取消", null);
builder.create().show();
}
});
scoreChangeListen = new ScoreChangeListen() {
@Override
public void OnNowScoreChange(int Score) {
text_nowScore.setText("當前得分\n" + Score);
if (Score > highestScore) {
if (flag && highestScore != 0) {
Toast.makeText(GameActivity.this, "打破最高紀錄啦,請繼續保持", Toast.LENGTH_SHORT).show();
flag = false;
}
temp = Score;
text_highestScore.setText("最高得分\n" + temp);
}
}
@Override
public void OnHighestScoreChange(int Score) {
gsEditor.putInt("HighestScore", Score);
gsEditor.commit();
}
};
gameView.scoreChangeListen = scoreChangeListen;
}
// 重寫返回鍵監聽事件
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
exit();
return false;
}
return super.onKeyDown(keyCode, event);
}
private void exit() {
if (!isExit) {
isExit = true;
if (gameView.isHalfway) {
Toast.makeText(this, "再按一次結束游戲,建議保存游戲", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "再按一次結束游戲", Toast.LENGTH_SHORT).show();
}
// 利用handler延遲發送更改狀態信息
mHandler.sendEmptyMessageDelayed(0, 2000);
} else {
finish();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (temp != 0) {
scoreChangeListen.OnHighestScoreChange(temp);
}
gameView.soundPool.release();
}
}
當中,可以通過mHandler實現“再按一次退出程序的效果”,這個效果需要靠boolean類型的isExit 來控制。
即如果用戶點擊了一次返回鍵後,mHandler就會在兩秒後發送一條消息改變isExit 的值,如果在這兩秒內用戶沒有再次點擊返回鍵,則就又需要連續點擊兩次返回鍵才能退出。
private void exit() {
if (!isExit) {
isExit = true;
if (gameView.isHalfway) {
Toast.makeText(this, "再按一次結束游戲,建議保存游戲", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "再按一次結束游戲", Toast.LENGTH_SHORT).show();
}
// 利用handler延遲發送更改狀態信息
mHandler.sendEmptyMessageDelayed(0, 2000);
} else {
finish();
}
}
GameActivity中使用到了一個自定義接口ScoreChangeListen
/**
* Created by ZY on 2016/7/18.
*/
public interface ScoreChangeListen {
void OnNowScoreChange(int Score);
void OnHighestScoreChange(int Score);
}
因為顯示當前分數以及歷史最高分的是兩個TextView,GameView無法直接控制,所以就使用回調函數來間接控制TextView的值。
MainActivity的布局也較為簡單,一共是六個ImageView,設定點擊不同的ImageView執行特定的函數
public class MainActivity extends AppCompatActivity {
// 游戲記錄
private SharedPreferences gameRecord;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportActionBar().hide();
gameRecord = getSharedPreferences("GameRecord", Context.MODE_PRIVATE);
}
//軟件說明
public void explain(View view){
Intent intent=new Intent(MainActivity.this,ExplainActivity.class);
startActivity(intent);
}
// 4乘4
public void fourRow(View view) {
Intent intent = new Intent(MainActivity.this, GameActivity.class);
Bundle bundle=new Bundle();
bundle.putInt("Row",4);
intent.putExtras(bundle);
startActivity(intent);
}
// 5乘5
public void fiveRow(View view) {
Intent intent = new Intent(MainActivity.this, GameActivity.class);
Bundle bundle=new Bundle();
bundle.putInt("Row",5);
intent.putExtras(bundle);
startActivity(intent);
}
// 6乘6
public void sixRow(View view) {
Intent intent = new Intent(MainActivity.this, GameActivity.class);
Bundle bundle=new Bundle();
bundle.putInt("Row",6);
intent.putExtras(bundle);
startActivity(intent);
}
//恢復游戲
public void recoverGame(View view){
if(gameRecord.contains("Row")){
int row=gameRecord.getInt("Row",4);
Bundle bundle=new Bundle();
Intent intent = new Intent(MainActivity.this, GameActivity.class);
if(row==4){
bundle.putInt("Row",4);
}else if(row==5){
bundle.putInt("Row",5);
}else{
bundle.putInt("Row",6);
}
bundle.putBoolean("RecoverGame",true);
intent.putExtras(bundle);
startActivity(intent);
}else{
Toast.makeText(MainActivity.this,"沒有保存記錄,來一局新游戲吧",Toast.LENGTH_SHORT).show();
}
}
//設置
public void settings(View view){
Intent intent = new Intent(MainActivity.this, SettingsActivity.class);
startActivity(intent);
}
}
ExplainActivity和SettingsActivity兩個Activity較為簡單這裡就不再贅述
代碼下載地址:Android游戲——2048的設計
訪問密碼:286a
如果鏈接失效,可以留言,我會補發的~
很多開發者一聽說Android終端的屏幕尺寸五花八門,屏幕分辨率千奇百怪,就覺得Android開發在屏幕適配方面是必定是一件頭疼的事情。因為在Android問世之前,廣大
前面文章講解了Android的藍牙基本用法,本文講得深入些,探討下藍牙方面的隱藏API。用過Android系統設置(Setting)的人都知道藍牙搜索之後可以建立配對和解
為了讓界面可以在平板上更好地展示,Android在3.0版本引入了Fragment(碎片)功能。首先需要注意,Fragment是在3.0版本引入的,如果你使用的是3.0之
1、頁面初始化在app開發中,若要使用HTML5+擴展api,必須等plusready事件發生後才能正常使用,mui將該事件封裝成了mui.plusReady()方法,涉