Android仿網易雲鯨雲音效動效

尋夢新聞LINE@每日推播熱門推薦文章,趣聞不漏接❤️

加入LINE好友

Android仿網易雲鯨雲音效動效 時尚 第1張

黑客技術

點擊右側關注,了解黑客的世界!

Android仿網易雲鯨雲音效動效 時尚 第3張

Java開發進階

點擊右側關注,掌握進階之路!

Android仿網易雲鯨雲音效動效 時尚 第5張

Python開發

點擊右側關注,探討技術話題!

作者丨Tyhj

來源丨安卓巴士Android開發者門戶

網易雲音樂出了一個叫鯨雲音效東西,效果還不錯,播放界面還帶了動效,這個就比較炫酷了,感覺比較有意思,所以也想自己做一個,其中一個我覺得比較好看的效果如下(動圖的來源也比較有意思,後面會講)

具體思路

Android仿網易雲鯨雲音效動效 時尚 第7張

首先自定義布局是了解的,可能會用到surfaceView去繪制,整個動畫可以分為四個部分,第一個是旋轉的圖片,這個好說;第二個是運動並且透明度漸變的三角形,這個畫畫也簡單;第三個是根據音樂變化而變化的一個曲線吧,這個可能比較難,我也沒接觸過,不過可以試試看,第四個是模糊的背景,這個簡單。

具體做到

做到模糊的背景

這個倒是簡單,之前也用過一個模糊背景的工具還不錯,不過存在一個問題,我是打算自定義一個surfaceView,給surfaceView畫一個背景倒是不難,也遇到兩個問題

1.怎麼將圖片以類似自動裁剪居中的方式畫上去,這個想想其實簡單,取得畫布的大小和bitmap的大小,滿足一邊進行縮放,裁剪掉多餘部分就好了

/***裁剪圖片**@paramrectBitmap*@paramrectSurface*/publicstaticvoidcenterCrop(RectrectBitmap,RectrectSurface){intverticalTimes=rectBitmap.height()/rectSurface.height();inthorizontalTimes=rectBitmap.width()/rectSurface.width();if(verticalTimes>horizontalTimes){rectBitmap.left=0;rectBitmap.right=rectBitmap.right;rectBitmap.top=(rectBitmap.height()-(rectSurface.height()*rectBitmap.width()/rectSurface.width()))/2;rectBitmap.bottom=rectBitmap.bottom-rectBitmap.top;}else{rectBitmap.top=0;rectBitmap.bottom=rectBitmap.bottom;rectBitmap.left=(rectBitmap.width()-(rectSurface.width()*rectBitmap.height()/rectSurface.height()))/2;rectBitmap.right=rectBitmap.right-rectBitmap.left;}}

2.由於我後面畫三角形必須得不停地刷新,背景需要重復繪制,感覺有點浪費資源,看了一下局部刷新什麼的感覺沒什麼用,所以就直接先設置為父布局的普通的背景好了,再將surfaceView設置為透明

@OverridepublicvoidsurfaceCreated(SurfaceHoldersurfaceHolder){setZOrderOnTop(true);getHolder().setFormat(PixelFormat.TRANSLUCENT);}

做到旋轉的圖片

這個更簡單,為了方便也是直接使用一個ImageView,通過自帶的視圖裁剪工具剪裁為圓形,然後通過屬性動畫來旋轉

設置一直旋轉的屬性動畫

objectAnimator=ObjectAnimator.ofFloat(ivShowPic,"rotation",0f,360f);objectAnimator.setDuration(20*1000);objectAnimator.setRepeatMode(ValueAnimator.RESTART);objectAnimator.setInterpolator(newLinearInterpolator());objectAnimator.setRepeatCount(-1);objectAnimator.start();

視圖裁剪

/***設置裁剪為圓形**@paramview*@parampading這個是設置間距是長或寬的幾分之一*/@RequiresApi(api=Build.VERSION_CODES.LOLLIPOP)publicstaticvoidsetCircleShape(Viewview,finalintpading){view.setClipToOutline(true);view.setOutlineProvider(newViewOutlineProvider(){@OverridepublicvoidgetOutline(Viewview,Outlineoutline){intmargin=Math.min(view.getWidth(),view.getHeight())/pading;outline.setOval(margin,margin,view.getWidth()-margin,view.getHeight()-margin);}});}

做到運動的三角形

為了保證性能,這個就得使用surfaceView來做了;大體思路就是隨機生成一些三角形,三角形速度大小一樣,方向隨機,從圓中心向外移動,移動過程將透明度減小到零

三角形有速度不過速度大小都一樣就先不用管,有速度方向用角度來代替,也好計算運動後的位置,有三個頂點坐標。

所以三角形的初步定義

publicclassTriangle{publicPointtopPoint1,topPoint2,topPoint3;publicintmoveAngle;publicTriangle(PointtopPoint1,PointtopPoint2,PointtopPoint3){this.topPoint1=topPoint1;this.topPoint2=topPoint2;this.topPoint3=topPoint3;moveAngle=getMoveAngel();}}

隨機生成了三角形

簡單的方法,就是先指定一個坐標區域比如xy從-50到50的這個矩形坐標區域內,隨機取點,如果構成三角形就為一個隨機三角形,到時候移到中心處只需要x和y坐標各加長寬的一半就好了,方向也是-180度到180度取隨機數,便於到時候用斜率計算移動後的位置

畫三角形

我們先清空畫布,然後可以隨機生成一些三角形,保存所有生成的三角形到一個集合里面,然後設定一個速度,根據每個三角形的方向來計算距離上一次刷新移動到了哪個位置,通過位置計算與中心點的距離來設置透明度,然後畫上去

//三角形移動速度privatedoublemoveSpeed=0.4;//刷新時間privatestaticintrefreshTime=20;//添加兩次三角形的間隔privatestaticintaddTriangleInterval=100;//每次添加的數量限制privatestaticintaddTriangleOnece=2;//總三角形數量privateintallTriangleCount=100;mCanvas=mSurfaceHolder.lockCanvas();mCanvas.drawColor(0,PorterDuff.Mode.CLEAR);manageTriangle((int)(refreshTime*moveSpeed));for(Triangletriangle:triangleList){drawTriangle(mCanvas,triangle,mPaintColor);}mSurfaceHolder.unlockCanvasAndPost(mCanvas);Thread.sleep(refreshTime);

具體代碼看項目源碼,這里注意需要設定幾個值來調整動畫效果到最佳,做的過程中也有出現一些很魔性的動畫,很有意思

然後發現,surfaceView的動畫會出現在imageView的上面,雖然我把imageView的高度調了一下還是沒效果,發現是之前設置surfaceView透明的時候setZOrderOnTop(true)導致的問題;但是如果不設置surfaceView又會遮擋背景,的確是沒好辦法解決

其實可以簡單點,判斷三角形的移動距離小於imageView的時候設置全透明就好了,做出來大概是這樣的效果:

Android仿網易雲鯨雲音效動效 時尚 第8張

視頻效果:http://oy5r220jg.bkt.clouddn.com/record__1107012332_1.mp4

其實還是有一點問題的,可以把Imageview的旋轉在surfaceView里面做到,這個應該三角形的出現可以會自然一點,其他解決辦法倒是暫時沒想到

優化

為了讓三角形出現自然一點,可以把Imageview的旋轉在surfaceView里面做到,但是好像不好做,因為還得裁剪圖片和控制旋轉,相比imageView來做到我覺得稍微有點麻煩了;那還可以不設置setZOrderOnTop(true),這樣背景變成了黑色,還需要畫一個背景上去;

那麼兩種方法比較一下,其實模糊化以後的背景質量非常小(圖片都模糊了肯定小呀),遠遠小於要旋轉的那張圖片的質量,所以繪制surfaceView背景可能比較好;

獲取控件的截圖

由於我的surfaceView不是寬高全屏的,只是中間一部分,而且給surfaceView設置的背景圖片肯定要和整個布局的背景重合,可以先獲取背景視圖的截圖,然後在這里面裁剪出surfaceView所在區域

//啟用DrawingCache並創建位圖iv_bg.setDrawingCacheEnabled(true);iv_bg.buildDrawingCache();//獲取bitmapBitmapbitmap2=Bitmap.createBitmap(iv_bg.getDrawingCache());//裁剪bitmap2=Bitmap.createBitmap(bitmap2,0,jinyunView.getTop(),jinyunView.getWidth(),jinyunView.getHeight());//bitmap2傳給surfaceViewjinyunView.setBitmapBg(bitmap2);//關閉DrawingCacheiv_bg.setDrawingCacheEnabled(false);

為什麼要先獲取背景視圖的截圖,而不直接用那個模糊化的圖片呢,因為模糊化的圖片尺寸超級小,顯示的時候被放大了,而且可能還被裁剪了(背景用的imageView顯示的),為保證裁剪後和背景重合還得做很多圖象處理,還是直接獲取截圖來的簡單

動態獲取顏色

關於三角形的顏色,其實也是要根據背景來設定的

Material Design鼓勵使用動態顏色,新的Palette支持庫可以提取圖片中的一部分顏色來設置你的UI的樣式來使界面顏色互相搭配以提供一種沉浸式體驗。提取出來的調色板(palette)包括突出的和柔和的色調Vibrant (有活力)Vibrant dark(有活力 暗色)Vibrant light(有活力 亮色)Muted (柔和)Muted dark(柔和 暗色)Muted light(柔和 亮色)

