<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Android實現(xiàn)拼圖小游戲功能

          共 9578字,需瀏覽 20分鐘

           ·

          2022-03-18 16:06

          效果圖:



          拋磚引玉:


          這是一個簡單的小Demo,還可以有更多的擴展,比如我們可以動態(tài)的從手機相冊中選取圖片作為拼圖底圖,可以動態(tài)的設(shè)置拼圖難易度(滑塊個數(shù))等等,看完這篇文章,請大家盡情發(fā)揮想象力吧~


          實現(xiàn)思路:


          簡單的過一下思路,首先我們需要一張圖作為拼圖背景,然后根據(jù)一定的比例把它分成n個拼圖滑塊并隨機打亂位置,指定其中一個滑塊為空白塊,當(dāng)用戶點擊這個空白塊相鄰(上下左右)的拼圖滑塊時,交換它們位置,每次交換位置后去判斷是否完成了拼圖,大概思路是這樣子,下面我們來看代碼實現(xiàn)。


          拼圖滑塊實體類:

          package jigsaw.lcw.com.jigsaw;
          import android.graphics.Bitmap;
          /**?*?拼圖實體類 */public class Jigsaw {
          private int originalX; private int originalY; private Bitmap bitmap; private int currentX; private int currentY;
          public Jigsaw(int originalX, int originalY, Bitmap bitmap) { this.originalX = originalX; this.originalY = originalY; this.bitmap = bitmap; this.currentX = originalX; this.currentY = originalY; }
          public int getOriginalX() { return originalX; }
          public void setOriginalX(int originalX) { this.originalX = originalX; }
          public int getOriginalY() { return originalY; }
          public void setOriginalY(int originalY) { this.originalY = originalY; }
          public Bitmap getBitmap() { return bitmap; }
          public void setBitmap(Bitmap bitmap) { this.bitmap = bitmap; }
          public int getCurrentX() { return currentX; }
          public void setCurrentX(int currentX) { this.currentX = currentX; }
          public int getCurrentY() { return currentY; }
          public void setCurrentY(int currentY) { this.currentY = currentY; }
          @Override public String toString() { return "Jigsaw{" + "originalX=" + originalX + ", originalY=" + originalY + ", currentX=" + currentX + ", currentY=" + currentY + '}'; }}


          首先我們需要一個滑塊的實體類,這個類用來記錄拼圖滑塊的原始位置點(originalX、originalY),當(dāng)前顯示的圖像(bitmap),當(dāng)前的位置點(currentX、currentY),我們在移動滑塊的時候,需要不斷的去交換顯示的圖像和當(dāng)前位置點,而原始位置點是用來判斷游戲是否結(jié)束的一個標(biāo)志,當(dāng)所有的原始位置點與所有的當(dāng)前位置點相等時,就代表游戲結(jié)束。


          拼圖底圖的實現(xiàn):


          既然要拼圖,那肯定需要有圖片了,有些朋友可能會想是不是需要準(zhǔn)備n張小圖片?其實是不用的,如果都這樣去準(zhǔn)備的話,要做一個拼圖闖關(guān)的游戲得預(yù)置多少圖片資源啊,包體積還不直接上天了,這里我們采用GridLayout來做,將一張圖片動態(tài)切割成n個小圖填充至ImageView,然后加入到GridLayout布局中。

              /**     * 獲取拼圖(大圖)     *     * @return     */    public Bitmap getJigsaw(Context context) {        //加載Bitmap原圖,并獲取寬高        Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.img);        int bitmapWidth = bitmap.getWidth();        int bitmapHeight = bitmap.getHeight();        //按屏幕寬鋪滿顯示,算出縮放比例        int screenWidth = getScreenWidth(context);        float scale = 1.0f;        if (screenWidth < bitmapWidth) {            scale = screenWidth * 1.0f / bitmapWidth;        }        bitmap = Bitmap.createScaledBitmap(bitmap, screenWidth, (int) (bitmapHeight * scale), false);        return bitmap;    }


          首先我們需要對資源圖片進行一定比例的壓縮,我們讓圖片充滿屏幕寬度,算出一定的縮放比例,然后壓縮圖片的高,這里有個createScaledBitmap方法,我們來看下底層源碼:

             /**     * Creates a new bitmap, scaled from an existing bitmap, when possible. If the     * specified width and height are the same as the current width and height of     * the source bitmap, the source bitmap is returned and no new bitmap is     * created.     *     * @param src       The source bitmap.     * @param dstWidth  The new bitmap's desired width.     * @param dstHeight The new bitmap's desired height.     * @param filter    true if the source should be filtered.     * @return The new scaled bitmap or the source bitmap if no scaling is required.     * @throws IllegalArgumentException if width is <= 0, or height is <= 0     */    public static Bitmap createScaledBitmap(@NonNull Bitmap src, int dstWidth, int dstHeight,            boolean filter) {        Matrix m = new Matrix();
          final int width = src.getWidth(); final int height = src.getHeight(); if (width != dstWidth || height != dstHeight) { final float sx = dstWidth / (float) width; final float sy = dstHeight / (float) height; m.setScale(sx, sy); } return Bitmap.createBitmap(src, 0, 0, width, height, m, filter); }


          其實它的原理就是根據(jù)我們傳入的壓縮寬高值,通過矩陣Matrix對圖片進行縮放。


          再來就是切割小塊拼圖滑塊了,我們把圖片分成3行5列,根據(jù)算出的寬高去創(chuàng)建3*5個小的Bitmap并裝載入ImageView,加入到GridLayout布局中,然后為每個ImageView設(shè)置一個Tag,這個Tag的信息就是我們之前創(chuàng)建的實體類數(shù)據(jù),并制定最后一個ImageView為空白塊。

              /**     * 初始化拼圖碎片     * @param jigsawBitmap     */    private void initJigsaw(Bitmap jigsawBitmap) {
          mGridLayout = findViewById(R.id.gl_layout);
          int itemWidth = jigsawBitmap.getWidth() / 5; int itemHeight = jigsawBitmap.getHeight() / 3;
          //切割原圖為拼圖碎片裝入GridLayout for (int i = 0; i < mJigsawArray.length; i++) { for (int j = 0; j < mJigsawArray[0].length; j++) { Bitmap bitmap = Bitmap.createBitmap(jigsawBitmap, j * itemWidth, i * itemHeight, itemWidth, itemHeight); ImageView imageView = new ImageView(this); imageView.setImageBitmap(bitmap); imageView.setPadding(2, 2, 2, 2); imageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //判斷是否可移動 boolean isNearBy = JigsawHelper.getInstance().isNearByEmptyView((ImageView) v, mEmptyImageView); if (isNearBy) { //處理移動 handleClickItem((ImageView) v, true); } } }); //綁定數(shù)據(jù) imageView.setTag(new Jigsaw(i, j, bitmap)); //添加到拼圖布局 mImageViewArray[i][j] = imageView; mGridLayout.addView(imageView); } } //設(shè)置拼圖空碎片 ImageView imageView = (ImageView) mGridLayout.getChildAt(mGridLayout.getChildCount() - 1); imageView.setImageBitmap(null);????????mEmptyImageView?=?imageView; }


          拼圖滑塊的移動事件:


          上面代碼我們?yōu)镮mageView設(shè)置了點擊事件,這邊就是用來判斷當(dāng)前點擊的ImageView是否是可以移動的,判斷的依據(jù):當(dāng)前點擊ImageView是否在空白塊相鄰(上下左右)的位置,而這個位置信息可以通過ImageView里的Tag得到,參考圖如下(這里的R,C不是指XY坐標(biāo),而是指所在的行和列):



              /**     * 判斷當(dāng)前view是否在可移動范圍內(nèi)(在空白View的上下左右)     *     * @param imageView     * @param emptyImageView     * @return     */    public boolean isNearByEmptyView(ImageView imageView, ImageView emptyImageView) {
          Jigsaw emptyJigsaw = (Jigsaw) imageView.getTag(); Jigsaw jigsaw = (Jigsaw) emptyImageView.getTag();
          if (emptyJigsaw != null && jigsaw != null) { //點擊拼圖在空拼圖的左邊 if (jigsaw.getOriginalX() == emptyJigsaw.getOriginalX() && jigsaw.getOriginalY() + 1 == emptyJigsaw.getOriginalY()) { return true; } //點擊拼圖在空拼圖的右邊 if (jigsaw.getOriginalX() == emptyJigsaw.getOriginalX() && jigsaw.getOriginalY() - 1 == emptyJigsaw.getOriginalY()) { return true; } //點擊拼圖在空拼圖的上邊 if (jigsaw.getOriginalY() == emptyJigsaw.getOriginalY() && jigsaw.getOriginalX() + 1 == emptyJigsaw.getOriginalX()) { return true; } //點擊拼圖在空拼圖的下邊 if (jigsaw.getOriginalY() == emptyJigsaw.getOriginalY() && jigsaw.getOriginalX() - 1 == emptyJigsaw.getOriginalX()) { return true; } } return false; }


          然后我們看一下移動拼圖滑塊的代碼,這里其實做了這么幾件事情:
          1、根據(jù)點擊ImageView位置去構(gòu)造出對應(yīng)的移動的動畫
          2、動畫結(jié)束后,需要處理對應(yīng)的數(shù)據(jù)交換
          3、動畫結(jié)束后,需要去判斷是否完成了拼圖(下文會提,這里先不管)


             /**     * 處理點擊拼圖的移動事件     *     * @param imageView     */    private void handleClickItem(final ImageView imageView) {        if (!isAnimated) {            TranslateAnimation translateAnimation = null;            if (imageView.getX() < mEmptyImageView.getX()) {                //左往右                translateAnimation = new TranslateAnimation(0, imageView.getWidth(), 0, 0);            }
          if (imageView.getX() > mEmptyImageView.getX()) { //右往左 translateAnimation = new TranslateAnimation(0, -imageView.getWidth(), 0, 0); }
          if (imageView.getY() > mEmptyImageView.getY()) { //下往上 translateAnimation = new TranslateAnimation(0, 0, 0, -imageView.getHeight()); }
          if (imageView.getY() < mEmptyImageView.getY()) { //上往下 translateAnimation = new TranslateAnimation(0, 0, 0, imageView.getHeight()); }
          if (translateAnimation != null) { translateAnimation.setDuration(80); translateAnimation.setFillAfter(true); translateAnimation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { isAnimated = true; }
          @Override public void onAnimationEnd(Animation animation) { //清除動畫 isAnimated = false; imageView.clearAnimation(); //交換拼圖數(shù)據(jù) changeJigsawData(imageView); //判斷游戲是否結(jié)束 boolean isFinish = JigsawHelper.getInstance().isFinishGame(mImageViewArray, mEmptyImageView); if (isFinish) { Toast.makeText(MainActivity.this, "拼圖成功,游戲結(jié)束!", Toast.LENGTH_LONG).show(); } }
          @Override public void onAnimationRepeat(Animation animation) {
          } });
          imageView.startAnimation(translateAnimation); } } }


          這里我們重點看一下數(shù)據(jù)的交換,我們都知道Android補間動畫只是給我們視覺上的改變,本質(zhì)上View的位置是沒有移動的,我們先通過setFillAfter讓其做完動畫保持在原處(視覺效果),在動畫執(zhí)行完畢的時候,我們進行ImageView數(shù)據(jù)的交換,這邊要特別注意的是,其實我們并沒有去交換View的位置,本質(zhì)上我們只是交換了Bitmap讓ImageView更改顯示和currentX、currentY的值,原來的View在哪,它還是在哪,當(dāng)數(shù)據(jù)交換完成后,記得更改空白塊的引用。

             /**     * 交換拼圖數(shù)據(jù)     *     * @param imageView     */    public void changeJigsawData(ImageView imageView) {        Jigsaw emptyJigsaw = (Jigsaw) mEmptyImageView.getTag();        Jigsaw jigsaw = (Jigsaw) imageView.getTag();
          //更新imageView的顯示內(nèi)容 mEmptyImageView.setImageBitmap(jigsaw.getBitmap()); imageView.setImageBitmap(null); //交換數(shù)據(jù) emptyJigsaw.setCurrentX(jigsaw.getCurrentX()); emptyJigsaw.setCurrentY(jigsaw.getCurrentY()); emptyJigsaw.setBitmap(jigsaw.getBitmap());
          //更新空拼圖引用 mEmptyImageView = imageView; }


          判斷游戲結(jié)束:


          我們之前在拼圖滑塊實體類中預(yù)置了這幾個屬性originalX、originalY(代表最開始的位置),currentX、currentY(經(jīng)過一系列移動后的位置),因為滑塊的移動只是視覺效果,本質(zhì)上是沒有改變View位置的,只是交換了數(shù)據(jù),所以我們最后可以根據(jù)originalX、currentX和originalY、currentY是否相等來判斷(空白塊除外):

             /**     * 判斷游戲是否結(jié)束     *     * @param imageViewArray     * @return     */    public boolean isFinishGame(ImageView[][] imageViewArray, ImageView emptyImageView) {
          int rightNum = 0;//記錄匹配拼圖數(shù)
          for (int i = 0; i < imageViewArray.length; i++) { for (int j = 0; j < imageViewArray[0].length; j++) { if (imageViewArray[i][j] != emptyImageView) { Jigsaw jigsaw = (Jigsaw) imageViewArray[i][j].getTag(); if (jigsaw != null) { if (jigsaw.getOriginalX() == jigsaw.getCurrentX() && jigsaw.getOriginalY() == jigsaw.getCurrentY()) { rightNum++; } } } } }
          if (rightNum == (imageViewArray.length * imageViewArray[0].length) - 1) { return true; } return false; }


          手勢交互:


          剛才我們已經(jīng)實現(xiàn)了點擊的交互事件,可以更炫酷點,我們把手勢交互也補上,用手指的滑動來帶動拼圖滑塊的移動,我們來看下核心代碼:

              /**     * 判斷手指移動的方向,     *     * @param startEvent     * @param endEvent     * @return     */    public int getGestureDirection(MotionEvent startEvent, MotionEvent endEvent) {        float startX = startEvent.getX();        float startY = startEvent.getY();        float endX = endEvent.getX();        float endY = endEvent.getY();        //根據(jù)滑動距離判斷是橫向滑動還是縱向滑動        int gestureDirection = Math.abs(startX - endX) > Math.abs(startY - endY) ? LEFT_OR_RIGHT : UP_OR_DOWN;        //具體判斷滑動方向        switch (gestureDirection) {            case LEFT_OR_RIGHT:                if (startEvent.getX() < endEvent.getX()) {                    //手指向右移動                    return RIGHT;                } else {                    //手指向左移動                    return LEFT;                }            case UP_OR_DOWN:                if (startEvent.getY() < endEvent.getY()) {                    //手指向下移動                    return DOWN;                } else {                    //手指向上移動                    return UP;                }        }        return NONE;    }


          首先我們根據(jù)手指的移動距離先判斷是左右滑動還是上下滑動,然后再根據(jù)坐標(biāo)的起始點判斷具體方向,有了對應(yīng)的移動方向,我們就可以來處理拼圖滑塊的移動了,這次是逆向思維,根據(jù)手勢方向判斷空白塊相鄰(上下左右)有沒有拼圖塊,如果有,把對應(yīng)的滑塊ImageView取出,交給上文提到的點擊滑塊移動代碼處理:

              /**     * 處理手勢移動拼圖     *     * @param gestureDirection     * @param animation        是否帶有動畫     */    private void handleFlingGesture(int gestureDirection, boolean animation) {        ImageView imageView = null;        Jigsaw emptyJigsaw = (Jigsaw) mEmptyImageView.getTag();        switch (gestureDirection) {            case GestureHelper.LEFT:                if (emptyJigsaw.getOriginalY() + 1 <= mGridLayout.getColumnCount() - 1) {                    imageView = mImageViewArray[emptyJigsaw.getOriginalX()][emptyJigsaw.getOriginalY() + 1];                }                break;            case GestureHelper.RIGHT:                if (emptyJigsaw.getOriginalY() - 1 >= 0) {                    imageView = mImageViewArray[emptyJigsaw.getOriginalX()][emptyJigsaw.getOriginalY() - 1];                }                break;            case GestureHelper.UP:                if (emptyJigsaw.getOriginalX() + 1 <= mGridLayout.getRowCount() - 1) {                    imageView = mImageViewArray[emptyJigsaw.getOriginalX() + 1][emptyJigsaw.getOriginalY()];                }                break;            case GestureHelper.DOWN:                if (emptyJigsaw.getOriginalX() - 1 >= 0) {                    imageView = mImageViewArray[emptyJigsaw.getOriginalX() - 1][emptyJigsaw.getOriginalY()];                }                break;            default:                break;        }        if (imageView != null) {            handleClickItem(imageView, animation);        }    }


          游戲的初始化:


          關(guān)于游戲的初始化,其實很簡單,我們可以構(gòu)造給隨機次數(shù),讓游戲開始的時候隨機方向,隨機次數(shù)的滑動即可:

             /**     * 游戲初始化,隨機打亂順序     */    private void randomJigsaw() {        for (int i = 0; i < 100; i++) {            int gestureDirection = (int) ((Math.random() * 4) + 1);            handleFlingGesture(gestureDirection, false);        }    }


          好了,到這里文章就結(jié)束了,很簡單的一個小游戲,很美好的一份童年回憶~


          源碼地址:

          https://github.com/Lichenwei-Dev/JigsawView

          瀏覽 66
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天码人妻一区二区三区在线看 | 蜜臀久久99精品久久久久久宅男 | 人妻系列AV | 亚洲成人AV电影在线观看 | 欧洲激情第一页 |