Android拼图游戏的设计逻辑,从切图到交互动画,从关卡到倒计时,实例提高!

简介: <div class="markdown_views"><h1 id="android拼图游戏的设计逻辑从切图到交互动画从关卡到倒计时实例提高">Android拼图游戏的设计逻辑,从切图到交互动画,从关卡到倒计时,实例提高!</h1><hr><blockquote> <p>群英传的最后一章,我大致的看了一下这个例子,发现鸿洋大神也做过,就参考两个人的设计逻辑,感觉都

Android拼图游戏的设计逻辑,从切图到交互动画,从关卡到倒计时,实例提高!


群英传的最后一章,我大致的看了一下这个例子,发现鸿洋大神也做过,就参考两个人的设计逻辑,感觉都差不多,就这样实现起来了

一.切图工具类

我们九宫格嘛,肯定要一个切图的工具,把一个图片给切成九张,那具体是怎么实现呢?我们先写一个bean来存储一切的状态

ImagePiece

package com.lgl.ninegame.utils;

import android.graphics.Bitmap;

/**
 *
 * Created by LGL on 2016/5/2.
 */
public class ImagePiece {

    private int index;
    private Bitmap bitmap;

    //构造方法
    public ImagePiece() {

    }

    //有参构造方法
    public ImagePiece(int index, Bitmap bitmap) {
        this.index = index;
        this.bitmap = bitmap;
    }

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    public Bitmap getBitmap() {
        return bitmap;
    }

    public void setBitmap(Bitmap bitmap) {
        this.bitmap = bitmap;
    }

}

然后就可以实现我们的切图工具类了

ImageSplitterUtil

package com.lgl.ninegame.utils;

import android.graphics.Bitmap;

import java.util.ArrayList;
import java.util.List;

/**
 * 切图工具
 * Created by LGL on 2016/5/2.
 */
public class ImageSplitterUtil {

    /**
     * 静态方法
     * 传递bitmap切成piece*piece块,返回List
    
    
     
     *
     *
     
      @param bitmap
     *
     
      @param piece
     *
     
      @return
     */
    
    
    public static List
   
    
    
    splitImage(Bitmap bitmap, 
    
    int piece) {

        
    
    //作为返回值传递
        List
    
    
      imagePieces = 
     
     new ArrayList<>();

        
     
     //获取图片的宽高
        
     
     int width = bitmap.getWidth();
        
     
     int height = bitmap.getHeight();

        
     
     //根据宽高取最小值达到正方形
        
     
     int pieceWidth = Math.min(width, height) / piece;

        
     
     //进行切割
        
     
     for (
     
     int i = 
     
     0; i < piece; i++) {
            
     
     for (
     
     int j = 
     
     0; j < piece; j++) {
                ImagePiece imagePiece = 
     
     new ImagePiece();
                imagePiece.setIndex(j + i * piece);

                
     
     int x = j * pieceWidth;
                
     
     int y = i * pieceWidth;

                
     
     //第一次循环为0,0,
                imagePiece.setBitmap(Bitmap.createBitmap(bitmap, x, y, pieceWidth, pieceWidth));
                
     
     /**
                 * 保存到list中进行返回
                 */
                imagePieces.add(imagePiece);
            }
        }
        
     
     return imagePieces;
    }


}

    
    
   
   

二.自定义容器

工具有了,就需要容器了,我们需要自定义一个,这里我们就继承相对布局,我们这个是一个游戏布局,所以我实现准备好了一张妹子的图片,分辨率是800*80

这里写图片描述

GameView

package com.lgl.ninegame.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;

import com.lgl.ninegame.R;
import com.lgl.ninegame.utils.ImagePiece;
import com.lgl.ninegame.utils.ImageSplitterUtil;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * 自定义容器
 * Created by LGL on 2016/5/2.
 */
public class GameView extends RelativeLayout implements View.OnClickListener {


    //默认3*3
    private int mColumn = 3;
    //容器的内边距
    private int mPadding;
    //小图的距离 dp
    private int mMagin = 3;
    //存储图片的,宽高 都是固定的,所以使用数组
    private ImageView[] mGameOintuItems;
    //宽度
    private int mItemWidth;
    //图片
    private Bitmap mBitmap;
    //切图后存储
    private List
   
   
     mItemBitmaps;
    
    
    //标记
    
    
    private 
    
    boolean once;

    
    
    //容器的一个宽度
    
    
    private 
    
    int mWidth;


    
    
    public 
    
    GameView(Context context) {

        
    
    this(context, 
    
    null);
    }

    
    
    public 
    
    GameView(Context context, AttributeSet attrs) {

        
    
    this(context, attrs, 
    
    0);
    }

    
    
    public 
    
    GameView(Context context, AttributeSet attrs, 
    
    int defStyleAttr) {
        
    
    super(context, attrs, defStyleAttr);

        init();
    }

    
    
    /**
     * 初始化
     */
    
    
    private 
    
    void 
    
    init() {
        
    
    //单位转换——dp-px
        mMagin = (
    
    int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 
    
    3, getResources().getDisplayMetrics());

        mPadding = min(getPaddingLeft(), getPaddingRight(), getPaddingTop(), getPaddingBottom());
    }

    
    
