Android拼图游戏的设计逻辑,从切图到交互动画,从关卡到倒计时,实例提高!
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<ImagePiece>
*
* @param bitmap
* @param piece
* @return
*/
public static List<ImagePiece> splitImage(Bitmap bitmap, int piece) {
//作为返回值传递
List<ImagePiece> 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<ImagePiece> 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<ImagePiece>() {
@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中引用
<?xml version="1.0" encoding="utf-8"?>
<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<ImagePiece> 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<ImagePiece>() {
@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);
}
}
}
我们最终的运行结果
笔记下载地址:http://pan.baidu.com/s/1c0U7k2W 密码:9v0g
Demo下载:http://download.csdn.net/detail/qq_26787115/9509305
我的群,通往Android的神奇之旅 :555974449,欢迎大家进来交流技术!
Android拼图游戏的设计逻辑,从切图到交互动画,从关卡到倒计时,实例提高!的更多相关文章
- Android拼图游戏
效果如下 游戏的设计 首先我们分析下如何设计这款游戏: 1.我们需要一个容器,可以放这些图片的块块,为了方便,我们准备使用RelativeLayout配合addRule实现 2.每个图片的块块,我们准 ...
- Android github上开源项目、酷炫的交互动画和视觉效果地址集合
Android上开源的酷炫的交互动画和视觉效果:http://blog.csdn.net/u013278099/article/details/50323689 Awesome-android-ui: ...
- android 2048游戏、kotlin应用、跑马灯、动画源码
Android精选源码 2048游戏源码 android实现获取号码归属地和其他信息诈骗.骚扰 android kotlin仿开眼app源码 android多种reveal动画效果 android K ...
- android指纹识别、拼图游戏、仿MIUI长截屏、bilibili最美创意等源码
Android精选源码 一个动画效果的播放控件,播放,暂停,停止之间的动画 用 RxJava 实现 Android 指纹识别代码 Android仿滴滴打车(滴滴UI)源码 Android高仿哔哩哔哩动 ...
- Android群英传-拼图游戏puzzle-代码设计和实现
上个周末,3个小时总体上读完了<Android群英传>,本周主要在研究代码层次的设计和实现. 编译安装在手机上,玩了几把,结合代码,一周时间才掌握了整体的思路. 大部分时间,其实花在了 ...
- Android 实战美女拼图游戏 你能坚持到第几关
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40595385,本文出自:[张鸿洋的博客] 1.概述 继2048之后,今天给大家带 ...
- 爱拼图游戏android源码完整版
这个是一款爱拼图游戏源码完整版,该游戏源码比较完整的,可以支持音乐的播放在游戏的玩的过程中,还可以控制系统的声音等,可以支持多种图片的选择来进行玩的,还可以根据自己的爱好选择不同的难度来的,级别分为: ...
- [Android]自己动手做个拼图游戏
目标 在做这个游戏之前,我们先定一些小目标列出来,一个一个的解决,这样,一个小游戏就不知不觉的完成啦.我们的目标如下: 游戏全屏,将图片拉伸成屏幕大小,并将其切成若干块. 将拼图块随机打乱,并保证其能 ...
- Android群英传-拼图游戏puzzle-6点吐槽
一.缘由 经常写文章,混了一些C币.最近在深入学习Android应用开发,就从商城里买了一本<Android群英传>.这本书的内容,不是纯粹的入门那种,分几个章节,重点讲解Activit ...
随机推荐
- 安卓获取清单文件meta-data数据
<application android:icon="@drawable/ic_launcher" android:label="@string/app_name& ...
- springMVC源码分析--SimpleControllerHandlerAdapter(三)
上一篇博客springMVC源码分析--HandlerAdapter(一)中我们主要介绍了一下HandlerAdapter接口相关的内容,实现类及其在DispatcherServlet中执行的顺序,接 ...
- linux中probe函数传递参数的寻找(下)
点击打开链接 linux中probe函数传递参数的寻找(下) 通过追寻driver的脚步,我们有了努力的方向:只有找到spi_bus_type的填充device即可,下面该从device去打通,当两个 ...
- activiti 任务节点 处理人设置
1.1.1. 前言 分享牛原创(尊重原创 转载对的时候第一行请注明,转载出处来自分享牛http://blog.csdn.net/qq_30739519) 我们在使用activiti 工作流引擎的时候, ...
- Xcode7.2如何真机调试iOS 9.3的设备
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 本猫的mac系统为10.10,Xcode版本为7.2 本猫将i ...
- GDAL使用插件方式编译HDF4、HDF5以及NetCDF的bug修改
GDAL库中提供了很方便的插件机制来扩展支持的数据格式,比如HDF4.HDF5.NetCDF.FileGDB.Postgre.Oralce等等.都可以通过插件的方式来使得GDAL支持相应的格式.最近将 ...
- 海量数据处理算法(top K问题)
举例 有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M.返回频数最高的100个词. 思路 首先把文件分开 针对每个文件hash遍历,统计每个词语的频率 使用堆进 ...
- 07 总结ProgressDialog 异步任务
1,ProgressDialog > //使用对象 设置标题 progressDialog.setTitle("标题"); ...
- Dynamics CRM 修改数据导出到EXCEL的最大条数
系统默认的最大导出数为一万,这个数可以通过执行以下SQL看到,那要增加导出的最大数量改变MaxRecordsForExportToExcel的值即可. <span style="fon ...
- UI设计--大象无形
UI设计,大象无形 UI设计,如同优雅的艺术品一样,优秀的UI设计也可以大象无形,大象无形的意思是有意化无意.大象化无形!就是不要显刻意,不要过分的主张,要兼容百态.无形态无框架才能容纳一切形体! ...