前言

想要美观的龙年头像框网上不是要VIP便是收费,找一圈还找不到自己想要的,索性自己开发一款能够生成恣意自己想要的头像框的软件

效果图

龙年大吉,是时分换一波龙年头像框了

龙年大吉,是时分换一波龙年头像框了

代码完成

布局代码

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">
    <RelativeLayout
        android:id="@+id/rl"
        android:layout_width="300dp"
        android:layout_height="300dp">
        <ImageView
            android:id="@+id/ivHead"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_centerInParent="true"
            android:scaleType="centerCrop"
            android:src="@drawable/a" />
        <ImageView
            android:id="@+id/ivHeadBorder"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="centerCrop"
            android:src="@drawable/a1" />
    </RelativeLayout>
    <Button
        android:id="@+id/btnHead"
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:background="@drawable/shape_bg"
        android:text="替换头像"
        android:textColor="@color/white" />
    <Button
        android:id="@+id/btnHeadBorder"
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:background="@drawable/shape_bg"
        android:text="替换头像框"
        android:textColor="@color/white" />
    <Button
        android:id="@+id/btnSave"
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:background="@drawable/shape_bg"
        android:text="保存头像"
        android:textColor="@color/white" />
</LinearLayout>

页面预览

龙年大吉,是时分换一波龙年头像框了

完成替换头像和头像框

首要替换头像和头像显现用到了Glide和头像挑选依靠库pictureselector

build.gradle.kts内增加如下依靠

dependencies {
    ...
    //glide
    implementation("com.github.bumptech.glide:glide:4.15.0")
    annotationProcessor("com.github.bumptech.glide:compiler:4.15.0")
    //图片挑选框架
    implementation("io.github.lucksiege:pictureselector:v2.7.3-rc08")
}

并且用到了viewBinding,增加viewBinding配置

android{
    viewBinding {
        enable = true
    }
}

替换头像和保存头像用到了摄影和文件存储权限,所以在AndroidManifest.xml中增加摄影权限和文件的写权限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><!--写权限-->
<uses-permission
        android:name="android.permission.CAMERA"
        tools:ignore="PermissionImpliesUnsupportedChromeOsHardware" /> <!-- 摄影权限 -->

替换头像和头像框点击事件

        //替换头像
        binding!!.btnHead.setOnClickListener {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                if (ContextCompat.checkSelfPermission(
                        this@MainActivity,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE
                    ) != PackageManager.PERMISSION_GRANTED
                ) {
                    //没有权限则请求权限
                    ActivityCompat.requestPermissions(
                        this@MainActivity,
                        arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
                        202
                    )
                } else {
                    //有权限直接履行
                    chooseHead()
                }
            } else {
                //小于6.0,不必请求权限,直接履行
                chooseHead()
            }
        }
        //替换头像框
        binding!!.btnHeadBorder.setOnClickListener {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                if (ContextCompat.checkSelfPermission(
                        this@MainActivity,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE
                    ) != PackageManager.PERMISSION_GRANTED
                ) {
                    //没有权限则请求权限
                    ActivityCompat.requestPermissions(
                        this@MainActivity,
                        arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
                        203
                    )
                } else {
                    //有权限直接履行
                    chooseHeadBorder()
                }
            } else {
                //小于6.0,不必请求权限,直接履行
                chooseHeadBorder()
            }
        }

请求权限回调

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when (requestCode) {
            201 -> if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                draw
            } else {
                Toast.makeText(
                    this,
                    "您拒绝了权限的请求,或许无法进行下面的操作哦~",
                    Toast.LENGTH_LONG
                ).show()
            }
            202 -> if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                chooseHead()
            } else {
                Toast.makeText(
                    this,
                    "您拒绝了权限的请求,或许无法进行下面的操作哦~",
                    Toast.LENGTH_LONG
                ).show()
            }
            203 -> if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                chooseHeadBorder()
            } else {
                Toast.makeText(
                    this,
                    "您拒绝了权限的请求,或许无法进行下面的操作哦~",
                    Toast.LENGTH_LONG
                ).show()
            }
            else -> {}
        }
    }

