提取音乐封面图片做主色渐变背景

该文章介绍了一个Android工具类,用于从View或Drawable中提取颜色,并根据深色或亮色模式生成渐变背景。工具类包含了将Bitmap转换为颜色Swatch,以及根据颜色生成渐变Drawable的功能,适配Android系统的深色模式设置。
摘要由CSDN通过智能技术生成
深色模式提取
深色模式
亮色模式提取
亮色模式

目录

工具类

获取封面

使用工具类

设置封面

依赖

Activity示例

XML示例


工具类

object ColorCoverUtils {

    private const val TAG = "ColorUtils"

    // UI定制化参数 此例子为包含夜间模式
    private const val START_LIGHT_S = 0.10f
    private const val CENTER_LIGHT_S = 0.10f
    private const val END_LIGHT_S = 0.40f

    private const val START_LIGHT_L = 0.90f
    private const val CENTER_LIGHT_L = 0.90f
    private const val END_LIGHT_L = 0.80f

    private const val START_NIGHT_S = 0.10f
    private const val CENTER_NIGHT_S = 0.10f
    private const val END_NIGHT_S = 0.40f

    private const val START_NIGHT_L = 0.10f
    private const val CENTER_NIGHT_L = 0.10f
    private const val END_NIGHT_L = 0.40f

    private fun viewToBitmap(view: View?): Bitmap? {
        if (view == null) {
            return null
        }
        val width: Int = view.width
        val height: Int = view.height
        if (width <= 0 || height <= 0) {
            return null
        }
        val bitmap = Bitmap.createBitmap(view.width, height, Bitmap.Config.ARGB_8888)
        val canvas = Canvas(bitmap)
        view.draw(canvas)
        return bitmap
    }

    fun drawableToBitmap(drawable: Drawable?): Bitmap? {
        if (drawable == null) {
            return null
        }
        val width = drawable.intrinsicWidth
        val height = drawable.intrinsicHeight
        if (width <= 0 || height <= 0) {
            return null
        }
        val config =
            if (drawable.opacity != PixelFormat.OPAQUE) Bitmap.Config.ARGB_8888 else Bitmap.Config.RGB_565
        val bitmap = Bitmap.createBitmap(width, height, config)
        val canvas = Canvas(bitmap)
        drawable.setBounds(0, 0, width, height)
        drawable.draw(canvas)
        return bitmap
    }

    private fun getMainSwatch(bitmap: Bitmap?): Palette.Swatch? {
        if (bitmap == null) {
            return null
        }
        val p: Palette = Palette.from(bitmap).generate()
        return if (p.dominantSwatch == null) {
            null
        } else p.dominantSwatch
    }

    fun getGradientCardColor(view: View, isNight: Boolean): GradientDrawable? {
        val bitmap = viewToBitmap(view) ?: return null
        return getGradientCardColor(bitmap, isNight)
    }


    /**
     * 通过view的颜色提取组成 三原色 组成渐变
     * @bitmap 待提取的图片
     * @isNight 是否是夜间模式
     */
    fun getGradientCardColor(bitmap: Bitmap, isNight: Boolean): GradientDrawable? {
        val swatch = getMainSwatch(bitmap)
        return if (swatch != null) {

            i(TAG, "main " + Integer.toHexString(swatch.rgb))
            val hsl = swatch.hsl
            i(TAG, "hsl " + hsl[0] + "," + hsl[1] + "," + hsl[2])

            val startS = if (isNight) START_NIGHT_S else START_LIGHT_S
            val startL = if (isNight) START_NIGHT_L else START_LIGHT_L
            val startColor: Int = hslToNewColor(hsl, startS, startL)
            i(TAG, "startColor:${Integer.toHexString(startColor)}")

            val centerS = if (isNight) CENTER_NIGHT_S else CENTER_LIGHT_S
            val centerL = if (isNight) CENTER_NIGHT_L else CENTER_LIGHT_L
            val centerColor: Int = hslToNewColor(hsl, centerS, centerL)
            i(TAG, "centerColor:${Integer.toHexString(centerColor)}")

            val endS = if (isNight) END_NIGHT_S else END_LIGHT_S
            val endL = if (isNight) END_NIGHT_L else END_LIGHT_L
            val endColor: Int = hslToNewColor(hsl, endS, endL)
            i(TAG, "endColor:${Integer.toHexString(endColor)}")

            GradientDrawable(
                GradientDrawable.Orientation.TOP_BOTTOM,
                intArrayOf(startColor, centerColor, endColor)
            )
        } else {
            e(TAG, "failed get music color")
            null
        }
    }


