目录.jpg
1. 定义相关变量 及 权限申请
val AUTHORITY = "com.cs.camerademo.fileProvider" //FileProvider的签名(后面会介绍)
val REQUEST_CODE_CAPTURE_RAW = 6 //startActivityForResult时的请求码
var imageFile: File? = null //拍照后保存的照片
var imgUri: Uri? = null //拍照后保存的照片的uri
2.调用系统相机
//拍照(返回原始图)
private fun gotoCaptureRaw() {
imageFile = createImageFile()
imageFile?.let {
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //如果是7.0以上,使用FileProvider,否则会报错
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
imgUri = FileProvider.getUriForFile(this, AUTHORITY, it)
intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUri) //设置拍照后图片保存的位置
} else {
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(it)) //设置拍照后图片保存的位置
}
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()) //设置图片保存的格式
intent.resolveActivity(packageManager)?.let {
startActivityForResult(intent, REQUEST_CODE_CAPTURE_RAW) //调起系统相机
}
}
}
//生成一个文件
fun createImageFile(isCrop: Boolean = false): File? {
return try {
var rootFile = File(rootFolderPath + File.separator + "capture")
if (!rootFile.exists())
rootFile.mkdirs()
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
val fileName = if (isCrop) "IMG_${timeStamp}_CROP.jpg" else "IMG_$timeStamp.jpg"
File(rootFile.absolutePath + File.separator + fileName)
} catch (e: Exception) {
e.printStackTrace()
null
}
}
调用相机之前,intent需要设置两个参数
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()) 设置图片保存的格式
intent.putExtra(MediaStore.EXTRA_OUTPUT,uri) 设置拍照后图片保存的位置,如果没有这句,相机则会返回一个拍照的缩略图,具体效果及代码请查看完整代码
注意:在设置图片保存的uri的时候,需要对Android7.0以上系统做处理,否则会报FileUriExposedException异常
3. 处理异常,兼容7.0
在7.0以上系统中,Android不再允许在app中把file://Uri暴露给其他app,因为这样做存在一定的风险,如:
官方给出的解决方案是使用FileProvider,具体使用步骤如下:
第一步:在AndroidManifest.xml清单文件中注册FileProvider
第二步:在res目录下创建一个名为xml的文件夹,然后在该文件夹下创建名为file_paths的xml文件
xml目录.png
这个xml文件最外层是一对
例如 :
external-path 表示 Environment.getExternalStorageDirectory()目录;
name 是我们给它起的名字叫"external-path" (当然你也可以用起个张三李四什么的,好像没啥用...);
path 表示Environment.getExternalStorageDirectory()目录下的CameraDemo文件夹
具体的路径与写法的对应规则如下:
写法 | 对应目录 |
---|---|
Context.getFilesDir() | |
Context.getCacheDir() | |
Environment.getExternalStorageDirectory() | |
Context.getExternalFilesDir(null) | |
Context.getExternalCacheDir() |
如果需要使用FileProvider获取某个目录下的文件的uri,按照上表中的对应关系在xml文件中声明就可以了
第一步在清单文件中注册FileProvider的时候, android:resource="@xml/file_paths" 对应的就是我们刚刚创建的这个xml文件
第三步: 在Activity使用FileProvider获取uri
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) //申请临时读取的权限
imgUri = FileProvider.getUriForFile(this, AUTHORITY , imageFile)
getUriForFile是FileProvider的一个静态方法,第一个参数是Context对象,第二个参数是在清单文件中定义的authorities,第三个参数是一个File对象。 返回值为传入文件的content uri对象。
当我们拿到返回的ContentUri对象后,就解决了Android7.0报FileUriExposedException的问题
4. 接收相机返回的结果
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
REQUEST_CODE_CAPTURE_RAW -> { //拍照成功后,压缩图片,显示结果
imageFile?.let {
compressImage(it) //照片生成后,压缩图片并显示
}
}
}
} else {
Log.d("tag", "错误码 $resultCode")
}
}
//压缩图片并显示
fun compressImage(file:File) {
thread {
var temp = System.currentTimeMillis()
var bitmap = BitmapFactory.decodeFile(file.absolutePath)
var compressBitmap = BitmapUtils.decodeBitmap(bitmap, 720, 1080)
runOnUiThread {
ivImage.setImageBitmap(compressBitmap) //ivImage是xml布局文件中的一个ImageView
Log.d("tag", "原始图片大小 ${bitmap.width} * ${bitmap.height}")
Log.d("tag", "压缩后图片大小 ${compressBitmap.width} * ${compressBitmap.height}")
Log.d("tag", "加载图片耗时 ${System.currentTimeMillis() - temp}")
}
}
}
在onActivityResult中成功收到返回结果后,压缩图片,并显示在ImageView中。
由于压缩图片是耗时操作,所以开启了一个子线程进行压缩处理,并在Log中输入的原始图片大小,压缩后图片大小和压缩所耗时间,小伙伴们可以在logcat中看到对应的图片信息
图片信息.png
由于现在手机像素都特别高,拍出来的照片非常大,如果不进行压缩处理直接展示在屏幕上的话不仅会耗费内存而且可能引起OutOfMemary(内存溢出)
好,到这里就完成了调用系统相机拍照并显示的功能了。贴一下效果图:
拍照.gif
第一部分已经介绍了如何调用系统相机进行拍照,但是有时候,我们不需要整张图片,而是只需要采取图片的某一部分就行,那就要用到裁剪功能了
要实现拍照 + 裁剪功能,步骤如下:
1. 进行调用系统相机拍照,并接收返回结果。
这一步在上面已经介绍介绍过,所以不在赘述。唯一一个地方与上面不同的是,在onActivityResult中收到成功的返回结果后不在是去压缩并显示图片,而是去调用系统的裁剪功能
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
REQUEST_CODE_CAPTURE -> { //拍照成功的返回结果
val sourceUri = FileProvider.getUriForFile(this, AUTHORITY, imageFile) //通过FileProvider创建一个content类型的Uri
gotoCrop(sourceUri) //裁剪图片
}
REQUEST_CODE_CAPTURE_CROP -> { //裁剪成功得返回结果
imageCropFile?.let { //显示图片
ivResult.setImageBitmap(BitmapFactory.decodeFile(it.absolutePath))
}
}
}
} else {
Log.d("tag", "错误码 $resultCode")
}
}
2. 调用系统的裁剪功能,并接收返回结果。
//裁剪(参数是需要裁剪的图片的uri)
private fun gotoCrop(sourceUri: Uri) {
imageCropFile = FileUtil.createImageFile(true) //创建一个保存裁剪后照片的File
imageCropFile?.let {
val intent = Intent("com.android.camera.action.CROP")
intent.putExtra("crop", "true")
intent.putExtra("aspectX", 1) //X方向上的比例
intent.putExtra("aspectY", 1) //Y方向上的比例
intent.putExtra("outputX", 500) //裁剪区的宽
intent.putExtra("outputY", 500) //裁剪区的高
intent.putExtra("scale ", true) //是否保留比例
intent.putExtra("return-data", false) //是否在Intent中返回图片
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()) //设置输出图片的格式
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) //添加这一句表示对目标应用临时授权该Uri所代表的文件
intent.setDataAndType(sourceUri, "image/*") //设置数据源,必须是由FileProvider创建的ContentUri
var imgCropUri = Uri.fromFile(it)
intent.putExtra(MediaStore.EXTRA_OUTPUT, imgCropUri) //设置输出 不需要ContentUri,否则失败
Log.d("tag", "输入 $sourceUri")
Log.d("tag", "输出 ${Uri.fromFile(it)}")
} else {
intent.setDataAndType(Uri.fromFile(imageFile!!), "image/*")
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(it))
}
startActivityForResult(intent, REQUEST_CODE_CAPTURE_CROP)
}
}
注意:
Android7.0以上,相机调用时intent.putExtra(MediaStore.EXTRA_OUTPUT, uri), Uri必须使用ContentUri,不能用Uri.fromFile(file)
在裁剪的时候就不一样,设置图片源的时候必须使用ContentUri,设置输出位置的时候继续使用 Uri.fromFile(file)。
疑问:
我对这个地方一直有疑问,7.0以上,在调用裁剪的时候,设置输入的时候不是也应该用ContentUri么???但实践后发现用ContentUri无法得到裁剪的图片。如果有知道原因的小伙们可以留言。想看源码,也没找到系统裁剪的源码在哪里-_-|| 小伙们可以留言告诉我一下
3. 接收裁剪结果并显示
REQUEST_CODE_CAPTURE_CROP -> { //裁剪成功后,显示结果
imageCropFile?.let {
ivResult.setImageBitmap(BitmapFactory.decodeFile(it.absolutePath))
}
}
到这里就完成了调用系统相机拍照+裁剪的功能了。贴一下效果图:
拍照+裁剪.gif
从相册中选择一张照片,然后裁剪并显示,流程如下:
1. 打开系统相册
//打开系统相册
private fun gotoGallery() {
var intent = Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
startActivityForResult(intent, REQUEST_CODE_ALBUM)
}
2. 在onActivityResult接收返回的url,调用裁剪方法
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
REQUEST_CODE_ALBUM -> { //从相册选择照片后,裁剪
data?.let {
gotoCrop(it.data)
}
}
REQUEST_CODE_CAPTURE_CROP -> { //裁剪成功后,显示结果
imageCropFile?.let {
ivResult.setImageBitmap(BitmapFactory.decodeFile(it.absolutePath))
}
}
}
} else {
Log.d("tag", "错误码 $resultCode")
}
}
注意:
打开系统相册选择照片后,返回结果在Intent数据中的data字段,是一个uri,表示选择的那张照片的位置
具体的裁剪相关代码在上面已有,此处不在展示
3. 接收裁剪结果并显示
REQUEST_CODE_CAPTURE_CROP -> { //裁剪成功后,显示结果
imageCropFile?.let {
ivResult.setImageBitmap(BitmapFactory.decodeFile(it.absolutePath))
}
}
效果图如下:
相册+裁剪.gif
调用系统的录制功能录制一段视频,并播放
1. 调用录制视频功能
//录制视频
private fun gotoCaptureVideo() {
var intent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
if (intent.resolveActivity(packageManager) != null)
startActivityForResult(intent, REQUEST_CODE_VIDEO)
}
2. 接收返回结果并使用VideoView播放
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
REQUEST_CODE_VIDEO -> { //录制视频成功后播放
data?.let {
var uri = it.data
videoView.visibility = View.VISIBLE
videoView.setVideoURI(uri)
videoView.start()
Log.d("tag", "视频uri $uri")
}
}
}
} else {
Log.d("tag", "错误码 $resultCode")
}
}
录制视频后返回的结果在 Intent的data字段中,是一个uri,指向视频所保存的位置
效果图如下:
录制视频.gif
https://github.com/smashinggit/Study
注:此工程包含多个module,本文所用代码均在camerademo文件夹下
转自:https://www.jianshu.com/p/eca7335602c1