有权限直接调用挑选图片和摄影

    private fun chooseHead() {
        PictureSelectorUtils.ofImage(this@MainActivity, codeHead)
    }
    private fun chooseHeadBorder() {
        PictureSelectorUtils.ofImage(this@MainActivity, codeHeadBorder)
    }

PictureSelectorUtils挑选图片东西封装

package com.cwj.dragon.util
import android.app.Activity
import android.content.Intent
import com.cwj.dragon.R
import com.cwj.dragon.util.GlideEngine.Companion.createGlideEngine
import com.luck.picture.lib.PictureSelector
import com.luck.picture.lib.config.PictureConfig
import com.luck.picture.lib.config.PictureMimeType
/**
 * created by cwj on 2024-1-22
 * Description:
 */
object PictureSelectorUtils {
    fun ofImage(activity: Activity?, requestCode: Int) {
        PictureSelector.create(activity)
            .openGallery(PictureMimeType.ofImage())
            .imageEngine(createGlideEngine())
            .theme(R.style.PictureSelectorStyle)
            .selectionMode(PictureConfig.SINGLE)
            .enableCrop(false) //是否裁剪
            .isDragFrame(false) // 是否可拖动裁剪框(固定)
            .withAspectRatio(1, 1) // int 裁剪份额 如16:9 3:2 3:4 1:1 可自定义
            .isCamera(true) //是否显现摄影按钮 true or false
            .isGif(true) //是否显现gif图片 true or false
            .previewImage(true) // 是否可预览图片 true or false
            .forResult(requestCode)
    }
    fun forResult(resultCode: Int, data: Intent?): String? {
        if (resultCode == Activity.RESULT_OK) {
            // 图片、视频、音频挑选成果回调
            val selectList = PictureSelector.obtainMultipleResult(data)
            if (selectList != null && selectList.size > 0) {
                return selectList[0].path
            }
        }
        return null
    }
}

GlideEngine东西

package com.cwj.dragon.util
import android.content.Context
import android.graphics.Bitmap
import android.graphics.PointF
import android.graphics.drawable.Drawable
import android.view.View
import android.widget.ImageView
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.BitmapImageViewTarget
import com.bumptech.glide.request.target.ImageViewTarget
import com.cwj.dragon.R
import com.luck.picture.lib.engine.ImageEngine
import com.luck.picture.lib.listener.OnImageCompleteCallback
import com.luck.picture.lib.tools.MediaUtils
import com.luck.picture.lib.widget.longimage.ImageSource
import com.luck.picture.lib.widget.longimage.ImageViewState
import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView
/**
 * created by cwj on 2024-1-22
 * Description:
 */
