Android仿百度地圖小度語音助手的貝塞爾曲線動畫

廢話不多說掀亥,看下面的動圖,和百度的還是有點點差別妥色,我也不修改了搪花,很簡單,我實在是沒有多余的時間嘹害,還要學(xué)習(xí)其他的東西鳍侣,累啊(復(fù)雜的動態(tài)View,可以使用SurfaceView吼拥,效率更高,我這里就簡單使用View了)

效果圖

image.png

仔細(xì)觀察一下百度那個動畫线衫,其實是由三層曲線組成的凿可;每層曲線又是由三個貝塞爾曲線組成的。所以一層一層疊加,加上顏色的漸變枯跑,就可以繪制出來類似于山峰的曲線惨驶;最后,動態(tài)改變曲線的高度就可以達(dá)到那種效果(你也可以加上聲音的波動值敛助,那就看起來更加逼真了)

一個草圖(見笑了):

??
image.png

源碼

package com.example.helang.volumewave;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Shader;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.animation.DecelerateInterpolator;

import java.util.Random;


/**
 * 仿百度的語音助手--波浪動畫控件
 */
public class VolumeWaveView extends View {
    private static final String TAG = "VolumeWaveView";
    private static final int HEIGHT = 400;//整個控件的高度

    private static final int HEIGHT1 = 60;//第一層曲線的高度
    private static final int HEIGHT2 = 40;//第二層曲線的高度
    private static final int HEIGHT3 = 50;//第三層曲線的高度

    private int h1 = 0,h2 = 0, h3 = 0,h4 = 0,h5 = 0;

    private int range = 0;//波動的幅度,你可以動態(tài)改變這個值粗卜,比如麥克風(fēng)錄入的音量的高低

    private Path path;
    private Paint paint1,paint2,paint3,paint4;

    private LinearGradient linearGradient1,linearGradient2,linearGradient3,linearGradient4;//四種漸變色

    private ValueAnimator animator1,animator2,animator3,animator4,animator5;//五種動畫


    public VolumeWaveView(Context context) {
        this(context,null);
    }

    public VolumeWaveView(Context context, @Nullable AttributeSet attrs) {
        this(context,attrs,0);
    }

    public VolumeWaveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs,defStyleAttr);
        initPaint();
        startAnimation();
    }

    /**
     * 初始化畫筆
     */
    private void initPaint(){
        path = new Path();

        paint1 = new Paint();
        paint1.setStyle(Paint.Style.FILL);
        paint1.setAntiAlias(true);//抗鋸齒
        //漸變色1
        linearGradient1 = new LinearGradient(0, 0, 0, HEIGHT1,
                Color.parseColor("#e652a6d2"), Color.parseColor("#e652d5a1"), Shader.TileMode.MIRROR);
        paint1.setShader(linearGradient1);

        paint2 = new Paint();
        paint2.setAntiAlias(true);//抗鋸齒
        paint2.setStyle(Paint.Style.FILL);
        //漸變色2
        linearGradient2 = new LinearGradient(0, 0, 0, HEIGHT2,
                Color.parseColor("#e68952d5"), Color.parseColor("#e6525dd5"), Shader.TileMode.MIRROR);
        paint2.setShader(linearGradient2);


        paint3 = new Paint();
        paint3.setAntiAlias(true);//抗鋸齒
        paint3.setStyle(Paint.Style.FILL);
        //漸變色3
        linearGradient3 = new LinearGradient(0, 0, 0, HEIGHT3,
                Color.parseColor("#e66852d5"), Color.parseColor("#e651b9d2"), Shader.TileMode.MIRROR);
        paint3.setShader(linearGradient3);


        paint4 = new Paint();
        paint4.setAntiAlias(true);//抗鋸齒
        paint4.setStyle(Paint.Style.FILL);
        //漸變色4
        linearGradient4 = new LinearGradient(0, 0, 0, HEIGHT2,
                Color.parseColor("#e6d5527e"), Color.parseColor("#e6bf52d5"), Shader.TileMode.MIRROR);
        paint4.setShader(linearGradient4);

    }


    /**
     * draw方法中不要創(chuàng)建大量對象,盡量復(fù)用對象
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawLayer3(canvas);
        drawLayer2(canvas);
        drawLayer1(canvas);

    }

    /**
     * 繪制第一層
     * @param canvas
     */
    private void drawLayer1(Canvas canvas){
        drawCurve(path,canvas,paint1,getWidth()/5,getWidth()/3,h1);
        drawCurve(path,canvas,paint1,getWidth()/3+getWidth()/5,getWidth()/3,h2);
    }

    /**
     * 繪制第二層
     * @param canvas
     */
    private void drawLayer2(Canvas canvas){
        drawCurve(path,canvas,paint2,0,getWidth()/2,h3);
        drawCurve(path,canvas,paint4,getWidth()/2-10,getWidth()/2,h4);

    }

    /**
     * 繪制第三層
     * @param canvas
     */
    private void drawLayer3(Canvas canvas){
        drawCurve(path,canvas,paint3,getWidth()/4,getWidth()/2,h5);
    }


    /**
     * 畫貝塞爾曲線
     * @param path
     * @param canvas
     * @param x 橫向起點的位置(用于擺放曲線的左右的位置)
     * @param width 曲線的整個寬度
     * @param height 曲線的高度
     */
    private void drawCurve(Path path,Canvas canvas,Paint paint,int x,int width,int height){
        path.reset();
        /*因為這個弧形(類似一個山峰的形狀)
         * 其實就是三個貝塞爾曲線組成纳击;
         * 而每個貝塞爾曲線需要三個點续扔,三個點連接起來也就是兩部分構(gòu)成;
         * 所以焕数,這三個貝塞爾曲線就是由六部分組成了(A纱昧,B,C堡赔,D识脆,E,F(xiàn)善已,G)灼捂,
         * 所以這里就平均分一下,建議用筆在紙上畫一下换团,就曉得了**/
        int subWidth = width/6;//每小部分的寬度
        path.moveTo(x,HEIGHT);//起點 A
        path.quadTo(x+subWidth,HEIGHT-height,x+subWidth*2,HEIGHT-height*2);//B - C

        path.lineTo(x+subWidth*2,HEIGHT-height*2);//C
        path.quadTo(x+subWidth*3,HEIGHT-height*3,x+subWidth*4,HEIGHT-height*2);//D - E

        path.lineTo(x+subWidth*4,HEIGHT-height*2);// E
        path.quadTo(x+subWidth*5,HEIGHT-height,x+subWidth*6,HEIGHT);//F - G

        canvas.drawPath(path,paint);
    }

    /**
     * 添加屬性動畫,每一個動畫的變化范圍和周期都不一樣悉稠,這樣錯開的效果才好看點
     */
    public void startAnimation() {
        Random random = new Random();
        range = random.nextInt(100)%(100-10+1) + 10;//波動的幅度,模擬動態(tài)音量輸入啥寇,你可以自己設(shè)置

        animator1 = ValueAnimator.ofInt(0,HEIGHT1,0);
        animator1.setDuration(1400);
        animator1.setInterpolator(new DecelerateInterpolator());
        //無限循環(huán)
        animator1.setRepeatCount(ValueAnimator.INFINITE);
        animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                h1 = (int) animation.getAnimatedValue();
                invalidate();

            }
        });
        animator1.start();

        animator2 = ValueAnimator.ofInt(0,HEIGHT1,0);
        animator2.setDuration(1700);
        animator2.setInterpolator(new DecelerateInterpolator());
        //無限循環(huán)
        animator2.setRepeatCount(ValueAnimator.INFINITE);
        animator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                h2 = (int) animation.getAnimatedValue();
                invalidate();

            }
        });
        animator2.start();



        animator3 = ValueAnimator.ofInt(0,HEIGHT2,0);
        animator3.setDuration(1600);
        animator3.setInterpolator(new DecelerateInterpolator());
        //無限循環(huán)
        animator3.setRepeatCount(ValueAnimator.INFINITE);
        animator3.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                h3 = (int) animation.getAnimatedValue();
                invalidate();

            }
        });
        animator3.start();


        animator4 = ValueAnimator.ofInt(0,HEIGHT2,0);
        animator4.setDuration(1300);
        animator4.setInterpolator(new DecelerateInterpolator());
        //無限循環(huán)
        animator4.setRepeatCount(ValueAnimator.INFINITE);
        animator4.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                h4 = (int) animation.getAnimatedValue();
                invalidate();

            }
        });
        animator4.start();


        animator5 = ValueAnimator.ofInt(0,HEIGHT3,0);
        animator5.setDuration(2000);
        animator5.setInterpolator(new DecelerateInterpolator());
        //無限循環(huán)
        animator5.setRepeatCount(ValueAnimator.INFINITE);
        animator5.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                h5 = (int) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator5.start();
    }

    /**
     * 關(guān)閉動畫
     */
    public void removeAnimation(){
        if (animator1 != null){
            animator1.cancel();
            animator1 = null;
        }
        if (animator2 != null){
            animator2.cancel();
            animator2 = null;
        }
        if (animator3 != null){
            animator3.cancel();
            animator3 = null;
        }
        if (animator4 != null){
            animator4.cancel();
            animator4 = null;
        }
        if (animator5 != null){
            animator5.cancel();
            animator5 = null;
        }
    }

}