    /**
     * 确定当前布局的大小,我们要设置成正方形
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    
    
    @Override
    
    
    protected 
    
    void 
    
    onMeasure(
    
    int widthMeasureSpec, 
    
    int heightMeasureSpec) {
        
    
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        
    
    //拿到容器的高宽最小值
        mWidth = Math.min(getMeasuredHeight(), getMeasuredWidth());

        
    
    if (!once) {
            
    
    //进行切图和排序
            initBitmap();

            
    
    //设置imageview(item)的宽高等属性
            initItem();

            once = 
    
    true;

        }
        setMeasuredDimension(mWidth, mWidth);
    }

    
    
    /**
     * 进行切图和排序
     */
    
    
    private 
    
    void 
    
    initBitmap() {
        
    
    //判断是否存在这张图片
        
    
    if (mBitmap == 
    
    null) {
            mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.img);
        }
        
    
    //进行裁剪
        mItemBitmaps = ImageSplitterUtil.splitImage(mBitmap, mColumn);

        
    
    //裁剪玩后需要进行顺序打乱sort
        Collections.sort(mItemBitmaps, 
    
    new Comparator
    
    
     () {
            
     
     @Override
            
     
     public 
     
     int 
     
     compare(ImagePiece lhs, ImagePiece rhs) {

                
     
     //生成随机数,如果》0.5返回1否则返回-1
                
     
     return Math.random() > 
     
     0.5 ? 
     
     1 : -
     
     1;
            }
        });

    }

    
     
     /**
     * 设置imageview(item)的宽高等属性
     */
    
     
     private 
     
     void 
     
     initItem() {
        
     
     //( 容器的宽度 - 内边距 * 2  - 间距  ) /  裁剪的数量
        mItemWidth = (mWidth - mPadding * 
     
     2 - mMagin * (mColumn - 
     
     1)) / mColumn;
        
     
     //几 * 几
        mGameOintuItems = 
     
     new ImageView[mColumn * mColumn];

        
     
     //开始排放
        
     
     for (
     
     int i = 
     
     0; i < mGameOintuItems.length; i++) {
            ImageView item = 
     
     new ImageView(getContext());
            item.setOnClickListener(
     
     this);
            
     
     //设置图片
            item.setImageBitmap(mItemBitmaps.get(i).getBitmap());
            
     
     //保存
            mGameOintuItems[i] = item;
            
     
     //设置ID
            item.setId(i + 
     
     1);
            
     
     //设置Tag
            item.setTag(i + 
     
     "_" + mItemBitmaps.get(i).getIndex());

            RelativeLayout.LayoutParams lp = 
     
     new RelativeLayout.LayoutParams(mItemWidth, mItemWidth);

            
     
     //判断不是最后一列
            
     
     if (i + 
     
     1 % mColumn != 
     
     0) {
                lp.rightMargin = mMagin;
            }

            
     
     //判断不是第一列
            
     
     if (i % mColumn != 
     
     0) {
                lp.addRule(RelativeLayout.RIGHT_OF, mGameOintuItems[i - 
     
     1].getId());
            }

            
     
     //判断如果不是第一行
            
     
     if ((i + 
     
     1) > mColumn) {
                lp.topMargin = mMagin;
                lp.addRule(RelativeLayout.BELOW, mGameOintuItems[i - mColumn].getId());
            }
            addView(item, lp);
        }
    }

    
     
     /**
     * 获取多个参数的最小值
     */
    
     
     private 
     
     int 
     
     min(
     
     int... params) {
        
     
     int min = params[
     
     0];
        
     
     //遍历
        
     
     for (
     
     int param : params) {
            
     
     if (param < min) {
                min = param;
            }
        }
        
     
     return min;
    }

    
     
     /**
     * 点击事件
     *
     * @param v
     */
    
     
     @Override
    
     
     public 
     
     void 
     
     onClick(View v) {

    }
}

    
    
   
   

代码的逻辑十分的简单,这点不假,而且注释也是相对来讲清晰易懂,这样,我们先测试一下,在XML中引用


    
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dp">

    <com.lgl.ninegame.view.GameView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true" />



    RelativeLayout>

我们运行一下看看现在裁剪后的效果

这里写图片描述

可以的,那我们继续

三.实现图片交互

这里大家应该都知道,要用我们的点击事件了,我们继续在GameView里面编写

 /**
     * 点击的第一张图和第二张图,他们进行交换
     */
    private ImageView mFirst;
    private ImageView mSecond;

    /**
     * 点击事件
     *
     * @param v
     */
    @Override
    public void onClick(View v) {

        //重复点击
        if (mFirst == v) {
            //去掉颜色
            mFirst.setColorFilter(null);
            mFirst = null;
            return;
        }

        if (mFirst == null) {
            mFirst = (ImageView) v;
            //设置选中效果
            mFirst.setColorFilter(Color.parseColor("#55FF0000"));
            //第二次点击
        } else {
            mSecond = (ImageView) v;
            //交换
            exchangeView();
        }
    }

这里我们需要去写一个图片交换的方法

 /**
     * 图片交换
     */
    private void exchangeView() {
        //先去掉颜色
        mFirst.setColorFilter(null);

        String firstTag = (String) mFirst.getTag();
        String secondTag = (String) mSecond.getTag();

        String[] firstParams = firstTag.split("_");
        String[] scendParams = secondTag.split("_");

        //获取到bitmap并且替换
        mSecond.setImageBitmap(mItemBitmaps.get(Integer.parseInt(firstParams[0])).getBitmap());
        mFirst.setImageBitmap(mItemBitmaps.get(Integer.parseInt(scendParams[0])).getBitmap());

        //回到最初始的状态
        mFirst = mSecond = null;

    }

