Zero, preface,

Finally think of a god level of custom control ahead of the high-energy warning, the new bring snacks and drinks this article preknowledge you need to understand simply: Android drawing function image and sinusoidal function is introduced, today play custom controls, and functions, what is the relationship between the recording? Just think about it with your toes…


Without further ado, lookTo copyEffect:

Don’t get excited… This is just to imitate the effect (OPPOR15X recording comes with), as to what I can imitate the heart also don’t know


Two, the official war

Take two pictures and analyze them
[1]-- Mirror the top and bottom, do one, and mirror the other. [2]-- Color gradient, Paint supports color gradient [3]-- one is dark, one is light, let's do the dark one [4]-- the two ends are thinner, you have to think about that [5]-- there are 5 intersections between the two lines, including the stagnation points, and a total of 2 cyclesCopy the code


2. Drawing the sine function

So let me just draw a sine function and I’ll do that with a little bit of spelling, but let’s think about it. I’ll do it with path, and I’ll do it with path

Analysis of 2.1 –
A: amplitude -- default 200 φ : initial phase, default 0 curve length: test phase: -600~600 A total of 1200 T: period --600 ω : 2π/TCopy the code

2.2– Draw the sine function

/** * Author: Zhang Feng Jiete Lie <br/> * Time: 2018/11/160016:9:04 <br/> * Email: [email protected]<br/> * Description: Public class RhythmView2 extends View {private Point mCoo = new Point(800, 500); Private double mMaxHeight = 200; Private double min = -600; private double min = -600; // private double Max = 600; // Max x private double φ = 0; Private double A = mMaxHeight; // private double ω; // Private Paint mPaint; // Private Path mPath; // Private Path mReflexPath; Public RhythmView2(Context Context) {this(Context, null); } public RhythmView2(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); // Initialize} private voidinit() {// Initialize the main paintbrush. MPaint = new Paint(paint.anti_alias_flag); mPaint.setColor(Color.BLUE); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(6); // Initialize the main Path mPath = new Path(); mReflexPath = new Path(); } @Override protected void onDraw(Canvas canvas) { mPath.reset(); mReflexPath.reset(); super.onDraw(canvas); canvas.save(); canvas.translate(mCoo.x, mCoo.y); formPath(); canvas.drawPath(mPath, mPaint); canvas.restore(); } /** * correspondence law ** @param x preimage * @return*/ private double f(double x) {double len = max-min; ω = 2 * math. PI/(rad(len) / 2); Double y = A * Math. Sin (ω * rad(x) - φ);return y;
    }

    private void formPath() {
        mPath.moveTo((float) min, (float) f(min));
        for (double x = min; x <= max; x++) {
            double y = f(x);
            mPath.lineTo((float) x, (float) y);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mAnimator.start();
                break;
        }
        return true;
    }

    private double rad(double deg) {
        returndeg / 180 * Math.PI; }}Copy the code

3. Let the sine function move

What affects the lateral displacement of the sine function — phase: φ So what’s the wait, ValueAnimator goes from 0 to 2 * Math.pi

MAnimator = valueAnimator.offloat (0, (float) (2 * Math.PI)); mAnimator.setDuration(1000); mAnimator.setRepeatMode(ValueAnimator.RESTART); mAnimator.setInterpolator(new LinearInterpolator()); MAnimator. AddUpdateListener (a - > {phi = (float) a.getAnimatedValue();
    invalidate();
});
Copy the code

Is it that simple? – yes


4. Let the bulge subside

It’s just A gradient of A… Very simple

MAnimator. AddUpdateListener (a - > {phi = (float) a.getAnimatedValue();
    A = (float) (mMaxHeight* (1 - (float) a.getAnimatedValue() / (2 * Math.PI)));
    invalidate();
});
Copy the code

Two, add attenuation function and gradient

1. Add the attenuation function

