IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> [安卓]实现苹果实现的效果之 流光溢彩背景 -> 正文阅读

[移动开发][安卓]实现苹果实现的效果之 流光溢彩背景

前言

本篇文章,代码部分均为Kotlin
如你所见,本专栏的名称叫做《把苹果抄的裤衩子都不剩》
目的就是实现applemusic中一些炫酷的效果。
这篇博文写于2021-12-17。本文发布时(2022-3-9),AppleMusic已经实现了更真实的流动效果。
本专栏的所有文章,均提供完整的代码,但仅仅为简易实现,不提供商业水平的版本。
涉及到逆向相关的部分,不过多深入!


毕竟在看文章的各位,每一个都可能是在未来和我竞争的对手!

正文

老模板,上代码之前,先给大家放一下我实现的效果。
由于AppleMusic在最近的版本中已经加强了效果,我就不上它的演示了。
请添加图片描述

思路

刚看到肯定是一点思路都没有的,甚至不知道这个效果应该叫什么名字(有人可能会问:不是已经知道它叫流光溢彩了吗?别着急,我们往下看)。
既然没有思路,那我们可以通过某些手段看看它是如何实现的。
身为一个合格的安卓程序员,开发和逆向必须都 了如指掌 的。
打开jadX,拖入AppleMusic的安装包,打开一看发现它的类名被混淆的亲妈都不认识了。遇到这种情况,肯定是选择 直接放弃 百度了~
于是我翻遍了github和gitee,终于发现了一个具有同样的音乐播放器软件<椒*音乐>(不为它做宣传)。
就是因为这个播放器把背景效果叫做流光溢彩,所有才有了这篇文章的命名。
经过漫长的源码定位后,发现了一些蛛丝马迹,椒*播放器中存在一个类,内部有一个Log,它的TAG设置的是"FlowingLightView",翻译过来正好是流光溢彩。版权保护原因,我补贴出相关的代码,我只介绍实现的方式。
实际上就是把一张图片切分成几个部分,然后对他进行降低分辨率,再提高分辨率的处理,最后让这些图片在canvas上做无规律旋转。为了避免重复感和撕裂感,还会对边缘进行混色处理,并且做一次随机的mesh。
这种写法会创建多个bitmap对象,稍稍了解过安卓开发的朋友都知道bitmap是很吃内存的,操作起来也很废性能。所以我实现的时候,选择了简化某些操作,但还让它的观感和AppleMusic相同。

实战

根据上述的分析,我们可以把过程分为以下几步:

  1. 分割图片
  2. 将图片缩小
  3. mesh处理,色调处理
  4. 将图片放大
  5. 高斯模糊
  6. 让图片旋转

我再把过程修改一下:

  1. 将图片缩小
  2. 高斯模糊
  3. mesh处理
  4. 将图片放大
  5. mesh处理
  6. 将图片放大
  7. 高斯模糊
  8. 处理色调

图片缩小是为了让后面高斯模糊的时,半径影响更大。
mesh,色调处理是为了去掉重复感和撕裂感。
图片放大,让其精度更高。
再次模糊是去除放大后颜色落差过大形成的波纹。


实现这些效果,我们可以撰写一个处理bitmap的工具类。得益于kotlin强大的 拓展函数 ,我们可以把功能直接加在Bitmap类中

fun Bitmap.zoom(newHeight: Float, newWidth: Float): Bitmap {
    val matrix = Matrix()
    val scaleWidth = newWidth / width
    val scaleHeight = newHeight / height
    matrix.postScale(scaleWidth, scaleHeight)
    return Bitmap.createBitmap(
        this, 0, 0, width,
        height, matrix, true
    )
}

fun Bitmap.blur(context: Context, radius: Float, ty: Float): Bitmap {
    val bitmap = Bitmap.createScaledBitmap(
        this,
        (width / ty).toInt(), (height / ty).toInt(), false
    ) //先缩放图片,增加模糊速度
    val rs = RenderScript.create(context)
    val input = Allocation.createFromBitmap(
        rs, bitmap, Allocation.MipmapControl.MIPMAP_NONE,
        Allocation.USAGE_SCRIPT
    )
    val output = Allocation.createTyped(rs, input.type)
    val script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs))
    script.setRadius(25F.coerceAtLeast(radius))
    script.setInput(input)
    script.forEach(output)
    output.copyTo(bitmap)
    rs.destroy()
    return bitmap
}

fun Bitmap.brightness(): Float {
    val bmp = zoom(3F, 3F) //转3*3大小的位图
    val pixel = bmp.getPixel(1, 1) //取中间位置的像素
    val r = (pixel shr 16 and 0xff) / 255.0f
    val g = (pixel shr 8 and 0xff) / 255.0f
    val b = (pixel and 0xff) / 255.0f
    return 0.299f * r + 0.587f * g + 0.114f * b //计算灰阶
}

fun Bitmap.drawColor(color: Int): Bitmap {
    val newBit = Bitmap.createBitmap(this)
    val canvas = Canvas(newBit)
    canvas.drawColor(color)
    return newBit
}
fun Bitmap.handleImageEffect(saturation: Float): Bitmap {
    val saturationMatrix = ColorMatrix()
    saturationMatrix.setSaturation(saturation)
    val imageMatrix = ColorMatrix()
    imageMatrix.postConcat(saturationMatrix)
    val paint = Paint()
    paint.colorFilter = ColorMatrixColorFilter(imageMatrix)
    val bitmap = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(bitmap)
    canvas.drawBitmap(this, 0F, 0F, paint)
    return bitmap
}
fun Bitmap.mesh(floats: FloatArray): Bitmap {
    val fArr2 = FloatArray(72)
    var i = 0
    while (i <= 5) {
        var i2 = 0
        var i3 = 5
        while (i2 <= i3) {
            val i4 = i * 12 + i2 * 2
            val i5 = i4 + 1
            fArr2[i4] = floats[i4] * width.toFloat()
            fArr2[i5] = floats[i5] * height.toFloat()
            i2++
            i3 = 5
        }
        i++
    }
    val newBit = Bitmap.createBitmap(this)
    val canvas = Canvas(newBit)
    canvas.drawBitmapMesh(newBit, 5, 5, fArr2, 0, null, 0, null)
    return newBit
}

然后我们实现对图片的链式处理

fun processBitmap(bitmap: Bitmap):Bitmap{
//这个mesh的参数是偷的AppleMusic中的一个 
//AppleMusic中有5个Mesh参数,每次随机使用一个
        val floats = floatArrayOf(-0.2351f, -0.0967f, 0.2135f, -0.1414f, 0.9221f, -0.0908f, 0.9221f, -0.0685f, 1.3027f, 0.0253f, 1.2351f, 0.1786f, -0.3768f, 0.1851f, 0.2f, 0.2f, 0.6615f, 0.3146f, 0.9543f, 0.0f, 0.6969f, 0.1911f, 1.0f, 0.2f, 0.0f, 0.4f, 0.2f, 0.4f, 0.0776f, 0.2318f, 0.6f, 0.4f, 0.6615f, 0.3851f, 1.0f, 0.4f, 0.0f, 0.6f, 0.1291f, 0.6f, 0.4f, 0.6f, 0.4f, 0.4304f, 0.4264f, 0.5792f, 1.2029f, 0.8188f, -0.1192f, 1.0f, 0.6f, 0.8f, 0.4264f, 0.8104f, 0.6f, 0.8f, 0.8f, 0.8f, 1.0f, 0.8f, 0.0f, 1.0f, 0.0776f, 1.0283f, 0.4f, 1.0f, 0.6f, 1.0f, 0.8f, 1.0f, 1.1868f, 1.0283f)
        val tmp = bitmap.zoom(150f, (bitmap.getHeight() * 150 / bitmap.getWidth()).toFloat())
            .blur(context, 25F, 1F)
            .mesh(floats)
            .zoom(1000F, 1000F)
            .mesh(floats)
            .blur(context, 12F, 1F)
            .handleImageEffect(1.8f)
        val float = tmp.brightness()
        return when {
            float > 0.8 -> 
                tmp.drawColor(Color.parseColor("#50000000"))
            float < 0.2 -> 
                tmp.drawColor(Color.parseColor("#50FFFFFF"))
            else -> 
                tmp
        }
    }

其实不需要用旋转的效果,只要让这个处理后的Bitmap动起来就可以了。
所以我选择交给开源库KenBurnsView

新建一个类FlowingLightView

package simon.music.widget

import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import android.util.AttributeSet
import com.flaviofaria.kenburnsview.KenBurnsView
import simon.tool.*
import com.flaviofaria.kenburnsview.RandomTransitionGenerator

/**
 * 如果这段代码能够正常工作,那么请记住作者是Simon。
 * 如果不能正常工作,那我也不知道是谁写的。
 */
class FlowingLightView @JvmOverloads constructor(
    context: Context?,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : KenBurnsView(context, attrs, defStyle) {
    init {
        val randomTransitionGenerator = RandomTransitionGenerator()
        randomTransitionGenerator.setTransitionDuration(3400)
        setTransitionGenerator(randomTransitionGenerator)
    }
    fun setFlowingLight(bitmap: Bitmap) {
        val floats = floatArrayOf(-0.2351f, -0.0967f, 0.2135f, -0.1414f, 0.9221f, -0.0908f, 0.9221f, -0.0685f, 1.3027f, 0.0253f, 1.2351f, 0.1786f, -0.3768f, 0.1851f, 0.2f, 0.2f, 0.6615f, 0.3146f, 0.9543f, 0.0f, 0.6969f, 0.1911f, 1.0f, 0.2f, 0.0f, 0.4f, 0.2f, 0.4f, 0.0776f, 0.2318f, 0.6f, 0.4f, 0.6615f, 0.3851f, 1.0f, 0.4f, 0.0f, 0.6f, 0.1291f, 0.6f, 0.4f, 0.6f, 0.4f, 0.4304f, 0.4264f, 0.5792f, 1.2029f, 0.8188f, -0.1192f, 1.0f, 0.6f, 0.8f, 0.4264f, 0.8104f, 0.6f, 0.8f, 0.8f, 0.8f, 1.0f, 0.8f, 0.0f, 1.0f, 0.0776f, 1.0283f, 0.4f, 1.0f, 0.6f, 1.0f, 0.8f, 1.0f, 1.1868f, 1.0283f)
        var tmp = bitmap.zoom(150f, (bitmap.getHeight() * 150 / bitmap.getWidth()).toFloat())
            .blur(context, 25F, 1F)
            .mesh(floats)
            .zoom(1000F, 1000F)
            .mesh(floats)
            .blur(context, 12F, 1F)
            .handleImageEffect(1.8f)
        val float = tmp.brightness()
        when {
            float > 0.8 -> {//判断图片大体颜色是深色还是浅色
                tmp = tmp.drawColor(Color.parseColor("#50000000"))
                setImageBitmap(tmp)//浅色就加入黑色遮罩
            }
            float < 0.2 -> {
                tmp = tmp.drawColor(Color.parseColor("#50FFFFFF"))
                setImageBitmap(tmp)//深色就加入白色遮罩
            }
            else -> {
                setImageBitmap(tmp)
            }
        }
    }
}

这样,通过最简单的方式,实现了和AppleMusic完全可以媲美的效果。
而且内存占用远比AppleMusic低!

结语

其实椒*播放器很不人道,我在实现效果后的几天里,通过不断的定位,找到了AppleMusic实现的控件。
经过对比,它和椒*的代码有90%的相似度!就是说,椒*通过逆向的手段直接盗版了AppleMusic的源码,而且没有进行标注!
逆向分析 对家 的实现是很正常的手段,我能理解。但是身为一个合格的开发者,就算知道了实现的流程,我们也应该自己做一个不一样的,我在此呼吁大家抵制这种行为。
其实分析椒*播放器的源码还是很有意思的。
它的播放器中,这个背景控件是addView实现的,所以在xml中根本定位不到。我的jadX开启了根据开源库包名剔除无关代码的功能,所以默认剔除了目录在androidx.*的代码。
初期逆向椒*播放器时找不到代码就是因为它把控件混淆到了androidx.*下,也算是一直巧妙的保护措施吧,值得借鉴。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-03-12 17:41:04  更:2022-03-12 17:43:08 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年10日历 -2024/10/27 14:33:43-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码