软件编程
位置:首页>> 软件编程>> Android编程>> Android拼图游戏 玩转从基础到应用手势变化

Android拼图游戏 玩转从基础到应用手势变化

作者:Mario_oo  发布时间:2021-08-24 02:41:59 

标签:Android,拼图游戏

相信大家在小的时候都玩过拼图游戏,现如今,手机普及,能在手机上玩的游戏越来越多,于是乎,重温小时候,编写这个简易拼图游戏,而且也能进一步加深Android的一些基础知识。
老规矩,先是效果图:

Android拼图游戏 玩转从基础到应用手势变化

这里我把为了演示效果,把图片打乱的很少,在代码里可以更改。

首先,有个默认的图片,可以用来拼图,也可以选择你喜欢的图片进行拼图,拼图的过程会记录移动的步数,并且当游戏胜利的时候会弹出一个笑脸提示,游戏胜利,用了多少步数。

ps:感兴趣的完全可以继续在这上面进行扩展,比如增加游戏难度的选项,可以将图片分成更多的小方块等等。

大体思路:将大图切割成各个小方块,用数组记录每个小方块的信息,用GridLayout来显示各个小方块,并且将某个小方块标记为空白方块(空白方块可以和相邻方块进行交换),在GridLayout上的各个小方块上增加点击事件和在整个屏幕上添加手势事件,每次点击或者有手势时,判断小方块是否能移动,最后在游戏胜利时弹出胜利提示。
话不多说,接下来,就是一步步实现拼图游戏的过程啦。

1.小方块相关的类。

这是小方块的各种变量的item,类,用来管理将大图切割成每个小方块的每个小方块的信息。很简单,就是各种变量和Setter和Getter方法直接上代码~


/**
* Created by yyh on 2016/10/21.
*/
public class GameItemView{
 /**
  * 每个小方块的信息
  */
 //每个小方块的实际位置x,
 private int x=0;
 //每个小方块的实际位置y,
 private int y=0;
 //每个小方块的图片,
 private Bitmap bm;
 //每个小方块的图片位置x,
 private int p_x=0;
 //每个小方块的图片位置y.
 private int p_y=0;
 public GameItemView(int x, int y, Bitmap bm) {
   super();
   this.x = x;
   this.y = y;
   this.bm = bm;
   this.p_x=x;
   this.p_y=y;
 }
 public int getX() {
   return x;
 }
 public void setX(int x) {
   this.x = x;
 }
 public int getY() {
   return y;
 }

public Bitmap getBm() {
   return bm;
 }

public void setBm(Bitmap bm) {
   this.bm = bm;
 }

public int getP_x() {
   return p_x;
 }

public void setP_x(int p_x) {
   this.p_x = p_x;
 }

public int getP_y() {
   return p_y;
 }

public void setP_y(int p_y) {
   this.p_y = p_y;
 }
 /**
  * 判断每个小方块的位置是否正确
  * @return
  */
 public boolean isTrue(){
   if (x==p_x&&y==p_y){
     return true;
   }
   return false;
 }
}

2.主界面的布局

主界面很简单,一个Button,用来换图片,一个ImageView,用来显示原图,一个GridLayout用来进行拼图游戏,最后,一个TextView,用来显示完成这个拼图用了多少步数。布局如下:


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 >
 <LinearLayout
   android:id="@+id/ll"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:orientation="horizontal"
   >
   <Button
     android:id="@+id/bt_choice"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:text="选择图片"
     android:adjustViewBounds="true"
     />

</LinearLayout>

<ImageView
   android:id="@+id/iv"
   android:layout_below="@id/ll"
   android:adjustViewBounds="true"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:src="@drawable/haizei"
   android:layout_marginTop="3dp"
   ></ImageView>

<!-- 游戏的主界面-->
 <GridLayout
   android:layout_marginTop="3dp"
   android:layout_below="@id/iv"
   android:id="@+id/gl"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:columnCount="5"
   android:rowCount="3"
   android:adjustViewBounds="true"
   >
 </GridLayout>
 <TextView
   android:id="@+id/tv_step"
   android:layout_below="@id/gl"
   android:layout_marginTop="3dp"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="已用步数:0"
   android:textSize="26sp"
   />

