调用摄像头
很多应用都会要求用户上传图片,这时打开摄像头拍照是最简单快捷的
新建一个项目,修改 activity_main.xml 中的代码,如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Take Photo"
tools:ignore="HardcodedText" />
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
</LinearLayout>
Button 用于打开摄像头拍照,ImageView 用于显示拍到的照片
class MainActivity : AppCompatActivity() {
var takePhoto = 1
lateinit var imageUri: Uri
lateinit var outputImage: File
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
// 创建File对象,用于存储拍照后的图片
// 调用 getExternalCacheDir() 获取应用关联缓存目录
outputImage = File(externalCacheDir, "output_image.jpg")
if (outputImage.exists()) {
outputImage.delete()
}
outputImage.createNewFile()
// 将 File 对象转换为 Uri 对象,Uri 对象标识图片的本地真实路径
// 如果系统版本低于 Android 7.0,就调用 Uri 的 fromFile() 方法
// 否则调用 FileProvider.getUriForFile() 方法,该方法接收三个参数:
// 参数1:Context 对象
// 参数2:任意唯一的字符串
// 参数3:刚刚创建的 File 对象
imageUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
FileProvider.getUriForFile(this, "com.example.cameraalbumtest.fileprovider", outputImage)
} else {
Uri.fromFile(outputImage)
}
// 启动相机程序
val intent = Intent("android.media.action.IMAGE_CAPTURE")
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri)
startActivityForResult(intent, takePhoto)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
takePhoto -> {
if (resultCode == Activity.RESULT_OK) {
// 将拍摄的照片显示出来
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(imageUri))
imageView.setImageBitmap(rotateIfRequired(bitmap))
}
}
}
}
private fun rotateIfRequired(bitmap: Bitmap): Bitmap {
val exif = ExifInterface(outputImage.path)
val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
return when (orientation) {
ExifInterface.ORIENTATION_ROTATE_90 -> rotateBitmap(bitmap, 90)
ExifInterface.ORIENTATION_ROTATE_180 -> rotateBitmap(bitmap, 180)
ExifInterface.ORIENTATION_ROTATE_270 -> rotateBitmap(bitmap, 270)
else -> bitmap
}
}
private fun rotateBitmap(bitmap: Bitmap, degree: Int): Bitmap {
val matrix = Matrix()
matrix.postRotate(degree.toFloat())
val rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
// 将不需要的Bitmap对象回收
bitmap.recycle()
return rotatedBitmap
}
}
刚才提到了 ContentProvider,那么我们自然要在 AndroidManifest.xml 中对它进行注册
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.fragmenttest">
<uses-permission android:name="android.permission.CALL_PHONE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.FragmentTest">
...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>
android:name 属性的值是固定的,而 android:authorities 属性的值必须和刚才 FileProvider.getUriForFile() 方法一致。另外,还在 <meta-data> 指定 Uri 的共享路径,并引用了一个 @xml/file_paths
资源。当然,这个资源现在还不存在,右击 res 目录 -> New -> Directory,创建一个 xml 目录,右击 xml 目录 -> New -> File,创建一个 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="/" />
</paths>
external-path 就是用来指定 Uri 共享路径,name 属性可以随便填,path 属性的值表示共享的具体路径,这里使用一个单斜线表示将整个 SD 卡共享
现在,点击按钮,就可以进行拍照了。
从相册中选择图片
在之前的项目基础上进行修改,加入从相册选择图片的逻辑
class MainActivity : AppCompatActivity() {
var fromAlbum = 2
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
// 打开文件选择器
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
// 指定只显示图片
intent.type = "image/*"
startActivityForResult(intent, fromAlbum)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
...
fromAlbum -> {
if (resultCode == Activity.RESULT_OK && data != null) {
data.data?.let { uri ->
// 将选择的图片
}
}
}
}
}
private fun getBitmapFromUri(uri: Uri) = contentResolver
.openFileDescriptor(uri, "r")?.use {
BitmapFactory.decodeFileDescriptor(it.fileDescriptor)
}
}