Android 自定義View(三)仿網易雲鯨雲音效

目錄表

孤獨星球

這是最終的實現效果(左上),主要包括波紋擴散效果、圓球旋轉縮小效果及顏色漸變效果。

在這裏插入圖片描述

此效果是由一個整體的自定義view繪製而成。其中波紋擴散效果,是通過定時改變波紋半徑實現的,此波紋是由先後兩個空心圓組成,在實現過程中要注意時間和各自的尺寸變化。這是參考代碼:

    public void startAnima() {
        if(drawTimingThread != null) {

            drawTimingThread.sendEmptyMessage(MSG_DRAW0);//開始1波紋

            float time = (mRMaxRadius - mRMinRadius) / distance * 0.5f;//先取整,再取中
            drawTimingThread.sendEmptyMessageDelayed(MSG_DRAW1, (int)(animaBotIntervalTime * time));//定時開啓2波紋
        }
    }

這是波紋1的半徑變化,參考代碼如下:

        if(mCurRadius0 <= mRMaxRadius){
            mCurRadius0 += distance;
        }else{
            mCurRadius0 = mRMinRadius + distance;
        }

        circlePointF0 = drawCircleOnRipple(MSG_DRAW0, curIndex0);

        mRPaint0.setAlpha(getAlphaOfRipple(curIndex0));//透明度
        mCirclePaint0.setAlpha(getAlphaOfRipple(curIndex0));
        curRadius0 = getRadiusOnRipple(curIndex0);

        curIndex0 ++;
        if(curIndex0 > (mRMaxRadius - mRMinRadius) / distance)
            curIndex0 = 0;

        cancleHandle(MSG_DRAW0);

圓球效果同樣是定時繪製的結果,平滑運動只是錯覺。在這裏是每隔200ms(波紋的定時值)在相應的位置進行繪製的,由於波紋擴散週期較短,所以我將圓球的隔旋轉週期定爲了45度,可根據業務自行修改。這裏的難點是在於怎麼找到圓球的圓心座標, 即根據圓心座標,半徑,扇形角度來求扇形終射線與圓弧交叉點的xy座標的問題。這個方法我已經在android自定義view系列之圓環刻度條介紹過了,有興趣的可以看下,或者直接在github上查看項目代碼。圓球參考代碼如下:

    private PointF drawCircleOnRipple(int msg, int index) {

        //週期開始,隨機初始角度
        if(index == 0)
            if(msg == MSG_DRAW0)
                cirAngel0 = (float) (Math.random() * - 360 + 180);
            else
                cirAngel1 = (float) (Math.random() * - 360 + 180);

        PointF progressPoint = CommentUtils
                .calcArcEndPointXY(mRMaxRadius + getPaddingLeft() + mStrokeWidth
                        , mRMaxRadius + getPaddingTop() + mStrokeWidth
                        , msg == MSG_DRAW0 ? mCurRadius0 : mCurRadius1
                        //每個週期旋轉45度
                        , (msg == MSG_DRAW0 ? curIndex0 : curIndex1) * 1.0f / ((mRMaxRadius - mRMinRadius) / distance) * 45f
                        , msg == MSG_DRAW0 ? cirAngel0 : cirAngel1);

        return progressPoint;
    }

圓球的不斷縮小效果,也是定時改變半徑進行繪製的結果,很常規,在這裏就不細說了。波紋和圓球的顏色漸變效果,由於不是漸變到全透明,所以我的alpha取值範圍105-255,參考代碼如下:

    private int getAlphaOfRipple(int curIndex) {
        int alpha = curIndex * 150 * distance / (mRMaxRadius - mRMinRadius);//只取150的二進制
        return 255 - alpha;
    }

其餘具體實現方法我就不細說了,源碼我放在了gitHub上,有需要的可以下載或在線看下。

動感環繞

這是最終的實現效果(左下),主要是不斷波動的圓環效果。但跟實際效果相比…嗯有點一言難盡…

在這裏插入圖片描述
這個效果是由四段貝塞爾曲線來擬合實現的。但這種方式出來的效果跟真正的鯨雲音效(動感環繞)差別很大,所以鯨雲音效不太可能是由這種方式實現的。如果有更貼近的實現方法,希望不吝賜教。

這是三階貝塞爾曲線的動態圖及公式,它通過控制曲線上的四個點(起始點、終止點以及兩個相互分離的控制點)來創造、編輯圖形。其中參數 t 的值等於線段上某一個點距離起點的長度除以該線段長度。

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述在這裏插入圖片描述
這是一階/二階貝塞爾曲線簡單的推導過程,即參數 t 所對應p2座標。三階貝塞爾曲線稍微麻煩點,但思路一樣,也是不斷帶入替換的過程。