    private fun hslToNewColor(hsl: FloatArray, newS: Float, newL: Float): Int {
        hsl[1] = newS
        hsl[2] = newL
        e(TAG, "new hsl " + hsl[0] + "," + hsl[1] + "," + hsl[2])
        return ColorUtils.HSLToColor(hsl)
    }

}

获取封面

 /**
     * Android13 获取封面
     */
    private fun getAlbumImage(path: String): Bitmap? {

        try {
            var data: Bitmap?
            val mmr = MediaMetadataRetriever()
            mmr.setDataSource(path)
            val byteArray = mmr.embeddedPicture
            data = if (byteArray != null) BitmapFactory.decodeByteArray(
                byteArray,
                0,
                byteArray.size
            ) else null
            return data
        } catch (e: Exception) {
            e(TAG, "getAlbumImage:$e")
        }
        return null

    }

    private fun getAlbumImageUri(uri: Uri): Bitmap? {

        try {
            var data: Bitmap?
            val mmr = MediaMetadataRetriever()
            mmr.setDataSource(this, uri)
            val byteArray = mmr.embeddedPicture
            data = if (byteArray != null) BitmapFactory.decodeByteArray(
                byteArray,
                0,
                byteArray.size
            ) else null
            return data
        } catch (e: Exception) {
            e(TAG, "getAlbumImage:$e")
        }
        return null

    }

使用工具类

    private fun setMainColor(album: Bitmap?) {
        // 提取封面颜色
        val mainColor = if (album == null) {
            // 如果封面为空则获取封面默认view的颜色
            ColorCoverUtils.getGradientCardColor(
                mBind.ivCover,
                isNightMode(this)
            )
        } else {
            ColorCoverUtils.getGradientCardColor(
                album,
                isNightMode(this)
            )
        }
        e(TAG, "updateSongInfo mainColor:${mainColor}")
        mBind.main.background = mainColor
    }

设置封面

 try {
// android 13无效
//            val album: Bitmap =
//                contentResolver.loadThumbnail(
//                    albumUri,
//                    Size(640, 480),
//                    null
//                )
            data.path?.let {
                val album: Bitmap? = getAlbumImage(it)
                album?.run {
                    mBind.ivCover.post {
                        mBind.ivCover.setImageBitmap(album)
                    }
                    setMainColor(album)
                    return
                }
            } ?: let {
                data.uri?.let {
                    val album: Bitmap? = getAlbumImageUri(it)
                    SkyLog.e(TAG, "updateSongInfo getAlbumImageUri :${album}")

                    album?.run {
                        mBind.ivCover.post {
                            mBind.ivCover.setImageBitmap(album)
                        }
                        setMainColor(album)
                        return
                    }
                }
            }

        } catch (e: FileNotFoundException) {
            e(TAG, "updateSongInfo:$e")
            mBind.ivCover.setImageResource(R.drawable.bg_default_cover)
            setMainColor(null)
        }

        mBind.ivCover.setImageResource(R.drawable.bg_default_cover)
        setMainColor(null)

依赖

implementation 'androidx.palette:palette:1.0.0'

Activity示例

package com.rex.functiondemo

import android.Manifest
import android.content.Context
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import com.rex.functiondemo.R
import com.rex.functiondemo.common.ColorCoverUtils
import com.rex.functiondemo.databinding.ActivityColorCoverBinding
import com.rex.functiondemo.databinding.ActivityMainBinding
import java.io.FileNotFoundException

class ColorCoverActivity : AppCompatActivity() {

