前一段時間爲了學習android應用開發,嘗試寫了個簡單的拼圖應用,在此記錄下實現流程的核心部分,同時也希望給其他開發者入門參考帶來幫助。
1. 基本的界面設計
首先應該設計出各個界面(Activity)的樣式以及界面間跳轉需要通過Intent傳遞哪些數據。本例包括4個Activity:
a. MainActivity主界面,只包含1個TextView和3個ButtonView,每個按鈕點擊應改變難度的值,這個值應該同過Intent繼續傳遞下去的;
b. SourceActivity圖片源選取,這個界面應成Dialog對話框形式展現 ,要在AndroidManifest.xml文件中聲明,然後控件採用ListView;
<activity
android:name="com.sean.puzzle.SourceActivity"
android:label="選擇素材源"
android:theme="@android:style/Theme.Dialog" >
c. GameActivity遊戲界面,核心就是上面的1個ImageView,此外有1個ButtonView和2個TextView;
d. EndActivity最後一個結束界面沒有什麼特殊的,主要是爲了提醒遊戲結束,同樣是一個Dialog風格的Activity,與b同。
有了以上4個界面這個簡單的遊戲框架就有了,接下來重點就是實現b和c界面中的功能了。
2. 拍照或從相冊選取圖片
這個首先要在AndroidManifest.xml文件中設置下允許SD卡讀寫
<!-- 在SD卡中創建與刪除文件權限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<!-- 從SD卡中讀入數據權限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 向SD卡中寫入數據權限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
然後,相機拍照並保存的代碼如下,主要圖片保存路徑的設置(SD卡路徑+圖片文件夾路徑)
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//獲取SD卡對應的存儲目錄
File sdCardDir = Environment.getExternalStorageDirectory();
//將拍照時間作爲照片文件名並保存
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd_HHmmss");//獲取當前時間,進一步轉化爲字符串
String path = null;
try {
path = sdCardDir.getCanonicalPath() + "/DCIM/Camera/";
} catch (IOException e) {
e.printStackTrace();
}
String fileName = "IMG_" + format.format(date) + ".jpg";
Uri imageUri = Uri.fromFile(new File(path, fileName));
filePath = path + fileName;
//指定照片保存路徑(SD卡)
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, 0);
從相冊選取圖片只需要如下幾行
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
startActivityForResult(intent, 1);
以上兩種選擇圖片都調用了startActivityForResult方法,主要requestCode參數需要不同,對應的響應方法及實現代碼如下:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 0 && resultCode == RESULT_OK) {
//pass
}
else if(requestCode == 1 && resultCode == RESULT_OK){
//The 3 lines below is useful!
Cursor cursor = this.getContentResolver().query(data.getData(), null, null, null, null);
cursor.moveToFirst();
filePath = cursor.getString(cursor.getColumnIndex("_data"));
}
Intent intent = new Intent();
intent.setClass(SourceActivity.this, GameActivity.class);
intent.putExtra("imgPath", filePath);//將獲得的圖片路徑傳遞到GameActivity即可
intent.putExtra("level", level);
startActivity(intent);
finish();
}
3. 將圖片隨機切割
上一步傳遞圖片路徑到GameActivity,但是我們讀取圖像不可能把幾MB的圖像放在ImageView裏,實際採用的是Bitmap讀入圖像(此時圖像已被縮小),用變量記錄縮小的scale。然後將這個Bitmap顯示到ImageView上是沒有問題的,通過設置匹配方式爲centerCrop,圖像會很舒服的顯示出來。難題在於,點擊開始後需要隨機切割圖像並顯示。由於Bitmap雖然縮小了原圖像,但是仍保持原圖的長寬比,而這個比例和ImageView的比例一般是不一樣的,所以如果直接在原始Bitmap上切割的話,顯示在ImageView裏的圖像一定不是規整的3*3或5*5圖塊。
解決辦法,需要在原始Bitmap上先進行按ImageView比例的centerCrop裁剪, 方法如下
private Bitmap centerCrop(Bitmap src, int W, int H){
int w = src.getWidth();
int h = src.getHeight();
float ratio = (float)W/H;
Bitmap dst = null;
if(((float)w/h) > ratio){//crop width
dst = Bitmap.createBitmap(src_bitmap, (int)((w-ratio*h)/2), 0, (int)(ratio*h), h);
}else{//crop height
dst = Bitmap.createBitmap(src_bitmap, 0, (int)((h-w/ratio)/2), w, (int)(w/ratio));
}
return dst;
}
然後顯示的就是規整的3*3(初級)或5*5(中級)模式了,這裏省略了隨機切割圖像塊的方法,主要就是通過Random類的對象生成隨機數(但是不能重複)。此外,用到了一個Bitmap的數組來保存每個圖塊,一個int數組保存每個位置的圖塊索引。
4. 點擊交換圖塊位置
給ImageView綁定一個OnTouchListener接口的實現,點擊圖像時觸發。
class ImageListener implements OnTouchListener{
@Override
public boolean onTouch(View arg0, MotionEvent arg1) {
float x = arg1.getX();//get clicked x position
float y = arg1.getY();//get clicked y position
int tmp_col = (int) (x / (width/col));
int tmp_row = (int) (y / (height/row));
int new_chosen_num = tmp_row * col + tmp_col;
if((chosen_num != -1) && (new_chosen_num != chosen_num)){//do swap
swapBlock(chosen_num, new_chosen_num);
txt_count.setText(step+"");
chosen_num = -1;
if(mis_count == 0){//Game Win!
Intent intent = new Intent();
intent.setClass(GameActivity.this, EndActivity.class);
startActivity(intent);
}
}else{//set one chosen
chosen_num = new_chosen_num;
}
return false;
}
}
最後交換圖像塊後ImageView上的顯示問題用到了Canvas,相當於把Bitmap作爲一個畫布,在上面畫出交換結果,然後將新Bitmap顯示到ImageView上。
Canvas to_draw = new Canvas(new_bitmap);
for(int i = 0; i < row; i++)
for(int j = 0; j < col; j++)
to_draw.drawBitmap(pic_arr[i * row + j], j*(tmp_width/col), i*(tmp_height/row), null);
to_draw.save(Canvas.ALL_SAVE_FLAG);
to_draw.restore();
img.setImageBitmap(new_bitmap);
img.setOnTouchListener(new ImageListener());
以上內容總結的很潦草,可能更方便自己日後查看,裏面的一些代碼可能缺少連貫性。只希望某些地方的處理方法能給入門的朋友一些借鑑,如果有不清楚想仔細瞭解的朋友請留言。