Without further ado, here’s to the above



Product Manager: “Two curves fluctuate up and down and roll forward depending on the volume of sound.”

Program ape: “Pain and joy”


I. Theoretical basis

Ok, to get back to the point, the first thing that comes to mind when a science student sees this curve should not be waves, twine, ponytails… It’s the sine function y is equal to sine of x.



I want it to scroll, so I shift it to the right y is equal to sine of x minus k.



When k increases in order over time, the picture keeps moving to the right.

In addition, to satisfy the requirement that the curve fluctuates up and down depending on the volume of sound, just adjust the A value in y=A*sin(x-k) :



At this point, the mathematical theoretical basis is described.

Amway can’t resist this beautiful function image rendering tools at www.desmos.com/calculator

Two, the implementation principle

Obviously, Android native controls can not meet our needs, can be on the custom View of the boat. We need to inherit the SurfaceView to draw the waveform. Why not inherit View pinch?

SurfaceView and View:

The View updates the drawing in the main thread, while the SurfaceView updates the drawing in a separate child thread. The View redraws the View by refreshing it. The refresh cycle is usually 16 milliseconds. Complex drawing logic can cause frame loss or stutter, or even block the main thread. The SurfaceView is drawn in a thread and does not consume resources from the main thread.



Create a custom SurfaceView that inherits the SurfaceView class and implements the surfaceholder.callback interface:

@override public void surfaceCreated(SurfaceHolder holder) {// The size of the surfaceView has changed} @override public void SurfaceChanged (SurfaceHolder holder, int format, int width, int height) {//surfaceView created Public void destroyed (surfaceView holder) {public void destroyed (surfaceView holder) {Copy the code


2. Create a drawing thread:

The drawing thread is an infinite loop. At a certain interval, the canvas is acquired by the SurfaceHolder for refreshing and redrawing operations. In order to realize the function of waveform pause, we nested a switch variable to control drawing in the dead loop. In addition, to avoid excessive drawing, the drawing thread sleeps for a period of time after each drawing.

private class DrawThread extends Thread {    
    private SurfaceHolder mHolder;    
    private boolean mIsRun = false;    
    private boolean mIsStop = false;    

    public DrawThread(SurfaceHolder holder) {        
        super(TAG);        
        mHolder = holder;    
    }    

    @Override    
    public void run() {        
        while(! mIsStop) { synchronized (mSurfaceLock) {if(mIsRun) { Canvas canvas = null; try { canvas = mHolder.lockCanvas(); // Lock the canvasif(null ! = canvas) {doDraw(canvas); }} catch (Exception e) {e.printStackTrace(); } finally {if(null ! = canvas) { mHolder.unlockCanvasAndPost(canvas); }}}} try {thread. sleep(SLEEP_TIME); } catch (InterruptedException e) { e.printStackTrace(); } } } public voidsetRun(boolean isRun) {        
        this.mIsRun = isRun;    
    }    

    public void setStop() {        
        this.mIsStop = true; }}Copy the code


3. Drawing waveform lines:

According to the above formula Y = A * sin(x-k), A value – changes in A positive correlation with the volume value, and k value – increases in an orderly way with the change of time. X takes the transverse coordinate of the SurfaceView, so that the corresponding Y value can be calculated, and then the point is drawn at the corresponding (x, y) position. Due to a lot of pixel screen, if every point calculation, the CPU consumption is very big, so we take the change of trace points for the line drawing way, through a certain pixel to calculate the corresponding value of y, then draw a line between two points in (think one or two three four stars look into line), so that it can greatly reduce the amount of calculation.

// In order to reduce the computation of drawing curves, instead of tracing points, draw lines (every 20 pixels draw a straight line)for (int x = 0; x < getWidth() + 20; x = x + 20) {
    float topY = (float) (mWaveA * Math.sin(deltaT - WAVE_K * x));
    topY = topY + mHeight / 2f;
    canvas.drawLine(x - 20, lastTopY, x, topY, mPaintA);
    lastTopY = topY;
}Copy the code


Complete code

As an aspirational custom View, of course, it needs to be general, such as support for setting color, line width, maximum sound wave, single and double line display, etc.

————— I am the splitter line, complete code attached to —————

styles.xml

<! -- Sound wave animation --> <declare-styleable name="SoundWaveView"> <! --> <attr name="lineWidth" format="dimension"/ > <! -- Sound wave line color 1--> <attr name="lineTopColor" format="color"/ > <! -- Sound wave line color 2--> <attr name="lineBottomColor" format="color"/ > <! --> <attr name="maxVolume" format="integer"/ > <! --> <attr name="waveStyle" format="enum">
        <enum name="doubleLine" value="0" />
        <enum name="singleLine" value="1" />
    </attr>
</declare-styleable>Copy the code

Java code

package com.xinwei.soundwave.view; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; import com.xinwei.soundwave.R; import java.util.concurrent.LinkedBlockingQueue; /** * Created by xinwei on 2019/2/28 */ Public Class SoundWaveView extends SurfaceView implements SurfaceHolder.Callback { private static final String TAG ="SoundWaveView"; private static final int DEFAULT_MAX_VOLUME = 100; Private static final int DEFAULT_LINE_WIDTH = 3; Private static final int WAVE_STYLE_DOUBLE = 0; Private static final int WAVE_STYLE_SINGLE = 1; Private static final int SLEEP_TIME = 30; private final Object mSurfaceLock = new Object(); private LinkedBlockingQueue<Integer> mVolumeQueue = new LinkedBlockingQueue<>(100); Private int mMaxVolume = DEFAULT_MAX_VOLUME; Private Boolean mIsSingleLine; Private DrawThread mThread; private Paint mPaintA, mPaintB; private staticfloat WAVE_K;
    private static float WAVE_AMPLITUDE;
    private static float WAVE_OMEGA;
    private float mWaveA;
    private long mBeginTime;
    private int mHeight;

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

    public SoundWaveView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SoundWaveView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs);
    }

    private void init(AttributeSet attrs) {
        TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.SoundWaveView);
        int lineWidth = typedArray.getDimensionPixelSize(R.styleable.SoundWaveView_lineWidth, DEFAULT_LINE_WIDTH);
        int lineTopColor = typedArray.getColor(R.styleable.SoundWaveView_lineTopColor, Color.BLUE);
        int lineBottomColor = typedArray.getColor(R.styleable.SoundWaveView_lineBottomColor, Color.GREEN);
        int index = typedArray.getInt(R.styleable.SoundWaveView_waveStyle, WAVE_STYLE_DOUBLE);
        mMaxVolume = typedArray.getInteger(R.styleable.SoundWaveView_maxVolume, DEFAULT_MAX_VOLUME);
        mIsSingleLine = (index == WAVE_STYLE_SINGLE);
        typedArray.recycle();

        mPaintA = new Paint();
        mPaintA.setStrokeWidth(lineWidth);
        mPaintA.setAntiAlias(true);
        mPaintA.setColor(lineTopColor);

        mPaintB = new Paint();
        mPaintB.setStrokeWidth(lineWidth);
        mPaintB.setAntiAlias(true); mPaintB.setColor(lineBottomColor); getHolder().addCallback(this); } /** * start animation */ public voidstart() {
        Log.d(TAG, "start()"); Synchronized (mSurfaceLock) {// here need to lock, otherwisedoCrash is possible in Drawif(null ! = mThread) { mThread.setRun(true); }} /** * stop animation */ public voidstop() {
        Log.d(TAG, "stop()");
        synchronized (mSurfaceLock) {
            mWaveA = WAVE_AMPLITUDE;
            if(null ! = mThread) { mThread.setRun(false); Public void addData(int volume) {mvolumequeue.offer (volume); } /** * Set the maximum sound wave * @param maxVolume maximum sound wave */ public voidsetMaxVolume(int maxVolume) {
        mMaxVolume = maxVolume;
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        Log.d(TAG, "surfaceCreated()");
        mBeginTime = System.currentTimeMillis();

        mThread = new DrawThread(holder);

        mThread.setRun(true);
        mThread.start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
                               int height) {
        Log.d(TAG, "surfaceChanged()"); WAVE_K = 0.02f; WAVE_K = 0.02f; WAVE_OMEGA = 0.0025f; WAVE_AMPLITUDE = getHeight(); mHeight = height; mWaveA = WAVE_AMPLITUDE; } @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.d(TAG,"surfaceDestroyed()"); Synchronized (mSurfaceLock) {// here need to lock, otherwisedoIt is possible to crash mThread. SetRun (false);
            mThread.setStop();
        }
    }

    private void doDraw(Canvas canvas) {
        if (null == canvas) {
            return;
        }

        Integer volume = mVolumeQueue.poll();
        //DebugLog.d(TAG, "doDraw() volume = " + volume);
        if(null ! = volume) {if (volume > mMaxVolume) {
                volume = mMaxVolume;
            }
            mWaveA = (mWaveA + (float) (WAVE_AMPLITUDE * (0.2 + 0.8 * volume/mMaxVolume))); } double deltaT = ((system.currentTimemillis () -mbeginTime)) * WAVE_OMEGA; canvas.drawColor(Color.WHITE);float lastTopY = 0;
        floatlastButtonY = 0; // In order to reduce the computation of drawing curves, instead of tracing points, draw lines (every 20 pixels draw a straight line)for(int x = 0; x < getWidth() + 20; X = x + 20) {// Line 1float topY = (float) (mWaveA * Math.sin(deltaT - WAVE_K * x)); topY = topY + mHeight / 2f; canvas.drawLine(x - 20, lastTopY, x, topY, mPaintA); lastTopY = topY; Draw a line / / 2if(! mIsSingleLine) {float buttonY = mHeight - topY;
                canvas.drawLine(x - 20, lastButtonY, x, buttonY, mPaintB);
                lastButtonY = buttonY;
            }
        }
    }

    private class DrawThread extends Thread {
        private SurfaceHolder mHolder;
        private boolean mIsRun = false;
        private boolean mIsStop = false;

        public DrawThread(SurfaceHolder holder) {
            super(TAG);
            mHolder = holder;
        }

        @Override
        public void run() {
            while(! mIsStop) { synchronized (mSurfaceLock) {if(mIsRun) { Canvas canvas = null; try { canvas = mHolder.lockCanvas(); // Lock the canvasif(null ! = canvas) {doDraw(canvas); }} catch (Exception e) {e.printStackTrace(); } finally {if(null ! = canvas) { mHolder.unlockCanvasAndPost(canvas); }}}} try {thread. sleep(SLEEP_TIME); } catch (InterruptedException e) { e.printStackTrace(); } } } public voidsetRun(boolean isRun) {
            this.mIsRun = isRun;
        }

        public void setStop() {
            this.mIsStop = true; }}}Copy the code


Four, conclusion

Full project code: github.com/xinwei94/So…

See this big guy if have time and the Internet speed is ok, help light up a little star ~