</RelativeLayout>

3.打开图片选择图片

给Button设置点击事件,调用startActivityForResult(Intent intent,int requestCode);方法,来获取图片。


bt_choice.setOnClickListener(new View.OnClickListener() {
     @Override
     public void onClick(View v) {
       Intent intent= new Intent("android.intent.action.GET_CONTENT");
       intent.setType("image/*");
       startActivityForResult(intent, CHOICE_PHOTO);//打开相册
     }
   });

在Activity中重写onActivityResult(int requestCode, int resultCode, Intent data)方法来显示选择的图片,以及初始化游戏。(图片选择完以后,就要进行图片的切割,和拼图游戏的开始。)


protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   super.onActivityResult(requestCode, resultCode, data);
   switch (requestCode){
     case CHOICE_PHOTO:
       if (resultCode==RESULT_OK){
         //判断手机系统版本
         if (Build.VERSION.SDK_INT>=19){
           handleImageOnKitKat(data);
           //得到imageview中的图片
           BitmapDrawable bitmapDrawable= (BitmapDrawable) photo.getDrawable();
           bt_tupan=bitmapDrawable.getBitmap();
           //将原来GridLayout中的小方块移除,
           removeGameItem();
           //将新图切割成小方块并加入GridLayout.
           setGameItem();
           //开始游戏
           startGame();
         }else {
           handleImageBeforeKitKat(data);
           //得到imageview中的图片
           BitmapDrawable bitmapDrawable= (BitmapDrawable) photo.getDrawable();
           bt_tupan=bitmapDrawable.getBitmap();
            //将原来GridLayout中的小方块移除,
           removeGameItem();
           //将新图切割成小方块并加入GridLayout.
           setGameItem();
           //开始游戏
           startGame();
         }
       }
   }
 }

然后是选择图片的具体方法的实现函数。注释很清楚,不多说。我们的重点在拼图以及手势变化的具体实现,这里选择图片的方式很多。不多讲,网上有现成的框架。


//手机不大于19的取数据方法
 private void handleImageBeforeKitKat(Intent data) {
   Uri uri =data.getData();
   String imagePath = getImagePath(uri, null);
   displayImage(imagePath);
 }
 /**
  * 手机大于19的取数据方法
  * @param data
  */
 @TargetApi(Build.VERSION_CODES.KITKAT)
 private void handleImageOnKitKat(Intent data) {
   String imagePath=null;
   Uri uri=data.getData();
   if (DocumentsContract.isDocumentUri(this,uri)){
     //如果是document类型的url,则通过document的id处理。
     String docId=DocumentsContract.getDocumentId(uri);
     if ("com.android.providers.media.documents".equals(uri.getAuthority())){
       String id =docId.split(":")[1];//解析出数字格式的id;
       String selection= MediaStore.Images.Media._ID+"="+id;
       imagePath=getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,selection);
     }else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())){
       Uri contenturi= ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),Long.valueOf(docId));
       imagePath=getImagePath(contenturi,null);
     }
   }else if ("content".equalsIgnoreCase(uri.getScheme())){
     //如果不是document类型的uri,则使用普通的方式处理。
     imagePath=getImagePath(uri,null);
   }
   displayImage(imagePath);
 }
 /**
  * 显示图片
  * @param imagePath //图片的路径。
  */
 private void displayImage(String imagePath) {
   if (imagePath != null) {
     Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
     if (isHeigthBigWidth(bitmap)) {
       Bitmap bt = rotaingImageView(bitmap);//将图片旋转90度。
       Bitmap disbitmapt = ajustBitmap(bt);
       photo.setImageBitmap(disbitmapt);
     } else {
       Bitmap disbitmap = ajustBitmap(bitmap);
       photo.setImageBitmap(disbitmap);
     }
   }
 }