class GlideEngine private constructor() : ImageEngine {
    /**
     * 加载图片
     *
     * @param context
     * @param url
     * @param imageView
     */
    override fun loadImage(context: Context, url: String, imageView: ImageView) {
        if (!ImageLoaderUtils.assertValidRequest(context)) {
            return
        }
        Glide.with(context)
            .load(url)
            .into(imageView)
    }
    /**
     * 加载网络图片适配长图计划
     * # 留意:此方法只有加载网络图片才会回调
     *
     * @param context
     * @param url
     * @param imageView
     * @param longImageView
     * @param callback      网络图片加载回调监听 {link after version 2.5.1 Please use the #OnImageCompleteCallback#}
     */
    override fun loadImage(
        context: Context, url: String,
        imageView: ImageView,
        longImageView: SubsamplingScaleImageView, callback: OnImageCompleteCallback
    ) {
        if (!ImageLoaderUtils.assertValidRequest(context)) {
            return
        }
        Glide.with(context)
            .asBitmap()
            .load(url)
            .into(object : ImageViewTarget<Bitmap?>(imageView) {
                override fun onLoadStarted(placeholder: Drawable?) {
                    super.onLoadStarted(placeholder)
                    callback.onShowLoading()
                }
                override fun onLoadFailed(errorDrawable: Drawable?) {
                    super.onLoadFailed(errorDrawable)
                    callback.onHideLoading()
                }
                override fun setResource(resource: Bitmap?) {
                    callback.onHideLoading()
                    if (resource != null) {
                        val eqLongImage = MediaUtils.isLongImg(
                            resource.width,
                            resource.height
                        )
                        longImageView.visibility = if (eqLongImage) View.VISIBLE else View.GONE
                        imageView.visibility = if (eqLongImage) View.GONE else View.VISIBLE
                        if (eqLongImage) {
                            // 加载长图
                            longImageView.isQuickScaleEnabled = true
                            longImageView.isZoomEnabled = true
                            longImageView.setDoubleTapZoomDuration(100)
                            longImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP)
                            longImageView.setDoubleTapZoomDpi(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER)
                            longImageView.setImage(
                                ImageSource.cachedBitmap(resource),
                                ImageViewState(0f, PointF(0f, 0f), 0)
                            )
                        } else {
                            // 普通图片
                            imageView.setImageBitmap(resource)
                        }
                    }
                }
            })
    }
    /**
     * 加载网络图片适配长图计划
     * # 留意:此方法只有加载网络图片才会回调
     *
     * @param context
     * @param url
     * @param imageView
     * @param longImageView
     * @ 已抛弃
     */
    @Deprecated("Deprecated in Java")
    override fun loadImage(
        context: Context, url: String,
        imageView: ImageView,
        longImageView: SubsamplingScaleImageView
    ) {
        if (!ImageLoaderUtils.assertValidRequest(context)) {
            return
        }
        Glide.with(context)
            .asBitmap()
            .load(url)
            .into(object : ImageViewTarget<Bitmap?>(imageView) {
                override fun setResource(resource: Bitmap?) {
                    if (resource != null) {
                        val eqLongImage = MediaUtils.isLongImg(
                            resource.width,
                            resource.height
                        )
                        longImageView.visibility = if (eqLongImage) View.VISIBLE else View.GONE
                        imageView.visibility = if (eqLongImage) View.GONE else View.VISIBLE
                        if (eqLongImage) {
                            // 加载长图
                            longImageView.isQuickScaleEnabled = true
                            longImageView.isZoomEnabled = true
                            longImageView.setDoubleTapZoomDuration(100)
                            longImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP)
                            longImageView.setDoubleTapZoomDpi(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER)
                            longImageView.setImage(
                                ImageSource.cachedBitmap(resource),
                                ImageViewState(0f, PointF(0f, 0f), 0)
                            )
                        } else {
                            // 普通图片
                            imageView.setImageBitmap(resource)
                        }
                    }
                }
            })
    }
    /**
     * 加载相册目录
     *
     * @param context   上下文
     * @param url       图片路径
     * @param imageView 承载图片ImageView
     */
    override fun loadFolderImage(context: Context, url: String, imageView: ImageView) {
        if (!ImageLoaderUtils.assertValidRequest(context)) {
            return
        }
        Glide.with(context)
            .asBitmap()
            .load(url)
            .override(180, 180)
            .centerCrop()
            .sizeMultiplier(0.5f)
            .placeholder(R.drawable.ic_launcher_background)
            .into(object : BitmapImageViewTarget(imageView) {
                override fun setResource(resource: Bitmap?) {
                    val circularBitmapDrawable =
                        RoundedBitmapDrawableFactory.create(context.resources, resource)
                    circularBitmapDrawable.cornerRadius = 8f
                    imageView.setImageDrawable(circularBitmapDrawable)
                }
            })
    }
    /**
     * 加载gif
     *
     * @param context   上下文
     * @param url       图片路径
     * @param imageView 承载图片ImageView
     */
    override fun loadAsGifImage(
        context: Context, url: String,
        imageView: ImageView
    ) {
        if (!ImageLoaderUtils.assertValidRequest(context)) {
            return
        }
        Glide.with(context)
            .asGif()
            .load(url)
            .into(imageView)
    }
    /**
     * 加载图片列表图片
     *
     * @param context   上下文
     * @param url       图片路径
     * @param imageView 承载图片ImageView
     */
    override fun loadGridImage(context: Context, url: String, imageView: ImageView) {
        if (!ImageLoaderUtils.assertValidRequest(context)) {
            return
        }
        Glide.with(context)
            .load(url)
            .override(200, 200)
            .centerCrop()
            .placeholder(R.drawable.ic_launcher_background)
            .into(imageView)
    }
    companion object {
        private var instance: GlideEngine? = null
        @JvmStatic
        fun createGlideEngine(): GlideEngine? {
            if (null == instance) {
                synchronized(GlideEngine::class.java) {
                    if (null == instance) {
                        instance = GlideEngine()
                    }
                }
            }
            return instance
        }
    }
}

