android (拼图游戏)数字推盘的简单实现

看了徐宜生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">

        
 
   
  

上面通过相对布局来规划控件位置,上边是对局信息区,中间是游戏区,下边是功能键。(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(拼图游戏的类型如38数字推盘415数字推盘)
     * @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 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,实现触摸事件。这样在我们的图片是从网络获取的时候,出现突发情况(如:网络链接异常),不至于闪退,提高程序的健壮性。

第三: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中,会更为规范些...

那么,到这里为止,一个简单的类似数字推盘的拼图游戏就实现出来了,当然还有很多可以优化的地方,如充网络获取更多的图片,拼图成功后的动画,难度关卡的选择,初始化的图片是否有解等等,各位可以自己试试,我后续也会继续更新。谢谢观看


源码下载链接:点击打开链接







 
 

你可能感兴趣的:(android,数字推盘,拼图,图片裁剪,RecyclerView)