这样,我们就可以运行了

这里写图片描述

但是,大家有没有发现,他的小bitmap排序是错误的,这就需要我们去处理了

实际上是因为我们虽然交换了,但是tag没改,我们在回到最初始的状态之前加上以下代码:

//改变tag
 mFirst.setTag(secondTag);
 mSecond.setTag(firstTag);

四.交互动画

既然我们卡片已经实现了,那么接下来,我们怎么的也要去加点动画呀,开搞,我们先把公用的东西抽取出来

 /**
     * 获取tag
     *
     * @param tag
     * @return
     */
    public int getImageIdByTag(String tag) {
        String[] split = tag.split("_");
        return Integer.parseInt(split[0]);
    }

    /**
     * 获取图片的tag
     *
     * @param tag
     * @return
     */
    public int getImageIndex(String tag) {
        String[] split = tag.split("_");
        return Integer.parseInt(split[1]);
    }

    /**
     * 动画层,覆盖在viewGroup中
     */
    private RelativeLayout mAnimLayout;


    /**
     * 交互动画
     */
    private void setUpAnimLayout() {
        if (mAnimLayout == null) {
            mAnimLayout = new RelativeLayout(getContext());
            //添加到整体
            addView(mAnimLayout);
        }
    }

接着,我们需要修改以下图片交换的方法,添加一层动画层

/**
     * 图片交换
     */
    private void exchangeView() {
        mFirst.setColorFilter(null);
        // 构造我们的动画层
        setUpAnimLayout();

        ImageView first = new ImageView(getContext());
        final Bitmap firstBitmap = mItemBitmaps.get(
                getImageIdByTag((String) mFirst.getTag())).getBitmap();
        first.setImageBitmap(firstBitmap);
        LayoutParams lp = new LayoutParams(mItemWidth, mItemWidth);
        lp.leftMargin = mFirst.getLeft() - mPadding;
        lp.topMargin = mFirst.getTop() - mPadding;
        first.setLayoutParams(lp);
        mAnimLayout.addView(first);

        ImageView second = new ImageView(getContext());
        final Bitmap secondBitmap = mItemBitmaps.get(
                getImageIdByTag((String) mSecond.getTag())).getBitmap();
        second.setImageBitmap(secondBitmap);
        LayoutParams lp2 = new LayoutParams(mItemWidth, mItemWidth);
        lp2.leftMargin = mSecond.getLeft() - mPadding;
        lp2.topMargin = mSecond.getTop() - mPadding;
        second.setLayoutParams(lp2);
        mAnimLayout.addView(second);

        // 设置动画
        TranslateAnimation anim = new TranslateAnimation(0, mSecond.getLeft()
                - mFirst.getLeft(), 0, mSecond.getTop() - mFirst.getTop());
        anim.setDuration(300);
        anim.setFillAfter(true);
        first.startAnimation(anim);

        TranslateAnimation animSecond = new TranslateAnimation(0,
                -mSecond.getLeft() + mFirst.getLeft(), 0, -mSecond.getTop()
                + mFirst.getTop());
        animSecond.setDuration(300);
        animSecond.setFillAfter(true);
        second.startAnimation(animSecond);

        // 监听动画
        anim.setAnimationListener(new Animation.AnimationListener()
        {
            @Override
            public void onAnimationStart(Animation animation)
            {
                mFirst.setVisibility(View.INVISIBLE);
                mSecond.setVisibility(View.INVISIBLE);

            }

            @Override
            public void onAnimationRepeat(Animation animation)
            {
                // TODO Auto-generated method stub

            }

            @Override
            public void onAnimationEnd(Animation animation)
            {

                String firstTag = (String) mFirst.getTag();
                String secondTag = (String) mSecond.getTag();

                mFirst.setImageBitmap(secondBitmap);
                mSecond.setImageBitmap(firstBitmap);

                mFirst.setTag(secondTag);
                mSecond.setTag(firstTag);

                mFirst.setVisibility(View.VISIBLE);
                mSecond.setVisibility(View.VISIBLE);

                mFirst = mSecond = null;
                mAnimLayout.removeAllViews();
            }
        });
    }

这样,我们就可以有动画效果了,我们来运行一下

这里写图片描述

到现在,我们就已经可以玩了,但是这样子就不叫游戏了,我们应该这样添加一些过关的逻辑

五.过关逻辑

其实过关的逻辑很简单的,只要我们在每次移动之后去判断是不是过关了就行,如下代码

 /**
     * 判断是否过关
     */
    private void checkSuccess() {
        boolean isSuccess = true;

        for (int i = 0; i < mGameOintuItems.length; i++) {
            //拿到所有的图片
            ImageView imageView  = mGameOintuItems[i];

            if(getImageIndex((String) imageView.getTag()) != i){
                isSuccess = false;
            }
        }
        if(isSuccess){
            Log.i("tag","成功");
            Toast.makeText(getContext(),"成功,进入下一关!",Toast.LENGTH_LONG).show();
        }
    }

OK,但是这个不是重点,重点是我们要下一关,而且要把相应的数据传递给MainActivity,这就要实现接口了

 private static final int TIME_CHANGED = 10;
    private static final int NEXT_LEVEL = 11;

    /**
     * 设置接口回调
     *
     * @param mListener
     */
    public void setOnGamemListener(GamePintuListener mListener) {

        this.mListener = mListener;
    }

    public GamePintuListener mListener;

    /**
     * 关数
     */
    private int level = 1;

    /**
     * 设置开启时间
     *
     * @param timeEnabled
     */
    public void setTimeEnabled(boolean timeEnabled) {
        isTimeEnabled = timeEnabled;
    }

    //接口
    private interface GamePintuListener {
        //关卡
        void nextLevel(int nextLevel);

        //时间
        void timechanged(int time);

        //游戏结束
        void gameOver();
    }

