关于Android点击头像显示窗口调用相机与相册
主要有七步:
点击事件,窗口初始化,点击回调事件,对照片裁剪,裁剪照片保存,声明FileProvider,编写FileProvider的xml文件
1.点击事件
这里只简单的设置了一个图片,还有他的点击事件
fun onClick(v:View){
when(v.id){
R.id.main_imagV -> show()
}
}
2.窗口的初始化
fun show() {
val dialog = Dialog(this)
val inflate = layoutInflater.inflate(R.layout.activity_second2, null)
val xiangji:Button = inflate.findViewById(R.id.xiangji)
val xiangce:Button = inflate.findViewById(R.id.xiangce)
xiangji.setOnClickListener {
dialog.dismiss() //点击按钮窗口消失
REQUEST_CODE = 1 //事件请求CODE为1
outputImage = File(getExternalFilesDir(Environment.DIRECTORY_PICTURES),"最近一次拍照图片".jpg")
if (outputImage.exists())
outputImage.delete()
outputImage.createNewFile()
imageUri = if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
FileProvider.getUriForFile(this,"com.example.kotlin_demo.MainActivity",outputImage)
}else Uri.fromFile(outputImage)
intent1 = Intent("android.media.action.IMAGE_CAPTURE")
intent1.putExtra(MediaStore.EXTRA_OUTPUT,imageUri)
startActivityForResult(intent1,REQUEST_CODE)
}
xiangce.setOnClickListener{
dialog.dismiss()
REQUEST_CODE = 2
intent1 = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent1.addCategory(Intent.CATEGORY_OPENABLE)
intent1.type = "image/*"
startActivityForResult(intent1,REQUEST_CODE)
}
dialog.setContentView(inflate)
dialog.window?.apply {
setGravity(Gravity.BOTTOM) //设置Dialog从窗体底部弹出
setContentView(inflate)
attributes.height = 500
attributes.width = 1000
}
dialog.show()
}
窗口用的是Dialog
窗口的布局文件R.layout.activity_second2很简单,两个按钮,中间的一条横线为TextView
<LinearLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
<Button
android:id="@+id/xiangce"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="相册"
android:alpha="0.6"
android:background="#FFF"
android:textColor="#555555"
android:textStyle="bold"
android:textSize="15sp"
android:gravity="center"/>
<TextView
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#9e9e9e"/>
<Button
android:id="@+id/xiangji"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:textColor="#555555"
android:textStyle="bold"
android:background="#FFF"
android:text="打开相机拍照"
android:alpha="0.6"
android:textSize="15sp"
android:gravity="center"
/>
</LinearLayout>
新建文件路径为Environment.DIRECTORY_PICTURES,该地址为手机Android/data/“包名”/files/Pictures,把图片命名固定是为了只保存一张最近拍照的图片。
特别注意:区分Android7.0上下文件Uri的获取方式
低于7.0直接用Uri的fromFile()方法
7.0开始直接使用本地真实路径的Uri不安全,会有异常抛出,使用一种特殊的ContentProvider,FileProvider
要使用FileProvider还有两步要做 :1、第一步 声明FileProvider 2i、第二步 编写XML文件 这两步会写在最后
3.点击事件回调函数onActivityResult
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when(requestCode){
1->{ //相机事件
if (resultCode==Activity.RESULT_OK){
photoClip(imageUri)
}
}
2->{ //相册事件
if (resultCode==Activity.RESULT_OK&&data!==null){
photoClip(data.data) //Intent.data为被选取照片的uri地址
}
}
3->{
if (resultCode==Activity.RESULT_OK){
val bitmap = data?.extras?.get("data") as Bitmap
val path = saveImage("touxiang_"+System.currentTimeMillis(), bitmap)
path?.let {println("保存成功,图片地址为:$path")}
Glide.with(this).load(bitmap).override(250,250).into(main_imagV)
} else startActivityForResult(intent1,REQUEST_CODE)
}
}
}
调用第五步的方法saveImage保存图片,第一个为图片名
图片命名为"touxiang_"+System.currentTimeMillis(),保存所有头像图片,防止名字重复
4.对照片进行裁剪
private fun photoClip(uri: Uri?) {
// 调用系统中自带的图片裁剪
val intent = Intent("com.android.camera.action.CROP")
intent.flags =
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
intent.setDataAndType(uri, "image/*")
// 下面这个crop=true是设置在开启的Intent中设置显示的VIEW可裁剪
intent.putExtra("crop", "true")
// aspectX aspectY 是宽高的比例
intent.putExtra("aspectX", 1)
intent.putExtra("aspectY", 1)
// outputX outputY 是裁剪图片宽高
intent.putExtra("outputX", 200)
intent.putExtra("outputY", 200)
intent.putExtra("return-data", true)
startActivityForResult(intent, 3)
}
5.对裁剪后的头像照片的保存
fun saveImage(name:String, bmp: Bitmap): String? {
val appDir = File (getExternalFilesDir(Environment.DIRECTORY_PICTURES).toString())
val fileName = name + ".jpg"
val file = File(appDir, fileName);
try {
val fos = FileOutputStream(file)
//将bitmap图片压缩输出到输出流对应uri地址
bmp.compress(Bitmap.CompressFormat.PNG,100, fos)
fos.flush()
fos.close()
return file.getAbsolutePath()
} catch (e:Exception) {
e.printStackTrace()
}
return null
}
6.在manifests文件中声明FileProvider
为什么要声明呢?
因为FileProvider是ContentProvider子类
注意需要设置一个meta-data,里面指向一个xml文件。
name值固定,android:authorities必须与刚刚FileProvider.getUriForFile()方法第二个参数一致
<provider
android:authorities="com.example.kotlin_demo.MainActivity"
android:name="androidx.core.content.FileProvider"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
7.编写XML文件
为什么要写这么个xml文件?
因为要使用content://uri替代file://uri,那么,content://的uri如何定义呢?总不能使用文件路径。
所以,需要一个虚拟的路径对文件路径进行映射,所以需要编写个xml文件,通过path以及xml节点确定可访问的目录,通过name属性来映射真实的文件路径。name随便填,path的/表示对整个sd卡进行共享
<paths>
<external-path
name = "photo"
path = "/"/>
</paths>
终于到最精彩的图片展示环节
作用域存储:
Android系统对SD卡的使用做了很大的限制。从Android 10开始,每个应用程序只能有权在自己的外置存储空间关联目录下读取和创建文件,获取该关联目录的代码是:context.getExternalFilesDir()。关联目录对应的路径大致如下:
/storage/emulated/0/Android/data/<包名>/files
这个目录中的文件会被计入到应用程序的占用空间当中,同时也会随着应用程序的卸载而被删除。
访问其他目录该怎么办呢?比如读取手机相册中的图片,或者向手机相册中添加一张图片。为此,Android系统针对文件类型进行了分类,图片、音频、视频这三类文件将可以通过MediaStore API来进行访问,而其他类型的文件则需要使用系统的文件选择器来进行访问。
在作用域存储当中,我们只能借助MediaStore API获取到图片的Uri,示例代码如下:
val cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, "${MediaStore.MediaColumns.DATE_ADDED} desc")
if (cursor != null) {
while (cursor.moveToNext()) {
val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
val uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
println("image uri is $uri")
}
cursor.close()
}
将网络上的图片存储到手机相册当中
fun writeInputStreamToAlbum(inputStream: InputStream, displayName: String, mimeType: String) {
val values = ContentValues()
values.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
} else {
values.put(MediaStore.MediaColumns.DATA, "${Environment.getExternalStorageDirectory().path}/${Environment.DIRECTORY_DCIM}/$displayName")
}
val bis = BufferedInputStream(inputStream)
val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
if (uri != null) {
val outputStream = contentResolver.openOutputStream(uri)
if (outputStream != null) {
val bos = BufferedOutputStream(outputStream)
val buffer = ByteArray(1024)
var bytes = bis.read(buffer)
while (bytes >= 0) {
bos.write(buffer, 0 , bytes)
bos.flush()
bytes = bis.read(buffer)
}
bos.close()
}
}
bis.close()
}