先看一张效果图:
首先我们要想实现以上的效果,就要先绘制棋盘和棋子。用系统中的组件是很难实现的,那么就需要我们自定义组件。
绘制棋盘:
在绘制棋盘之前,我们先应该测量棋盘的大小,然后再进行绘制。所以一定要重写onMeasure()和onDraw()方法。
第一步:
重写view的onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法,代码如下:
MeasureSpec类:
主要是封装父布局对子布局的一个布局要求,主要有三种模式。UNSPECIFIED(未指定),父元素不对子元素施加任何束缚,子元素可以得到任意想要的大小;
EXACTLY(完全),父元素决定子元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;AT_MOST(至多),子元素至多达到指定大小的值。
它有三个常用的方法,分别为:
getMode()根据提供的测量值(格式)提取模式(上述三个模式之一)
getSize()根据提供的测量值(格式)提取大小值
makeMeasureSpec()根据提供的大小值和模式创建一个测量值
setMeasuredDimension():设置自定义view的大小注意:当自定义的view的外面套上Linerlayout,而Linerlayout外面又套上ScrollView时,Linerlayout的高度设置成warp_content,这时heightMode可能为UNSPECIFIED,那么heightSize可能就为0。所以Math.min(widthSize,heightSize)可能取值为0,那么width就可能取值为0,这样就会造成无法显示自定义view的尴尬,所以要进行
if(widthMode==MeasureSpec.UNSPECIFIED){ width=heightSize; }else if(heightMode==MeasureSpec.UNSPECIFIED){ width=widthSize; }这样的判断。
View中有一个onSizeChanged(int w, int h, int oldw, int old)方法,这个是系统回调方法,是系统调用的,这个方法会在这个view的大小发生改变是被系统调用,我们要记住的就是view大小变化,这个方法就被执行就可以了。
在五子棋的绘制中,我们在此方法中初始化了棋盘的宽度,行高,以及对棋子的图片进行了缩放。代码如下:
protected void onSizeChanged(int w, int h, int oldw, int oldh) { mWidthPanel=w; mHeightLine=mWidthPanel*1.0f/MAX_LINE; int pieceWidth= (int) (mHeightLine * roatePieceOfLineHeight); //createScaledBitmap缩放一个bitmap mWhitePiece=Bitmap.createScaledBitmap(mWhitePiece,pieceWidth,pieceWidth,false); mBlackPiece=Bitmap.createScaledBitmap(mBlackPiece,pieceWidth,pieceWidth,false); }
MAX_LINE:自定义的常量,表示行数。
第二步:重写view的 onDraw (Canvas canvas)方法,代码如下:
drawBoard(canvas):绘制棋盘。
注意:当完成上述操作之后,我们发现还是无法保存状态,这是因为我们需要在主布局中给添加的自定义View设置id,为什么??drawPiece(canvas):绘制棋子。
既然讲到绘制棋子,那么我们应该怎样会绘制棋子呢!?我们知道棋子是在动态绘制的,也就说我们触摸棋盘规定的位置就会出现一颗棋子,而且棋子是根据坐标坐标集合来绘制的。所以我们可以在onTouchEvent(MotionEvent event)事件里面记录棋子的坐标,由于有两种颜色的棋子,所以需要准备两个集合。代码如下:这里要注意onTouchEvent中的ACTION_DOWN必须返回true,这表明onTouchEvent会处理点击事件,否则表明,触摸事件不被响应,通过里面的代码逻辑的第一句话我哦们也可以知道,当游戏结束的时候,返回false,这时候不可以再在棋盘上下棋子。对于view的触摸事件处理机制,将在下一篇文章中介绍。棋子绘制之后,接下来就是一些逻辑处理了,这里就不再啰嗦,在源码中会有具体实现。
这样一个简单的五子棋就实现了,最后还有一点,就是当重新绘制界面的时候,我们要向保持上一个界面的状态的时候,该怎么做。
我们知道在Activity中,我们要向在保存上一次的状态,需要在onPause中做状态保存,然后在onCreate中获取保存的状态即可。而对于自定义的View,我们也可以重写里面的onSaveInstanceState()和onRestoreInstanceState(Parcelable state)方法来保存和恢复状态。这里我们要保存的就是棋子的坐标和游戏是否结束的判断。代码如下:
其实我也不知道原因,哈哈。不过我在网上搜索之后,看到下面这篇文章写的很详细易懂。网址:
android中正确保存View状态
这里大致总结下:
首先看看保存和恢复的示意图:
保存状态时,安卓framework调用saveHierarchyState(SparseArray<Parcelable> container)方法,saveHierarchyState会调用dispatchSaveInstanceState() ,而在dispatchSaveInstanceState()内部又会调用onSaveInstanceState()方法。
onSaveInstanceState()返回一个代表当前状态的Parcelable,这个Parcelable被保存在container参数中,container参数是一个键值对的map集合。View的ID是键Parcelable是值。至此我们就应该明白了为什么我们需要给View设置id了,这个id是标识当前view的唯一标识,如果这个id我们就无法识别当前view,也无法保存当前view的状态。
下面给出五子棋实现的源码: