9.1 前置工作
如果想使用自己的手机而不是虚拟机对应用进行开发,就可以参照下面的步骤进行操作,否则下面的步骤可以跳过。
为了方便演示,这里是在虚拟机演示如何开启USB调试,手机端步骤类似。
打开手机开发者模式
开启USB调试
9.2 使用通知
通知的简介
通知是Android系统中比较有特色的一个功能,当某个应用程序希望向用户发出一些**提示信息**,而该应用程序又不在前台运行时,就可以借助通知来实现。发出一条通知后,手机最上方的状态栏中会显示一个通知的图标,下拉状态栏后可以看到通知的详细内容。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22022942/1650871607797-db97eb7e-efbd-4369-b52d-1d291a154627.png#clientId=u3f4a4ca5-88cb-4&from=paste&height=691&id=uade93873&originHeight=864&originWidth=408&originalType=binary&ratio=1&rotation=0&showTitle=false&size=125193&status=done&style=none&taskId=u194cf785-840f-45ec-b50c-103e64ac4a4&title=&width=326.4)
通知的渠道
为了让用户自主过滤各种推送,Android 8.0系统引入了**通知渠道**这个概念,指的是每条通知都要属于一个对应的渠道。每个应用程序都可以自由地创建当前应用拥有哪些通知渠道,但是这些通知渠道的控制权是掌握在用户手上的。用户可以自由地选择这些通知渠道的重要程度,是否响铃、是否振动或者是否要关闭这个渠道的通知。
简单的来说,为了方便管理各个通知,加入了渠道这个概念,用户可以选择关闭不想要的通知的渠道。
这个渠道大家可以在设置里面的应用管理中查看。
创建通知渠道
实例
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/sendNotice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send Notice" />
</LinearLayout>
package com.example.notificationtest
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.graphics.BitmapFactory
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import androidx.core.app.NotificationCompat
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel("normal", "Normal",NotificationManager.IMPORTANCE_DEFAULT)
manager.createNotificationChannel(channel)
}
val sendNotice: Button = findViewById(R.id.sendNotice)
sendNotice.setOnClickListener {
val notification = NotificationCompat.Builder(this, "normal")
.setContentTitle("This is content title")
.setContentText("This is content text")
.setSmallIcon(R.drawable.small_icon)
.setLargeIcon(BitmapFactory.decodeResource(resources,R.drawable.large_icon))
.build()
manager.notify(1, notification)
}
}
}
创建通知管理者NotificationManager
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
很显然,通知这个功能属于系统服务,所以需要调用getSystemService
用于获取系统服务,其中的参数就根据我们需要的服务来写入,这里就是通知服务,所以是Context.NOTIFICATION_SERVICE
,最后记得使用as
关键词,可以理解为把getSystemService
返回的对象转化为NotificationManager
类型。
getSystemService
原方法返回的是Object
类型。
构建通知渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel("normal", "Normal",NotificationManager.IMPORTANCE_DEFAULT)
manager.createNotificationChannel(channel)
}
首先这里外面嵌套了一个if
语句,这个if
语句的作用是用于判断手机SDK版本是否支持创建消息渠道。Build.VERSION.SDK_INT
表示的是当前的SDK
,Build.VERSION_CODES._O_
表示的是支持创建消息渠道的最低版本。
查看源码可得,这个最低版本就是
26
。
里面两句就是创建渠道的语法:
val channel = NotificationChannel("normal", "Normal",NotificationManager.IMPORTANCE_DEFAULT)
manager.createNotificationChannel(channel)
首先要定义一个渠道channel
,需要三个参数:
- 渠道ID
- 渠道名称
- 重要等级
- IMPORTANCE_HIGH
- IMPORTANCE_DEFAULT
- IMPORTANCE_LOW
- IMPORTANCE_MIN
前面两个ID
和名称
很好理解,最后一个参数,对应的重要程度依次从高到低,不同的重要等级会决定通知的不同行为。
接下来创建渠道,就只需要把刚才的channel
当做参数传入即可。
发送通知
val sendNotice: Button = findViewById(R.id.sendNotice)
sendNotice.setOnClickListener {
val notification = NotificationCompat.Builder(this, "normal")
.setContentTitle("This is content title")
.setContentText("This is content text")
.setSmallIcon(R.drawable.small_icon)
.setLargeIcon(BitmapFactory.decodeResource(resources,R.drawable.large_icon))
.build()
manager.notify(1, notification)
}
首先需要通过NotificationCompat
定义构建一个通知
:
val notification = NotificationCompat.Builder(this, "normal")
接下来就是对这个通知设置相应的属性:
setContentTitle()
方法用于指定通知的标题内容,下拉系统状态栏就可以看到这部分内容。setContentText()
方法用于指定通知的正文内容,同样下拉系统状态栏就可以看到这部分内容。setSmallIcon()
方法用于设置通知的小图标,注意,只能使用纯alpha图层的图片进行设置,小图标会显示在系统状态栏上。setLargeIcon()
方法用于设置通知的大图标,当下拉系统状态栏时,就可以看到设置的大图标了。BitmpFactory.decodeResource(Resource res,int id)
根据给定的资源Id解析成位图
build()
方法用于构建
更多参见:
Android开发——Notification通知的使用及NotificationCopat.Builder常用设置API - Stars-one - 博客园
最后使用**manager**
发送通知即可:
manager.notify(1, notification)
notify()
方法接收两个参数:
- 第一个参数是
id
,要保证为每个通知指定的id
都是不同的; - 第二个参数则是
Notification
对象;
点击通知
如果你使用过Android手机,此时应该会下意识地认为这条通知是可以点击的。但是当你去点击它的时候,会发现没有任何效果。
要想实现通知的点击效果,我们还需要在代码中进行相应的设置——PendingIntent
。PendingIntent
类似于intent
,都有转跳activity
的作用,PendingIntent
可以简单地理解为延迟执行的Intent
。
点击效果
新建一个NotificationActivity
:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="24sp"
android:text="This is notification layout"
/>
</RelativeLayout>
修改一下前面的MainActivity
中的点击事件:
sendNotice.setOnClickListener {
val intent = Intent(this, NotificationActivity::class.java)
val pi = PendingIntent.getActivity(this, 0, intent, 0)
val notification = NotificationCompat.Builder(this, "normal")
.setContentTitle("This is content title")
.setContentText("This is content text")
.setSmallIcon(R.drawable.small_icon)
.setLargeIcon(BitmapFactory.decodeResource(resources,R.drawable.large_icon))
.setContentIntent(pi)
.build()
manager.notify(1, notification)
}
重点是这两句:
val intent = Intent(this, NotificationActivity::class.java)
val pi = PendingIntent.getActivity(this, 0, intent, 0)
第一句很简单,就是定义一个意图,转跳到NotificationActivity
。
第二句根据需求来选择是使用getActivity()
方法、getBroadcast()
方法,还是getService()
方法。他们的参数说明如下:
- 第一个参数是
上下文
- 第二个参数
requestCode
,一般写0
就可以了。 - 第三个参数是
Intent
,用来存储信息 - 第四个参数用于确定
PendingIntent
的行为(通常写**0**
就可以了):- FLAG_CANCEL_CURRENT
- FLAG_UPDATE_CURRENT
- FLAG_ONE_SHOT
- FLAG_NO_CREATE
注意配置通知属性的时候要加上
setContentIntent(pi)
这样之后,点击通知,就可以跳转到NotificationActivity
。
通知的状态图标
配置通知属性的时候加上:
setAutoCancel(true)
就表示当点击这个通知的时候,通知会自动取消。
也可以写在NotificationActivity
当中:
class NotificationActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_notification)
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as
NotificationManager
manager.cancel(1)
}
}
这里在`cancel()`方法中传入了`**1**`,创建通知的时候给每条通知指定的`id`条通知设置的`id`就是`**1**`。因此,如果想取消哪条通知,在`cancel()`方法中传入该通知的`id`就行了。
通知的进阶技巧
NotificationCompat.Builder
中提供了非常丰富的API
,以便我们创建出更加多样的通知效果。其中就包含了**setStyle()**
方法,这个方法允许我们构建出富文本的通知内容。也就是说,通知中不光可以有文字和图标,还可以包含更多的东西。
显示长文字通知
使用setStyle()
方法替代setContentText()
方法,并在setStyle()
方法中创建一个NotificationCompat.BigTextStyle
对象,这个对象就是用于封装长文字信息的,只要调用它的bigText()
方法并将文字内容传入就可以了。
sendNotice.setOnClickListener {
val intent = Intent(this, NotificationActivity::class.java)
val pi = PendingIntent.getActivity(this, 0, intent, 0)
val notification = NotificationCompat.Builder(this, "normal")
.setContentTitle("This is content title")
.setStyle(NotificationCompat.BigTextStyle().bigText("Learn how to build notifications, send and sync data, and use voice actions. Get the official Android IDE and developer tools to build apps for Android."))
.setSmallIcon(R.drawable.small_icon)
.setLargeIcon(BitmapFactory.decodeResource(resources,
R.drawable.large_icon))
.setContentIntent(pi)
.setAutoCancel(true)
.build()
manager.notify(1, notification)
}
显示大图片
仍然调用的setStyle()
方法,在参数中创建一个NotificationCompat.BigPictureStyle
对象,这个对象就是用于设置大图片的,然后调用它的bigPicture()
方法并将图片传入即可。
sendNotice.setOnClickListener {
val intent = Intent(this, NotificationActivity::class.java)
val pi = PendingIntent.getActivity(this, 0, intent, 0)
val notification = NotificationCompat.Builder(this, "normal")
.setContentTitle("This is content title")
.setContentText("This is content text")
.setStyle(NotificationCompat.BigPictureStyle().bigPicture(
BitmapFactory.decodeResource(resources, R.drawable.big_image)))
.setSmallIcon(R.drawable.small_icon)
.setLargeIcon(BitmapFactory.decodeResource(resources,
R.drawable.large_icon))
.setContentIntent(pi)
.setAutoCancel(true)
.build()
manager.notify(1, notification)
}
注意这里需要提前导入图片。
显示重要通知
通知渠道的重要等级越高,发出的通知就越容易获得用户的注意。比如高重要等级的通知渠道发出的通知可以弹出横幅、发出声音,而低重要等级的通知渠道发出的通知不仅可能会在某些情况下被隐藏,而且可能会被改变显示的顺序,将其排在更重要的通知之后。
修改一下MainActivity
:
需要添加和修改的部分已经给出注释了
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel("normal", "Normal",
NotificationManager.IMPORTANCE_DEFAULT)
// 添加一个新的渠道 并且设立更高的重要等级
val channel2 = NotificationChannel("important", "Important",
NotificationManager.IMPORTANCE_HIGH)
manager.createNotificationChannel(channel2)
manager.createNotificationChannel(channel)
}
val sendNotice: Button = findViewById(R.id.sendNotice)
sendNotice.setOnClickListener {
val intent = Intent(this, NotificationActivity::class.java)
val pi = PendingIntent.getActivity(this, 0, intent, 0)
// 这里启动重要等级的通知渠道
val notification = NotificationCompat.Builder(this, "important")
.setContentTitle("This is content title")
.setContentText("This is content text")
.setStyle(NotificationCompat.BigPictureStyle().bigPicture(
BitmapFactory.decodeResource(resources, R.drawable.big_image)))
.setSmallIcon(R.drawable.small_icon)
.setLargeIcon(BitmapFactory.decodeResource(resources,
R.drawable.large_icon))
.setContentIntent(pi)
.setAutoCancel(true)
.build()
manager.notify(1, notification)
}
运行结果如下:<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22022942/1651047453526-6b0d457d-fccd-4eed-a221-6a5805fc5cfe.png#clientId=u7d148cba-efce-4&from=paste&height=678&id=u1e731cb4&originHeight=847&originWidth=476&originalType=binary&ratio=1&rotation=0&showTitle=false&size=54026&status=done&style=none&taskId=u3eb3b8a0-1a39-4181-8526-1891e2f0aa5&title=&width=380.8)
9.3 调用摄像头和相册
调用摄像头
初始化创建
创建CameraAlbumTest
项目
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/takePhotoBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Take Photo" />
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
</LinearLayout>
修改`MainActivity`:
package com.example.cameraalbumtest
import android.app.Activity
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.media.ExifInterface
import android.net.Uri
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.MediaStore
import android.widget.Button
import android.widget.ImageView
import androidx.core.content.FileProvider
import java.io.File
class MainActivity : AppCompatActivity() {
val takePhoto = 1
lateinit var imageUri: Uri
lateinit var outputImage: File
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val takePhotoBtn: Button = findViewById(R.id.takePhotoBtn)
takePhotoBtn.setOnClickListener {
// 创建File对象,用于存储拍照后的图片
outputImage = File(externalCacheDir, "output_image.jpg")
if (outputImage.exists()) {
outputImage.delete()
}
outputImage.createNewFile()
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)
val imageView: ImageView = findViewById(R.id.imageView)
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.recycle() // 将不再需要的Bitmap对象回收
return rotatedBitmap
}
}
点击左侧三角按钮展开
主要逻辑
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
take_photo.setOnClickListener {
//创建File对象,用于存储拍照后的照片,存储位置是SD卡的应用关联缓存目录下,6.0后读写SD卡是危险权限,使用关联目录cache可以跳过这一步。Android10.0后使用作用域存储。
outputimage = File(externalCacheDir, "output_image.jpg")
//如果File已经存在,删掉,并调用createNewFile创建新文件
if (outputimage.exists()) {
outputimage.delete()
}
outputimage.createNewFile()
// 把图片转化为uri
imageuri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//如果系统版本大于7.0(Build.VERSION_CODES.N),本地真实路径uri不安全,会抛出异常。 FileProvider.getUriForFile可以将File对象转换为一个封装后的uri对象。
//getUriForFile接收三个参数,一个是context,另一个是任意字符串,第三个是File对象。FileProvider使用类似ContentProvider的机制进行保护,提高程序安全性。
FileProvider.getUriForFile(this, "com.example.camera.fileprovider", outputimage)
} else {
//如果系统版本低于7.0,调用Uri.fromFile将File对象转换为Uri对象,这个对象是本地真实路径
Uri.fromFile(outputimage)
}
//Intent的action进行指定,Intent的putExtra指定图片的输出地址,刚刚得到uri对象
val intent = Intent("android.media.action.IMAGE_CAPTURE")
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageuri)
//启动Activity,隐式的。调用之后返回到onActivityResult方法。
startActivityForResult(intent, takePhoto)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
// 这里takePhoto = 1,也就是requestCode = 1的时候就显示照片
takePhoto -> {
//将拍摄的照片显示出来,拍照成功的话使用BitmapFactory.decodeStream将图片解析为bitmap对象
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(imageuri))
//最后设置到ImageView当中,再加上照片旋转的处理。
image_view.setImageBitmap(rotateIfRequired(bitmap))
}
}
}
旋转图片
调用照相机程序去拍照有可能会在一些手机上发生照片旋转的情况。这是因为这些手机认为打开摄像头进行拍摄时手机就应该是横屏的,因此回到竖屏的情况下就会发生90度的旋转。为此,这里我们又加上了判断图片方向的代码:
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.recycle() // 将不再需要的Bitmap对象回收
return rotatedBitmap
}
这里就不细讲了,`rotateBitmap`方法用于旋转图片,`bitmap`对象就是图片,`degree`是需要旋转的度数;`rotateIfRequired`用于判断图片需要旋转多少度,把旋转过的图片旋转回来,返回的是一个图片对象`bitmap`。
注册ContentProvider
上文中还提到了ContentProvider
的保护机制,所以还需要在AndroidManifest
里面注册:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.cameraalbumtest">
<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.CameraAlbumTest">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!--注册ContentProvider-->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.cameraalbumtest.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() `方 法 中 的 **第二个参数一致**。 另外,这里还在<provider>标签的内部使用<meta-data>指定Uri的共享路径,并引用了一个`@xml/file_paths`资源。<br />这个资源现在还是不存在的,接下来要创建它。右击`res目录→New→Directory`,创建一个`xml`目录,接着右击`xml目录→New→File`,创建一个`file_paths.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="/" />
</paths>
拍摄图像的位置
相册中选择图片
添加按钮
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/takePhotoBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Take Photo" />
<!--添加图片按钮-->
<Button
android:id="@+id/fromAlbumBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="From Album" />
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
</LinearLayout>
添加主要逻辑
package com.example.cameraalbumtest
import android.app.Activity
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.media.ExifInterface
import android.net.Uri
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.MediaStore
import android.widget.Button
import android.widget.ImageView
import androidx.core.content.FileProvider
import java.io.File
class MainActivity : AppCompatActivity() {
val takePhoto = 1
// 新增选择图片求情码
// -----开始-----
val fromAlbum = 2
// -----结束------
lateinit var imageUri: Uri
lateinit var outputImage: File
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val takePhotoBtn: Button = findViewById(R.id.takePhotoBtn)
takePhotoBtn.setOnClickListener {
// 创建File对象,用于存储拍照后的图片
outputImage = File(externalCacheDir, "output_image.jpg")
if (outputImage.exists()) {
outputImage.delete()
}
outputImage.createNewFile()
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)
}
// 新增打卡相册选择图片事件
// --------------开始-------------
val fromAlbumBtn:Button = findViewById(R.id.fromAlbumBtn)
fromAlbumBtn.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)
val imageView: ImageView = findViewById(R.id.imageView)
when (requestCode) {
takePhoto -> {
if (resultCode == Activity.RESULT_OK) {// 将拍摄的照片显示出来
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(imageUri))
imageView.setImageBitmap(rotateIfRequired(bitmap))
}
}
// 新增显示图片
// -----------开始------------
fromAlbum -> {
if (resultCode == Activity.RESULT_OK && data != null) {
data.data?.let { uri ->
// 将选择的图片显示
val bitmap = getBitmapFromUri(uri)
imageView.setImageBitmap(bitmap)
}
}
}
// -----------结束------------
}
}
// 新增图片转化uri方法
// ---------------开始------------------
private fun getBitmapFromUri(uri: Uri) = contentResolver.openFileDescriptor(uri, "r")?.use {
BitmapFactory.decodeFileDescriptor(it.fileDescriptor)
}// --------------结束------------------
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.recycle() // 将不再需要的Bitmap对象回收
return rotatedBitmap
}
}