前言:
如果觉得掘金上看图片放大看不清楚,可以跳到另外的同步发布的链接看,放大图片下部有个可以查看原图功能,很清楚:项目需求讨论 - WebView下拍照及图片选择功能
现在很多app里面,都会有这么一个需求,就是上传图片的按钮,当然按了这个按钮之后,就会出现二种选择: 1. 直接拍照,2. 相册选择现有图片。
因为现在的app这块功能会有二个大的情况:
- 全部原生的 app 来实现。
- HyBird 的 app 来实现。

本文先讨论HyBird的app的实现情况,下次再讨论原生,不过其实大部分实现都是相似的。
其实这种在WebView配合下实现这类功能的文章很多很多,但是大多数都是上传一大段代码,然后让大家自己看,千篇一律,所以本文主要是写的完整的思路。
正文:
我们知道用户会在网页上点击了某个按钮,然后调用起安卓方面的相关操作。然后实现完整的功能。
1. 网页端:
其实网页端很简单,只需要实现一个简单的<input>
标签即可。
总体思路是一个<input>
标签和一个<img>
标签重叠在一起(<input>
在上,<img>
在下,类似可以理解<img>
作为背景),当选完照片后,最后把图片赋值给<img>
标签。
但是在给<img>
赋值的时候我遇到过不同的情况:
-
当在Android这边拍照或者进入图库选完照片后,把图片信息给了网页端后,
<input>
标签的onchange监听到了图片选择好了,网页端直接把图片上传到服务器并传回来一个地址,显示时把地址拼接成可以找到路径的地址放在<img>
标签中就可以了。 -
配合FileReader,FileReader是作为文件API的重要成员用于读取文件。可以参考: h5 实现调用系统拍照或者选择照片并预览
2. Android端:
2.1 WebChromeClient
因为Android端访问网页大部分使用的是WebView
,所以我们这里还是用WebView
来说明。
既然用户在网页上点击了<input>
,我们肯定需要WebView
能监听到,好比原生的Button
点击我们要监听也要写一个OnclickListener
来实现监听。我们这里使用的是WebChromeClient
。
public class ImgWebChromeClient extends WebChromeClient {
//.......
//.......
public ImgWebChromeClient(Activity activity) {
this.activity = activity;
}
}
我们实现我们的类,继承WebChromeClient。然后我们就可以把这个我们自己定义的WebChromeClient设置给我们的WebView。
webView.setWebChromeClient(new ImgWebChromeClient(this));
我们可以看到我们在WebChromeClient在监听<input>
点击事件的时候,还要根据不同的版本来区分,主要是以Android 5.0版本来进行大的划分。
-
Android 5.0及以上版本:
-
Android 5.0以下版本:
都是openFileChooser方法,不同版本的里面参数不同。
所以我们可以看到主要是openFileChooser
和onShowFileChooser
方法。
// For Android < 3.0
public void openFileChooser(ValueCallback<Uri> valueCallback) {
***
}
// For Android >= 3.0
public void openFileChooser(ValueCallback valueCallback, String acceptType) {
***
}
//For Android >= 4.1
public void openFileChooser(ValueCallback<Uri> valueCallback,
String acceptType, String capture) {
***
}
// For Android >= 5.0
@Override
public boolean onShowFileChooser(WebView webView,
ValueCallback<Uri[]> filePathCallback,
WebChromeClient.FileChooserParams fileChooserParams) {
***
return true;
}
不管是什么版本,我们看到这几个方法的参数里面都有ValueCallback
参数。
/**
* A callback interface used to provide values asynchronously.
*/
public interface ValueCallback<T> {
/**
* Invoked when the value is available.
* @param value The value.
*/
public void onReceiveValue(T value);
};
所以我们知道了,我们最后是调用openFileChooser
和onShowFileChooser
方法里面的ValueCallback
参数,调用它的onReceiveValue
方法把我们的选择的图片的Uri传入,网页端那边就会收到信息了,就知道用户到底选择了什么图片。
所以我们最终目标就是获取选择的图片的Uri,然后给ValueCallback.onReceiveValue方法,这就是我们整个文章的最主要的流程。
2.2 获取相关图片的Uri
上面我们提到了,我们最终目标是获取用户选取的图片Uri
,然后传给ValueCallback
就可以。
所以我们这里就要讲二大块:
- 用户怎么跳到自己想要的界面(相机 or 图库)
- 用户在自己想要的界面选择好了图片后 (拍好了照片 or 在图库选择好了图片),如何获取相关图片的
Uri
。
根据这二点,我们一步步来分析。
2.2.1 相机 or 图库
我们肯定想到是用户点击了某个按钮后,我们需要跳出一个弹框,然后上面有拍照和图库按钮: 比如我使用系统自带的选择框(不同手机显示的弹框不同):
所以我们这里知道了这个又要细分任务:
- 获取相关权限
- 如何点击按钮后可以跳到相应界面(拍照 or 图库)。
- 如何创建弹框,把上面的按钮显示在上面
2.2.1.1 获取相关权限
emmm......这块我觉得应该不需要花更多的时间来说明了吧,主要就是:
- 检查权限 (checkSelfPermission)
- 请求权限(requestPermissions)
- 回调事件处理(onRequestPermissionsResult)
而我们要申请的权限无非就是 Camera的权限,还有读写外部存储的权限。
2.2.1.2 如何点击按钮后可以跳到相应界面(拍照 or 图库):
我们先来看拍照:
2.2.1.2.1 设置打开相机Intent的Action
我们知道打开某个界面,就是startActivity(Intent);平常比如跳到拨号界面等。只要对Intent设定相应的Action即可。 具体我们可以看谷歌的Android官方教程网页即可:
我们可以看到有这些:
我们可以这个目录中看到了相机,我们具体看相机的介绍:
注:当您使用 ACTION_IMAGE_CAPTURE拍摄照片时,相机可能还会在结果 Intent 中返回缩小尺寸的照片副本(缩略图),这个副本以 Bitmap 形式保存在名为
data
的extra
字段中。
所以我们这里跳到拍照界面也是一样,只要建立跳到相机界面的Intent即可:
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
2.2.1.2.2 设置相机拍摄的照片的存储位置
因为有些人需要在自己的APP中调用拍照的功能,存在自己指定的目录下面,所以需要在startActivity启动相机界面时候同时传递过去信息,告诉拍照了之后照片存的位置。
1.我们先指定我们的要存储的照片的路径Uri:
其实很简单,设定我们接下去要拍的照片的完整存储路径,然后得到File
对象,再通过Uri.fromFile
方法再通过刚才我们的File
对象来获得Uri
。
(当然如果这里你只需要打开系统相机,以下第二部分可以忽略)
2.获取所有相机的Intent集合:
因为我们手机上面可能有很多个相机软件,所以我们需要先找到能打开各自相机软件的Intent,我们通过PackageManager.queryIntentActivities
的方式来进行符合拍照Action的Intent的软件,然后得到它们具体的详细信息,比如包名及对应的activity名字等,然后把相应的Intent变得更加详细即可。
3.把uri赋值给Intent:
在上面贴出的Android 官方网页上面的相机部分其实也提到过了如何设置存储位置:
所以这里我们只需要找到相应的Intent,然后把我们的Uri位置赋值给Intent即可:
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
最后只需要startActivity去启动我们这个指定了打开相机的特定Intent即可。
拍照说完了,我们再来看在图库界面选择图片: 其实总体思路和拍照是一模一样,无非就是指定Intent是打开了图库的Intent。
还是在刚才的Android 官网我们可以看到:
因为我们是查看本地的图片,所以我们要使用 ACTION_GET_CONTENT
,同时指定MIME
类型是图片类型,如果要进行图片多选,就再指定EXTRA_ALLOW_MULTIPLE
为true
。
同时也给出了实例代码:
这里我要提一下,我们在设置Intent
的Action
的时候不只是可以使用ACTION_GET_CONTENT
,还可以使用ACTION_PICK
。
我们可以看到上面写着可以用来选择数据,然后返回被选中的选项。
但是在具体手机操作上有点不同(不知道不同的手机系统会不会结果不同,我只测了模拟器):
ps:最坑的是用ACTION_GET_CONTEN时候多选图片要长按操作,一直以为没成功,以为多选图片功能没实现,后来在 Android: Intent.EXTRA_ALLOW_MULTIPLE allows only single picking 上面找到别人的答案,说需要长按
2.2.2 出现选择框让用户选择
我们可以看到可以自定义弹框,比如我们设定固定的按钮,然后再点击特定按钮后启动我们的上面提过的特定的Intent即可。
这里我们讲如果只是给定我们想要启动的多个Intent的选项,让系统帮我们弹出弹框及相关按钮,关键字就是Intent.createChooser
方法
直接看图片即可,写的很详细了,或者大家搜相关的关键字也是有很多文章的。比如:Android createChooser方法源码简析等。
2.2.3 获取用户在相机或者图库选择的图片Uri
因为我们不是单纯的跳到了相机界面或者是图库界面就可以了,我们还需要获取用户在那些应用外的界面到底选了什么图片,所以单纯的startActivity
肯定不够,所以大家肯定想到了使用startActivityForResult
来启动,这样才能根据用户不同的操作来进行相应的处理。
我们知道需要复写onActivityResult
来处理,主要也就三个参数(int requestCode, int resultCode, Intent data)
。具体的内容图片里面也写的很清楚。
2.3 Uri 和 ValueCallback
所以我们ValueCallback实例在 WebChromeClient的方法里面拿到了,Uri也通过相机或者图库的选择下获取到了。最终调用把获取的Uri 赋值给ValueCallback.onReceiveValue()即可。
PS: 取消这次网页点击选取图片的请求,只需要调用onReceiveValue(null)即可。
结语:
emm.......大家轻喷即可。。。。