/**
  * 调整图片的方向
  * @param bitmap
  * @return
  */
 private Bitmap rotaingImageView(Bitmap bitmap) {
   //旋转图片 动作
   Matrix matrix = new Matrix();;
   matrix.postRotate(270);
   // 创建新的图片
   Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0,
       bitmap.getWidth(), bitmap.getHeight(), matrix, true);
   return resizedBitmap;
 }
 /**
  * 得到图片的路径
  * @param externalContentUri
  * @param selection
  * @return
  */
 private String getImagePath(Uri externalContentUri, String selection) {
   String path=null;
   Cursor cursor=getContentResolver().query(externalContentUri, null, selection, null, null);
   if (cursor!=null){
     if (cursor.moveToFirst()){
       path=cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
     }
   }
   cursor.close();
   return path;
 }

4.拼图的各个小方块的形成过程。

看着各个小方块,我们用GridLayout来实现是最为方便的。所以,用一个GridLayout来显示大图切割后的各个小方块,用一个ImageView数组来保存各个小方块的信息,并且,我们默认把最后一个小方块设置为空白方块。

首先是各种需要的变量。注释很清楚。


/**
  * 利用二维数组创建若干个游戏小方框
  */
 private ImageView [][] iv_game_arr=new ImageView[3][5];
 /**
  *游戏主界面
  *
  */
 private GridLayout gl_game_layout;
 //小方块的行和列
 private int i;
 private int j;
 /**空方块的全局变量*/
 private ImageView iv_null_imagview;

接着是从Imageview获取图片,并且将图片按照一定的行和列进行切割(这里将拼图设置为3行5列)。将切割后的各个小方块的信息保存在一个ImageView数组中。给每个小方块设置Tag,和点击监听。