同时要实现下一关的方法

 /**
     * 下一关
     */
    public void nextLevel() {
        this.removeAllViews();
        mAnimLayout = null;
        mColumn++;
        isGameSuccess = false;
        initBitmap();
        initItem();
    }

这样,我们就可以在handler中操作了

//子线程操作
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case TIME_CHANGED:

                    break;
                case NEXT_LEVEL:

                    level = level + 1;

                    if(mListener!=null){

                        mListener.nextLevel(level);
                    }else{
                        nextLevel();
                    }
                    break;
            }
            super.handleMessage(msg);
        }
    };

这边的逻辑OK了之后,我们需要回到MainActivity去操作显示UI

gameview = (GameView) findViewById(R.id.gameview);
        gameview.setOnGamemListener(new GameView.GamePintuListener() {
            @Override
            public void nextLevel(int nextLevel) {

                new AlertDialog.Builder(MainActivity.this).setTitle("拼图完成").setMessage("美女抱回家").setPositiveButton("下一关", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        gameview.nextLevel();
                    }
                }).show();
            }

            @Override
            public void timechanged(int time) {

            }

            @Override
            public void gameOver() {

            }
        });

这样,我们再来演示一遍

这里写图片描述

六.记录时间

开发也到了最后了,我们把时间记录给实现了,我们还是的创建几个方法

/**
     * 是否显示时间
     */
    private void checkTimeEnable() {
        //如果我们开启了
        if (isTimeEnabled) {
            countTimeBaseLevel();
            handler.sendEmptyMessage(TIME_CHANGED);
        }
    }

    /**
     * 根据当前等级设置时间
     */
    private void countTimeBaseLevel() {
        mTime = (int) Math.pow(2, level) * 60;
    }

然后发送handler

                 case TIME_CHANGED:
                    if (isGameSuccess || isGameOver) {
                        return;
                    }

                    if (mListener != null) {
                        mListener.timechanged(mTime);
                        if (mTime == 0) {
                            isGameOver = true;
                            mListener.gameOver();
                            return;
                        }
                    }
                    mTime--;
                    handler.sendEmptyMessageDelayed(TIME_CHANGED, 1000);

                    break;

现在我们可以去MainActivityity实现逻辑了,这里的gameover逻辑可以这样下

@Override
            public void gameOver() {
                new AlertDialog.Builder(MainActivity.this).setTitle("游戏结束").setMessage("很遗憾没有成功抱到美女!").setPositiveButton("重新开始", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //先不考虑
                    }
                }).setNegativeButton("退出", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        finish();
                    }
                }).show();
            }
        });

时间

          @Override
            public void timechanged(int time) {
                //设置时间
                tv_time.setText("倒计时:"+time);
            }

这里要记住设置显示效果

 //显示时间
 gameview.setTimeEnabled(true);

现在就可以倒计时了,同时,也可以监听到结束了

这里写图片描述

七.最后的补充

我们gameover以后也是需要操作的,我们有一个重新开始,我们只要写一个初始化的方法就可以了

    /**
     * 重新开始
     */
    public void restartGame() {
        isGameOver = false;
        mColumn--;
        nextLevel();

    }

这样就可以了

当然,我们游戏一般都是有暂停的,这个我们也加上,我们在GameView中新建方法

 /**
     * 暂停
     */
    public void pauseGame() {
        isPause = true;
        handler.removeMessages(TIME_CHANGED);
    }


    /**
     * 恢复
     */
    public void resumeGame() {
        if (isPause) {
            isPause = false;
            handler.sendEmptyMessage(TIME_CHANGED);
        }
    }

不过我们的目的是他后台时=不记录时间,所以只要和生命周期绑定就可以了


    @Override
    protected void onPause() {
        super.onPause();
        gameview.pauseGame();
    }

    @Override
    protected void onResume() {
        super.onResume();
        gameview.resumeGame();
    }

到这里,整个游戏算是正式的开发完整了,贴上完整的代码

MainActivity

package com.lgl.ninegame;

import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;

import com.lgl.ninegame.view.GameView;

public class MainActivity extends AppCompatActivity {

    private GameView gameview;

    private TextView tv_level, tv_time;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tv_level = (TextView) findViewById(R.id.tv_level);
        tv_time = (TextView) findViewById(R.id.tv_time);

        gameview = (GameView) findViewById(R.id.gameview);

        //显示时间
        gameview.setTimeEnabled(true);

        gameview.setOnGamemListener(new GameView.GamePintuListener() {
            @Override
            public void nextLevel(final int nextLevel) {

                new AlertDialog.Builder(MainActivity.this).setTitle("拼图完成").setMessage("美女抱回家").setPositiveButton("下一关", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        gameview.nextLevel();
                        tv_level.setText("当前关卡" + nextLevel);
                    }
                }).show();
            }

            @Override
            public void timechanged(int time) {
                //设置时间
                tv_time.setText("倒计时:" + time);
            }

