目录
工具类
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>