简介
这篇文章是我看了 Mars老师 讲的开发数独教程,得到学习笔记和感想。这里面的代码和框架我都改了一下。在这里要感谢一下互联网,和 Mars老师。
数独界面展示
源码
数独分析
基础知识
手机的坐标系
如上图所示,左上角0点为手机屏幕最左上方。
Paint和Canvas
Paint 是画笔,这个画笔你可以设置为你想要的画笔,比如颜色、大小、形状。本文用到的有:
// 设置颜色
public native void setColor(@ColorInt int color);
// 设置样式
public void setStyle(Style style);
// 设置文字对齐
public void setTextAlign(Align align);
// 设置大小
public native void setTextSize(float textSize);
// 消除锯齿一样的边缘
public native void setAntiAlias(boolean aa);
Canvas 是画布,在这个画布上你可以任意画出你想要的东西。本文用到的方法有:
// 画一个矩形, (left, top) 为左上点、(right, botton) 为右下点。
public void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint);
// 画一条直线, (startX, startY) 为左坐标、(stopX, stopY) 为右坐标。
public void drawLine(float startX, float startY, float stopX, float stopY, @NonNull Paint paint);
// 写字, text 为内容, (x, y) 为首字最左边的坐标,其余字按照直线依次显示。
public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint);
FontMetrics
上图是字在 FontMetrics 中的各种属性值。等下我们要用这个来调节字体的位置,大家只要记住里面的几个名字就行。
Baseline 是字体的水平线。也就是横轴和手机的坐标系一样。向上为负值,向下为正值。
获取 FontMetrics 对象如下:
//其中 paint 为所用的画笔对象Paint.FontMetrics fontMetrics = paint.getFontMetrics();
步骤解析
九宫格的生成
这个界面是自定义生成的,我们要把九宫格给画出来。
一. 新建一个类继承 View 类,重写里面得 onDraw 方法。
二. 先画一个外矩形,将所有的东西包住。
Paint paint = new Paint();
// 消除锯齿一样的边缘
paint.setAntiAlias(true);
// 先画一个空心的矩形 作为外背景。Paint.Style.STROKE 为设置空心
paint.setStyle(Paint.Style.STROKE);
// 加载颜色
paint.setColor(ContextCompat.getColor(mContext, R.color.top2));
// getWidth() getHeight() 得到界面的宽和高
canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
这是我的颜色表
三. 由数独的界面可知道,横轴和纵轴都有 9 根线来等间隔隔开。因此要先获得界面的宽和高,再除以 9。
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// 得到 九宫格 的高度和宽度
width = w / 9f;
height = h / 9f;
super.onSizeChanged(w, h, oldw, oldh);
}
四. 进行绘画,在横(X)轴上每隔 width 个大小就画一条。在纵(Y)轴上每隔 height 个大小就画一条。
在这里,进行的条纹加深就是间隔隔开一点点,然后再次画线,颜色选深色。这样看起来就有条纹。
因为九宫格为 9 x 9, 而且里面又有 9 个 3 x 3,所以每有 3 个 width 时,横轴就画一个更深颜色的线用来区分。同理纵轴也是一样。
// 画9条横轴 纵轴 网格
for (int i = 0; i < 9; i++) {
paint.setColor(ContextCompat.getColor(mContext, R.color.back));
// 横轴
canvas.drawLine(0, i * height, getWidth(), i * height, paint);
// 纵轴
canvas.drawLine(i * width, 0, i * width, getHeight(), paint);
// 进行条纹加深
paint.setColor(ContextCompat.getColor(mContext, R.color.top1));
// 横轴
canvas.drawLine(0, i * height + 3, getWidth(), i * height + 3, paint);
// 纵轴
canvas.drawLine(i * width + 3, 0, i * width + 3, getHeight(), paint);
// 将目前的 九宫格 分为 3 块
if (i % 3 == 0) {
paint.setColor(ContextCompat.getColor(mContext, R.color.top2));
// 横轴
canvas.drawLine(0, i * height + 3, getWidth(), i * height + 3, paint);
// 纵轴
canvas.drawLine(i * width + 3, 0, i * width + 3, getHeight(), paint);
}
}
到这里九宫格已生成。也就是说棋盘已经可以用了。
填入初始数字
要将数字填入九宫格中,为了美观,将设置文字所在的位置。
如图,黑色为设置字的大小。黄色为九宫格中每个格的大小。水平方向上的绿色为Baseline。紫色为格子的中心。
由图知,字的宽度为 width / 2;
字的高度为 height / 2 - (fontMetrics.ascent + fontMetrics.descent) / 2;
height / 2 为水平紫线,要想达到水平的绿线上就必须高度增加。这里要增加的数值是随意。一般来说用 -(fontMetrics.ascent + fontMetrics.descent) / 2 得到的数值来进行增加(ascent 为负值,descent 为正数,括号里面得到负值,加一个负号为正数)。
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
// 字体设置水平居中, 也就是写入字的坐标为首字中心
paint.setTextAlign(Paint.Align.CENTER);
paint.setTextSize((float) (height * 0.75));
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
float x = width / 2;
float y = height / 2 - (fontMetrics.ascent + fontMetrics.descent) / 2;
这里要填入的数据源为:
private String STR = "360000000004230800000004200"
+ "070460003820000014500013020" + "001900000007048300000000045";
当 getValue(x, y) 返回的值不为0时,就说明当前的 (x, y)上有数字。
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
// getValue(x,y)获得对应的数字
int point = getValue(j, i);
if (point != 0) {
canvas.drawText(String.valueOf(point), j * width + x, i * height + y, paint);
}
}
}
得到点击的坐标
在这里重写触摸事件
event.getX() 得到当前点击到的横坐标, 除以 width 时就可以得到横轴上是第几个格子(从零开始数)。同理 event.getY() 也是一样。
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = 0;
float y = 0;
if (event.getAction() == MotionEvent.ACTION_DOWN) {
x = (int) (event.getX() / width);
y = (int) (event.getY() / height);
}
return super.onTouchEvent(event);
}
进行可填数字判断
这里传入的 (x, y) 为点击到的点。
used数值为存储已存在的数。
进行横轴判断。y 不变,i 从0到9
// 查找横轴 (X)
for (int i = 0; i < 9; i++) {
int point = getValue(i, y);
if (point != 0) {
used[point - 1] = point;
}
}
进行纵轴判断。x 不变,i 从0到9
// 查找纵轴 (Y)
for (int i = 0; i < 9; i++) {
int point = getValue(x, i);
if (point != 0) {
used[point - 1] = point;
}
}
进行小网格(3 x 3)判断。
int xStart = (x / 3) * 3;
int yStart = (y / 3) * 3;
xStart 和 yStart 就是得到(x, y)所在的 3 x 3 的最初点。也就是上图中红圈所在位置。
// 查找网格
// 计算出 x y 在网格中的最初位置
int xStart = (x / 3) * 3;
int yStart = (y / 3) * 3;
for (int i = xStart; i < xStart + 3; i++) {
for (int j = yStart; j < yStart + 3; j++) {
int point = getValue(i, j);
if (point != 0) {
used[point -1] = point;
}
}
}
这里 used 数组就是在(x, y)坐标中横轴、纵轴和 3 x 3 网格中不能填入的数字。
弹出可选数字的界面
这里我用的是 Alertdialog 自定义样式。
我的 XML 布局文件生成的界面如下:
bt 数组为所有 Button 的集合。
更改数据 setSTR(x, y, t);
重写绘制 reDraw();
// 出现对话框,提供用户可添选数据
final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
final AlertDialog dialog = builder.create();
View item = LayoutInflater.from(mContext).inflate(R.layout.item_choose_layout, null);
// 找到所有的 Button
findAllBt(item);
// 将对应的 x y 上已经出现的数字进行屏蔽
for (int i = 0; i < 9; i++) {
if (used[i] != 0) {
bt[i].setVisibility(View.INVISIBLE);
}
}
// 设置点击事件
for (int i = 0; i < 9; i++) {
// 得到 ascii 值
final char t = (char) (i + 1 + '0');
bt[i].setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 更改数据
setSTR(x, y, t);
// 重写绘制
reDraw();
dialog.dismiss();
}
});
}
dialog.setView(item);
dialog.setTitle("请选择数字");
dialog.setCancelable(true);
dialog.show();
更改数据
获得 (x, y) 和要修改的数据。
// 进行修改
public void setSTR(int x, int y, char c) {
// 得到 x y 的长度
int index = y * 9 + x;
String reSTR = STR.substring(0, index);
reSTR += c;
reSTR += STR.substring(index + 1, STR.length());
STR = reSTR;
}
重写绘制
这里就调用了 getUsedArray() 进行不能填入的数字刷新。
invalidate() 就是请求重新draw()。
// 进行重新绘图
public void reDraw() {
// 进行数据刷新
getUsedArray();
invalidate();
}
到这里,简单的数独游戏就完成了!
感想
其实一开始,我不打算写这个数独的。因为我感觉逻辑很简单,剩下的就是代码编写。但不知道是什么原因我开始写了,估计是好奇!
不过,有些东西看起来简单,真正写起来。还是有点问题,不过还好。我还是把这个数独给写了。
通过这次数独的编写,我觉得我还是应该要脚踏实地,每一行代码都应该去实现一下。这次的数独就是一个警告,看起来是一回事,但是敲起来又是另外一件事了。既然选择了代码,就应该要下手敲!