就是可以從bitmap中獲取幾種特殊的顏色,注意獲取到的swatche可能為空的

//Palette的部分Palettepalette=Palette.generate(bitmap);Palette.Swatchswatche=null;//獲取不同風格的顏色,swatche=palette.getVibrantSwatch();swatche=palette.getLightVibrantSwatch();swatche=palette.getDarkVibrantSwatch();swatche=palette.getMutedSwatch();//我用這個和網易雲接近,其他顏色也都挺漂亮swatche=palette.getLightMutedSwatch();swatche=palette.getDarkMutedSwatch();swatche=palette.getVibrantSwatch();//獲取顏色intcolor=swatche.getRgb();

視頻效果:http://lc-fgtnb2h8.cn-n1.lcfile.com/7f08b2eea6a4039cf453.mp4換個顏色:http://lc-fgtnb2h8.cn-n1.lcfile.com/45e70109d2cbc9b7371b.mp4

改變圖片的亮度

但是發現一個問題,背景顏色太亮了,我選擇palette.getLightMutedSwatch()是最亮的顏色,還是會被背景干擾,這個設置最上層的布局背景為半透明,發現我surfaceView也跟著被半透明覆蓋了呀,如果只覆蓋背景的話,surfaceView繪制的背景是從作為背景的ImageVIew截取的圖片,會和背景顏色不一樣的,只能從背景ImageView入手,還真的有改變亮度的辦法,不僅可以改變亮度,還可以改變色相飽和度

ColorMatrixcolorMatrix=newColorMatrix();//改變圖片亮度colorMatrix.setScale(0.5f,0.5f,0.5f,1);ColorMatrixColorFiltercolorFilter=newColorMatrixColorFilter(colorMatrix);iv_bg.setColorFilter(colorFilter);

改變了亮度後對動態獲取顏色會有影響,亮色的可能獲取不到了,獲取顏色應該提前獲取

開始畫線

仔細看了一下,先畫圍繞這個圓畫很多點,隔一段一個點,然後把點用曲線圈起來就ok了,動的時候就是設置一個上下移動的距離,一個點變成兩個,兩個點先連線,然後同一側的點重新連成曲線,感覺是是這樣的,先試試

圍繞圓畫點

這個就是直線和圓的交點問題,從-180度到180度,每間隔一個角度,取斜率計算交點,差不多是這個意思

y=(Math.sin(angle)*circleR);x=(Math.cos(angle)*circleR);

Android仿網易雲鯨雲音效動效 時尚 第10張

畫出來一看,這是什麼情況,根本不均勻,沒道理呀,原來是Math.sin(angle)Math.cos(angle)里面的值指的是弧度,不是角度,所以轉換一下

y=(Math.sin(Math.toRadians(angle))*circleR);x=(Math.cos(Math.toRadians(angle))*circleR);

畫貝塞爾曲線

我先用二階貝塞爾曲線把相鄰的點連了起來,中間的點取的是兩個點的圓弧中間的點,反正看起來是一個圓

Pathpath=newPath();path.moveTo(point.x,point.y);//畫二階貝塞爾曲線path.quadTo(bezierPoint.x,bezierPoint.y,next.x,next.y);canvas.drawPath(path,paint);

原理如下圖

處理點的跳動

到了最後一步,讓點分裂成兩個分別上下移動後,再次將同一邊的連成曲線並將移動後的上下兩個點連線,移動距離先取隨機數,效果好了再看音頻相關東西,這個有點難度,我嘗試了很多次,都不是我想要的結果

看起來都失敗了,感覺這個移動距離不能取隨機數,最後一個看起來比較像是手動輸入了一組均勻的數據,並且是直接畫的直線

獲取音頻信息

感覺模擬數據不行,還是先看看怎麼獲取音頻信息;獲取音頻信息比較簡單

1.使用MediaPlayer播放傳入的音樂,並拿到mediaPlayerId2.使用Visualizer類拿到拿到MediaPlayer播放中的音頻數據(wave/fft)3.將數據用自定義控件展現出來使用Visualizer需要錄音的動態權限, 如果播放sd卡音頻需要STORAGE權限

<uses-permissionandroid:name="android.permission.RECORD_AUDIO"/><uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

播放音樂

MediaPlayermediaPlayer=MediaPlayer.create(this,R.raw.music_wheresilove);mediaPlayer.setLooping(true);mediaPlayer.setOnPreparedListener(newMediaPlayer.OnPreparedListener(){@OverridepublicvoidonPrepared(MediaPlayermediaPlayer){mediaPlayer.start();}});

Visualizer回調

Visualizer.OnDataCaptureListener 有2個回調,一個用於顯示FFT數據,展示不同頻率的振幅,另一個用於顯示聲音的波形圖

