先看一下效果然后贴代码
最上面是自定义的ActionBar,布局里面有两个TextView。一个用于显示level,一个用于显示倒计时
下面就是自定义的RelativeLayout。用于显示九个一打乱的bitmap
接下来就是代码:
首先是将图片分割的工具类
ImageUtil.java
package com.example.util;import java.util.ArrayList;import java.util.List;import android.graphics.Bitmap;public class ImageUtil {/** 将图片分成piece*piece份小图片,每份就是ImagePiece,存在imgList里面*/public static List<ImagePiece> splitImage(Bitmap bitmap, int piece) {List<ImagePiece> imgList = new ArrayList<ImagePiece>();//每张图片的宽高int pieceWidth = bitmap.getWidth() / piece;int pieceHeight = bitmap.getHeight() / 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 * pieceHeight;imagePiece.setBitmap(Bitmap.createBitmap(bitmap, x, y,pieceWidth, pieceHeight));imgList.add(imagePiece);}}return imgList;}}
分割后的每一个ImagePiece的实体类
ImagePiece.java
package com.example.util;import android.graphics.Bitmap;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;}@Overridepublic String toString() {return "ImagePiece [index=" + index + ", bitmap=" + bitmap + "]";}}
接下来是自定义的GameLayout.java
package com.example.view;import java.util.Collections;import parator;import java.util.List;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.TypedValue;import android.view.View;import android.view.View.OnClickListener;import android.view.animation.Animation;import android.view.animation.Animation.AnimationListener;import android.view.animation.TranslateAnimation;import android.widget.ImageView;import android.widget.RelativeLayout;import android.widget.Toast;import com.example.pintu.R;import com.example.util.ImagePiece;import com.example.util.ImageUtil;public class GameLayout extends RelativeLayout implements OnClickListener {private int GameLayoutWidth;private int GameLayoutHeight;private int column;private int padding;// 类似于xml中的android:paddingprivate int margin;// 类似于xml中的android:layout_marginprivate ImageView[] gameItem;private int itemWidth;private int itemHeight;private Bitmap bitmap;private List<ImagePiece> imgList;private boolean once;public GameLayout(Context context) {super(context);init();}public GameLayout(Context context, AttributeSet attrs) {super(context, attrs);init();}public GameLayout(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init();}// 初始化参数private void init() {column = 3;margin = (int) TypedValue.applyDimension(PLEX_UNIT_DIP,2, getResources().getDisplayMetrics());padding = min(getPaddingLeft(), getPaddingRight(), getPaddingTop(),getPaddingBottom());once = false;}private int min(int... params) {int min = params[0];for (int param : params) {min = Math.min(min, param);}return min;}@Override// onMeasure和onLayout会多次调用,所有要有once标识符protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);if (!once) {GameLayoutHeight = getMeasuredHeight();GameLayoutWidth = getMeasuredWidth();// 进行切图,并打乱次序initBitmap();// 九宫格的绘制initItem();// 判断是否开启时间checkTimeEnable();once = true;}setMeasuredDimension(GameLayoutWidth, GameLayoutHeight);}// 进行切图,并排序private void initBitmap() {// 获取bitmap,然后转化为和屏幕一样大小if (bitmap == null) {bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.girl);bitmap = Bitmap.createScaledBitmap(bitmap, GameLayoutWidth,GameLayoutHeight, true);}// 切图imgList = ImageUtil.splitImage(bitmap, column);// 使用sort打乱次序Collections.sort(imgList, new Comparator<ImagePiece>() {@Overridepublic int compare(ImagePiece lhs, ImagePiece rhs) {return Math.random() > 0.5 ? 1 : -1;}});}// 九宫格的绘制private void initItem() {// 设置gameItem的宽高itemWidth = (GameLayoutWidth - padding * 2 - margin * (column - 1))/ column;itemHeight = (GameLayoutHeight - padding * 2 - margin * (column - 1))/ column;gameItem = new ImageView[column * column];// 设置九宫格中每个格子对应的Bitmap,id和tag以及LayoutParams// 最后添加到GameLayout中for (int i = 0; i < gameItem.length; i++) {ImageView item = new ImageView(getContext());item.setImageBitmap(imgList.get(i).getBitmap());item.setId(i + 1);// **********************************// index是本质编号,id是打乱后的编号item.setTag(i + "_" + imgList.get(i).getIndex());item.setOnClickListener(this);// 实现图片的交换gameItem[i] = item;RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(itemWidth, itemHeight);// 设置横向间距,通过leftMargin。如果不是第一列if ((i % column) != 0) {lp.leftMargin = margin;lp.addRule(RelativeLayout.RIGHT_OF, gameItem[i - 1].getId());}// 设置横向间距。如果不是第一行,设置topMargin和ruleif ((i + 1) > column) {lp.topMargin = margin;lp.addRule(RelativeLayout.BELOW, gameItem[i - column].getId());// anchor------The id of another view to use as an anchor,// or a boolean value(represented as RelativeLayout.TRUE) for// true// or 0 for false). For verbs that don't refer to another// sibling// (for example, ALIGN_WITH_PARENT_BOTTOM) just use -1.// 注意,后面的参数不能为0(0 for false),所以打星号的地方要+1}addView(item, lp);}}private int time;private boolean isTimeEnabled = false;// 默认不开启时间private void checkTimeEnable() {if (isTimeEnabled) {// 根据当前等级设置时间time = (int) Math.pow(3.0, column);myHandler.sendEmptyMessage(TIME_CHANGED);}}private ImageView mFirst;private ImageView mSecond;private boolean isAnimation = false;// 实现图片的交换@Overridepublic void onClick(View v) {// 防止用户疯狂的点if (isAnimation) {return;}// 两次点击同一个itemif (mFirst == v) {mFirst.setColorFilter(null);mFirst = null;return;}if (mFirst == null) {// 第一次点击的是新的图片mFirst = (ImageView) v;mFirst.setColorFilter(Color.parseColor("#55FF0000"));} else {// 第二次点击也是新的图片mSecond = (ImageView) v;exchageView();// 交换图片}}private RelativeLayout mAnimLayout;// 实现动画切换的变量// 交换图片private void exchageView() {mFirst.setColorFilter(null);final String firstTag = (String) mFirst.getTag();final String secondTag = (String) mSecond.getTag();final Bitmap firstBitmap = imgList.get(getImageIdByTag(firstTag)).getBitmap();final Bitmap secondBitmap = imgList.get(getImageIdByTag(secondTag)).getBitmap();// 构造动画层if (mAnimLayout == null) {mAnimLayout = new RelativeLayout(getContext());addView(mAnimLayout);}// 构造图片交换动画final ImageView first = new ImageView(getContext());first.setImageBitmap(firstBitmap);LayoutParams lp = new LayoutParams(itemWidth, itemHeight);lp.leftMargin = mFirst.getLeft() - padding;lp.topMargin = mFirst.getTop() - padding;first.setLayoutParams(lp);mAnimLayout.addView(first);final ImageView second = new ImageView(getContext());second.setImageBitmap(secondBitmap);LayoutParams lp2 = new LayoutParams(itemWidth, itemHeight);lp2.leftMargin = mSecond.getLeft() - padding;lp2.topMargin = mSecond.getTop() - padding;second.setLayoutParams(lp2);mAnimLayout.addView(second);TranslateAnimation animation = new TranslateAnimation(0,mSecond.getLeft() - mFirst.getLeft(), 0, mSecond.getTop()- mFirst.getTop());animation.setDuration(300);animation.setFillAfter(true);first.startAnimation(animation);TranslateAnimation animation2 = new TranslateAnimation(0,mFirst.getLeft() - mSecond.getLeft(), 0, mFirst.getTop()- mSecond.getTop());animation2.setDuration(300);animation2.setFillAfter(true);second.startAnimation(animation2);animation.setAnimationListener(new AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {isAnimation = true;mFirst.setVisibility(View.INVISIBLE);mSecond.setVisibility(View.INVISIBLE);}@Overridepublic void onAnimationRepeat(Animation animation) {}@Overridepublic void onAnimationEnd(Animation animation) {// 交换图片(注意tag也要交换)isAnimation = false;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();// 判断是否过关}});}private int getImageIdByTag(String tag) {String[] split = tag.split("_");return Integer.parseInt(split[0]);}// item.setTag(i + "_" + imgList.get(i).getIndex());private int getImageIndexByTag(String tag) {String[] split = tag.split("_");return Integer.parseInt(split[1]);}// 判断是否过关。过关条件就是0到8有序排列private void checkSuccess() {boolean isSuccess = true;for (int i = 0; i < gameItem.length; i++) {ImageView imageView = gameItem[i];if (getImageIndexByTag((String) imageView.getTag()) != i) {isSuccess = false;}}if (isSuccess) {isGameSuccess = true;myHandler.removeMessages(TIME_CHANGED);Toast.makeText(getContext(), "Success ! Level Up !",Toast.LENGTH_SHORT).show();myHandler.sendEmptyMessage(NEXT_LEVEL);// 进入下一关}}private static final int NEXT_LEVEL = 0x001;private static final int TIME_CHANGED = 0x002;private boolean isGameSuccess;private boolean isGameOver;private Handler myHandler = new Handler() {public void handleMessage(Message msg) {switch (msg.what) {case NEXT_LEVEL:// 进入下一关column++;if (mListener != null) {mListener.nextLevel(column);}break;case TIME_CHANGED:if (isGameSuccess || isGameOver || isPause)return;if (mListener != null) {mListener.timeChanged(time);if (time == 0) {isGameOver = true;mListener.gameOver();return;}}time--;myHandler.sendEmptyMessageDelayed(TIME_CHANGED, 1000);break;}};};public void nextLevel() {this.removeAllViews();mAnimLayout = null;isGameSuccess = false;checkTimeEnable();initBitmap();initItem();};public void restart() {isGameOver = false;nextLevel();}private boolean isPause;public void pause(){isPause = true;myHandler.removeMessages(TIME_CHANGED);}public void resume(){if(isPause){isPause = false;myHandler.sendEmptyMessage(TIME_CHANGED);}}// 定义接口,用于通知主界面的level和timepublic interface GameListener {void nextLevel(int level);void timeChanged(int currentTime);void gameOver();}public GameListener mListener;public void setOnGameListener(GameListener Listener) {this.mListener = Listener;}public void setTimeEnabled(boolean isTimeEnabled) {this.isTimeEnabled = isTimeEnabled;}}
GameLayout实现如下几个功能
1.自定义容器
2.实现图片交换onCLickListener
3.实现过关逻辑interface GameListener
4.实现游戏的结束和暂停,这需要配合activity的onPause和onResume
接下来就是MainActivity.java
package com.example.pintu;import android.app.Activity;import android.app.AlertDialog;import android.content.DialogInterface;import android.content.DialogInterface.OnClickListener;import android.os.Bundle;import android.view.Window;import android.widget.TextView;import com.example.view.GameLayout;import com.example.view.GameLayout.GameListener;public class MainActivity extends Activity {private GameLayout gameLayout;private TextView levelView, timeView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 自定义ActionBar,// 用法参考/jia20003/article/details/12966489requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);setContentView(R.layout.main);getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.title);levelView = (TextView) findViewById(R.id.id_level);levelView.setText("level 1");timeView = (TextView) findViewById(R.id.id_time);gameLayout = (GameLayout) findViewById(R.id.pintu);gameLayout.setTimeEnabled(true);gameLayout.setOnGameListener(new GameListener() {@Overridepublic void timeChanged(int currentTime) {timeView.setText("" + currentTime);}@Overridepublic void nextLevel(int level) {new AlertDialog.Builder(MainActivity.this).setTitle("LEVEL UP").setPositiveButton("next level", new OnClickListener() {@Overridepublic void onClick(DialogInterface dialog,int which) {gameLayout.nextLevel();}}).show();// 3*3对应level 1;4*4对应level 2levelView.setText("level " + (level - 2));}@Overridepublic void gameOver() {new AlertDialog.Builder(MainActivity.this).setTitle("Game Over").setPositiveButton("RESTART", new OnClickListener() {@Overridepublic void onClick(DialogInterface dialog,int which) {gameLayout.restart();}}).setNegativeButton("QUIT", new OnClickListener() {@Overridepublic void onClick(DialogInterface dialog,int which) {finish();}}).show();}});}@Overrideprotected void onPause() {super.onPause();gameLayout.pause();}@Overrideprotected void onResume() {super.onResume();gameLayout.resume();}}
适合自定义ActionBar还要在styles.xml添加
<style name="MyTheme" parent="android:Theme.Light"> </style>
以及修改AndroidManifest.xml
android:theme="@style/MyTheme"<!-- 就算MyTheme和自动生成的AppBaseTheme一样,还是要重新定义 --><!-- 因为ActionBar布局改变了 -->
最后给出两个布局
main.xml
<RelativeLayout xmlns:android="/apk/res/android"xmlns:tools="/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.example.pintu.MainActivity" ><com.example.view.GameLayoutandroid:id="@+id/pintu"android:layout_width="match_parent"android:layout_height="match_parent"android:padding="2dp" ></com.example.view.GameLayout></RelativeLayout>
以及自定义的actionbar的布局title.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"xmlns:tools="/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="horizontal"tools:context=".MainActivity"android:background="#FFFFFF" ><TextView android:id="@+id/id_level"android:layout_width="0sp"android:layout_weight="1"android:gravity="left"android:textSize="20sp"android:layout_height="wrap_content" /><TextView android:id="@+id/id_time"android:layout_width="0sp"android:layout_weight="1"android:gravity="right"android:textSize="20sp"android:layout_height="wrap_content" /></LinearLayout>
如果觉得《美女拼图小游戏》对你有帮助,请点赞、收藏,并留下你的观点哦!