Android 发现系列之不一样的拍照方式

阅读数:160 2019 年 9 月 26 日 10:02

Android发现系列之不一样的拍照方式

1 繁琐的 Android 拍照

Android 中的拍照是一件很繁琐的事情,因为它必须使用 Intent,并通过 startActivityForResult 方法去启动相机,完成后需要在 onActivityResult 中接受回调,获得拍照的结果,也就是说拍照和拍照的回调结果是隔离的。我们看下一段伪代码。

复制代码
1private File photoFile = createFile();// 创建 file 用来保存拍照的路径
2
3onClick.. goToPhoto// 通过 Intent 去拍照
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);//startActivityForResult
9}
10
11// 只能在 Activity 或者 Fragment 的 onActivityResult 回调方法中接受拍照结果
12@Override
13 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 }
13
14 @Override
15 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 // 创建 File 对象,用于存储拍照后的图片
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 //Android 7.0 系统开始 使用本地真实的 Uri 路径不安全, 使用 FileProvider 封装共享 Uri
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 }
118
119 /**
120 * data/packagename/files/
121 */
122 public static String createFile(Context context) {
123 return context.getExternalFilesDir(null) + "/" + DATE_FORMAT.format(new Date()) + ".jpg";
124 }
125
126 public void onActivityResult(int requestCode, int resultCode, Intent data) {
127 if (resultCode != Activity.RESULT_OK) return;
128
129 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 }
148
149 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 @Override
4 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

评论

发布