            @Override
            public void gameOver() {
                new AlertDialog.Builder(MainActivity.this).setTitle("游戏结束").setMessage("很遗憾没有成功抱到美女!").setPositiveButton("重新开始", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        gameview.restartGame();
                    }
                }).setNegativeButton("退出", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        finish();
                    }
                }).show();
            }
        });
    }


    @Override
    protected void onPause() {
        super.onPause();
        gameview.pauseGame();
    }

    @Override
    protected void onResume() {
        super.onResume();
        gameview.resumeGame();
    }
}

GameView

package com.lgl.ninegame.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.Toast;

import com.lgl.ninegame.R;
import com.lgl.ninegame.utils.ImagePiece;
import com.lgl.ninegame.utils.ImageSplitterUtil;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * 自定义容器
 * Created by LGL on 2016/5/2.
 */
public class GameView extends RelativeLayout implements View.OnClickListener {


    //默认3*3
    private int mColumn = 3;
    //容器的内边距
    private int mPadding;
    //小图的距离 dp
    private int mMagin = 3;
    //存储图片的,宽高 都是固定的,所以使用数组
    private ImageView[] mGameOintuItems;
    //宽度
    private int mItemWidth;
    //图片
    private Bitmap mBitmap;
    //切图后存储
    private List
   
   
     mItemBitmaps;
    
    
    //标记
    
    
    private 
    
    boolean once;

    
    
    //记录时间
    
    
    private 
    
    int mTime;

    
    
    //容器的一个宽度
    
    
    private 
    
    int mWidth;

    
    
    //判断游戏是否成功
    
    
    private 
    
    boolean isGameSuccess;

    
    
    //是否显示时间
    
    
    private 
    
    boolean isTimeEnabled = 
    
    false;

    
    
    /**
     * 动画层,覆盖在viewGroup中
     */
    
    
    private RelativeLayout mAnimLayout;

    
    
    private 
    
    boolean isGameOver;
    
    
    /**
     * 动画限制
     */
    
    
    private 
    
    boolean isAniming;

    
    
    private 
    
    static 
    
    final 
    
    int TIME_CHANGED = 
    
    10;
    
    
    private 
    
    static 
    
    final 
    
    int NEXT_LEVEL = 
    
    11;

    
    
    /**
     * 设置接口回调
     *
     * @param mListener
     */
    
    
    public 
    
    void 
    
    setOnGamemListener(GamePintuListener mListener) {

        
    
    this.mListener = mListener;
    }

    
    
    public GamePintuListener mListener;

    
    
    /**
     * 关数
     */
    
    
    private 
    
    int level = 
    
    1;

    
    
    /**
     * 设置开启时间
     *
     * @param timeEnabled
     */
    
    
    public 
    
    void 
    
    setTimeEnabled(
    
    boolean timeEnabled) {
        isTimeEnabled = timeEnabled;
    }

    
    
    //接口
    
    
    public 
    
    interface GamePintuListener {
        
    
    //关卡
        
    
    void nextLevel(
    
    int nextLevel);

        
    
    //时间
        
    
    void timechanged(
    
    int time);

        
    
    //游戏结束
        
    
    void gameOver();
    }

    
    
    //子线程操作
    
    
    private Handler handler = 
    
    new Handler() {
        
    
    @Override
        
    
    public 
    
    void 
    
    handleMessage(Message msg) {
            
    
    switch (msg.what) {
                
    
    case TIME_CHANGED:
                    
    
    if (isGameSuccess || isGameOver || isPause) {
                        
    
    return;
                    }

                    
    
    if (mListener != 
    
    null) {
                        mListener.timechanged(mTime);
                        
    
    if (mTime == 
    
    0) {
                            isGameOver = 
    
    true;
                            mListener.gameOver();
                            
    
    return;
                        }
                    }
                    mTime--;
                    handler.sendEmptyMessageDelayed(TIME_CHANGED, 
    
    1000);

                    
    
    break;
                
    
    case NEXT_LEVEL:

                    level = level + 
    
    1;

                    
    
    if (mListener != 
    
    null) {

                        mListener.nextLevel(level);
                    } 
    
    else {
                        nextLevel();
                    }
                    
    
    break;
            }
            
    
    super.handleMessage(msg);
        }
    };


    
    
    public 
    
    GameView(Context context) {

        
    
    this(context, 
    
    null);
    }

    
    
    public 
    
    GameView(Context context, AttributeSet attrs) {

        
    
    this(context, attrs, 
    
    0);
    }

    
    
    public 
    
    GameView(Context context, AttributeSet attrs, 
    
    int defStyleAttr) {
        
    
    super(context, attrs, defStyleAttr);

        init();
    }

    
    
    /**
     * 初始化
     */
    
    
    private 
    
    void 
    
    init() {
        
    
    //单位转换——dp-px
        mMagin = (
    
    int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 
    
    3, getResources().getDisplayMetrics());

        mPadding = min(getPaddingLeft(), getPaddingRight(), getPaddingTop(), getPaddingBottom());
    }

    
    
    /**
     * 确定当前布局的大小,我们要设置成正方形
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    
    
    @Override
    
    
    protected 
    
    void 
    
    onMeasure(
    
    int widthMeasureSpec, 
    
    int heightMeasureSpec) {
        
    
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        
    
    //拿到容器的高宽最小值
        mWidth = Math.min(getMeasuredHeight(), getMeasuredWidth());

        
    
    if (!once) {
            
    
    //进行切图和排序
            initBitmap();

            
    
    //设置imageview(item)的宽高等属性
            initItem();

            
    
    //根据关卡设置时间
            checkTimeEnable();

            once = 
    
    true;

        }
        setMeasuredDimension(mWidth, mWidth);
    }

    
    
    /**
     * 是否显示时间
     */
    
    
    private 
    