在這裏插入圖片描述
由n段三階貝塞爾曲線擬合圓形時,曲線端點到該端點最近的控制點的最佳距離是(4/3)tan(π/(2n))。且t=0.5時的點一定落在圓弧上。
在這裏插入圖片描述
所以當我們想用4條貝塞爾曲線擬合圓,可以進行簡單推導 h 的值:

在這裏插入圖片描述
下面我們就拿四段貝塞爾曲線(h = 0.552284749831)組合成一條完整的圓,作爲我們的初始態。求此 h 這個臨界值的另一個作用是,我們需要運動的b曲線都是向外凸的。起始點和控制點的參考代碼如下:

    private void calculateCp() {

        b = 0.552284749831;

        if(startP == null || endP == null) {
            startP = new PointF(0, - mRadius);
            endP = new PointF(mRadius, 0);
        }

        /**
         * 平移後的畫布座標,座標(0,0)爲圓心
         */

        cp1 = new PointF((float) (mRadius * b), - mRadius);
        cp2 = new PointF(mRadius, - (float) (mRadius * b));
    }

運動中的圓環,是不斷的隨機更改控制點的座標,併爲起始點添加偏移量的結果,這是一個不斷調試的過程…,需要不斷調整控制點來控制凸起的幅度,很難找到一個完美的效果,難受,上圖效果的座標如下。

    private void calculateDynamicCp() {

        b = Math.random() * 0.44 + 0.55;

        /**
         * 平移後的畫布座標,座標(0,0)爲圓心
         * 8個控制點和4個起始點,順時針(12點->3點->6點->9點)
         */
        if(points != null && points.size() != 0)
            points.clear();

        points.add(new PointF((float) (Math.random() * - 20 + 10) , - mRadius - (float) (Math.random() * 20)));
        points.add(new PointF((float) (mRadius * b), - mRadius - (float) (Math.random() * 20)));
        points.add(new PointF(mRadius + (float) (Math.random() * 20), - mRadius - (float) (Math.random() * 10 + 10)));

        points.add(new PointF(mRadius + (float) (Math.random() * 10 + 10), (float) (Math.random() * - 20 + 10)));
        points.add(new PointF(mRadius + (float) (Math.random() * 20), (float) (Math.random() * 0.5 * mRadius * b + 0.5 *mRadius * b)));
        points.add(new PointF((float) (mRadius * b + 10), mRadius + (float) (Math.random() * 20)));

        points.add(new PointF((float) (Math.random() * - 20 + 10), mRadius + (float) (Math.random() * 20)));
        points.add(new PointF((float) (- mRadius * b), mRadius + (float) (Math.random() * 20)));
        points.add(new PointF(- mRadius - (float) (Math.random() * 20), (float) (mRadius * b)));

        points.add(new PointF(- mRadius - (float) (Math.random() * 10 + 10), (float) (Math.random() * - 20 + 10)));
        points.add(new PointF(- mRadius - (float) (Math.random() * 20), (float) (- mRadius * b)));
        points.add(new PointF((float) (- mRadius * b), - mRadius - (float) (Math.random() * 20)));
    }

這是繪製方法。初始圓環因爲端點座標是對稱的,所以只需不斷旋轉畫布繪製即可,很簡單。而動態的圓環因爲端點都有了偏移量,所以只能依次繪製四條貝塞爾曲線,每條曲線以lineTo相接。參考代碼如下:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.save();
        canvas.translate(mRadius + mStrokeWidth + getPaddingLeft()// 平移畫布
                , mRadius + mStrokeWidth + getPaddingTop());

        if(isFirst) {
            /**
             * 旋轉畫布
             */
            for(int index = 0; index < 4; index ++) {
                canvas.rotate(90f);

                bPath.moveTo(startP.x, startP.y);
                bPath.cubicTo(cp1.x, cp1.y, cp2.x, cp2.y, endP.x, endP.y);

                canvas.drawPath(bPath, bPaint);
                bPath.reset();
            }
        } else {

            /**
             * 8個控制點和4個起始點,不旋轉
             */
            for (int index = 0; index < 4; index ++) {
                if(index == 0)
                    bPath.moveTo(points.get(0).x, points.get(0).y);
                else
                    bPath.lineTo(points.get(index * 3).x, points.get(index * 3).y);

                bPath.cubicTo(points.get(index * 3 + 1).x, points.get(index * 3 + 1).y
                        , points.get(index * 3 + 2).x, points.get(index * 3 + 2).y
                        , index != 3 ? points.get(index * 3 + 3).x : points.get(0).x
                        , index != 3 ? points.get(index * 3 + 3).y : points.get(0).y);
            }

            canvas.drawPath(bPath, bPaint);
            bPath.reset();
        }

        canvas.restore();
    }

其餘實現部分我就不細說了,具體的代碼我都放在了gitHub上,有需要的可以下載或在線看下。

gitHub - CustomViewCollection

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章