最近研究了一下android,写了一个数独游戏,具体如下:
游戏界面需要重写一个ShuduView继承View,
然后自定义一个Dialog:
1.需要继承 Dialog 类,
2.并要定义一个有参构造函数(因为父类里面没有无参构造函数)
3.重写 onCreate方法,一切操作将在此方法进行
流程:
为每个按钮添加监听事件,
刷新九宫格里的数字,也就是重新绘制画面(在view类中调用 invalidate();),
更新备选数字数组 ( 每次修改之后都得 进行重新计算 不可用的值 calculateAllUsedTiles() ; )
下面介绍主要代码:
ShuduView:
1 package com.soccer.shudu; 2 3 import android.content.Context; 4 import android.graphics.*; 5 import android.graphics.Paint.FontMetrics; 6 7 8 import android.view.MotionEvent; 9 import android.view.View; 10 import android.view.WindowManager; 11 12 13 public class ShuduView extends View{ 14 15 public ShuduView(Context context) { 16 super(context); 17 // TODO Auto-generated constructor stub 18 } 19 20 private float width; 21 private float height; 22 23 24 private Game game = new Game(); 25 26 @Override 27 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 28 //计算当前单元格宽度和高度 29 this.height = h / 9f; 30 this.width = w / 9f; 31 super.onSizeChanged(w, h, oldw, oldh); 32 } 33 34 35 36 //当android的系统需要绘制一个view对象时,就需要调用该对象的onDraw方法 37 @Override 38 protected void onDraw(Canvas canvas) { 39 //生成用户绘制背景色的画笔 40 Paint backgroundPaint = new Paint(); 41 //设置画笔颜色 42 backgroundPaint.setColor(getResources().getColor(R.color.shudu_background)); 43 //绘制背景色 44 canvas.drawRect(0, 0, getWidth(), getHeight(), backgroundPaint); 45 46 Paint darkPaint = new Paint(); 47 darkPaint.setColor(getResources().getColor(R.color.shudu_dark)); 48 49 Paint hilitePaint = new Paint(); 50 hilitePaint.setColor(getResources().getColor(R.color.shudu_hilite)); 51 52 Paint lightPaint = new Paint(); 53 lightPaint.setColor(getResources().getColor(R.color.shudu_light)); 54 55 for(int i = 0;i < 9;i++){ 56 //绘制横向的线 57 canvas.drawLine(0, i * height,getWidth(), i * height, lightPaint); 58 canvas.drawLine(0, i * height + 1,getWidth(), i * height + 1, hilitePaint); 59 //绘制纵向的线 60 canvas.drawLine(i * width, 0, i * width, getHeight(), lightPaint); 61 canvas.drawLine(i * width + 1, 0, i * width + 1, getHeight(), hilitePaint); 62 } 63 64 for(int i = 0;i < 3;i++){ 65 //绘制横向的线 66 canvas.drawLine(0, i * 3 * height, getWidth(), i * 3 * height, darkPaint); 67 canvas.drawLine(0, i * 3 * height + 1, getWidth(), i * 3 * height + 1, darkPaint); 68 //绘制纵向的线 69 canvas.drawLine(i * 3 * width, 0, i * 3 * width, getHeight(), darkPaint); 70 canvas.drawLine(i * 3 * width + 1, 0, i * 3 * width + 1, getHeight(), darkPaint); 71 } 72 //绘制数字 73 Paint numberPaint = new Paint(); 74 numberPaint.setColor(Color.BLACK); 75 //numberPaint.setStyle(Paint.Style.STROKE); 76 numberPaint.setTextSize( height * 0.75f); 77 numberPaint.setTextAlign(Paint.Align.CENTER); 78 //将数字加到格子中 79 FontMetrics fm = numberPaint.getFontMetrics(); 80 float x = width / 2; 81 float y = height / 2 - (fm.ascent + fm.descent) / 2; 82 //canvas.drawText("1", 3 * width + x, height + y , numberPaint); 83 for(int i = 0;i < 9;i++){ 84 for(int j = 0;j < 9;j++){ 85 canvas.drawText(game.getTileString(i, j), i * width+x, j * height + y, numberPaint); 86 } 87 } 88 89 90 super.onDraw(canvas); 91 } 92 93 int selectedX; 94 int selectedY; 95 96 97 98 @Override 99 public boolean onTouchEvent(MotionEvent event) { 100 if(event.getAction() != MotionEvent.ACTION_DOWN){ 101 return super.onTouchEvent(event); 102 } 103 104 selectedX = (int)(event.getX() / width); 105 selectedY = (int)(event.getY() / height); 106 107 int used[] = game.getUsedTileByCoor(selectedX, selectedY); 108 StringBuffer sb = new StringBuffer(); 109 for(int i = 0; i < used.length;i++){ 110 System.out.println(used[i] ); 111 //sb.append(used[i]); 112 } 113 114 //生成一个LayoutInflater对象 115 //LayoutInflater layoutInflater = LayoutInflater.from(this.getContext()); 116 //View layoutView = layoutInflater.inflate(R.layout.dialog, null); 117 //从生成好的textView中 取出相应的控件 118 //TextView textView =(TextView)layoutView.findViewById(R.id.usedTextId); 119 120 //textView.setText(sb.toString()); 121 122 //AlertDialog.Builder builder = new AlertDialog.Builder(this.getContext()); 123 //builder.setView(layoutView); 124 125 //AlertDialog dialog = builder.create(); 126 //dialog.show(); 127 //dialog.getWindow().setLayout(300, 200); 128 KeyDialog keyDialog = new KeyDialog(getContext(), used,this); 129 //透明度 130 WindowManager.LayoutParams lp=keyDialog.getWindow().getAttributes(); 131 lp.alpha=0.9f; 132 keyDialog.getWindow().setAttributes(lp); 133 134 keyDialog.show(); 135 return true; 136 } 137 138 public void setSelectedTile(int tile){ 139 140 if(game.setTileIfValid(selectedX,selectedY,tile)){ 141 invalidate(); 142 } 143 } 144 145 }
KeyDialog:
1 package com.soccer.shudu; 2 3 import android.app.Dialog; 4 import android.content.Context; 5 import android.os.Bundle; 6 import android.view.View; 7 import android.widget.Button; 8 9 10 //该类用于实现dialog自定义对话框功能 11 public class KeyDialog extends Dialog{ 12 private final View keys [] = new View[9]; 13 private final int used[]; 14 15 private ShuduView shuduView = null; 16 17 public KeyDialog(Context context,int [] used,ShuduView shuduView){ 18 super(context); 19 this.used = used; 20 this.shuduView = shuduView; 21 } 22 //当一个dialog第一次显示的时候,会调用其onCreate方法 23 @Override 24 protected void onCreate(Bundle savedInstanceState) { 25 super.onCreate(savedInstanceState); 26 setTitle("可选数字"); 27 setContentView(R.layout.keypad); 28 findViews(); 29 for(int i = 0;i < used.length;i++){ 30 if(used[i] != 0){ 31 keys[used[i] - 1].setVisibility(View.INVISIBLE); 32 } 33 } 34 //为每个键设置监听器 35 setListeners(); 36 37 //为返回按钮设置监听器 38 Button back_Button = (Button)findViewById(R.id.back_1); 39 back_Button.setOnClickListener(new View.OnClickListener() { 40 @Override 41 public void onClick(View v) { 42 dismiss(); 43 44 } 45 }); 46 } 47 48 49 50 51 private void findViews(){ 52 keys[0] = findViewById(R.id.keypad_1); 53 keys[1] = findViewById(R.id.keypad_2); 54 keys[2] = findViewById(R.id.keypad_3); 55 keys[3] = findViewById(R.id.keypad_4); 56 keys[4] = findViewById(R.id.keypad_5); 57 keys[5] = findViewById(R.id.keypad_6); 58 keys[6] = findViewById(R.id.keypad_7); 59 keys[7] = findViewById(R.id.keypad_8); 60 keys[8] = findViewById(R.id.keypad_9); 61 } 62 63 //通知ShuduView对象,刷新整个九宫格显示的数据 64 private void returnResult(int tile){ 65 shuduView.setSelectedTile(tile); 66 dismiss(); 67 } 68 69 private void setListeners(){ 70 for(int i = 0; i < keys.length; i++){ 71 final int t = i + 1; 72 keys[i].setOnClickListener(new View.OnClickListener() { 73 74 @Override 75 public void onClick(View v) { 76 returnResult(t); 77 78 } 79 }); 80 } 81 } 82 83 }
keypad.xml:
1 <?xml version="1.0" encoding="utf-8"?> 2 <TableLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:id="@+id/keypad" 4 android:orientation="vertical" 5 android:layout_width="wrap_content" 6 android:layout_height="wrap_content" 7 android:stretchColumns="*" > 8 9 <TableRow> 10 <Button android:id="@+id/keypad_1" 11 android:text="1"> 12 </Button> 13 <Button android:id="@+id/keypad_2" 14 android:text="2"> 15 </Button> 16 <Button android:id="@+id/keypad_3" 17 android:text="3"> 18 </Button> 19 </TableRow> 20 21 <TableRow> 22 <Button android:id="@+id/keypad_4" 23 android:text="4"> 24 </Button> 25 <Button android:id="@+id/keypad_5" 26 android:text="5"> 27 </Button> 28 <Button android:id="@+id/keypad_6" 29 android:text="6"> 30 </Button> 31 </TableRow> 32 33 <TableRow> 34 <Button android:id="@+id/keypad_7" 35 android:text="7"> 36 </Button> 37 <Button android:id="@+id/keypad_8" 38 android:text="8"> 39 </Button> 40 <Button android:id="@+id/keypad_9" 41 android:text="9"> 42 </Button> 43 </TableRow> 44 45 <TableRow> 46 <Button android:text=""> 47 </Button> 48 <Button android:id="@+id/back_1" 49 android:text="@string/back_1"> 50 </Button> 51 <Button android:text=""> 52 </Button> 53 </TableRow> 54 55 </TableLayout>
Game.java:
1 package com.soccer.shudu; 2 3 public class Game { 4 //数独初始化数据 5 private final String str = "360000000004230800000004200" 6 +"070460003820000014500013020" 7 +"001900000007048300000000045"; 8 private int sudoku [] = new int[9*9]; 9 //用于存储每个单元格已经不可用的数据 10 private int used[][][] = new int[9][9][]; 11 12 public Game(){ 13 sudoku = fromPuzzleString(str); 14 calculateAllUsedTile(); 15 } 16 //根据九宫格当中的坐标,返回该坐标对应的数字 17 private int getTile(int x,int y){ 18 return sudoku[y*9 + x]; 19 } 20 private void setTile(int x,int y,int value){ 21 sudoku[y * 9 + x] = value; 22 } 23 24 public String getTileString(int x, int y){ 25 int v = getTile(x,y); 26 if(v == 0){ //0就不显示 27 return ""; 28 } 29 else 30 return String.valueOf(v); 31 } 32 protected boolean setTileIfValid(int x,int y,int value){ 33 int tiles[] = getUsedTileByCoor(x,y); 34 if(value != 0){ 35 for(int tile : tiles){ 36 if(tile == value) 37 return false; 38 } 39 } 40 setTile(x,y,value); 41 calculateAllUsedTile(); 42 return true; 43 } 44 45 //根据一个字符串数据生成一个整形数组,作为数独游戏的初始化数据 46 protected int[] fromPuzzleString(String src){ 47 int []sudo = new int[src.length()]; 48 for(int i = 0;i < sudo.length;i++) 49 { 50 sudo[i] = src.charAt(i) - '0'; 51 } 52 return sudo; 53 } 54 55 //计算所有单元格对应的不可用数据 56 public void calculateAllUsedTile(){ 57 for(int x = 0;x < 9;x++){ 58 for(int y = 0;y < 9;y++){ 59 used[x][y] = calculateUsedTiles(x, y); 60 } 61 } 62 } 63 64 //取出某一单元格当中已经不可用的数据 65 public int[] getUsedTileByCoor(int x,int y){ 66 return used[x][y]; 67 } 68 69 //计算某一单元格当中已经不可用的数据 70 public int[] calculateUsedTiles(int x,int y){ 71 int c[] = new int[9]; 72 73 for(int i = 0;i < 9;i++){ 74 if(i == y) 75 continue; 76 int t = getTile(x, i); 77 if(t != 0) 78 c[t - 1] = t; 79 } 80 81 for(int i = 0;i < 9;i++){ 82 if(i == x) 83 continue; 84 int t = getTile(i, y); 85 if(t != 0) 86 c[t - 1] = t; 87 } 88 89 int startx = (x / 3) * 3; 90 int starty = (y / 3) * 3; 91 for(int i = startx; i < startx + 3; i++){ 92 for(int j = starty;j < starty + 3; j++){ 93 if(i == x && j == y) 94 continue; 95 int t = getTile(i,j); 96 if(t!=0) 97 c[t-1] = t; 98 } 99 } 100 //compress 101 int nused = 0; 102 for(int t:c){ 103 if(t!=0) 104 nused++; 105 } 106 int c1[] = new int[nused]; 107 nused = 0; 108 for(int t:c){ 109 if(t!=0) 110 c1[nused++] = t; 111 } 112 return c1; 113 } 114 }
界面效果如下: