1. 程式人生 > >【Android】自定義錄音、播放動畫View,讓你的錄音浪起來

【Android】自定義錄音、播放動畫View,讓你的錄音浪起來

前言

先看效果圖
這裡寫圖片描述
嗯,然後大致就是這樣,按住錄音,然後有一個倒計時,最外層一個進度條,還有一個類似模擬聲波的動畫效果(其實中間的波浪會根據聲音的大小浪起來的~)

實現思路

然後,我們適當的來分析一下這個錄音動畫的實現方式。這個肯定是通過自定義控制元件,咱們來把這個效果完完全全畫出來。
大致包括以下幾個點:
1. 最外層的進度條,最坑的就是一開始的一個漸變的效果
2. 然後進度條最前方是有一個點的(我肯定選擇用圖片來實現)
3. 中間的波浪(關鍵是要隨著聲音的大小浪起來)
4. 中間的倒計時

實現過程

1.畫最外層的圓

/**
* 畫一個大圓(純色)
*/
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(dip2px(mContext, widthing));
mPaint.setColor(mContext.getResources().getColor(R.color.RoundColor));
RectF oval1 = new
RectF( dip2px(mContext, pandding) , dip2px(mContext, pandding) , getWidth()-dip2px(mContext, pandding) , getHeight()-dip2px(mContext, pandding)); canvas.drawArc(oval1, progress, 360, false, mPaint); //繪製圓弧

2.畫提示的文字

Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setTextSize
(dip2px(mContext,textHintSize)); paint.setColor(mContext.getResources().getColor(R.color.RoundHintTextColor)); // 下面這行是實現水平居中,drawText對應改為傳入targetRect.centerX() paint.setTextAlign(Paint.Align.CENTER); canvas.drawText("剩餘錄製時間", getWidth()/2, getHeight()/2+50, paint);

3.畫倒計時(靜止時間)

/**
* 畫時間
* */
Paint paint2 = new Paint(Paint.ANTI
_ALIAS_FLAG); paint2.setTextSize(dip2px(mContext,60)); paint2.setColor(mContext.getResources().getColor(R.color.TimeTextColor)); paint2.setTextAlign(Paint.Align.CENTER); canvas.drawText(countdownTime2+"", getWidth()/2, getHeight()/2-20, paint2);

4.畫聲波

if (lastTime == 0) {
    lastTime = System.currentTimeMillis();
    translateX += 5;
} else {
    if (System.currentTimeMillis() - lastTime > lineSpeed) {
        lastTime = System.currentTimeMillis();
        translateX += 5;
    } else {
        return;
    }
}
if (volume < targetVolume && isSet) {
    volume += getHeight() / 30;
} else {
    isSet = false;
    if (volume <= 10) {
        volume = 10;
    } else {
        if (volume < getHeight() / 30) {
            volume -= getHeight() / 60;
        } else {
            volume -= getHeight() / 30;
        }
    }
}
//我是分隔線-------------------------------
mPaint.setColor(voiceLineColor);
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(2);
canvas.save();
int moveY = getHeight()*3/4;
for (int i = 0; i < paths.size(); i++) {
paths.get(i).reset();
paths.get(i).moveTo(getWidth()*5/6, getHeight() *3/4);
}
for (float j = getWidth()*5/6 - 1; j >= getWidth()/6; j -= fineness) {
float i = j-getWidth()/6;
//這邊必須保證起始點和終點的時候amplitude = 0;
amplitude = 5 * volume *i / getWidth() - 5 * volume * i / getWidth() * i/getWidth()*6/4;
for (int n = 1; n <= paths.size(); n++) {
    float sin = amplitude * (float) Math.sin((i - Math.pow(1.5, n)) * Math.PI / 180 - translateX+n*amplitude/(Math.PI*6));
    paths.get(n - 1).lineTo(j, (2 * n * sin / paths.size() - 15 * sin / paths.size() + moveY));
}
}
for (int n = 0; n < paths.size(); n++) {
if (n == paths.size() - 1) {
    mPaint.setAlpha(255);
} else {
    mPaint.setAlpha(n * 130 / paths.size());
}
if (mPaint.getAlpha() > 0) {
    canvas.drawPath(paths.get(n), mPaint);
}
}
canvas.restore();

這邊程式碼就不展開了,畫的有點煩,簡單說下,還需要自己體會哈。上面分隔線之前的說白了就是讓聲波動起來,也就是改變volume的值,然後後面有3個for迴圈。第一個for迴圈是為了確定聲波水平線的位置,第二個是畫聲波,第三個是顏色的漸變。

5.畫外圈進度的那個點

我們先會個圖分析一下,如下圖。A點就是起始座標,一開始我們的小圓點是隱藏的,如果不算padding的話,x=witdh/2,y=0;
這裡寫圖片描述
嗯,然後呢畫圖片我們用的是

canvas.drawBitmap(......);

這裡寫圖片描述
那麼要知道,drawBitmap()這個方法畫的時候是我們圖片的左上角去畫到A點的,其實我們應該往左上角挪一點,才能讓圖片的中心真正意義上的和A點重合,對吧對吧,嗯,仔細思考一下。

然後繼續看上面那個圖,當我們A點隨著時間運動到B點之後,我們要算出B點的座標。這邊用一下三角函式啦,我們設A到B,轉過的角度為α,設圓的半徑為r,那麼A到B其實橫向增加的距離應該就是

m = x+r*sin(α);
n = y+r*cos(α);

那麼我們該圖片的所有程式碼就是:

/**
* 畫一個點(圖片)
* */
if(r>0){
   if(progress >360)
       return;
   double hu = Math.PI*Double.parseDouble(String.valueOf(progress))/180.0;
   Log.d(TAG,"hu: "+hu);
   double p = Math.sin(hu)*r;
   Log.d(TAG,"p: "+p);
   double q = Math.cos(hu)*r;
   Log.d(TAG,"q: "+q);
   float x = (float) ((getWidth()-progressDrawable.getIntrinsicWidth())/2f+p);
   Log.d(TAG,"x: "+x);
   float y = (float) ((dip2px(mContext,pandding)-progressDrawable.getIntrinsicHeight()/2f)+r-q);
   Log.d(TAG,"y: "+y);
   canvas.drawBitmap(((BitmapDrawable)progressDrawable).getBitmap(),x,y,mPaint);
}

6.畫外邊的帶進度和帶漸變的大圓

我的實現方式很簡單,從我們的UI圖看出,外面的大圓在1/4進度的時候是漸變的,然後剩下的3/4圓其實都是一種顏色,對吧,那我就畫2個圓來實現這個效果唄。當progress<90的時候,我們畫那個漸變的圓環,當>90的時候,我們同時畫出那個漸變的和純色的圓環(當progress的時候,這個時候其實那個漸變的圓環沒變化,只是純色的圓環一直在變)。如圖:A是那個漸變的圓環,B是純色不變的圓環
這裡寫圖片描述

/**
* 這邊畫進度
*/
if(progress > 90){
    mPaint.setColor(getResources().getColor(R.color.RoundFillColor));
    mPaint.setStrokeWidth(dip2px(mContext, widthing));
    RectF oval = new RectF( dip2px(mContext, pandding)
            , dip2px(mContext, pandding)
            , getWidth()-dip2px(mContext, pandding)
            , getHeight()-dip2px(mContext, pandding));
    canvas.drawArc(oval, 0, progress-90, false, mPaint);    //繪製圓弧
    r = getHeight()/2f-dip2px(mContext,pandding);
}
/**
 * 畫一個大圓(漸變)
 */
mPaint.setStyle(Paint.Style.STROKE);
canvas.rotate(-90, getWidth() / 2, getHeight() / 2);
mPaint.setStrokeWidth(dip2px(mContext, widthing));
mPaint.setShader(new SweepGradient(getWidth() / 2, getHeight() / 2, doughnutColors, null));
RectF oval = new RectF( dip2px(mContext, pandding)
        , dip2px(mContext, pandding)
        , getWidth()-dip2px(mContext, pandding)
        , getHeight()-dip2px(mContext, pandding));
//看這裡,其實這裡當progress大於90以後就一直只畫0-90度的圓環
canvas.drawArc(oval, 0, progress<90?progress:90, false, mPaint);    //繪製圓弧
canvas.rotate(90, getWidth() / 2, getHeight() / 2);
mPaint.reset();

7.然後最後就剩下一個計時器了,還有那個上面一直出現的progress

private int countdownTime2 = 9;//倒計時時間,預設時間10秒.這是會變的
...
progress += 360.00/(countdownTime*950.00/5.00);
Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
    if(msg.what == 1){
        countdownTime2--;
        if(countdownTime2 == 0){
            listener.onCountDown();
            canSetVolume = false;
            timeTask.cancel();
            postInvalidate();
        }
    }else if(msg.what == 2){
        progress += 360.00/(countdownTime*950.00/5.00);
//                Log.d(TAG,"progress:"+progress);
        if(progress >360){
            targetVolume = 1;
            postInvalidate();
            progressTask.cancel();
        }else
            postInvalidate();
    }
}
};

8.最後就是提供各種介面,各種繪製和啟動機制了,最主要的還是上面的繪製方法。

比如你的自定義屬性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="recordView">
        <attr name="hintText" format="string"/>
        <attr name="playHintText" format="string"/>
        <attr name="timeTextColor" format="reference"/>
        <attr name="hintTextSize" format="dimension"/>
        <attr name="middleLineHeight" format="dimension"/>
        <attr name="progressSrc" format="reference"/>
        <attr name="middleLineColor" format="reference"/>
        <attr name="unit" format="string"/>
        <attr name="model">
            <enum name="record_model" value="1"/>
            <enum name="play_model" value="2"/>
        </attr>
    </declare-styleable>
</resources>

以及一切其餘自定義View的東西,對自定義View不熟的同學可以先去學習下怎麼自定義View(其實很簡單,新手不要怕),然後再去實現一些看上去挺棒的效果。

總結

嗯,大致就是這樣,對於公司這些動畫的需求我只想說其實你想要咋弄,都是沒問題的,最重要的就是時間!本身其實最後留給開發人員的時間就不多,然後如果還要加各種動畫,那不是天天加班的節奏麼~

下載地址