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

2019 年 9 月 26 日

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}1011// 只能在Activity或者Fragment的onActivityResult回调方法中接受拍照结果12@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    // 创建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封装共享Uri109      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


2019 年 9 月 26 日 10:02385

评论

发布
暂无评论
发现更多内容

ARTS 第 1 周

乌拉里

ARTS 打卡计划

[ARTS打卡] week 02

Mau

ARTS 打卡计划

利其器

宋胖子

IDEA

因为 MongoDB 没入门,我丢了一份实习工作

沉默王二

mongodb

架构师训练营-每周学习总结1

水边

极客大学架构师训练营

架构设计文档之食堂就餐卡系统设计

itrickzhang

架构设计 架构文档 架构样例 架构分析 架构总结

架构文档

陈皮

架构 极客大学架构师训练营

Java 25周年:波澜壮阔的25年

北风

「Java 25周年」

架构师训练营-命题作业1

水边

极客大学架构师训练营

Flink源码分析之-如何保存 offset

shengjk1

「架构师训练营」第1周作业 - 食堂就餐卡系统设计

guoguo 👻

极客大学架构师训练营

MySQL 笔记(一)基础架构

奈何花开

Java MySQL

架构师课程第一周作业

杉松壁

食堂就餐卡系统设计-uml练习

森林

教你动手写UDP协议栈

Rice嵌入式开发技术分享

TCP udp 协议栈

【架构师训练营-作业-1】食堂就餐卡系统设计

小动物

系统设计 极客大学架构师训练营 作业

架构师训练营第一周学习总结

王鑫龙

极客大学架构师训练营

【ARTS打卡】Week02

Rex

程序员摆地摊?你别痴心妄想了,还不如当「在地青年」呢

非著名程序员

程序员 提升认知 职业规划 认知提升

UML练习1 食堂就餐卡系统设计「架构师训练营」

Young

在 Windows WSL 2 中使用 Docker Desktop

FeiLong

Docker WSL2

架构方法:运用合适的工具表达设计

NORTH

极客大学架构师训练营

如何用一台 MacBook 创造高额年化收益 | ETH2.0 Staking 教程

陈东泽 EuryChen

区块链 Ethereum

《OKR工作法》读书笔记

大饼土博

读书笔记 管理 OKR

软件架构第一章总结

itrickzhang

不可不知的 7 个 JDK 命令

武培轩

Java 程序员 jdk 后端 JVM

2020年6月7日 接口、lambda表达式与内部类

瑞克与莫迪

程序员的晚餐 | 6 月 5 日 爆炒鱿鱼

清远

美食

程序员的晚餐 | 6 月 4 日 最好吃的土豆

清远

优秀架构师具备的能力

阿飞

极客大学架构师训练营

repo 导出本地 git tag 给他人

zqb-all

git

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