編輯:關於Android編程
一:需求描述
拼圖是一款益智類經典游戲了,本游戲學習了一些前輩們的經驗,整體來說講,將圖片用切圖工具進行切割,監聽用戶手指滑動事件,當用戶對凌亂的圖片,在一定的時間內拼湊恢復成原來的樣子,則成功闖關。 根據游戲不同的關卡對圖片進行動態的切割。玩家可以在隨意交換任意兩張圖片,通過遍歷切割好的每塊圖片,將用戶選中的圖片,進行替換;
其中主要的功能為:
二:主要功能分析
在拼圖游戲開發過程中,實現的主要的功能;提供給用戶所使用,具體功能分析如下所示:
編寫切片工具:由於拼圖游戲需要准備一個完整的圖片,從直觀上來看,我們不能每次都將一個完整的圖片進行分割,如果是3*3,分成9塊,4*4分成16份,這樣帶來的圖片資源極大的混亂,不利於後期的維護,然後Andorid就提供了具體的方法來實現對特定圖片的切圖工具,通過傳入的參數的不同,對圖片分割成所需要的矩陣,並設置每塊的寬高。利用兩個for循環進行切圖。並設置每塊圖片的大小位置和每塊圖片的塊號下標Index。
自定義容器:自定義相對布局文件,用來存放切割好的圖片,並設置圖片之間的間隙,以及確定圖片上下左右的關系。以及設置圖片與容器的內邊距設置。
實現圖片交換:實現手指的監聽事件,將對選中的兩張圖片進行位置的變換。
實現交換圖片的動畫效果:構造動畫層,設置動畫,監聽動畫
實現游戲過關邏輯:成功的判斷,關卡的回調。
實現游戲時間邏輯:游戲時間的更新,以及Handler不斷的回調,時間超時後游戲狀態的處理,以及成功闖關後,游戲時間的變更。
游戲的結束與暫停:當用戶返回主頁面的時候,游戲能夠暫停,當用戶返回游戲的時候,游戲可以重新開始。
三:概要設計
**切圖工具類**ImagePiece和ImageSplitterUtil。其中ImagePiece對Bitmap圖片的塊號與每一塊圖片的位置進行屬性的基本設置;在切圖工具類ImageSplitterUtil中,提供一個切圖方法splitImage,將傳入的Bitmap圖片分割成Piece*Piece塊,並設置每塊寬度,將分割好的圖片放入到List中。
自定義View:GamePintuLayout.java中運用的主要工具有:
單位轉換:將傳入的數值進行單位轉換成3PX,使得屏幕可識別。
//單位的轉換
mMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
3, getResources().getDisplayMetrics());
/*獲取多個參數的最小值*/
private int min(int... params) {
int min = params[0];
for (int param : params) {
if (param < min)
min = param;
}
return min;
}
圖片亂序的實現:
// 使用sort完成我們的亂序
Collections.sort(mItemBitmaps, new Comparator() {
public int compare(ImagePiece a, ImagePiece b) {
return Math.random() > 0.5 ? 1 : -1;
}
});
圖片的交換:在監聽事件中,當用戶選中了兩張圖片,則對圖片進行交換,並對第一次選中的圖片,進行樣式的設置。如果用戶重復點擊一張圖片,則消除圖片的選中狀態。通過給圖片設置的Tag,找到Id, 然後找到Bitmap圖片的index,然後進行交換同時交換Tag。
String firstTag = (String) mFirst.getTag();
String secondTag = (String) mSecond.getTag();
mFirst.setImageBitmap(secondBitmap);
mSecond.setImageBitmap(firstBitmap);
mFirst.setTag(secondTag);
mSecond.setTag(firstTag);
圖片動畫切換:構造動畫層,mAnimLayout並addView,然後在exchangeView中,先構造動畫層,復制兩個ImageView,為兩個ImageView設置動畫,監聽動畫的開始,讓原本的View隱藏,結束以後,將圖片交換,將圖片顯示,移除動畫層。
通過接口對關卡進行回調:實現關卡進階、時間控制、游戲結束接口。並利用Handler更新UI,在nextLevel方法中實現移除之前的View布局,以及將動畫層設置為空,增加mColumn++,然後初始化initBitmap()進行重新切圖亂序並InitItem()設置圖片的圖片寬高。
public interface GamePintuListener {
void nextLevel(int nextLevel);
void timechanged(int currentTime);
void gameover();
}
public GamePintuListener mListener;
/*
* 設置接口回調
*/
public void setOnGamePintuListener(GamePintuListener mListener) {
this.mListener = mListener;
}
根據當前等級設置游戲的時間:mTime = (int)Math.pow(2, level)*60;進而更行我們的Handler。mHandler.sendEmptyMessageDelayed(TIME_CHANGED, 1000)使得時間動態的減一。
游戲暫停開始:
mHandler.removeMessages(TIME_CHANGED);
而重新開始游戲則是:mHandler.sendEmptyMessage(TIME_CHANGED);
四:系統實現
工具類:
ImagePiece.java ImageSplitterUtil.java自定義容器:
GamePintuLayout.javaImagePiece.java
package com.example.utils;
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;
}
public String toString() {
return "ImagePiece [index=" + index + ", bitmap=" + bitmap + "]";
}
}
ImageSplitterUtil.java
//ImageSplitterUtil.java
package com.example.utils;
import java.util.ArrayList;
import java.util.List;
import android.graphics.Bitmap;
public class ImageSplitterUtil {
/*
* 傳入Bitmap切成Piece*piece塊,返回List
*/
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;
imagePiece.setBitmap(Bitmap.createBitmap(bitmap, x, y,
pieceWidth, pieceWidth));
imagePieces.add(imagePiece);
}
}
return imagePieces;
}
}
GamePintuLayout.java
package com.example.game_pintu.view;
import java.util.Collections;
import java.util.Comparator;
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.util.AttributeSet;
import android.util.Log;
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.game_pintu.R;
import com.example.utils.ImagePiece;
import com.example.utils.ImageSplitterUtil;
public class GamePintuLayout extends RelativeLayout implements OnClickListener {
private int mColumn = 3;
/*
* 容器內邊距
*/
private int mPadding;
/*
* 每張小圖之間的距離(橫縱)dp
*/
private int mMargin = 3;
private ImageView[] mGamePintuItems;
private int mItemWidth;
/*
* 游戲的圖片
*/
private Bitmap mBitmap;
private List mItemBitmaps;
private boolean once;
/*
* 游戲面板的寬度
*/
private int mWidth;
private boolean isGameSuccess;
private boolean isGameOver;
public interface GamePintuListener {
void nextLevel(int nextLevel);
void timechanged(int currentTime);
void gameover();
}
public GamePintuListener mListener;
/*
* 設置接口回調
*/
public void setOnGamePintuListener(GamePintuListener mListener) {
this.mListener = mListener;
}
private int level = 1;
private static final int TIME_CHANGED = 0x110;
private static final int NEXT_LEVEL = 0x111;
private Handler mHandler = new Handler() {
public void handleMessage(android.os.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--;
mHandler.sendEmptyMessageDelayed(TIME_CHANGED, 1000);
break;
case NEXT_LEVEL:
level = level + 1;
if (mListener != null) {
mListener.nextLevel(level);
} else {
nextLevel();
}
break;
default:
break;
}
};
};
private boolean isTimeEnabled = false;
private int mTime;
/*
* 設置是否開啟時間
*/
public void setTimeEnabled(boolean isTimeEnabled) {
this.isTimeEnabled = isTimeEnabled;
}
public GamePintuLayout(Context context) {
this(context, null);
}
public GamePintuLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GamePintuLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
/*
* 單位的轉換3--px
*/
mMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
3, getResources().getDisplayMetrics());
mPadding = min(getPaddingLeft(), getPaddingRight(), getPaddingTop(),
getPaddingBottom());
}
@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){
//根據當前等級設置時間
contTimeBaseLevel();
mHandler.sendEmptyMessage(TIME_CHANGED);
}
}
private void contTimeBaseLevel() {
mTime = (int)Math.pow(2, level)*60;
}
// 進行切圖,以及排序
private void initBitmap() {
// TODO Auto-generated method stub
if (mBitmap == null) {
mBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.image1);
}
mItemBitmaps = ImageSplitterUtil.splitImage(mBitmap, mColumn);
// 使用sort完成我們的亂序
Collections.sort(mItemBitmaps, new Comparator() {
public int compare(ImagePiece a, ImagePiece b) {
return Math.random() > 0.5 ? 1 : -1;
}
});
}
// 設置ImageView(Item)寬高等屬性
private void initItem() {
mItemWidth = (mWidth - mPadding * 2 - mMargin * (mColumn - 1))
/ mColumn;
mGamePintuItems = new ImageView[mColumn * mColumn];
// 生成Item, 設置Rule;
for (int i = 0; i < mGamePintuItems.length; i++) {
ImageView item = new ImageView(getContext());
item.setOnClickListener(this);
item.setImageBitmap(mItemBitmaps.get(i).getBitmap());
mGamePintuItems[i] = item;
item.setId(i + 1);
// item中tag存儲了index
item.setTag(i + "_" + mItemBitmaps.get(i).getIndex());
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
mItemWidth, mItemWidth);
// 設置item艱橫向間隙,通過RightMargin
// 不是最後一列
if ((i + 1) % mColumn != 0) {
lp.rightMargin = mMargin;
}
// 不是第一列
if (i % mColumn != 0) {
lp.addRule(RelativeLayout.RIGHT_OF,
mGamePintuItems[i - 1].getId());
}
// 如果不是第一行,設置TopMargin and rule
if ((i + 1) > mColumn) {
lp.topMargin = mMargin;
lp.addRule(RelativeLayout.BELOW,
mGamePintuItems[i - mColumn].getId());
}
addView(item, lp);
}
}
public void restart()
{
isGameOver = false;
mColumn--;
nextLevel();
}
private boolean isPause;
public void pause()
{
isPause = true;
mHandler.removeMessages(TIME_CHANGED);
}
public void resume()
{
if(isPause)
{
isPause = false;
mHandler.sendEmptyMessage(TIME_CHANGED);
}
}
public void nextLevel() {
this.removeAllViews();
mAnimLayout = null;
mColumn++;
isGameSuccess = false;
checkTimeEnable();
initBitmap();
initItem();
}
/*
* 獲取多個參數的最小值
*/
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;
public void onClick(View v) {
if (isAniming)
return;
// 兩次點擊同一個Item
if (mFirst == v) {
mFirst.setColorFilter(null);
mFirst = null;
return;
}
if (mFirst == null) {
mFirst = (ImageView) v;
mFirst.setColorFilter(Color.parseColor("#55FF0000"));
} else {
mSecond = (ImageView) v;
// 交換我們的Item
exchangeView();
}
}
/*
* 動畫層
*/
private RelativeLayout mAnimLayout;
private boolean isAniming;
/*
* 交換Item
*/
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 AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
mFirst.setVisibility(View.INVISIBLE);
mSecond.setVisibility(View.INVISIBLE);
isAniming = true;
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@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;
// 判斷游戲用戶是否成功
checkSuccess();
isAniming = false;
}
});
}
private void checkSuccess() {
boolean isSuccess = true;
for (int i = 0; i < mGamePintuItems.length; i++) {
ImageView imageView = mGamePintuItems[i];
if (getImageIndexByTag((String) imageView.getTag()) != i) {
isSuccess = false;
}
}
if (isSuccess) {
isGameSuccess = true;
mHandler.removeMessages(TIME_CHANGED);
Toast.makeText(getContext(), "Success, level up!",
Toast.LENGTH_LONG).show();
mHandler.sendEmptyMessage(NEXT_LEVEL);
}
}
public int getImageIdByTag(String tag) {
String[] split = tag.split("_");
return Integer.parseInt(split[0]);
}
public int getImageIndexByTag(String tag) {
String[] split = tag.split("_");
return Integer.parseInt(split[1]);
}
/**
* 構造我們的動畫層
*/
private void setUpAnimLayout() {
if (mAnimLayout == null) {
mAnimLayout = new RelativeLayout(getContext());
addView(mAnimLayout);
} else {
mAnimLayout.removeAllViews();
}
}
}
MainActivity.java
package com.example.game_pintu;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.widget.TextView;
import com.example.game_pintu.view.GamePintuLayout;
import com.example.game_pintu.view.GamePintuLayout.GamePintuListener;
public class MainActivity extends Activity {
private GamePintuLayout mGamePintuLayout;
private TextView mLevel;
private TextView mTime;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTime = (TextView) findViewById(R.id.id_time);
mLevel = (TextView) findViewById(R.id.id_level);
mGamePintuLayout = (GamePintuLayout) findViewById(R.id.id_gamepintu);
mGamePintuLayout.setTimeEnabled(true);
mGamePintuLayout.setOnGamePintuListener(new GamePintuListener() {
@Override
public void timechanged(int currentTime) {
mTime.setText("" + currentTime);
}
@Override
public void nextLevel(final int nextLevel) {
new AlertDialog.Builder(MainActivity.this)
.setTitle("GAME INFO").setMessage("LEVEL UP!!!")
.setPositiveButton("NEXT LEVEL", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
mGamePintuLayout.nextLevel();
mLevel.setText("" + nextLevel);
}
}).show();
}
@Override
public void gameover() {
new AlertDialog.Builder(MainActivity.this)
.setTitle("GAME INFO").setMessage("GAME OVER!!!")
.setPositiveButton("RESTART", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
// mGamePintuLayout.nextLevel();
mGamePintuLayout.restart();
}
}).setNegativeButton("QUIT", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
finish();
}
}).show();
}
});
}
@Override
protected void onPause() {
super.onPause();
mGamePintuLayout.pause();
}
@Override
protected void onResume() {
super.onResume();
mGamePintuLayout.resume();
}
}
activity_main.xml
in drawable new textbg.xml
五:測試
開始游戲
成功
成功進階
源代碼<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCgk8cD48YSBocmVmPQ=="https://yunpan.cn/cRP9uz6hNTHNp">雲盤下載代碼,點擊前復制訪問密碼訪問密碼 5a96
Enhancing Android UI with Custom Views 通過自定義view來讓你的UI更屌!
There are many great advantages to building your own UI components, such as the abili
android4.4 webview chromium顯示網頁的chromium內核結構
android4.4 webview chromium是單進程的,圖中所有組件都運行在Browser進程中。 按從上而下的順序介紹這張圖中與顯示網頁相關的chromiu
手機QQ透明頭像怎麼設置 QQ透明頭像制作步驟
手機QQ透明頭像怎麼制作?下面就跟著小編一起來看看吧!QQ透明頭像制作方法方法一:在電腦版上操作即可同步到手機qq首先下載透明頭像的圖片,接著打開電腦版QQ
Android 仿支付寶中的余額寶收益進度條
一、 看效果二、上代碼package com.framework.widget;import android.app.Activity;import android.co