看了徐宜生android群英传的拼图例子,也想参照他的写一个拼图游戏。本文的拼图游戏中对图片处理的方式和书中的基本是一直的,但是游戏方法却与之不同。
书中的拼图是通过GridView的点击事件,交换两张图片,达成拼图目的,但是个人认为这样的拼图过于简单,很容易就能达成目标,
于是收到小时候玩的数字推盘的游戏的启发,将数字替换成文字,大大增加了游戏的难度(ps:本人目前没玩出来过)。主要使用recyclerView与ontouch结合,实现拼图效果,当然判断拼图是否有解的算法还没给出,敬请期待后续更新。
首先简单介绍一下布局:
xml version="1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tool="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tool:context=".activity.GameActivity">
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:popupTheme="@style/Theme.AppCompat.Light.NoActionBar">
android:id="@+id/lay_top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:orientation="horizontal">
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="@dimen/margin_8dp"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:text="@string/jigsaw_time"
android:textSize="@dimen/sp_middle" />
android:id="@+id/jigsaw_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="@dimen/margin_8dp"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:textSize="@dimen/sp_middle" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="@dimen/margin_8dp"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:text="@string/jigsaw_step"
android:textSize="@dimen/sp_middle" />
android:id="@+id/jigsaw_step"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="@dimen/margin_8dp"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:textSize="@dimen/sp_small" />
android:id="@+id/lay_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal">
android:id="@+id/recycleView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/lay_bottom"
android:layout_below="@id/lay_top"
android:layout_centerInParent="true"
android:layout_margin="@dimen/margin_8dp">
上面通过相对布局来规划控件位置,上边是对局信息区,中间是游戏区,下边是功能键。(ps:界面简陋,请担待些)。
然后是代码结构:
因为只是简单的实现拼图功能,并没有做过多的优化,所以代码结构并不复杂。基本上通过对包和class的命名就能知道其代表的功能。
那么,接下来对功能实现的几个要点进行简单的解释:
第一:图片处理(具体的代码里面有详细的注释):
package com.example.jigsawgame.unilts;
/**
* Created by Administrator on 2016/12/27 0027.
*/
import android.graphics.Bitmap;
import android.graphics.Matrix;
import com.example.jigsawgame.AppContacts;
import com.example.jigsawgame.MyAppcalition;
import com.example.jigsawgame.bean.ImageBean;
import com.example.jigsawgame.infaceView.GameViewListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author zhoukeda on 2016/12/27 0027 20:51
* @email:[email protected] 实现图片分割与自适应
*/
public class ImageUnilt {
private ImageBean imageBean;
private GameViewListener viewListener;
private List bitmapList = new ArrayList<>();
private List firstBeanList = new ArrayList<>();
public ImageUnilt(GameViewListener viewListener) {
this.viewListener = viewListener;
}
public void reStartBitmap() {
viewListener.initBitmapView(bitmapList);
}
public void totalBitmap() {
viewListener.initBitmapView(firstBeanList);
}
/**
* 对图片随机排序(新的一局)
*/
public void randomBitmap() {
Collections.shuffle(bitmapList);
for (int i = 0; i <bitmapList.size() ; i++) {
bitmapList.get(i).setmItemId(i);
}
viewListener.initBitmapView(bitmapList);
}
/**
* 切图
* @param type(拼图游戏的类型如:3为8数字推盘,4为15数字推盘)
* @param bitmapSelect(传入要进行拼图的图片)
*/
public void creatInitImage(int type, Bitmap bitmapSelect) {
Bitmap bitmap = null;
int widthItem = bitmapSelect.getWidth() / type;//裁剪后图片的宽度
int heightItem = bitmapSelect.getHeight() / type;//裁剪后图片的高度
//创建图片
for (int i = 0; i < type; i++) {
for (int j = 0; j < type; j++) {
bitmap = Bitmap.createBitmap(bitmapSelect,
j * widthItem, i * heightItem, widthItem, heightItem);
//createBitmap(图片左上角x,y坐标,图片宽高)
imageBean = new ImageBean(i * type + j, i * type + j, reSize(ScreenUtil.getScreenWidth(MyAppcalition.getmContext()) / type,
ScreenUtil.getScreenWidth(MyAppcalition.getmContext()) / type, bitmap),(i * type + j)%type,(i * type + j)/type);
//保存图片信息
bitmapList.add(imageBean);
firstBeanList.add(imageBean);
}
}
//将被隐藏图片保存下来
AppContacts.lastBitmap = bitmapList.get(type * type - 1);
bitmapList.remove(type * type - 1);
Collections.shuffle(bitmapList);//随机打乱图片
viewListener.initBitmapView(bitmapList);
}
/**
* 处理图片大小,缩放,放大到合适的位置
*
* @param newWidth
* @param newHeight
* @param bitmap
*/
public Bitmap reSize(float newWidth, float newHeight, Bitmap bitmap) {
Matrix matrix = new Matrix();
matrix.postScale(newWidth / bitmap.getWidth(), newHeight / bitmap.getHeight());
//matrix.postScale(0.5f, 0.5f);// 缩小为原来的一半
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
}
补充:本demo使用的是mvc模式,在activity中调用uilts的方法后,并不直接用return返回数据,而是通过接口将数据返回到activity中。
第二步:通过recyclerview,将分割好,并随机打乱的图片显示出来:
这个相对简单,由于篇幅,简单解释一下,就不贴代码了(本文最下方有demo的源码提供下载)
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, type); gameAdapter = new GameAdapter(0, null); recycleView.setLayoutManager(gridLayoutManager); recycleView.setAdapter(gameAdapter);通过recyclerview的Gridmanger可以实现gridview的效果,而且它的item移动的效果,和显示的效果的更加的美观。
@Override public void initBitmapView(List这里,我们是先初始化一个数据是空的的recyclerview,然后,初始化图片操作,当图片获取成功的时候,更新recyclerview,实现触摸事件。这样在我们的图片是从网络获取的时候,出现突发情况(如:网络链接异常),不至于闪退,提高程序的健壮性。bitmaps) { if(bitmaps!=null&&!bitmaps.isEmpty()){ beanList.clear(); beanList.addAll(bitmaps); if (beanList.size() < type * type) { beanList.add(new ImageBean(type * type - 1, type * type - 1, null, type - 1, type - 1)); } for (int i = 0; i < beanList.size(); i++) { beanList.get(i).setSelectionX(i % type); beanList.get(i).setSelectionY(i / type); Log.i("###", "initBitmapView: " + beanList.get(i).toString()); } gameAdapter.setNewData(beanList); gameAdapter.openLoadAnimation(2); } recycleView.setOnTouchListener(this); }
第三:recyclerview触摸事件:
这个触摸事件只有在recyclerview的控件范围内才有效果。
@Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: x = event.getX(); y = event.getY(); break; case MotionEvent.ACTION_UP: int nullX = 0, nullY = 0; for (ImageBean imageBean : gameAdapter.getData()) { if (imageBean.getmBitmap() == null) { nullX = imageBean.getSelectionX(); nullY = imageBean.getSelectionY(); break; } } if (Math.abs(event.getX() - x) > 100 || Math.abs(event.getY() - y) > 100) { Log.i("###", "onTouch: " + "在滑动了"); if (Math.abs(event.getX() - x) > Math.abs(event.getY() - y)) { Log.i("###", "onTouch: " + "水平滑动"); if (x > event.getX()) { step++; jigsawstep.setText(step+""); gameUnilt.selectionChange(TouthType.right_To_Left, gameAdapter, nullX, nullY, type); } else { step++; jigsawstep.setText(step+""); gameUnilt.selectionChange(TouthType.left_To_Right, gameAdapter, nullX, nullY, type); } } else { if (y > event.getY()) { step++; jigsawstep.setText(step+""); gameUnilt.selectionChange(TouthType.bottom_To_Up, gameAdapter, nullX, nullY, type); } else { step++; jigsawstep.setText(step+""); gameUnilt.selectionChange(TouthType.up_To_Bottom, gameAdapter, nullX, nullY, type); } Log.i("###", "onTouch: " + "竖直滑动"); } } break; case MotionEvent.ACTION_MOVE: break; } return false; }实现逻辑:在按下,抬起的时候记录位置,当同一坐标的两个位置的绝对值差值大于100时,判定有滑动事件,然后判定x,y轴上差值的绝对值大的为主要滑动方向(竖直or水平)。最后判定上下左右。
第四:根据滑动方向,决定recyclerview的位置移动策略主要逻辑在Gameunit(这个看下源码稍微想一下,就能理解,没什么技术含量,就不多解释了):
public class GameUnilt { private String TAG = "GameUnilt"; public void selectionChange(TouthType type, GameAdapter gameAdapter, int nullX, int nullY,int range) { switch (type) { case left_To_Right: for (int i = 0; i < gameAdapter.getData().size(); i++) { if (gameAdapter.getData().get(i).getSelectionY() == nullY) { if (gameAdapter.getData().get(i).getSelectionX() == nullX - 1) { ImageBean imageBean = gameAdapter.getData().get(i); gameAdapter.getData().set(i,gameAdapter.getData().get(i+1)); gameAdapter.getData().get(i).setSelectionX(i%range); gameAdapter.getData().set(i+1,imageBean); gameAdapter.getData().get(i+1).setSelectionX((i+1)%range); gameAdapter.notifyItemChanged(i); gameAdapter.notifyItemChanged(i + 1); break; } } } break; case right_To_Left: for (int i = 0; i < gameAdapter.getData().size(); i++) { if (gameAdapter.getData().get(i).getSelectionY() == nullY) { if (gameAdapter.getData().get(i).getSelectionX() == nullX + 1) { ImageBean imageBean = gameAdapter.getData().get(i-1); gameAdapter.getData().set(i-1,gameAdapter.getData().get(i)); gameAdapter.getData().get(i-1).setSelectionX((i-1)%range); gameAdapter.getData().set(i,imageBean); gameAdapter.getData().get(i).setSelectionX((i)%range); gameAdapter.notifyItemChanged(i-1); gameAdapter.notifyItemChanged(i); break; } } } break; case up_To_Bottom: for (int i = 0; i < gameAdapter.getData().size(); i++) { if (gameAdapter.getData().get(i).getSelectionX() == nullX) { if (gameAdapter.getData().get(i).getSelectionY() == nullY - 1) { ImageBean imageBean = gameAdapter.getData().get(i); gameAdapter.getData().set(i,gameAdapter.getData().get(i+range)); gameAdapter.getData().get(i).setSelectionY(i/range); gameAdapter.getData().set(i+range,imageBean); gameAdapter.getData().get(i+range).setSelectionY((i+range)/range); gameAdapter.notifyItemChanged(i); gameAdapter.notifyItemChanged(i + range); Log.i(TAG, "selectionChange: "+i+"--->"+gameAdapter.getData().get(i).toString()); Log.i(TAG, "selectionChange: "+i+"--->"+gameAdapter.getData().get(i+range).toString()); break; } } } break; case bottom_To_Up: for (int i = 0; i < gameAdapter.getData().size(); i++) { if (gameAdapter.getData().get(i).getSelectionX() == nullX) { if (gameAdapter.getData().get(i).getSelectionY() == nullY + 1) { ImageBean imageBean = gameAdapter.getData().get(i-range); gameAdapter.getData().set(i-range,gameAdapter.getData().get(i)); gameAdapter.getData().get(i-range).setSelectionY((i-range)/range); gameAdapter.getData().set(i,imageBean); gameAdapter.getData().get(i).setSelectionY(i/range); gameAdapter.notifyItemChanged(i-range); gameAdapter.notifyItemChanged(i); Log.i(TAG, "selectionChange: "+gameAdapter.getData().get(i-range).toString()); Log.i(TAG, "selectionChange: "+gameAdapter.getData().get(i).toString()); break; } } } break; } } }当然,个人认为这里的GameAdapter可以不用传入,而是通过接口将要调整位置的元素位置传递到activity中,会更为规范些...
那么,到这里为止,一个简单的类似数字推盘的拼图游戏就实现出来了,当然还有很多可以优化的地方,如充网络获取更多的图片,拼图成功后的动画,难度关卡的选择,初始化的图片是否有解等等,各位可以自己试试,我后续也会继续更新。谢谢观看
源码下载链接:点击打开链接