Although there is a bit of feeling, but still a lot of difference, the key is the corresponding law, it is easy to say but it is very difficult to operate, the attenuation function together for a long time…

/** * correspondence law ** @param x preimage (independent variable) * @return*/ private double f(double x) {double len = max-min; double a = 4 / (4 + Math.pow(rad(x / Math.PI * 800 / len), 4)); Double aa = math.pow (a, 2.5); ω = 2 * math. PI/(rad(len) / 2); Double y = aa * A * Math. Sin (ω * rad(x) - φ);return y;
}
Copy the code

2. Add gradient

What color? Okay, I’m being lazy. Let’s go with a rainbow.

int[] colors = new int[]{
        Color.parseColor("#F60C0C"), / / red Color. ParseColor ("#F3B913"), / / orange Color. ParseColor ("#E7F716"), / / yellow Color. ParseColor ("#3DF30B"), / / green Color. ParseColor ("#0DF6EF"), / / green Color. ParseColor ("#0829FB"), / / blue Color. ParseColor ("#B709F4"), / / purple};float[] pos = new float[]{
        1.f / 7, 2.f / 7, 3.f / 7, 4.f / 7, 5.f / 7, 6.f / 7, 1
};
mPaint.setShader(
        new LinearGradient(
                (int) min, 0, (int) max, 0,
                colors, pos,
                Shader.TileMode.CLAMP
        ));
Copy the code

3. Drawing the second curve

1. Path formation

I’ll do one, and I’ll just mirror the other Y.

private void formPath() {
    mPath.moveTo((float) min, (float) f(min));
    mReflexPath.moveTo((float) min, (float) f(min));
    for (double x = min; x <= max; x++) {
        double y = f(x);
        mPath.lineTo((float) x, (float) y);
        mReflexPath.lineTo((float) x, -(float) y); }}Copy the code

2. Draw the second curve:onDraw

The second one is lighter

mPaint.setAlpha(255);
canvas.drawPath(mPath, mPaint);
mPaint.setAlpha(66);
canvas.drawPath(mReflexPath, mPaint);
Copy the code

3. Height setting

My intention is to listen to the volume in the recording, and then let the image undulated to expose the setting height method, in the setting to perform animation, below is the click on the set random height effect

/** * set height * @param maxHeight */ public voidsetMaxHeight(double maxHeight) {
    mMaxHeight = maxHeight;
    mAnimator.start();
    invalidate();
}
Copy the code

Four, finish – encapsulation

The DP dp, the delete of the delete, the encapsulation of the encapsulation, the optimization of the optimization, directly paste code