    companion object {
        const val TAG = "ColorCoverActivity"
    }
    lateinit var mBind: ActivityColorCoverBinding
    private var mGetContentLauncher: ActivityResultLauncher<String>? = null
    private var mGetPermissionLauncher: ActivityResultLauncher<Array<String>>? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBind = ActivityColorCoverBinding.inflate(layoutInflater)
        setContentView(mBind.root)
        initUtils()
        getPermission()
    }

    private fun initUtils() {
        mGetContentLauncher = registerForActivityResult(
            ActivityResultContracts.GetContent()
        ) { result: Uri? ->
            result?.run {
                updateCover(this)
            }
        }

        mGetPermissionLauncher =
            registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
                it?.forEach { (t, u) ->
                    if (Manifest.permission.READ_EXTERNAL_STORAGE == t && u) {
                        successPermission()
                    }

                }
//                if (it == true && !isResultSuccess) {
//                    successPermission()
//                    isResultSuccess = true
//                }
            }
        mBind.ivCover.setOnClickListener {
            mGetContentLauncher?.launch("audio/*")
        }
    }

    private fun successPermission() {
        Log.i(TAG, "successPermission:${Build.VERSION.SDK_INT}")
    }

    private fun getPermission() {
        mGetPermissionLauncher?.run {

            Log.i(TAG, "checkPermissionAll Build.VERSION.SDK_INT:${Build.VERSION.SDK_INT}")
//            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
//                Log.i(TAG, "checkPermissionAll READ_MEDIA_AUDIO")
//                mGetPermissionLauncher?.launch(
//                    arrayOf(
//                        Manifest.permission.READ_MEDIA_AUDIO,
//                        Manifest.permission.POST_NOTIFICATIONS
//                    )
//                )
//            } else {
                Log.i(TAG, "checkPermissionAll READ_EXTERNAL_STORAGE")
                mGetPermissionLauncher?.launch(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE))
//            }

        }
    }

    private fun updateCover(data: Uri) {
        Log.d(TAG,"updateCover $data")

        try {
// android 13无效
//            val album: Bitmap =
//                contentResolver.loadThumbnail(
//                    albumUri,
//                    Size(640, 480),
//                    null
//                )
            data.path?.let {
                val album: Bitmap? = getAlbumImage(it)
                album?.run {
                    mBind.ivCover.post {
                        mBind.ivCover.setImageBitmap(album)
                    }
                    setMainColor(album)
                    return
                }
            } ?: let {
                data.let {
                    val album: Bitmap? = getAlbumImageUri(it)
                    album?.run {
                        mBind.ivCover.post {
                            mBind.ivCover.setImageBitmap(album)
                        }
                        setMainColor(album)
                        return
                    }
                }
            }

        } catch (e: FileNotFoundException) {
            mBind.ivCover.setImageResource(R.drawable.ic_launcher_foreground)
            setMainColor(null)
        }

        mBind.ivCover.setImageResource(R.drawable.ic_launcher_foreground)
        setMainColor(null)
    }

    private fun setMainColor(album: Bitmap?) {
        Log.d(TAG,"setMainColor $album")

        // 提取封面颜色
        val mainColor = if (album == null) {
            ColorCoverUtils.getGradientCardColor(
                mBind.ivCover,
                isNightMode(this)
            )
        } else {
            ColorCoverUtils.getGradientCardColor(
                album,
                isNightMode(this)
            )
        }
        mBind.main.background = mainColor
    }

    /**
     * 判断是否是深色模式
     * @param context
     * @return
     */
    private fun isNightMode(context: Context): Boolean {
        try {
            return (context.resources.configuration.uiMode and
                    Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
        } catch (e: Exception) {
            Log.e(TAG,"isNightMode $e")
        }
        return false
    }

    /**
     * Android13 获取封面
     */
    private fun getAlbumImage(path: String): Bitmap? {

        try {
            var data: Bitmap?
            val mmr = MediaMetadataRetriever()
            mmr.setDataSource(path)
            val byteArray = mmr.embeddedPicture
            data = if (byteArray != null) BitmapFactory.decodeByteArray(
                byteArray,
                0,
                byteArray.size
            ) else null
            return data
        } catch (e: Exception) {
            Log.e(TAG,"getAlbumImage $e")
        }
        return null

    }

    private fun getAlbumImageUri(uri: Uri): Bitmap? {

        try {
            var data: Bitmap?
            val mmr = MediaMetadataRetriever()
            mmr.setDataSource(this, uri)
            val byteArray = mmr.embeddedPicture
            data = if (byteArray != null) BitmapFactory.decodeByteArray(
                byteArray,
                0,
                byteArray.size
            ) else null
            return data
        } catch (e: Exception) {
           Log.e(TAG,"getAlbumImageUri $e")
        }
        return null

    }


}

XML示例

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/ivCover"
        android:layout_width="336dp"
        android:layout_height="336dp"
        android:background="@color/teal_200"
        android:layout_centerInParent="true"
        android:layout_marginTop="84.5dp"
        android:elevation="60dp"
        android:scaleType="centerCrop" />

</RelativeLayout>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值