前一段时间为了学习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());
以上内容总结的很潦草,可能更方便自己日后查看,里面的一些代码可能缺少连贯性。只希望某些地方的处理方法能给入门的朋友一些借鉴,如果有不清楚想仔细了解的朋友请留言。