/** * Author: Zhang Feng Jiete Lie <br/> * Time: 2018/11/160016:9:04 <br/> * Email: [email protected]<br/> * Description: */ public class RhythmView extends View {private double rhythmmaxHeight = 0; Private double mPerHeight = 0; // Private double min; // min x private double Max; // Max x private double φ = 0; Private double A = mMaxHeight; // private double ω; // Private Paint mPaint; // Private Path mPath; // Private Path mReflexPath; // Image path private ValueAnimator mAnimator; private int mHeight; private int mWidth; public RhythmView(Context context) { this(context, null); } public RhythmView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); // Initialize} private voidinit() {// Initialize the main paintbrush. MPaint = new Paint(paint.anti_alias_flag); mPaint.setColor(Color.BLUE); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(dp(2)); // Initialize the main Path mPath = new Path(); mReflexPath = new Path(); MAnimator = valueAnimator.offloat (0, (float) (2 * Math.PI)); mAnimator.setDuration(1000); mAnimator.setRepeatMode(ValueAnimator.RESTART); mAnimator.setInterpolator(new LinearInterpolator()); MAnimator. AddUpdateListener (a - > {phi = (float) a.getAnimatedValue();
            A = (float) (mMaxHeight * mPerHeight * (1 - (float) a.getAnimatedValue() / (2 * Math.PI)));
            invalidate();
        });
    }

    public void setPerHeight(double perHeight) {
        mPerHeight = perHeight;
        mAnimator.start();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = MeasureSpec.getSize(widthMeasureSpec);
        mHeight = MeasureSpec.getSize(heightMeasureSpec);
        mMaxHeight = mHeight / 2 * 0.9;
        min = -mWidth / 2;
        max = mWidth / 2;
        handleColor();
        setMeasuredDimension(mWidth, mHeight);
    }


    private void handleColor() {
        int[] colors = new int[]{
                Color.parseColor("#33F60C0C"), / / red Color. ParseColor ("#F3B913"), / / orange Color. ParseColor ("#E7F716"), / / yellow Color. ParseColor ("#3DF30B"), / / green Color. ParseColor ("#0DF6EF"), / / green Color. ParseColor ("#0829FB"), / / blue Color. ParseColor ("#33B709F4"), / / purple};float[] pos = new float[]{ 1.f / 10, 2.f / 7, 3.f / 7, 4.f / 7, 5.f / 7, 9.f / 10, 1 }; mPaint.setShader( new LinearGradient( (int) min, 0, (int) max, 0, colors, pos, Shader.TileMode.CLAMP )); } @Override protected void onDraw(Canvas canvas) { mPath.reset(); mReflexPath.reset(); super.onDraw(canvas); canvas.save(); canvas.translate(mWidth / 2, mHeight / 2); formPath(); mPaint.setAlpha(255); canvas.drawPath(mPath, mPaint); mPaint.setAlpha(66); canvas.drawPath(mReflexPath, mPaint); canvas.restore(); } /** * correspondence law ** @param x preimage * @return*/ private double f(double x) {double len = max-min; double a = 4 / (4 + Math.pow(rad(x / Math.PI * 800 / len), 4)); Double aa = math.pow (a, 2.5); ω = 2 * math. PI/(rad(len) / 2); Double y = aa * A * Math. Sin (ω * rad(x) - φ);return y;
    }

    private void formPath() {
        mPath.moveTo((float) min, (float) f(min));
        mReflexPath.moveTo((float) min, (float) f(min));
        for (double x = min; x <= max; x++) {
            double y = f(x);
            mPath.lineTo((float) x, (float) y);
            mReflexPath.lineTo((float) x, -(float) y);
        }

    }

    private double rad(double deg) {
        return deg / 180 * Math.PI;
    }

    protected float dp(float dp) {
        returnTypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics()); }}Copy the code

Five, MediaRecode to achieve recording

The first day with AudioTrack to achieve recording, MediaRecode can record recording can also record video the difference between the two AudioTrack is a bit troublesome, you need to operate the byte stream, but you can delicate operation MediaRecode equivalent to give you a package, you step by step, give a file on the line


