如果我们的应用必须在有相机的设备上才能正常使用,那么如何限制只在有相机的设备上安装应用?
<manifest ... >
<uses-feature android:name="android.hardware.camera"
android:required="true" />
...
</manifest>
如果我们不进行上面的设置或者设置了android:required ="false",那么我们在使用的过程中就要进行动态判断,通过hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)进行判断,如果相机不可用,则禁用相应的功能。
如何通过系统相机来拍一张照片出来
val REQUEST_IMAGE_CAPTURE = 1
private fun dispatchTakePictureIntent() {
Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
takePictureIntent.resolveActivity(packageManager)?.also {
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
}
}
}
分三步来调用系统相机拍照:
第一步:创建启动相机的意图Intent。
第二步:通过resolveActivity来判断是否能找到处理该意图Intent的组件。
第三步:通过startActivityForResult启动改组件。
拍完照片以后,android相机应用会将拍摄的照片进行返回,我们通过下面的方式来获取返回的数据
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == Activity.RESULT_OK) {
/**
* 其中data中返回的是拍摄照片的缩略图
*/
val imageBitmap = data?.extras?.get("data") as Bitmap
imageView.setImageBitmap(imageBitmap)
}
}
那么,如果能保存一张全尺寸的图片呢?就需要我们提供一个文件路径给相机应用来进行图片的保存。
图片保存的位置有两种类型,一种是保存的图片可以被其他应用共享([getExternalStoragePublicDirectory()](https://developer.android.com/reference/android/os/Environment.html#getExternalStoragePublicDirectory(java.lang.String))
,
),另一种是应用内私有文件目录(getExternalFilesDir())。
涉及到文件的读写,就需要进行权限的处理。
如果我们选择是在应用内部私有目录进行文件的保存,也就是使用getExternalFilesDir()。针对这个目录,Android 4.3 以及更低的系统中,需要请求WRITE_EXTERNAL_STORAGE权限,Android4.4开始就不再需要申请该权限。所以只需要在4.3及以下的版本中请求改权限。
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />
...
</manifest>
如果决定要保存一张图片,可以尝试通过时间戳来为图片进行唯一的命名。
/**
* 保存图片
*/
@Throws(IOException::class)
private fun createImageFile(): File {
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
val storageDir: File = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
return File.createTempFile(
"jpeg_$timeStamp",
".jpeg",
storageDir
).apply {
currentPhotoPath = this.absolutePath
}
}
当我们保存了一张照片以后,如何才能访问它呢?Android7.0以后,使用FileProvider
来进行配置。
配置如下:
<application>
...
<provider
android:authorities="com.happy.camera.fileprovider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true"
tools:replace="android:authorities">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"
tools:replace="android:resource"/>
</provider>
...
</application>
然后在res/xml/file_paths.xml文件中进行配置
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="Android/data/com.happy.camera/files/Pictures"/>
</paths>
配置完FileProvider,我们看一下如何调用?
Intent(MediaStore.ACTION_IMAGE_CAPTURE)?.also { takePictureIntent ->
takePictureIntent.resolveActivity(packageManager)?.also {
// 创建图片要保存的文件
val photoFile: File? = try {
createImageFile()
} catch (e: Exception) {
null
}
photoFile?.also { file ->
val photoURI = if (Build.VERSION.SDK_INT >= 24) {
FileProvider.getUriForFile(
this, "com.happy.camera.fileprovider", file
)
} else {
Uri.fromFile(file)
}
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
}
}
}
如果我们直接处理多张全尺寸的图片会非常消耗内存。所以我们在显示图片的时候都需要对图片的尺寸进行合适的裁剪。
private fun setPic(){
// 获得imageView的宽度和高度
val targetW:Int = imageView.width
val targetH:Int = imageView.height
val bmOptions = BitmapFactory.Options().apply{
inJustDecodeBounds = true
val photoW:Int = outHeight
val photoH:Int = outWidth
// 获得图片的裁剪比例
val scaleFactor:Int = Math.min(photoW/targetW,photoH/targetH)
inJustDecodeBounds = false
inSampleSize = scaleFactor
}
BitmapFactory.decodeFile(currentPhotoPath,bmOptions)?.also {
bitmap ->
imageView.setImageBitmap(bitmap)
}
}
我们在来看一下onActivityResult()方法,如果我们自定义了Uri,那么data:Intent拿到的data数据就会返回null,我们就需要从当前图片的路径来获取图片资源。
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {}
当我们使用相机拍摄照片后,经常会遇到显示的图片相对于实际拍摄照片的进行旋转,这就需要我们在显示图片的时候对图片的角度进行一下判断和处理。
/**
* 首先,读取照片的角度
*/
private fun readPictureDegree(path:String):Int{
var degree = 0
val exifInterface = ExifInterface(path)
when(exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL)){
ExifInterface.ORIENTATION_ROTATE_90 ->
degree = 90
ExifInterface.ORIENTATION_ROTATE_180 ->
degree = 180
ExifInterface.ORIENTATION_ROTATE_270 ->
degree = 270
}
return degree
}
/**
* 然后,对图片进行旋转
*/
private fun rotateBitmap(angle: Int, bitmap: Bitmap): Bitmap {
var returnBmp: Bitmap? = null
var matrix = Matrix()
matrix.postRotate(angle.toFloat())
try {
returnBmp = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
} catch (e: Exception) {
}
if (returnBmp == null) {
returnBmp = bitmap
}
if (bitmap != returnBmp) {
bitmap.recycle()
}
return returnBmp
}
最后补充一下onActivityResult()方法
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == Activity.RESULT_OK) {
setPic()
}
}
private fun setPic() {
// 获得imageView的宽度和高度
val targetW: Int = imageView.width
val targetH: Int = imageView.height
val bmOptions = BitmapFactory.Options().apply {
inJustDecodeBounds = true
val photoW: Int = outHeight
val photoH: Int = outWidth
// 获得图片的裁剪比例
val scaleFactor: Int = Math.min(photoW / targetW, photoH / targetH)
inJustDecodeBounds = false
inSampleSize = scaleFactor
}
BitmapFactory.decodeFile(currentPhotoPath, bmOptions)?.also { bitmap ->
val degree = readPictureDegree(currentPhotoPath)
if (degree != 0) {
// 旋转图像
val resultBitmap = rotateBitmap(degree, bitmap)
imageView.setImageBitmap(resultBitmap)
} else {
imageView.setImageBitmap(bitmap)
}
}
}
如何添加拍摄的照片到相册?(如果是保存在getExternalFilesDir()目录下,那么media scanner就不能访问该文件,因为这些照片是app私有的)
fun galleryAddPic(view: View) {
Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).also { mediaScanIntent ->
val f = File(currentPhotoPath)
mediaScanIntent.data = Uri.fromFile(f)
sendBroadcast(mediaScanIntent)
}
}
通过上面的步骤,我们知道了如何通过系统相机来拍摄一张照片并保存在手机上,那么如何通过系统相机来录制一段视频呢?
首先,也是要先确认我们有没有添加摄像头的feature
<uses-feature
android:name="android.hardware.camera"
android:required="true" />
然后,我们使用一个VideoView来简单展示我们录制的视频
/**
* 调用系统相机开始录制
*/
fun captureVideoBtn(view: View) {
Intent(MediaStore.ACTION_VIDEO_CAPTURE).also { takeVideoIntent ->
takeVideoIntent.resolveActivity(packageManager)?.also {
startActivityForResult(takeVideoIntent, REQUEST_VIDEO_CAPTURE)
}
}
}
/**
* 对返回的数据进行使用
*/
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
if (requestCode == REQUEST_VIDEO_CAPTURE && resultCode == Activity.RESULT_OK) {
val videoUri = intent?.data
showVideoView.setVideoURI(videoUri)
showVideoView.start()
showVideoView.requestFocus()
}
}