編輯:關於Android編程
視頻鏈接:http://www.imooc.com/learn/641阿法狗讓圍棋突然就被熱議了,鴻洋大神也順勢出了篇五子棋單機游戲的視頻,我看到了就像膜拜膜拜,就學習了一下,寫篇博客梳理一下自己的思路,加深一下印象
我們一看就知道,我們必須自定義View,這裡我們定義一個GameView來做游戲主類,第一步,先測量,我們這裡不難知道,五子棋他的棋盤是一個正方形,所以我們需要去測量
/**
* 測量
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//獲取高寬值
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int hightSize = MeasureSpec.getSize(heightMeasureSpec);
int hightMode = MeasureSpec.getMode(heightMeasureSpec);
//拿到寬和高的最小值,也就是寬
int width = Math.min(widthSize, heightMeasureSpec);
//根據測量模式細節處理
if (widthMode == MeasureSpec.UNSPECIFIED) {
width = hightSize;
} else if (hightMode == MeasureSpec.UNSPECIFIED) {
width = widthSize;
}
//設置這樣就是一個正方形了
setMeasuredDimension(width, width);
}
這裡的邏輯還是十分簡單的,我們拿到長和寬去比較一下,設置這個View的長寬Wie最小值,就是一個正方形了,所以我們的layout_main.xml是這樣寫的
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="@drawable/main_bg"> <com.lgl.fiverow.gameview android:layout_width="match_parent" android:layout_height="match_parent"> </com.lgl.fiverow.gameview></linearlayout> </code>
這裡我在構造方法中設置了一個半透明的紅色背景,是在我們調試的時候可以更加清晰的看清楚GameView的大小,所以,運行的結果
這個應該也算是棋盤的一部分吧,就是棋盤上的線條,我們應該怎麼去畫,首先,我們要去定義一些屬性
//線條數量
private static final int MAX_LINE = 10;
//線條的寬度
private int mPanelWidth;
//線條的高度
private float mLineHeight;
然後,我們要去確定大小
/**
* 測量大小
*
* @param w
* @param h
* @param oldw
* @param oldh
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//拿到寬
mPanelWidth = w;
//分割
mLineHeight = mPanelWidth * 1.0f / MAX_LINE;
}
不要著急,這些都只是一些准備的工作,我們畫線條是必須要在onDraw(0方法裡的,但是前期我們要准備一只畫筆,對吧,所以我們要初始化畫筆
/**
* 初始化畫筆
*/
private void initPaint() {
//設置顏色
mPaint.setColor(0x88000000);
//抗鋸齒
mPaint.setAntiAlias(true);
//設置防抖動
mPaint.setDither(true);
//設置Style
mPaint.setStyle(Paint.Style.STROKE);
}
現在我們可以去繪制了,我們在OnDraw(0方法裡寫一個drawLine方法來專門繪制線條
/**
* 繪制棋盤的方法
*
* @param canvas
*/
private void drawLine(Canvas canvas) {
//獲取高寬
int w = mPanelWidth;
float lineHeight = mLineHeight;
//遍歷,繪制線條
for (int i = 0; i < MAX_LINE; i++) {
//橫坐標
int startX = (int) (lineHeight / 2);
int endX = (int) (w - lineHeight / 2);
//縱坐標
int y = (int) ((0.5 + i) * lineHeight);
//繪制橫
canvas.drawLine(startX, y, endX, y, mPaint);
//繪制縱
canvas.drawLine(y, startX, y, endX, mPaint);
}
}
我們運行一下
好的,這裡,注意一下,我在activity_main.xml中定義了一個
android:gravity="center"
屬性,所以讓他居中,同樣的,我們在initPaint中加上點代碼讓我們看的更加直觀一點
//設置顏色
mPaint.setColor(Color.BLACK);
//設置線條寬度
mPaint.setStrokeWidth(3);
同樣的,我們把構造法裡的設置背景的測試代碼注釋掉
//測試代碼
//setBackgroundColor(0x44ff0000);
這樣,我們運行一下
得,我們現在有模有樣了
棋子我們事先准備好了兩張圖片,但是這裡我們要考慮他的大小的問題了,我們的思路是讓他是行高的四分之三大小,所以先聲明
//黑棋子
private Bitmap mBlack;
//白棋子
private Bitmap mWhite;
//比例,棋子的大小是高的四分之三
private float rowSize = 3 * 1.0f / 4;
然後我們定義一個方法區初始化Bitmap
/**
* 初始化棋子
*/
private void initBitmap() {
//拿到圖片資源
mBlack = BitmapFactory.decodeResource(getResources(), R.drawable.stone_black);
mWhite = BitmapFactory.decodeResource(getResources(), R.drawable.stone_white);
}
拿到資源之後我們就可以設置大小了,我們在onSizeChanged()裡面設置
//棋子寬度
int mWhiteWidth = (int) (mLineHeight * rowSize);
//修改棋子大小
mWhite = Bitmap.createScaledBitmap(mWhite, mWhiteWidth, mWhiteWidth, false);
mBlack = Bitmap.createScaledBitmap(mBlack, mWhiteWidth, mWhiteWidth, false);
不過棋子可沒我們想象的那麼簡單,我們要點擊一下再去繪制一個棋子,這樣的思路該怎麼去實現呢?我們實現它的點擊事件,這裡先定義幾個變量
//存儲用戶點擊的坐標
private List mWhiteArray = new ArrayList<>();
private List mBlackArray = new ArrayList<>();
//標記,是執黑子還是白子 ,白棋先手
private boolean mIsWhite = true;
這樣才和觸摸事件相得映彰
/**
* 觸摸事件
*
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
//按下事件
case MotionEvent.ACTION_UP:
int x = (int) event.getX();
int y = (int) event.getY();
//封裝成一個Point
Point p = getValidPoint(x, y);
//判斷當前這個點是否有棋子了
if(mWhiteArray.contains(p) || mBlackArray.contains(p)){
//點擊不生效
return false;
}
//判斷如果是白子就存白棋集合,反之則黑棋集合
if (mIsWhite) {
mWhiteArray.add(p);
} else {
mBlackArray.add(p);
}
//刷新
invalidate();
//改變值
mIsWhite = !mIsWhite;
break;
}
return true;
}
這樣,有幾點是要說明一下的,首先我們new Point的時候為了避免重復繪制我們是實現了一個方法
/**
* 不能重復點擊
*
* @param x
* @param y
* @return
*/
private Point getValidPoint(int x, int y) {
return new Point((int) (x / mLineHeight), (int) (y / mLineHeight));
}
緊接著我們就判斷,要是重復點擊,返回false,而且我們在action選擇也是選擇了ACTION_UP,為什麼?為什麼不是ACTION_DOWN?因為這個畢竟是一個View,父View會攔截(某些場景),所以我們選在UP上才是合情合理的
好的,當我們點擊之後就要繪制棋子了,這裡我們也寫一個方法
/**
* 繪制棋子的方法
*
* @param canvas
*/
private void drawPieces(Canvas canvas) {
for (int i = 0; i < mWhiteArray.size(); i++) {
//獲取白棋子的坐標
Point whitePoint = mWhiteArray.get(i);
canvas.drawBitmap(mBlack, (whitePoint.x + (1 - rowSize) / 2) * mLineHeight, (whitePoint.y + (1 - rowSize) / 2) * mLineHeight, null);
}
for (int i = 0; i < mBlackArray.size(); i++) {
//獲取黑棋子的坐標
Point blackPoint = mBlackArray.get(i);
canvas.drawBitmap(mWhite, (blackPoint.x + (1 - rowSize) / 2) * mLineHeight, (blackPoint.y + (1 - rowSize) / 2) * mLineHeight, null);
}
}
OK,我們實際運行一下
現在什麼都有了,基本上都可用玩了,但是還少了重要的一點就是游戲結束,你到了五顆也需要判斷是否勝利呀,對吧,我們寫一個方法,在每次繪制完成之後就去判斷是否有贏家
/**
* 判斷是否勝利
*/
private void checkWin() {
//判斷白棋是否有五個相同的棋子相連
boolean mWhiteWin = checkFiveLine(mWhiteArray);
//判斷黑棋是否有五個相同的棋子相連
boolean mBlackWin = checkFiveLine(mBlackArray);
//只要有一個勝利,游戲就結束
if (mWhiteWin || mBlackWin) {
mIsGameOver = true;
mIsWhiteWin = mWhiteWin;
Toast.makeText(getContext(), mIsWhiteWin ? "白棋勝利" : "黑棋勝利", Toast.LENGTH_SHORT).show();
}
}
好的,我們重點邏輯就在checkFiveLine這個方法上了,這裡,我們所知道的勝利有四種情況
我們先定義一個常量
//勝利棋子數量
private static final int MAX_COUNT_IN_LINE = 5;
OK,接下來我們可以實現以下勝利的邏輯了
/**
* //判斷棋子是否有五個相同的棋子相連
*
* @param points
* @return
*/
private boolean checkFiveLine(List points) {
//遍歷棋子
for (Point p : points) {
//拿到棋盤上的位置
int x = p.x;
int y = p.y;
/**
* 四種情況勝利,橫,豎,左斜,右斜
*/
//橫
boolean win = checkHorizontal(x, y, points);
if (win) return true;
//豎
win = checkVertical(x, y, points);
if (win) return true;
//左斜
win = checkLeft(x, y, points);
if (win) return true;
//右斜
win = checkRight(x, y, points);
if (win) return true;
}
return false;
}
橫我們不管哪個方向只要返回true就返回true,然後彈Toast,這裡,四個方向的邏輯
/**
* 判斷橫向的棋子
*
* @param x
* @param y
* @param points
*/
private boolean checkHorizontal(int x, int y, List points) {
//棋子標記,記錄是否有五個 =1是因為自身是一個
int count = 1;
//左
for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
//如果有
if (points.contains(new Point(x - i, y))) {
count++;
} else {
break;
}
}
//有五個就為true
if (count == MAX_COUNT_IN_LINE) {
return true;
}
//右
for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
//如果有
if (points.contains(new Point(x + i, y))) {
count++;
} else {
break;
}
}
//有五個就為true
if (count == MAX_COUNT_IN_LINE) {
return true;
}
return false;
}
橫
/**
* 判斷縱向的棋子
*
* @param x
* @param y
* @param points
*/
private boolean checkVertical(int x, int y, List points) {
//棋子標記,記錄是否有五個 =1是因為自身是一個
int count = 1;
//上
for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
//如果有
if (points.contains(new Point(x, y - i))) {
count++;
} else {
break;
}
}
//有五個就為true
if (count == MAX_COUNT_IN_LINE) {
return true;
}
//下
for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
//如果有
if (points.contains(new Point(x, y + i))) {
count++;
} else {
break;
}
}
//有五個就為true
if (count == MAX_COUNT_IN_LINE) {
return true;
}
return false;
}
左斜
/**
* 判斷左斜向的棋子
*
* @param x
* @param y
* @param points
*/
private boolean checkLeft(int x, int y, List points) {
//棋子標記,記錄是否有五個 =1是因為自身是一個
int count = 1;
for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
//如果有
if (points.contains(new Point(x - i, y + i))) {
count++;
} else {
break;
}
}
//有五個就為true
if (count == MAX_COUNT_IN_LINE) {
return true;
}
for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
//如果有
if (points.contains(new Point(x + i, y - i))) {
count++;
} else {
break;
}
}
//有五個就為true
if (count == MAX_COUNT_IN_LINE) {
return true;
}
return false;
}
右斜
/**
* 判斷右斜向的棋子
*
* @param x
* @param y
* @param points
*/
private boolean checkRight(int x, int y, List points) {
//棋子標記,記錄是否有五個 =1是因為自身是一個
int count = 1;
for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
//如果有
if (points.contains(new Point(x - i, y - i))) {
count++;
} else {
break;
}
}
//有五個就為true
if (count == MAX_COUNT_IN_LINE) {
return true;
}
for (int i = 1; i < MAX_COUNT_IN_LINE; i++) {
//如果有
if (points.contains(new Point(x + i, y + i))) {
count++;
} else {
break;
}
}
//有五個就為true
if (count == MAX_COUNT_IN_LINE) {
return true;
}
return false;
}
這樣,我們運行一下
嘿嘿,好玩吧!<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjwvYmxvY2txdW90ZT4NCjxoMiBpZD0="五游戲狀態存儲">五.游戲狀態存儲
這個就是當我們游戲掛後台之後,再回來游戲就沒了,我們應該存儲他的狀態,讓他每一次進入的時候要是上一句沒有下完接著下,那我們該怎麼去實現呢?和Activity一樣,我們View也有存儲狀態的方法
/** * 存儲狀態 * * @return */ @Override protected Parcelable onSaveInstanceState() { Bundle bundle = new Bundle(); bundle.putParcelable(INSTANCE, super.onSaveInstanceState()); bundle.putBoolean(INSTANCE_GAMEOVER, mIsGameOver); bundle.putParcelableArrayList(INSTANCE_WHITE_ARRAY, mWhiteArray); bundle.putParcelableArrayList(INSTANCE_BLACK_ARRAY, mBlackArray); return bundle; } /** * 重新運行 * * @param state */ @Override protected void onRestoreInstanceState(Parcelable state) { //取值 if (state instanceof Bundle) { Bundle bundle = (Bundle) state; mIsGameOver = bundle.getBoolean(INSTANCE_GAMEOVER); mWhiteArray = bundle.getParcelableArrayList(INSTANCE_WHITE_ARRAY); mBlackArray = bundle.getParcelableArrayList(INSTANCE_BLACK_ARRAY); //調用 super.onRestoreInstanceState(bundle.getParcelable(INSTANCE)); return; } super.onRestoreInstanceState(state); }
這樣就可以了,但是,有一點要知道,不要忘記在布局文件上給控件加上ID,不然狀態不會存儲哦
六.再來一局
既然我們的游戲邏輯差不多了,那我們應該考慮一下當你勝利的時候,你是不是應該再來一局,所以我們還要實現這個邏輯,這個很簡單
/** * 再來一局 */ public void RestartGame() { mWhiteArray.clear(); mBlackArray.clear(); mIsGameOver = false; mIsWhiteWin = false; invalidate(); }
這樣,我們就可以直接調用了,我們來看看MainActivity
package com.lgl.fiverow; import android.support.design.widget.FloatingActionButton; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; /** * 五子棋游戲 */ public class MainActivity extends AppCompatActivity { //重來按鈕 private FloatingActionButton fab; //游戲 private GameView game; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); game = (GameView) findViewById(R.id.mGameView); fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { game.RestartGame(); } }); } }
OK,我們最終運行一下
OK,到這裡,就算開發完成了
Android DNS 代碼都在bionic/libc/netbsd中 (雖然netbsd 是個廢棄的項目,但dns功能部分代碼被 Android用上了) netbsd
Android系統根據生命周期的不同階段喚起對應的回調函數來執行代碼。系統存在啟動與銷毀一個activity的一套有序的回調函數。本節來討論下不同生命周期的回調函數裡都該
要想讓您的控件水平居中或垂直居中其實很簡單,只要在控件的上一級中設置【android:gravity=center】屬性即可如:<LinearLayout xmln
BottomNavigationView 很早之前就在 Material Design 中出現了,但是直到 Android Support Library 25 中才增加