Android—显示窗口调用相机与相册

关于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()
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值