主要是利用Path中的貝塞爾曲線偎球,然后加上屬性動畫,動態(tài)改變曲線的高度就可以了

喜歡的話辑甜,就去github給個star吧

https://github.com/helang1991/VolumeWave

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末衰絮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子磷醋,更是在濱河造成了極大的恐慌猫牡,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邓线,死亡現(xiàn)場離奇詭異淌友,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)骇陈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門震庭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人你雌,你說我怎么就攤上這事器联《矗” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵拨拓,是天一觀的道長肴颊。 經(jīng)常有香客問我,道長渣磷,這世上最難降的妖魔是什么婿着? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮醋界,結(jié)果婚禮上竟宋,老公的妹妹穿的比我還像新娘。我一直安慰自己物独,他們只是感情好袜硫,可當(dāng)我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著挡篓,像睡著了一般婉陷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上官研,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天秽澳,我揣著相機(jī)與錄音,去河邊找鬼戏羽。 笑死担神,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的始花。 我是一名探鬼主播妄讯,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼酷宵!你這毒婦竟也來了亥贸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤浇垦,失蹤者是張志新(化名)和其女友劉穎炕置,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體男韧,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡朴摊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了此虑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甚纲。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖朦前,靈堂內(nèi)的尸體忽然破棺而出贩疙,到底是詐尸還是另有隱情讹弯,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布这溅,位于F島的核電站,受9級特大地震影響棒仍,放射性物質(zhì)發(fā)生泄漏悲靴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一莫其、第九天 我趴在偏房一處隱蔽的房頂上張望癞尚。 院中可真熱鬧,春花似錦乱陡、人聲如沸浇揩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胳徽。三九已至,卻和暖如春爽彤,著一層夾襖步出監(jiān)牢的瞬間养盗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工适篙, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留往核,地道東北人。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓嚷节,卻偏偏與公主長得像聂儒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子硫痰,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,081評論 25 707
  • 1衩婚、通過CocoaPods安裝項目名稱項目信息 AFNetworking網(wǎng)絡(luò)請求組件 FMDB本地數(shù)據(jù)庫組件 SD...
    陽明先生_X自主閱讀 15,979評論 3 119
  • 我在開發(fā)iOS的過程中,逐漸形成了一些對iOS性能優(yōu)化的認(rèn)識碍论,準(zhǔn)備總結(jié)出來谅猾。懇請各位斧正。 在我的眼中鳍悠,app的性...
    伯陽閱讀 1,393評論 1 3
  • 一藏研、到底什么是核心素養(yǎng)敬矩? 疑問1:素養(yǎng)與能力、品質(zhì)的關(guān)系蠢挡? 素養(yǎng)是先天的還是后天修得弧岳? 疑問2:提升核心素養(yǎng)與解決...
    水晶飛揚(yáng)閱讀 245評論 0 1