ImageLoaderUtils东西

package com.cwj.dragon.util
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
/**
 * created by cwj on 2024-1-22
 * Description:
 */
object ImageLoaderUtils {
    fun assertValidRequest(context: Context?): Boolean {
        if (context is Activity) {
            return !isDestroy(context)
        } else if (context is ContextWrapper) {
            if (context.baseContext is Activity) {
                val activity = context.baseContext as Activity
                return !isDestroy(activity)
            }
        }
        return true
    }
    private fun isDestroy(activity: Activity?): Boolean {
        return if (activity == null) {
            true
        } else activity.isFinishing || activity.isDestroyed
    }
}

图片挑选成功后页面回显

    @Deprecated("Deprecated in Java")
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == codeHead && resultCode == RESULT_OK) {
            val head = PictureSelectorUtils.forResult(resultCode, data)
            if (head != null) {
                Glide.with(this).load(head).into(binding!!.ivHead)
            }
        } else if (requestCode == codeHeadBorder && resultCode == RESULT_OK) {
            val headBorder = PictureSelectorUtils.forResult(resultCode, data)
            if (headBorder != null) {
                Glide.with(this).load(headBorder).into(binding!!.ivHeadBorder)
            }
        }
    }

保存合成的带头像框的头像

保存图片权限判断和保存图片

        //保存头像
        binding!!.btnSave.setOnClickListener {
            if (ContextCompat.checkSelfPermission(
                    this@MainActivity,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                ActivityCompat.requestPermissions(
                    this@MainActivity,
                    arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
                    201
                )
            } else {
                draw
            }
        }
     private val draw: Unit
        get() {
            // 获取图片某布局
            binding!!.rl.isDrawingCacheEnabled = true
            binding!!.rl.buildDrawingCache()
            mHandler.postDelayed({
                // 要在运转在子线程中
                val bmp = binding!!.rl.drawingCache // 获取图片
                savePicture(bmp) // 保存图片
                binding!!.rl.destroyDrawingCache() // 保存过后开释资源
            }, 100)
        }

保存合成头像图片到本地相册

    private fun savePicture(bm: Bitmap) {
        val file = createImageFile()
        //从头写入文件
        try {
            // 写入文件
            val fos = FileOutputStream(file)
            //默许jpg
            bm.compress(Bitmap.CompressFormat.PNG, 100, fos)
            fos.flush()
            fos.close()
            bm.recycle()
            sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)))
            Toast.makeText(this, "保存成功,请到相册中检查!", Toast.LENGTH_LONG).show()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
    companion object {
        fun createImageFile(): File {
            val dir = File(
                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
                "Pic"
            )
            if (!dir.exists()) {
                val isSuccess = dir.mkdirs()
                Log.i("Pic", "文件夹创立状况--->$isSuccess")
            }
            return File(dir.path + File.separator + "img_" + System.currentTimeMillis() + ".png")
        }
    }

假如不想相册中挑选头像框还能够点击头像框随机切换提供的头像框

private val headBorder = intArrayOf(R.drawable.a1, R.drawable.a2)
private var i = 0
...
        //点击头像框更改头像框
        binding!!.ivHeadBorder.setOnClickListener {
            i++
            if (headBorder.size == i) {
                i = 0
            }
            binding!!.ivHeadBorder.setImageResource(headBorder[i])
        }

现在项目中只提供两张和龙相关的头像框,如有需要请下载头像框。假如想要更多随机切换头像框请运转项目自行网上寻觅头像框相关图片并增加多张头像框图片到drawable-xxhdpi文件下并在项目代码中增加引证即可。

龙年大吉,是时分换一波龙年头像框了

龙年大吉,是时分换一波龙年头像框了

项目地址

gitee.com/juer2017/dr…

软件下载体验地址:

gitee.com/juer2017/dr…