Android自定義View實(shí)現(xiàn)數(shù)獨(dú)游戲
先說一下數(shù)獨(dú)游戲的規(guī)則:
1.在整個橫坐標(biāo)和縱坐標(biāo)的9個格子上只能填土1-9的數(shù)字且不重復(fù)
2.在當(dāng)前3*3 的格子上填入1-9數(shù)字且不重復(fù)
先給大家看效果圖

項(xiàng)目思路
1、UI呈現(xiàn):這個放在 GameView 類里面
顯示原始數(shù)據(jù)
顯示當(dāng)然用戶填寫的數(shù)據(jù)
顯示用戶當(dāng)前點(diǎn)擊的位置
顯示候選區(qū)數(shù)據(jù)
2、邏輯處理:這個是放在Matrix類里面的
原始數(shù)據(jù):游戲開始的時候就要創(chuàng)建出來的,
當(dāng)前數(shù)據(jù):用戶填寫上去的實(shí)時數(shù)據(jù)
數(shù)據(jù)判斷:判斷這個位置可以修改數(shù)據(jù)嗎? 比如,原始數(shù)據(jù)就是不可以修改的
判斷這個位置可以填入的數(shù)據(jù),比如,原始數(shù)據(jù)這個位置有8了,就不能填8了。
代碼 GameView 類
public class GameView extends View {
private int PhoneWidth; // 手機(jī)屏幕的寬度
private int mGridWidth; // 當(dāng)前格子的寬度
private int[] mFalseNumber; // 候選區(qū)不能填寫的數(shù)字
private Paint mLinePaint; // 白線
private Paint mDarkPaint; // 淺藍(lán)色的 方格子
private Paint mOptDarkPaint; // 用戶點(diǎn)擊 淺綠色的格子
private Paint numberPaint; // 原始數(shù)據(jù) 數(shù)字
private Paint changePaint; // 用戶填寫的數(shù)字
private Paint mOptPaint; // 候選區(qū)數(shù)字
private Matrix M; // 用戶計(jì)算類
private float tCX;
private float tCY;
private int mOptBoard;
private int mOptNumber;
public GameView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
public GameView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public GameView(Context context) {
super(context);
initView();
}
private void initView() {
PhoneWidth = getResources().getDisplayMetrics().widthPixels;
mGridWidth = (PhoneWidth - 40) / 9;
tCX = mGridWidth / 2;
tCY = tCX - tCX / 2;
mFalseNumber = new int[9];
for (int i = 0; i < 9; i++) {
mFalseNumber[i] = i;
}
M = new Matrix();
initPaint();
invalidate();
}
private void initPaint() {
mLinePaint = new Paint();
mLinePaint.setColor(Color.WHITE);
mLinePaint.setStyle(Paint.Style.STROKE);
mLinePaint.setStrokeWidth(2f);
mDarkPaint = new Paint();
mDarkPaint.setColor(Color.parseColor("#52E7CD"));
mDarkPaint.setStyle(Paint.Style.FILL);
numberPaint = new Paint();
numberPaint.setColor(Color.WHITE);
numberPaint.setTextSize(mGridWidth * 0.65f);
numberPaint.setTextAlign(Paint.Align.CENTER);
numberPaint.setShadowLayer(10F, -5F, 8F, Color.parseColor("#999999"));
numberPaint.setAntiAlias(true);
mOptPaint = new Paint();
mOptPaint.setColor(Color.WHITE);
mOptPaint.setTextSize(mGridWidth * 0.65f+15);
mOptPaint.setTextAlign(Paint.Align.CENTER);
mOptPaint.setShadowLayer(10F, -5F, 8F, Color.parseColor("#999999"));
mOptPaint.setAntiAlias(true);
changePaint = new Paint();
changePaint.setColor(Color.parseColor("#FCA454"));
changePaint.setTextSize(mGridWidth * 0.65f);
changePaint.setTextAlign(Paint.Align.CENTER);
changePaint.setShadowLayer(10F, -5F, 8F, Color.parseColor("#999999"));
changePaint.setAntiAlias(true);
mOptDarkPaint = new Paint();
mOptDarkPaint.setColor(Color.parseColor("#52E76E"));
mOptDarkPaint.setStyle(Paint.Style.FILL);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(PhoneWidth, PhoneWidth + mGridWidth+20);
}
@Override
protected void onDraw(Canvas canvas) {
drawBoard(canvas);
int x = mOptBoard / 9;
int y = mOptBoard % 9;
// 畫出棋盤選擇框
canvas.drawRect(x * mGridWidth+22, y * mGridWidth+22, x * mGridWidth+20 + mGridWidth, y * mGridWidth+20 + mGridWidth, mOptDarkPaint);
// 當(dāng)前棋盤數(shù)據(jù)
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
int cutData = M.getCutData(i, j);
if (M.getOnClicked(i,j) && cutData>0) {
canvas.drawText(Integer.toString(cutData), i * mGridWidth + tCX+20, j*mGridWidth + mGridWidth - tCY+20, changePaint);
}
}
}
// 候選區(qū)文字
drawTrueText(canvas);
}
private void drawTrueText(Canvas canvas) {
float startY = PhoneWidth + 30;
// 畫平行四邊形
canvas.drawLine(50, startY, PhoneWidth-50, startY, mLinePaint);
canvas.drawLine(10, startY + mGridWidth - 40, PhoneWidth-10, startY + mGridWidth-40, mLinePaint);
canvas.drawLine(50, startY, 10, startY + mGridWidth - 40, mLinePaint);
canvas.drawLine(PhoneWidth-50, startY, PhoneWidth-10, startY + mGridWidth-40, mLinePaint);
float y = (mGridWidth - 30)/2.0f;
for (int i = 0; i < 9; i++) {
if (mFalseNumber[i] == 0) {
canvas.drawText(Integer.toString(i+1), i * mGridWidth + tCX+ 20, startY + (mGridWidth - tCY) - y, mOptPaint);
}
}
}
private void drawBoard(Canvas canvas) {
// 畫底色
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
int x = i / 3;
int y = j / 3;
if ((x == 0 || x == 2) && (y == 0 || y == 2)) {
canvas.drawRect(mGridWidth * j + 20, 20 + mGridWidth * i,
mGridWidth * j + 20 + mGridWidth, 20 + mGridWidth
* i + mGridWidth, mDarkPaint);
} else if (y == 1 && x == 1) {
canvas.drawRect(mGridWidth * j + 20, 20 + mGridWidth * i,
mGridWidth * j + 20 + mGridWidth, 20 + mGridWidth
* i + mGridWidth, mDarkPaint);
}
}
}
// 畫白線
for (int i = 0; i < 10; i++) {
canvas.drawLine(20, mGridWidth * i + 1 + 20, 9 * mGridWidth + 20,
mGridWidth * i + 1 + 20, mLinePaint);
canvas.drawLine(mGridWidth * i + 1 + 20, 0 + 20, mGridWidth * i + 1
+ 20, 9 * mGridWidth + 20, mLinePaint);
}
//畫初始數(shù)字
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (!M.getOnClicked(i, j)) {
canvas.drawText(M.getText(i, j), i * mGridWidth +20+tCX, mGridWidth * j + 20+mGridWidth-tCY, numberPaint);
}
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() != MotionEvent.ACTION_DOWN) {
return super.onTouchEvent(event);
}
if (event.getX()<20 || event.getY()<20 || event.getX()>PhoneWidth-20) {
Log.e("123", "點(diǎn)到邊了");
return super.onTouchEvent(event);
}
int choX = (int) ((event.getX()-20) / mGridWidth);
int choY = (int) ((event.getY()-20) / mGridWidth);
Log.i("game ", "optX "+choX+" optY "+choY);
if (event.getY() < PhoneWidth-20) { // 棋盤的點(diǎn)擊
if (M.getOnClicked(choX, choY)) {
mFalseNumber = new int[9];
int[] trueData = M.getFalseData(choY, choX);
mOptBoard = choX * 9 + choY;
for (int i : trueData) {
mFalseNumber[i - 1] = 1;
}
}
} else { // 候選區(qū)點(diǎn)擊
Log.e("game ","opt Number " + choX + 1);
System.out.println(Arrays.toString(mFalseNumber));
if (mFalseNumber[choX] == 0) {
mOptNumber = choX;
int x = mOptBoard / 9;
int y = mOptBoard % 9;
M.setCutData(x, y, mOptNumber+1);
}
}
invalidate();
return true;
}
// 再來一局
public void play() {
initView();
}
// 重頭開始
public void repeat(){
M.initCutData();
invalidate();
}
代碼 Matrix類
public class Matrix {
private int [][]mData ; // 原始數(shù)據(jù)
private int [][]mCutData; // 當(dāng)前數(shù)據(jù)
public Matrix() {
int i = (int)(Math.random()*5);
switch (i) {
case 1:
mData = GAMEDATA1;
break;
case 2:
mData = GAMEDATA2;
break;
case 3:
mData = GAMEDATA3;
break;
case 4:
mData = GAMEDATA4;
break;
case 0:
mData = GAMEDATA2;
break;
}
initCutData();
Log.e("Matrix", "random :"+i);
}
/** 得到當(dāng)前坐標(biāo)上的文字 */
public String getText(int x, int y){
String index = mData[x][y]+"";
if ("0".equals(index)) {
index = "";
}
return index;
}
/** 判斷該坐標(biāo)是否可以點(diǎn)擊 */
public boolean getOnClicked(int x, int y){
if (mData[x][y] == 0) {
return true;
}
return false;
}
/** 判斷該坐標(biāo)有哪些數(shù)不可用 */
public int[] getFalseData(int x, int y){
Set<Integer> set = new TreeSet<Integer>();
// 檢查X 軸有哪些不能點(diǎn)
for (int i = 0; i < 9; i++) {
int d = mData[y][i];
if (d!=0) {
set.add(d);
// LogUtils.e("x: "+d);
}
}
// 檢查 y 軸有哪些不能點(diǎn)
for (int i = 0; i < 9; i++) {
int d = mData[i][x];
if (d!=0) {
set.add(d);
// LogUtils.e("Y: "+d);
}
}
// 檢查 3*3 方格哪些不能點(diǎn)
x = x/3*3;
y = y/3*3;
// LogUtils.e(" x "+x+" Y "+y);
for (int i = x; i < x+3; i++) {
for (int j = y; j < y+3; j++) {
int d = mData[j][i];
if (d!=0) {
set.add(d);
// LogUtils.e("i "+i+"j "+j+" xy: "+d);
}
}
}
Integer[] arr2 = set.toArray(new Integer[0]);
// 數(shù)組的包裝類型不能轉(zhuǎn) 只能自己轉(zhuǎn);吧Integer轉(zhuǎn)為為int數(shù)組;
int[] result = new int[arr2.length];
for (int i = 0; i < result.length; i++) {
result[i] = arr2[i];
}
System.out.println("false Number : "+Arrays.toString(result));
return result;
}
/** 當(dāng)前棋盤數(shù)據(jù) */
public void initCutData(){
mCutData = new int[9][9];
for (int i = 0; i < mData.length; i++) {
for (int j = 0; j < mData[i].length; j++) {
mCutData[i][j] = mData[i][j];
}
}
for (int i = 0; i < mCutData.length; i++) {
System.out.println(Arrays.toString(mCutData[i]));
}
}
public void setCutData(int x, int y, int data){
if (getOnClicked(x, y)) {
mCutData[x][y] = data;
}
}
public int getCutData(int x, int y){
return mCutData[x][y];
}
}
代碼 MainActivity 類
public class MainActivity extends Activity {
private GameView gV;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
gV = (GameView) findViewById(R.id.game);
}
public void rePay(View v){
gV.repeat();
}
public void newPay(View v){
gV.play();
}
acitivity_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:background="#0EC5A5" android:orientation="vertical" > <TextView android:layout_width="match_parent" android:layout_height="52dp" android:layout_marginTop="10dp" android:gravity="center" android:text="SUDOKU" android:textColor="@android:color/white" android:textSize="30sp" /> <com.xuan.sudokugame.GameView android:id="@+id/game" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <LinearLayout android:layout_width="match_parent" android:layout_height="60dp" android:padding="10dp" android:orientation="horizontal" > <Button android:layout_width="0dp" android:onClick="rePay" android:layout_height="match_parent" android:layout_weight="1" android:layout_marginRight="10dp" android:text="重新開始" android:textColor="@android:color/white" android:background="@drawable/radius_border_gray"/> <Button android:onClick="newPay" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="再來一局" android:textColor="@android:color/white" android:background="@drawable/radius_border_gray"/> </LinearLayout> </LinearLayout>
然后運(yùn)行起來就是這個樣子的了。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Native.js獲取監(jiān)聽開關(guān)等操作Android藍(lán)牙設(shè)備實(shí)例代碼
本文為大家分享了Native.js對Android藍(lán)牙設(shè)備的操作實(shí)例代碼包括:監(jiān)聽藍(lán)牙開關(guān)狀態(tài),開啟關(guān)閉藍(lán)牙,獲取藍(lán)牙設(shè)備列表,藍(lán)牙連接票據(jù)打印機(jī)2018-09-09
Android Notification使用方法總結(jié)
這篇文章主要介紹了Android Notification使用方法總結(jié)的相關(guān)資料,這里提供了四種使用方法,需要的朋友可以參考下2017-09-09
Android中自定義控件的declare-styleable屬性重用方案
這篇文章主要介紹了Android中自定義控件的declare-styleable屬性重用方案,本文給出了一個終極重用解決方案,需要的朋友可以參考下2015-01-01
Android模仿Toast實(shí)現(xiàn)提示框效果
這篇文章主要為大家詳細(xì)介紹了Android模仿Toast實(shí)現(xiàn)提示框效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-08-08
Flutter實(shí)現(xiàn)資源下載斷點(diǎn)續(xù)傳的示例代碼
在項(xiàng)目開發(fā)中,特別是C端的產(chǎn)品,資源下載實(shí)現(xiàn)斷點(diǎn)續(xù)傳是非常有必要的。今天我們不講過多原理的知識,分享下簡單實(shí)用的資源斷點(diǎn)續(xù)傳2022-07-07
用于cocos2d-x引擎(ndk)中為android項(xiàng)目生成編譯文件列表
在android的ndk項(xiàng)目中,添加很多源文件之后總要手動編寫makefile來添加所有的源文件, 很麻煩,所以寫了一個自動生成編譯源文件列表的小工具2014-05-05
Android SwipeRefreshLayout超詳細(xì)講解
在android開發(fā)中,使用最多的數(shù)據(jù)刷新方式就是下拉刷新了,而完成此功能我們使用最多的就是第三方的開源庫PullToRefresh。現(xiàn)如今,google也忍不住推出了自己的下拉組件SwipeRefreshLayout,下面我們通過api文檔和源碼來分析學(xué)習(xí)如何使用SwipeRefreshLayout2022-11-11