private void setGameItem() {
   //调整图片的尺寸
   Bitmap abitmap=ajustBitmap(bt_tupan);
   int ivWidth=getWindowManager().getDefaultDisplay().getWidth()/5;//每个游戏小方块的宽和高。切成正方形
   int tuWidth=abitmap.getWidth()/5;
   for (int i=0;i<iv_game_arr.length;i++){
     for (int j=0;j<iv_game_arr[0].length;j++){
       //将大图切成小方块
       Bitmap bm=Bitmap.createBitmap(abitmap,j*tuWidth,i*tuWidth,tuWidth,tuWidth);
       iv_game_arr[i][j]=new ImageView(this);
       iv_game_arr[i][j].setImageBitmap(bm);//设置每一个小方块的图案
       iv_game_arr[i][j].setLayoutParams(new RelativeLayout.LayoutParams(ivWidth, ivWidth));
       //设置方块之间的间距
       iv_game_arr[i][j].setPadding(2, 2, 2, 2);
       iv_game_arr[i][j].setTag(new GameItemView(i, j, bm)); //绑定自定义数据
       iv_game_arr[i][j].setOnClickListener(new View.OnClickListener() {
       .......

);

当然,我们选择的图片不可能都是符合标准的大小的,所以,在切割图片之前我们要对图片进行调整。将图片调整为5:3的比例。(这样切割成3行5列的小方块才能正确切割)这里对于width,我把每个小方块的间隔事先也算到里面去。


//调整图片的大小
 private Bitmap ajustBitmap(Bitmap bitmap) {
   int width=getWindowManager().getDefaultDisplay().getWidth()-(iv_game_arr[0].length-1)*2;
   int heigth=width/5*3;
   Bitmap scaledBitmap=Bitmap.createScaledBitmap(bitmap, width, heigth, true);
   return scaledBitmap;
 }

将每个小方格放入到GridLayout中。


/**
  * 将小方格放入GridLayout
  */
 private void startGame() {
   tv_step.setText("已用步数:0");
   for (i = 0; i <iv_game_arr.length; i++){
     for (j = 0; j <iv_game_arr[0].length; j++){
       gl_game_layout.addView(iv_game_arr[i][j]);
     }
   }
   //将最后一个方块设置为设置空方块。
   setNullImageView(iv_game_arr[i-1][j-1]);

5.小方块的点击事件和手势判断过程。

这里是拼图游戏的核心,弄懂了小方块的移动变化规律,也就弄懂了拼图游戏。

对于点击事件,首先拿到被点击的小方块的各种信息(位置、图案)和空白小方块的位置信息,判断被点击的小方块是否和空白小方块相邻,如果相邻,就移动交换数据(用TranslateAnimation来实现移动动画),如果不相邻则无操作。
a.判断点击方块和空白方块是否相邻的方法


/**
  *  判断当前点击的方块,是否和空方块相邻。
  * @param imageView 当前点击的方块
  * @return true:相邻。 false:不相邻。
  */
 public boolean isAdjacentNullImageView(ImageView imageView){
   //获取当前空方块的位置与点击方块的位置
   GameItemView null_gameItemView= (GameItemView) iv_null_imagview.getTag();
   GameItemView now_gameItem_view = (GameItemView) imageView.getTag();
  if(null_gameItemView.getY()==now_gameItem_view.getY()&&now_gameItem_view.getX()+1==null_gameItemView.getX()){//当前点击的方块在空方块的上面
     return true;
   }else if(null_gameItemView.getY()==now_gameItem_view.getY()&&now_gameItem_view.getX()==null_gameItemView.getX()+1){//当前点击的方块在空方块的下面
     return true;
   }else if(null_gameItemView.getY()==now_gameItem_view.getY()+1&&now_gameItem_view.getX()==null_gameItemView.getX()){//当前点击的方块在空方块的左面
     return true;
   }else if(null_gameItemView.getY()+1==now_gameItem_view.getY()&&now_gameItem_view.getX()==null_gameItemView.getX()){ ////当前点击的方块在空方块的右面
     return true;
   }
   return false;
 }

b.接着是如果相邻就进入方块数据交换的方法
这里有个方法重载,是否需要动画效果,没有动画效果的数据交换是为初始化游戏时打乱拼图做准备的。这里将核心交换代码列出。每次交换后还要判断是否游戏胜利(即是否拼图完成~)。


   //得到点击方块绑定的数据
     GameItemView gameItemView = (GameItemView) itemimageView.getTag();
     //将空方块的图案设置为点击方块
     iv_null_imagview.setImageBitmap(gameItemView.getBm());
     //得到空方块绑定的数据
     GameItemView null_gameItemView = (GameItemView) iv_null_imagview.getTag();
     //交换数据(将点击方块的数据传入空方块)
     null_gameItemView.setBm(gameItemView.getBm());
     null_gameItemView.setP_x(gameItemView.getP_x());
     null_gameItemView.setP_y(gameItemView.getP_y());
     //设置当前点击的方块为空方块。
     setNullImageView(itemimageView);
     if (isStart){
       isGameWin();//成功时,会弹一个吐司。
     }

c.交换时的动画设置
交换设置动画时,首先判断移动的方向,根据方向设置不同的移动动画,然后再监听动画完成后,进行数据交换操作。即上面b.接着是如果相邻就进入方块数据交换的方法.最后执行动画。


//1.创建一个动画,设置方向,移动的距离
//判断方向,设置动画
   if (itemimageView.getX()>iv_null_imagview.getX()){//当前点击的方块在空方块的上面
     //下移
     translateAnimation = new TranslateAnimation(0.1f,-itemimageView.getWidth(),0.1f,0.1f);
   }else if (itemimageView.getX()<iv_null_imagview.getX()){//当前点击的方块在空方块的下面
     //上移
     boolean f=itemimageView.getX()<iv_null_imagview.getX();
     //Log.i("点击方块","sssssssssssssssssssssssss"+f);
     translateAnimation = new TranslateAnimation(0.1f,itemimageView.getWidth(),0.1f,0.1f);
   }else if (itemimageView.getY()>iv_null_imagview.getY()){//当前点击的方块在空方块的左面
     //右移
     translateAnimation=new TranslateAnimation(0.1f,0.1f,0.1f,-itemimageView.getWidth());
   }else if(itemimageView.getY()<iv_null_imagview.getY()){//当前点击的方块在空方块的右面
     //左移
     translateAnimation=new TranslateAnimation(0.1f,0.1f,0.1f,itemimageView.getWidth());
   }
   //2.设置动画的各种参数
   translateAnimation.setDuration(80);
   translateAnimation.setFillAfter(true);
   //3.设置动画的监听
   translateAnimation.setAnimationListener(new Animation.AnimationListener() {
     @Override
     public void onAnimationStart(Animation animation) {
       isAminMove=true;
     }
     @Override
     public void onAnimationEnd(Animation animation) {
       //动画结束,交换数据
       ......
     }

//动画执行
   itemimageView.startAnimation(translateAnimation);

点击事件的流程就完了,接下来是手势判断的事件。即不仅可以通过点击小方块进行移动,也可以通过手势移动小方块。

One.创建手势对象
在onFling方法中完成手势相关的操作。


//创建手势对象
   gestureDetector =new GestureDetector(this, new GestureDetector.OnGestureListener() {
     @Override
     public boolean onDown(MotionEvent e) {
       return false;
     }
     @Override
     public void onShowPress(MotionEvent e) {
     }
     @Override
     public boolean onSingleTapUp(MotionEvent e) {
       return false;
     }
     @Override
     public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
       return false;
     }
     @Override
     public void onLongPress(MotionEvent e) {
     }
     @Override
     public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
     //手势相关的操作
     ......
 }

接着我们onFling方法中做具体操作

Two.判断手势移动的方向
根据返回值的不同得到不同的移动方向。


/**
  * 增加手势滑动,根据手势判断是上下左右滑动
  * @param start_x 手势起始点x
  * @param start_y 手势起始点y
  * @param end_x 手势终止点 x
  * @param end_y 手势终止点y
  * @return 1:上 2:下 3:左 4:右
  */
 public int getDirctionByGesure(float start_x,float start_y,float end_x,float end_y){
   boolean isLeftOrRight =(Math.abs(end_x-start_x)>Math.abs(end_y-start_y))?true:false; //是否是左右
   if(isLeftOrRight){//左右
     boolean isLeft=(end_x-start_x)>0?false:true;
     if(isLeft){
       return 3;
     }else {
       return 4;
     }
   }else{//上下
     boolean isUp=(end_y-start_y)>0?false:true;
     if (isUp){
       return 1;
     }else {
       return 2;
     }
   }
 }

Three.根据空方块和移动的方向,判断能否移动以及进行移动操作。
因为是手势,移动的肯定是空方块周围的方块,所以重点就是要判断空方块在要移动的方块的那个方向,再根据方向判断能否移动,进行移动操作。(其中changeDateByImageView()中的方法就是具体的方块交换数据及移动的操作。就是点击事件的那个方法。)


/**重载changeByDirGes(int type)方法;
  * 根据手势的方向,对空方块相邻位置的方块进行移动。
  * @param type 方向的返回值 1:上 2:下 3:左 5:右
  * @param isAnim 是否有动画 true:有动画,false:无动画
  */
 public void changeByDirGes(int type,boolean isAnim){
   //1.获取当前空方块的位置。
   GameItemView null_gameItemView= (GameItemView) iv_null_imagview.getTag();
   int new_x=null_gameItemView.getX();
   int new_y=null_gameItemView.getY();
   //2.根据方向,设置相应相邻的位置坐标。
   if (type==1){//说明空方块在要移动的方块的上面。
     new_x++;
   }else if (type==2){//空方块在要移动的方块的下面
     new_x--;
   }else if (type==3){//空方块在要移动的方块的左面
     new_y++;
   }else if (type==4){//空方块在要移动的方块的右面
     new_y--;
   }
   //3.判断这个新坐标是否存在
   if(new_x>=0&&new_x<iv_game_arr.length&&new_y>=0&&new_y<iv_game_arr[0].length){
     //存在,可以移动交换数据
     if(isAnim){//有动画
       changeDateByImageView(iv_game_arr[new_x][new_y]);
     }else{
       changeDateByImageView(iv_game_arr[new_x][new_y],isAnim);
     }
   }else{
     //什么也不做
   }
 }
0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com