1 繁琐的 Android 拍照
Android 中的拍照是一件很繁琐的事情,因为它必须使用 Intent,并通过 startActivityForResult 方法去启动相机,完成后需要在 onActivityResult 中接受回调,获得拍照的结果,也就是说拍照和拍照的回调结果是隔离的。我们看下一段伪代码。
1private File photoFile = createFile(); 2 3onClick.. goToPhoto 4 5public static void goToPhoto(Activity activity,File file) { 6 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 7 intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); 8 activity.startActivityForResult(intent, ConstantUtil.TAKE_PHOTOS); 9}101112@Override13 protected void onActivityResult(int requestCode, int resultCode, Intent data) {14 switch (requestCode) {15 case ConstantUtil.TAKE_PHOTOS:16 if (photoFile != null && photoFile.exists()) {17 18 }19 break;20 }21 }
复制代码
由于拍照和取结果是异步的,所以我们需要在调用拍照的地方,先声明一个 file 成员变量,因为 onActivityResult 中是无法拿到我们拍的照片的路径的。
2 传统的 Android 拍照方式
我们概括一下,传统的拍照方式(不考虑高版本适配)
声明一个 file 成员,用来记录拍的照片的存储路径
通过 Intent 发起拍照
在 onActivityResult 中获取拍照的回调(注意这里并没有照片地址,需要使用前面的 file)
可能你会说,这也不算麻烦啊。
是的,如果是在 Activity 里,是不算太麻烦。
如果放到 fragment 中呢?也还好。我们可以使用 fragment 的 startActivityForResult 去拍照。
但是!!如果业务场景是在一个列表中,item 中需要进行拍照。那就比较恶心了。
为什么麻烦呢,我来解释一下。
由于拍照的回调和拍照是隔离的,也就是说我们只能在onActivityResult,也就是Activity或者Fragment中才能拿到拍照的结果。而我们拍照的地方是在Adapter里,即使我们可以通过接口把拍照的行为放到Activity或Fragment中处理,但是一样是隔离的,我们无法关联拍照和拍照结果。
这意味着,你在列表里的某个item发起拍照,拍照完成后,你根本不知道是这个照片要应用到哪个item上。你可以通过复杂的回调来解决。总之,你可能需要很麻烦的处理,才能友好的处理。
省省吧!!!
今天我就给你介绍一种不一样的拍照方法。
3Android6.0 版本适配之动态权限
明明是在说拍照,为什么来了个版本适配。做过 Android 动态权限适配的人都知道:和拍照类似,动态权限的申请必须由 Activty 或 Fragment 发起,权限的授予结果必须在 Activity 或者 Fragment 的 onRequestPermissionsResult 回调方法中获取。
同样的问题,申请和结果回调是隔离的,如果我们在一个普通类中发起权限申请,我们是无法在这个类中获取回调结果的。当然,你可以定义一个回调,把activity中的结果回调过来。是的这样可以解决问题。但是有没有更好的方式呢。当然。
前面我们有说到 Fragment 也可以用来调起拍照、权限申请。那么我们是否可以利用一个 Fragment 去作为一个承载来完成动作与回调的同步呢。当然可以。Github 有很多开源的动态权限工具类,如 RxPermission,AndPermission,他们使用的都是同一种思想。
我们在Activity中添加一个空的Fragment,用这个Fragment来发起权限申请,授权回调同样会回调给这个Fragment。然后我们通过接口将这个结果回调出来。从而实现申请和结果回调聚合在一起。
4PhotoTaker
好了,回到我们今天的主题。话不多说,你明白这种巧妙的思想之后,你也就能理解我们的拍照 PhotoTaker 了。直接上代码。
1public class TakePhotoFragment extends Fragment { 2 private PhotoTaker photoTaker; 3 4 public void bindRxPhotoTaker(PhotoTaker photoTaker) { 5 this.photoTaker = photoTaker; 6 } 7 8 @Override 9 public void onCreate(@Nullable Bundle savedInstanceState) {10 super.onCreate(savedInstanceState);11 setRetainInstance(true);12 }1314 @Override15 public void onActivityResult(int requestCode, int resultCode, Intent data) {16 super.onActivityResult(requestCode, resultCode, data);17 photoTaker.onActivityResult(requestCode, resultCode, data);18 }19}
复制代码
我们的 Fragment 很简单,就是用来发送和接收拍照结果,将结果回调给 PhotoTaker。然后我们看下 PhotoTaker 的代码。
1 2 * android快速实现拍照的方式 3 */ 4public class PhotoTaker { 5 private static final String TAG = "PhotoTaker"; 6 private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMddmmss"); 7 public static int RESULT_TAKE_PHOTO = 1; 8 public static int RESULT_PEEK_ALBUM = 2; 9 10 private TakePhotoFragment takePhotoFragment; 11 private BaseDialog dialog; 12 private FragmentActivity activity; 13 private String savePhotoPath; 14 private Callback callback; 15 16 public PhotoTaker(FragmentActivity activity, Callback callback) { 17 this.activity = activity; 18 this.callback = callback; 19 takePhotoFragment = getRxTakePhotoFragment(activity); 20 takePhotoFragment.bindRxPhotoTaker(this); 21 } 22 23 private TakePhotoFragment getRxTakePhotoFragment(FragmentActivity activity) { 24 TakePhotoFragment fragment = findRxPermissionsFragment(activity); 25 boolean isNewInstance = fragment == null; 26 if (isNewInstance) { 27 fragment = new TakePhotoFragment(); 28 FragmentManager fragmentManager = activity.getSupportFragmentManager(); 29 fragmentManager 30 .beginTransaction() 31 .add(fragment, TAG) 32 .commitAllowingStateLoss(); 33 fragmentManager.executePendingTransactions(); 34 } 35 return fragment; 36 } 37 38 private TakePhotoFragment findRxPermissionsFragment(FragmentActivity activity) { 39 return (TakePhotoFragment) activity.getSupportFragmentManager().findFragmentByTag(TAG); 40 } 41 42 public void show() { 43 if (dialog == null) { 44 dialog = new BaseDialog(activity); 45 dialog.setGravity(Gravity.BOTTOM); 46 dialog.setContentView(R.layout.dialog_take_photo); 47 dialog.setLayout(-1, -2); 48 dialog.findViewById(R.id.tv_take_photo).setOnClickListener(new View.OnClickListener() { 49 @Override 50 public void onClick(View v) { 51 dialog.dismiss(); 52 takePhoto(); 53 } 54 }); 55 dialog.findViewById(R.id.tv_peek_album).setOnClickListener(new View.OnClickListener() { 56 @Override 57 public void onClick(View v) { 58 dialog.dismiss(); 59 peekImageFromAlbum(takePhotoFragment, RESULT_PEEK_ALBUM); 60 } 61 }); 62 dialog.findViewById(R.id.tv_cancel).setOnClickListener(new View.OnClickListener() { 63 @Override 64 public void onClick(View v) { 65 dialog.dismiss(); 66 } 67 }); 68 } 69 dialog.show(); 70 } 71 72 73 * 从相册中选择照片 74 */ 75 private void peekImageFromAlbum(Fragment fragment, int resultCode) { 76 Intent intent = new Intent(Intent.ACTION_PICK, null); 77 intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); 78 fragment.startActivityForResult(intent, resultCode); 79 } 80 81 private void takePhoto() { 82 83 RxPermissions rxPermissions = new RxPermissions(activity); 84 rxPermissions.request(Manifest.permission.CAMERA) 85 .subscribe(new Action1<Boolean>() { 86 @Override 87 public void call(Boolean granted) { 88 if (granted) { 89 savePhotoPath = createFile(activity); 90 openCamera(takePhotoFragment, savePhotoPath, RESULT_TAKE_PHOTO); 91 } 92 } 93 }); 94 } 95 96 97 * 打开相机,兼容7.0+ 98 */ 99 private void openCamera(Fragment fragment, String outputPath, int resultCode) {100 101 File outputFile = new File(outputPath);102 FileUtil.createFile(outputFile);103 Uri imageUri;104 if (105 Build.VERSION.SDK_INT < 24) {106 imageUri = Uri.fromFile(outputFile);107 } else {108 109 imageUri =110 FileProvider.getUriForFile(fragment.getContext(), "org.jaaksi.photoker.demo.fileprovider",111 outputFile);112 }113 114 Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");115 intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);116 fragment.startActivityForResult(intent, resultCode);117 }118119 120 * data/packagename/files/121 */122 public static String createFile(Context context) {123 return context.getExternalFilesDir(null) + "/" + DATE_FORMAT.format(new Date()) + ".jpg";124 }125126 public void onActivityResult(int requestCode, int resultCode, Intent data) {127 if (resultCode != Activity.RESULT_OK) return;128129 if (requestCode == PhotoTaker.RESULT_TAKE_PHOTO) { 130 callback.onResult(savePhotoPath);131 } else if (requestCode == PhotoTaker.RESULT_PEEK_ALBUM) { 132 if (data != null) {133 Uri selectedImage = data.getData();134 if (selectedImage == null) return;135 String[] filePathColumns = { MediaStore.Images.Media.DATA };136 Cursor cursor = activity.getContentResolver()137 .query(selectedImage, filePathColumns, null, null, null);138 if (cursor != null) {139 cursor.moveToFirst();140 int columnIndex = cursor.getColumnIndex(filePathColumns[0]);141 String imagePath = cursor.getString(columnIndex);142 callback.onResult(imagePath);143 cursor.close();144 }145 }146 }147 }148149 public interface Callback {150 151 * @param filePath 图片路径152 */153 void onResult(String filePath); 154 }155}
复制代码
代码很简单,支持拍照和从系统相册选取图片,适配高版本拍照。下面我们看下使用示例。老规矩,直接上代码。
1public void onClick(View view) {2 new PhotoTaker(this, new PhotoTaker.Callback() {3 @Override4 public void onResult(String filePath) {5 imageview.setImageURI(Uri.fromFile(new File(filePath)));6 }7 }).show();8 }
复制代码
是不是很方便。通过这个示例,简单说一下。通过直接 new PhotoTaker 来创建 PhotoTaker。PhotoTaker 有两个参数,Activity,Callback。Callback 的回调方法 onResult,将拍照或者从相册选择的图片地址回调出来。这样我们就可以直接处理图片。在列表的 adapter 中是一样的使用方式。
最后附上 demo地址
作者介绍:
公台(企业代号名),贝壳找房 Android 工程师,目前负责新房 B 端 Android 研发工作。
本文转载自公众号贝壳产品技术(ID:gh_9afeb423f390)。
原文链接:
https://mp.weixin.qq.com/s/L8vuGvRao9HXm8OSxXqxig
评论