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

前言

想要好看的龙年头像框网上不是要VIP就是收费,找一圈还找不到自己想要的,索性自己开发一款可以生成任意自己想要的头像框的软件。

效果图

代码实现

布局代码

ini 复制代码
<?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内添加如下依赖

scss 复制代码
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配置

ini 复制代码
android{
    viewBinding {
        enable = true
    }
}

更换头像和保存头像用到了拍照和文件存储权限,所以在AndroidManifest.xml中添加拍照权限和文件的写权限

xml 复制代码
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><!--写权限-->
<uses-permission
        android:name="android.permission.CAMERA"
        tools:ignore="PermissionImpliesUnsupportedChromeOsHardware" /> <!-- 拍照权限 -->

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

less 复制代码
        //更换头像
        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()
            }
        }

申请权限回调

kotlin 复制代码
    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 -> {}
        }
    }

有权限直接调用选择图片和拍照

kotlin 复制代码
    private fun chooseHead() {
        PictureSelectorUtils.ofImage(this@MainActivity, codeHead)
    }

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

PictureSelectorUtils选择图片工具封装

kotlin 复制代码
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工具

kotlin 复制代码
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工具

kotlin 复制代码
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
    }
}

图片选择成功后页面回显

kotlin 复制代码
    @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)
            }
        }
    }

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

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

less 复制代码
        //保存头像
        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)
        }

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

kotlin 复制代码
    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")
        }
    }

如果不想相册中选择头像框还可以点击头像框随机切换提供的头像框

ini 复制代码
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...

相关推荐
CYRUS STUDIO24 分钟前
ARM64汇编寻址、汇编指令、指令编码方式
android·汇编·arm开发·arm·arm64
weixin_449310841 小时前
高效集成:聚水潭采购数据同步到MySQL
android·数据库·mysql
Zender Han1 小时前
Flutter自定义矩形进度条实现详解
android·flutter·ios
白乐天_n3 小时前
adb:Android调试桥
android·adb
姑苏风7 小时前
《Kotlin实战》-附录
android·开发语言·kotlin
数据猎手小k11 小时前
AndroidLab:一个系统化的Android代理框架,包含操作环境和可复现的基准测试,支持大型语言模型和多模态模型。
android·人工智能·机器学习·语言模型
你的小1011 小时前
JavaWeb项目-----博客系统
android
风和先行12 小时前
adb 命令查看设备存储占用情况
android·adb
AaVictory.12 小时前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
似霰13 小时前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder