android实现音乐跳动效果

效果图

dancer.gif

实现

整体的流程图如下

在这里插入图片描述

上面主要步骤分为3个
1、计算宽度能放下多少列的音频块。
2、计算每一列中音频块的个数
3、绘制音频块

1、计算宽度能放下多少列的音频块。
设置音频块的宽度为danceWidth,音频块横向之间的间距为danceGap,那么可以算出能放的列数:

/**
         * 先计算当前宽度能够放下多少个音频块
         */
        val widthNum = (getAvailableWith() / (danceGap + danceWidth)).toInt()

  /**
     * 获取可以用的宽度
     */
    private fun getAvailableWith() = mCanvasWidth - paddingLeft - paddingRight

2、计算每一列中音频块的个数
在算出横向能放置多少音频块后,遍历横,然后绘制列中的音频块,列中的音频块的个数跟音频的高低相关,这里实现方式是通过Visualizer这个类然后获取到mRawAudioBytes数组,

 mVisualizer.setDataCaptureListener(new Visualizer.OnDataCaptureListener() {
            @Override
            public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes,
                                              int samplingRate) {
                BaseVisualizer.this.mRawAudioBytes = bytes;
                invalidate();
            }

            @Override
            public void onFftDataCapture(Visualizer visualizer, byte[] bytes,
                                         int samplingRate) {
            }
        }, Visualizer.getMaxCaptureRate() / 2, true, false);

这里设置的获取的mRawAudioBytes数组的大小是128,数组的区间范围[-128,127],计算列的时候这里做了两个比较重要的操作,第一个是怎么把mRawAudioBytes数组的值与音频的个数做映射,第二个是怎么取mRawAudioBytes数组的值。

 /**
         * 先计算当前宽度能够放下多少个音频块
         */
        val widthNum = (getAvailableWith() / (danceGap + danceWidth)).toInt()
        Log.d(
            TAG,
            "widthNum $widthNum"
        )
        /**
         * 算出横向能放多少后,进行绘制
         */

        /**
         * 绘制的时候用于标记开始绘制的位置
         */
        var lastDanceRight = paddingLeft.toFloat()
        if (widthNum > 0 && mRawAudioBytes != null && mRawAudioBytes.isNotEmpty())
            for (i in 0 until widthNum) {
                //先算出当前高度,然后再算这个高度能放下多少个音频块
                val num = (getAvailableHeight() / (danceHeight + danceGap)).toInt()
                val index = (mRawAudioBytes.size) * (i.toFloat() / widthNum)
                val b = (mRawAudioBytes[index.toInt()] + 128).toFloat() / 255f
                var heightNum =
                    (b * num).toInt()
                if (heightNum < miniNum) {
                    heightNum = miniNum
                }
                if (heightNum > maxNum) {
                    heightNum = maxNum
                }
                //拿到最顶部的高度
                var lastHeight = mCanvasHeight - paddingStart.toFloat()
                Log.d(
                    TAG,
                    "heightNum $heightNum lastHeight $lastHeight lastDanceRight $lastDanceRight ${mRawAudioBytes[i]} $num $b $index"
                )
                lastHeight = drawItem(heightNum, lastDanceRight, lastHeight, canvas)
                lastDanceRight += danceWidth + danceGap
            }

上面做了两个映射,首先可能有0~n横,但是mRawAudioBytes大小是128,遍历横的时候对下标进行一个映射,保证获得的值是均匀的,

/**
通过这个映射得到index
*/
val index = (mRawAudioBytes.size) * (i.toFloat() / widthNum)

第二个映射,是得到了代表音频大小的mRawAudioBytes数组,现在要把这里面的值跟列的高度做一个映射,值越大高度越高,音频块就越多。

val num = (getAvailableHeight() / (danceHeight + danceGap)).toInt()
val b = (mRawAudioBytes[index.toInt()] + 128).toFloat() / 255f
var heightNum =(b * num).toInt()

上面是先得到列最多能展示多少音频块,再根据mRawAudioBytes的值来算出当前列展示多少个音频块。这一步也叫归一化,区间映射。

3、绘制每一个音频块

    private fun drawItem(
        heightNum: Int,
        lastDanceRight: Float,
        lastHeight: Float,
        canvas: Canvas?
    ): Float {
        var lastHeight1 = lastHeight
        for (j in 0 until heightNum) {
            mDanceRect.set(
                lastDanceRight,
                lastHeight1 - danceHeight,
                lastDanceRight + danceWidth,
                lastHeight1
            )
            mPaint.shader = null
            if (j >= heightNum - shaderNum) {
                val backGradient = LinearGradient(
                    lastDanceRight,
                    lastHeight1 - danceHeight,
                    lastDanceRight + danceWidth,
                    lastHeight1,
                    intArrayOf(colorStart, colorCenter, colorEnd),
                    null,
                    Shader.TileMode.CLAMP
                )
                mPaint.shader = backGradient
            }
            canvas?.drawRoundRect(mDanceRect, 8f, 8f, mPaint)
            lastHeight1 -= (danceHeight + danceGap)
        }
        return lastHeight1
    }

就是根据高度来绘制rectangle,算出一列能绘制多少个音频块,每一个音频块是一个rectangle,然后绘制rectangle,为了效果更好,判断上面的音频块加上渐变。

github地址

欢迎点赞收藏,后期会优化
https://github.com/hankinghu/AudioVisulizer

使用方法

<com.masoudss.lib.DanceView
              android:id="@+id/danceView"
              android:layout_width="320dp"
              android:layout_height="300dp"
              android:layout_gravity="center"
              app:color_center="@color/red"
              app:color_end="@color/white"
              app:color_start="@color/yellow"
              app:dance_color="@color/yellow"
              app:dance_corner_radius="2dp"
              app:dance_gap="2dp"
              app:max_dance_num="30"
              app:min_dance_num="2"
              app:shader_num="3" />
  • shader_num 顶部加渐变的个数
  • color_end 渐变尾部颜色
  • color_start 渐变开头颜色
  • color_center 渐变中间颜色
  • min_dance_num 每一列中最少显示的个数
  • max_dance_num 每一列中最大显示的个数
  • dance_gap 每一个音频格之间的间距
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,919评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,567评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,316评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,294评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,318评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,245评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,120评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,964评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,376评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,592评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,764评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,460评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,070评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,697评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,846评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,819评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,665评论 2 354