privateVisualizer.OnDataCaptureListenerdataCaptureListener=newVisualizer.OnDataCaptureListener(){@OverridepublicvoidonWaveFormDataCapture(Visualizervisualizer,finalbyte[]waveform,intsamplingRate){//到waveform為波形圖數據}@OverridepublicvoidonFftDataCapture(Visualizervisualizer,finalbyte[]fft,intsamplingRate){//FFT數據,展示不同頻率的振幅}};

Visualizer 有兩個比較重要的參數設置可視化數據的數據大小 範圍[Visualizer.getCaptureSizeRange()[0]~Visualizer.getCaptureSizeRange()1]設置可視化數據的采集頻率 範圍[0~Visualizer.getMaxCaptureRate()]

visualizer=newVisualizer(mediaPlayer.getAudioSessionId());//采樣的最大值intcaptureSize=Visualizer.getCaptureSizeRange[1]();//采樣的頻率intcaptureRate=Visualizer.getMaxCaptureRate()*3/4;visualizer.setCaptureSize(captureSize);visualizer.setDataCaptureListener(dataCaptureListener,captureRate,true,true);visualizer.setScalingMode(Visualizer.SCALING_MODE_NORMALIZED);visualizer.setEnabled(true);

有一個很有意思的地方,如果audioSessionId設置為零,就直接獲取系統的音頻,這個很有意思,連蒙帶猜搞出來的

visualizer=newVisualizer(0);

這樣紙我們就拿到了兩組數據,波形圖和頻譜圖,很顯然頻譜圖是展示不同頻率的振幅的,一般情況下只有少部分頻率會變動,所以我選擇波形圖。

拿到的波形圖是一個byte數組,里面也是類似每個點的振幅,我們把數組里的數據作為高度畫一條線,排成一排正常畫出來

//畫音頻線privatevoiddrawAudioLine(Canvascanvas){if(mPoints==null||mPoints.length<mBytes.length*4){mPoints=newfloat[mBytes.length*4];}for(inti=1;i<pointSize;i++){if(mBytes[i]<0){mBytes[i]=127;}mPoints[i*4]=getWidth()*i/pointSize;mPoints[i*4+1]=getHeight()/2;mPoints[i*4+2]=getWidth()*i/pointSize;mPoints[i*4+3]=2+getHeight()/2-mBytes[i];}canvas.drawLines(mPoints,mPaint);}

效果是這樣紙,用另一個頻譜圖也差不多,就是變化的區域有點少

這樣紙的話,那是不是我把它繞圓一圈,然後在按相反方向繞一圈,同樣跳動的兩個點連線,然後隨便畫畫曲線是不是就ok啦;做完就發現里面的值太大了,都看不出來是個圓了,那就都減去一點高度什麼的,調整一下大小;然後這次就先畫一個三次貝塞爾曲線吧,畫出來跟跟屎一樣,這個曲線是真的難畫呀,而且畫的慢,看起來不是很流暢;我再次嘗試用簡單的方法畫

折線的頂點時候用圓角,並沒有什麼亂用

mPaint.setStrokeJoin(Paint.Join.ROUND);

設置path中的連接處有個角度,看起來接近了一些,不過還是差很遠

CornerPathEffectcornerPathEffect=newCornerPathEffect(130);mPaint.setPathEffect(cornerPathEffect);

視頻效果:http://lc-fgtnb2h8.cn-n1.lcfile.com/fada1f97f943dd6e944d.mp4

其實可以看出來做法是沒有問題的,但是必須先對數據進行處理才能得到想要的效果,但是具體怎麼處理這個的確需要不斷嘗試;如果處理好可以做出更多更好看的效果;

其他效果:http://lc-hfysfg0s.cn-n1.lcfile.com/19962ada548649c692ed.mp4

上面的視頻效果在github的之前的提交版本里,有興趣可以找找,現在在不斷嘗試新的效果,有找到比較好的,會更新上來;有誰搞出炫酷的效果,希望大家不吝賜教

有想法的同學記得告訴我呀,使用前需要先播放音樂哦,聲音不能設置太小

項目地址:

https://github.com/tyhjh/Jinyuneffect

推薦↓↓↓

Android仿網易雲鯨雲音效動效 時尚 第14張

👉16個技術公眾號】都在這里!

涵蓋:工程師大咖、源碼共讀、工程師共讀、數據結構與算法、黑客技術和網路安全、大數據科技、編程前端、Java、Python、Web編程開發、Android、iOS開發、Linux、數據庫研發、幽默工程師等。

Android仿網易雲鯨雲音效動效 時尚 第15張

萬水千山總是情,點個 「在看」 行不行

About 尋夢園
尋夢園是台灣最大的聊天室及交友社群網站。 致力於發展能夠讓會員們彼此互動、盡情分享自我的平台。 擁有數百間不同的聊天室 ,讓您隨時隨地都能找到志同道合的好友!