    void 
    
    checkTimeEnable() {
        
    
    //如果我们开启了
        
    
    if (isTimeEnabled) {
            countTimeBaseLevel();
            handler.sendEmptyMessage(TIME_CHANGED);
        }
    }

    
    
    /**
     * 根据当前等级设置时间
     */
    
    
    private 
    
    void 
    
    countTimeBaseLevel() {
        mTime = (
    
    int) Math.pow(
    
    2, level) * 
    
    60;
    }

    
    
    /**
     * 进行切图和排序
     */
    
    
    private 
    
    void 
    
    initBitmap() {
        
    
    //判断是否存在这张图片
        
    
    if (mBitmap == 
    
    null) {
            mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.img);
        }
        
    
    //进行裁剪
        mItemBitmaps = ImageSplitterUtil.splitImage(mBitmap, mColumn);

        
    
    //裁剪玩后需要进行顺序打乱sort
        Collections.sort(mItemBitmaps, 
    
    new Comparator
    
    
     () {
            
     
     @Override
            
     
     public 
     
     int 
     
     compare(ImagePiece lhs, ImagePiece rhs) {

                
     
     //生成随机数,如果》0.5返回1否则返回-1
                
     
     return Math.random() > 
     
     0.5 ? 
     
     1 : -
     
     1;
            }
        });

    }

    
     
     /**
     * 设置imageview(item)的宽高等属性
     */
    
     
     private 
     
     void 
     
     initItem() {
        
     
     //( 容器的宽度 - 内边距 * 2  - 间距  ) /  裁剪的数量
        mItemWidth = (mWidth - mPadding * 
     
     2 - mMagin * (mColumn - 
     
     1)) / mColumn;
        
     
     //几 * 几
        mGameOintuItems = 
     
     new ImageView[mColumn * mColumn];

        
     
     //开始排放
        
     
     for (
     
     int i = 
     
     0; i < mGameOintuItems.length; i++) {
            ImageView item = 
     
     new ImageView(getContext());
            item.setOnClickListener(
     
     this);
            
     
     //设置图片
            item.setImageBitmap(mItemBitmaps.get(i).getBitmap());
            
     
     //保存
            mGameOintuItems[i] = item;
            
     
     //设置ID
            item.setId(i + 
     
     1);
            
     
     //设置Tag
            item.setTag(i + 
     
     "_" + mItemBitmaps.get(i).getIndex());

            RelativeLayout.LayoutParams lp = 
     
     new RelativeLayout.LayoutParams(mItemWidth, mItemWidth);

            
     
     //判断不是最后一列
            
     
     if (i + 
     
     1 % mColumn != 
     
     0) {
                lp.rightMargin = mMagin;
            }

            
     
     //判断不是第一列
            
     
     if (i % mColumn != 
     
     0) {
                lp.addRule(RelativeLayout.RIGHT_OF, mGameOintuItems[i - 
     
     1].getId());
            }

            
     
     //判断如果不是第一行
            
     
     if ((i + 
     
     1) > mColumn) {
                lp.topMargin = mMagin;
                lp.addRule(RelativeLayout.BELOW, mGameOintuItems[i - mColumn].getId());
            }
            addView(item, lp);
        }
    }

    
     
     /**
     * 获取多个参数的最小值
     */
    
     
     private 
     
     int 
     
     min(
     
     int... params) {
        
     
     int min = params[
     
     0];
        
     
     //遍历
        
     
     for (
     
     int param : params) {
            
     
     if (param < min) {
                min = param;
            }
        }
        
     
     return min;
    }

    
     
     /**
     * 点击的第一张图和第二张图,他们进行交换
     */
    
     
     private ImageView mFirst;
    
     
     private ImageView mSecond;

    
     
     /**
     * 点击事件
     *
     * @param v
     */
    
     
     @Override
    
     
     public 
     
     void 
     
     onClick(View v) {
        
     
     //如果点击了一次,你还点击,则无效
        
     
     if (isAniming) {
            
     
     return;
        }

        
     
     //重复点击
        
     
     if (mFirst == v) {
            
     
     //去掉颜色
            mFirst.setColorFilter(
     
     null);
            mFirst = 
     
     null;
            
     
     return;
        }

        
     
     if (mFirst == 
     
     null) {
            mFirst = (ImageView) v;
            
     
     //设置选中效果
            mFirst.setColorFilter(Color.parseColor(
     
     "#55FF0000"));
            
     
     //第二次点击
        } 
     
     else {
            mSecond = (ImageView) v;
            
     
     //交换
            exchangeView();
        }
    }

    
     
     /**
     * 图片交换
     */
    
     
     private 
     
     void 
     
     exchangeView() {
        mFirst.setColorFilter(
     
     null);
        
     
     // 构造我们的动画层
        setUpAnimLayout();

        ImageView first = 
     
     new ImageView(getContext());
        
     
     final Bitmap firstBitmap = mItemBitmaps.get(
                getImageIdByTag((String) mFirst.getTag())).getBitmap();
        first.setImageBitmap(firstBitmap);
        LayoutParams lp = 
     
     new LayoutParams(mItemWidth, mItemWidth);
        lp.leftMargin = mFirst.getLeft() - mPadding;
        lp.topMargin = mFirst.getTop() - mPadding;
        first.setLayoutParams(lp);
        mAnimLayout.addView(first);

        ImageView second = 
     
     new ImageView(getContext());
        
     
     final Bitmap secondBitmap = mItemBitmaps.get(
                getImageIdByTag((String) mSecond.getTag())).getBitmap();
        second.setImageBitmap(secondBitmap);
        LayoutParams lp2 = 
     
     new LayoutParams(mItemWidth, mItemWidth);
        lp2.leftMargin = mSecond.getLeft() - mPadding;
        lp2.topMargin = mSecond.getTop() - mPadding;
        second.setLayoutParams(lp2);
        mAnimLayout.addView(second);

        
     
     // 设置动画
        TranslateAnimation anim = 
     
     new TranslateAnimation(
     
     0, mSecond.getLeft()
                - mFirst.getLeft(), 
     
     0, mSecond.getTop() - mFirst.getTop());
        anim.setDuration(
     
     300);
        anim.setFillAfter(
     
     true);
        first.startAnimation(anim);

        TranslateAnimation animSecond = 
     
     new TranslateAnimation(
     
     0,
                -mSecond.getLeft() + mFirst.getLeft(), 
     
     0, -mSecond.getTop()
                + mFirst.getTop());
        animSecond.setDuration(
     
     300);
        animSecond.setFillAfter(
     
     true);
        second.startAnimation(animSecond);

        
     
     // 监听动画
        anim.setAnimationListener(
     
     new Animation.AnimationListener() {
            
     
     @Override
            
     
     public 
     
     void 
     
     onAnimationStart(Animation animation) {
                mFirst.setVisibility(View.INVISIBLE);
                mSecond.setVisibility(View.INVISIBLE);

                isAniming = 
     
     true;
            }

            
     
     @Override
            
     
     public 
     
     void 
     
     onAnimationRepeat(Animation animation) {
                
     
     // TODO Auto-generated method stub

            }

            
     
     @Override
            
     
     public 
     
     void 
     
     onAnimationEnd(Animation animation) {

                String firstTag = (String) mFirst.getTag();
                String secondTag = (String) mSecond.getTag();

                mFirst.setImageBitmap(secondBitmap);
                mSecond.setImageBitmap(firstBitmap);

                mFirst.setTag(secondTag);
                mSecond.setTag(firstTag);

                mFirst.setVisibility(View.VISIBLE);
                mSecond.setVisibility(View.VISIBLE);

                mFirst = mSecond = 
     
     null;
                mAnimLayout.removeAllViews();

                
     
     //每次移动完成判断是否过关
                checkSuccess();

                isAniming = 
     
     false;
            }
        });
    }

    
     
     /**
     * 判断是否过关
     */
    
     
     private 
     
     void 
     
     checkSuccess() {
        
     
     boolean isSuccess = 
     
     true;

        
     
     for (
     
     int i = 
     
     0; i < mGameOintuItems.length; i++) {
            
     
     //拿到所有的图片
            ImageView imageView = mGameOintuItems[i];

            
     
     if (getImageIndex((String) imageView.getTag()) != i) {
                isSuccess = 
     
     false;
            }
        }
        
     
     if (isSuccess) {

            isGameSuccess = 
     
     true;

            handler.removeMessages(TIME_CHANGED);

            Log.i(
     
     "tag", 
     
     "成功");
            Toast.makeText(getContext(), 
     
     "成功,进入下一关!", Toast.LENGTH_LONG).show();
            handler.sendEmptyMessage(NEXT_LEVEL);

        }
    }


    
     
     /**
     * 获取tag
     *
     * @param tag
     * @return
     */
    
     
     public 
     
     int 
     
     getImageIdByTag(String tag) {
        String[] split = tag.split(
     
     "_");
        
     
     return Integer.parseInt(split[
     
     0]);
    }

    
     
     /**
     * 获取图片的tag
     *
     * @param tag
     * @return
     */
    
     
     public 
     
     int 
     
     getImageIndex(String tag) {
        String[] split = tag.split(
     
     "_");
        
     
     return Integer.parseInt(split[
     
     1]);
    }

    
     
     /**
     * 交互动画
     */
    
     
     private 
     
     void 
     
     setUpAnimLayout() {
        
     
     if (mAnimLayout == 
     
     null) {
            mAnimLayout = 
     
     new RelativeLayout(getContext());
            
     
     //添加到整体
            addView(mAnimLayout);
        }
    }

    
     
     /**
     * 下一关
     */
    
     
     public 
     
     void 
     
     nextLevel() {
        
     
     this.removeAllViews();
        mAnimLayout = 
     
     null;
        mColumn++;
        isGameSuccess = 
     
     false;
        checkTimeEnable();
        initBitmap();
        initItem();
    }

    
     
     /**
     * 重新开始
     */
    
     
     public 
     
     void 
     
     restartGame() {
        isGameOver = 
     
     false;
        mColumn--;
        nextLevel();

    }

    
     
     //暂停状态
    
     
     private 
     
     boolean isPause;

    
     
     /**
     * 暂停
     */
    
     
     public 
     
     void 
     
     pauseGame() {
        isPause = 
     
     true;
        handler.removeMessages(TIME_CHANGED);
    }


    
     
     /**
     * 恢复
     */
    
     
     public 
     
     void 
     
     resumeGame() {
        
     
     if (isPause) {
            isPause = 
     
     false;
            handler.sendEmptyMessage(TIME_CHANGED);
        }
    }
}

    
    
   
   