1. The auxiliary class of recording
/** * Author: Zhang Feng Jiete Lie * Time: 2018/4/16:10:33 * Email: [email protected] * Description: Public Class MediaRecorderTask {private MediaRecorder mRecorder; private long mStartTime; Private int mAllTime; Private Boolean isRecording; // Whether recording is going on private File mFile; // Private Timer mTimer; private final Handler mHandler; publicMediaRecorderTask() { mTimer = new Timer(); // Create Timer mHandler = new Handler(); Public void start(File File) {mAllTime = 0; public void start(File File) {mAllTime = 0; mFile = file;if(mRecorder == null) {// [1] Get an instance of the MediaRecorder class mRecorder = new MediaRecorder(); } / / configure the MediaRecorder / / [2] the source of the audio Settings mRecorder. SetAudioSource (MediaRecorder. AudioSource. MIC); / / [3] set format of the audio output mRecorder. SetOutputFormat (MediaRecorder. OutputFormat. MPEG_4); . / / [4] sampling frequency mRecorder setAudioSamplingRate (44100); / / [5] set audio encoding mRecorder. SetAudioEncoder MediaRecorder. AudioEncoder. (AAC); / / [6] sound code frequency: 96 KBPS mRecorder setAudioEncodingBitRate (96000); [7] mRecorder. SetOutputFile (File.geTabSolutePath ()); try { mRecorder.prepare(); } catch (IOException e) { e.printStackTrace(); } mStartTime = System.currentTimeMillis();if(mRecorder ! = null) { mRecorder.start(); isRecording =true; cbkVolume(); } /** * Private void. */ private voidcbkVolume() {
        mTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                if (isRecording) {
                    floatper; Try {/ / get the volume per = mRecorder getMaxAmplitude () / 32767 f. } catch (IllegalStateException e) {e.printStackTrace(); per = (float) Math.random();
                    }
                    if(mOnVolumeChangeListener ! = null) {floatfinalPer = per; mHandler.post(() -> { mOnVolumeChangeListener.volumeChange(finalPer); }); }}}}, 0, 1000); } public voidpause() { mAllTime += System.currentTimeMillis() - mStartTime; mRecorder.pause(); [7] Recording =false;
        mStartTime = System.currentTimeMillis();

    }

    public void resume() { mRecorder.resume(); [8] Recording =true; } /** * Stop recording */ public voidstop() { try { mAllTime += System.currentTimeMillis() - mStartTime; mRecorder.stop(); [7] Stop recording isRecording =false; mRecorder.release(); mRecorder = null; } catch (RuntimeException e) { mRecorder.reset(); //[8] You can reuse the object by going back mRecorder.release(); //[9] Now the object cannot be reused mRecorder = null; isRecording =false;
            if (mFile.exists())
                mFile.delete();
        }
    }

    public int getAllTime() {
        returnmAllTime / 1000; Public interface OnVolumeChangeListener {void volumeChange(public interface OnVolumeChangeListener {void volumeChange(float per);
    }

    private OnVolumeChangeListener mOnVolumeChangeListener;

    public void setOnVolumeChangeListener(OnVolumeChangeListener onVolumeChangeListener) { mOnVolumeChangeListener = onVolumeChangeListener; }}Copy the code

2. Use the —The Activity of

The basic routine and the first recording is consistent, the following only gives the core steps do not understand see the first article or source code

// Initialize MediaRecorderTask mMediaRecorderTask = new MediaRecorderTask(); / / set to monitor - the effect of core mMediaRecorderTask. SetOnVolumeChangeListener (per - > {mIdRth. SetPerHeight (per); }); /** * Enable recording */ private voidstartRecord() {mFile = fileHelper.get ().createFile();"The MediaRecorder recording/" + StrUtil.getCurrentTime_yyyyMMddHHmmss() + ".m4a"); mMediaRecorderTask.start(mFile); } /** * Stop recording */ private voidstopRecode() {
    mMediaRecorderTask.stop();
    mIdTvState.setText("Record" + mMediaRecorderTask.getAllTime() + "Seconds");
}
Copy the code


3. Play audio

Yesterday, I implemented MediaPlayer to play audio, so no more nonsense, just use it

mMusicPlayer = new MusicPlayer();

mMusicPlayer.start("/ sdcard/MediaRecorder recording / 20190104195319. : m4a");
Copy the code

About the audio coding, compression, format of these three feel very annoying, the next article to put them down and play with the audio of the variable speed and sound operation, today is over


Postscript: Jie wen standard

1. Growth record and Errata of this paper
Program source code The date of note
V0.1 – making The 2018-1-5 Android custom control (god level)+MediaRecode recording
2. More about me
Pen name QQ WeChat hobby
Zhang Feng Jie te Li 1981462002 zdl1994328 language
My lot My Jane books I’m the nuggets Personal website
3. The statement

1—- this article is originally written by Zhang Feng Jetelie, please note 2—- all programming enthusiasts are welcome to communicate with each other 3—- personal ability is limited, if there is any error, you are welcome to criticize and point out, will be modest to correct 4—- See here, I thank you here for your love and support