我们最终的运行结果

这里写图片描述

目录
相关文章
|
7月前
|
Android开发 开发者
Android利用SVG实现动画效果
本文介绍了如何在Android中利用SVG实现动画效果。首先通过定义`pathData`参数(如M、L、Z等)绘制一个简单的三角形SVG图形,然后借助`objectAnimator`实现动态的线条绘制动画。文章详细讲解了从配置`build.gradle`支持VectorDrawable,到创建动画文件、关联SVG与动画,最后在Activity中启动动画的完整流程。此外,还提供了SVG绘制原理及工具推荐,帮助开发者更好地理解和应用SVG动画技术。
345 30
|
7月前
|
Android开发 UED 计算机视觉
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
本文介绍了一款受游戏“金铲铲之战”启发的Android自定义View——线条等待动画的实现过程。通过将布局分为10份,利用`onSizeChanged`测量最小长度,并借助画笔绘制动态线条,实现渐变伸缩效果。动画逻辑通过四个变量控制线条的增长与回退,最终形成流畅的等待动画。代码中详细展示了画笔初始化、线条绘制及动画更新的核心步骤,并提供完整源码供参考。此动画适用于加载场景,提升用户体验。
548 5
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
|
7月前
|
API Android开发 开发者
Android颜色渐变动画效果的实现
本文介绍了在Android中实现颜色渐变动画效果的方法,重点讲解了插值器(TypeEvaluator)的使用与自定义。通过Android自带的颜色插值器ArgbEvaluator,可以轻松实现背景色的渐变动画。文章详细分析了ArgbEvaluator的核心代码,并演示了如何利用Color.colorToHSV和Color.HSVToColor方法自定义颜色插值器MyColorEvaluator。最后提供了完整的源码示例,包括ColorGradient视图类和MyColorEvaluator类,帮助开发者更好地理解和应用颜色渐变动画技术。
251 3
|
3月前
|
XML 存储 Java
【06】AI辅助编程完整的安卓二次商业实战-背景布局变更增加背景-二开发现页面跳转逻辑-替换剩余图标-优雅草卓伊凡
【06】AI辅助编程完整的安卓二次商业实战-背景布局变更增加背景-二开发现页面跳转逻辑-替换剩余图标-优雅草卓伊凡
116 3
【06】AI辅助编程完整的安卓二次商业实战-背景布局变更增加背景-二开发现页面跳转逻辑-替换剩余图标-优雅草卓伊凡
|
7月前
|
Android开发 开发者
Android SVG动画详细例子
本文详细讲解了在Android中利用SVG实现动画效果的方法,通过具体例子帮助开发者更好地理解和应用SVG动画。文章首先展示了动画的实现效果,接着回顾了之前的文章链接及常见问题(如属性名大小写错误)。核心内容包括:1) 使用阿里图库获取SVG图形;2) 借助工具将SVG转换为VectorDrawable;3) 为每个路径添加动画绑定属性;4) 创建动画文件并关联SVG;5) 在ImageView中引用动画文件;6) 在Activity中启动动画。文末还提供了完整的代码示例和源码下载链接,方便读者实践操作。
357 65
|
7月前
|
XML Java Maven
Android线条等待动画JMWorkProgress(可添加依赖直接使用)
这是一篇关于Android线条等待动画JMWorkProgress的教程文章,作者计蒙将其代码开源至GitHub,提升可读性。文章介绍了如何通过添加依赖库使用该动画,并详细讲解了XML与Java中的配置方法,包括改变线条颜色、宽度、添加文字等自定义属性。项目已支持直接依赖集成(`implementation &#39;com.github.Yufseven:JMWorkProgress:v1.0&#39;`),开发者可以快速上手实现炫酷的等待动画效果。文末附有GitHub项目地址,欢迎访问并点赞支持!
232 26
|
7月前
|
XML Android开发 数据格式
Android中SlidingDrawer利用透明动画提示效果
本文介绍了在Android中使用`SlidingDrawer`实现带有透明动画提示效果的方法。通过XML布局配置`SlidingDrawer`的把手(handle)和内容(content),结合Activity中的代码实现动态动画效果。最终实现了交互性强、视觉效果良好的滑动抽屉功能。
Android中SlidingDrawer利用透明动画提示效果
|
7月前
|
XML Java Android开发
Android 动画之帧动画 + 补间动画 + 属性动画
本文介绍了Android开发中的三种动画类型:帧动画、补间动画和属性动画。帧动画通过依次播放一系列静态图片实现动态效果,支持Java代码与XML两种实现方式。补间动画基于起始和结束位置自动生成过渡效果,涵盖透明度、位移、旋转、缩放及组合动画等多种形式,并可搭配插值器优化动画过程。属性动画则通过改变对象属性实现动画,支持透明度、位移、旋转、缩放及组合动画,灵活性更高且适用于更复杂的场景。文中提供了详细的代码示例,帮助开发者快速上手。
416 15
|
7月前
|
Android开发 开发者
Android自定义view之围棋动画(化繁为简)
本文介绍了Android自定义View的动画实现,通过两个案例拓展动态效果。第一个案例基于`drawArc`方法实现单次动画,借助布尔值控制动画流程。第二个案例以围棋动画为例,从简单的小球直线运动到双向变速运动,最终实现循环动画效果。代码结构清晰,逻辑简明,展示了如何化繁为简实现复杂动画,帮助读者拓展动态效果设计思路。文末提供完整源码,适合初学者和进阶开发者学习参考。
144 0
Android自定义view之围棋动画(化繁为简)