總算想到一個神級的自定義控件了
前方高能預警,萌新自帶零食飲料
本文的前置知識你需簡單瞭解:Android繪製函數圖象及正弦函數的介紹
沒錯,今天玩自定義控件,和函數、錄音有什麼關係?用腳趾頭稍微想一下就知道了...git
待仿
效果:別激動...這只是
待仿
的效果(OPPOR15X錄音自帶),至於能仿成什麼樣我內心也沒底
github
[1]--上下鏡像有沒有,作一條,另外一條鏡像一下就好了
[2]--顏色漸變色,Paint支持顏色漸變
[3]--一條深,一條淺,就拿深的開刀吧
[4]--兩端線較細,這個得琢磨一下
[5]--算上駐點兩條線一共有5個交點,一共兩個週期
複製代碼
先別看別的,先畫一個正弦函數再說
那一篇用點拼的,如今想一想能夠用path,這篇用path來畫編程
A:振幅---默認200
φ:初相,默認0
曲線總長:測試階段:-600~600 共1200
T:週期--600
ω:2π/T
複製代碼
/**
* 做者:張風捷特烈<br/>
* 時間:2018/11/16 0016:9:04<br/>
* 郵箱:1981462002@qq.com<br/>
* 說明:旋律視圖
*/
public class RhythmView2 extends View {
private Point mCoo = new Point(800, 500);//原點座標
private double mMaxHeight = 200;//最到點
private double min = -600;//最小x
private double max = 600;//最大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();//初始化
}
private void init() {
//初始化主畫筆
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(6);
//初始化主路徑
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();
}
/**
* 對應法則
*
* @param x 原像(自變量)
* @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) {
return deg / 180 * Math.PI;
}
}
複製代碼
什麼影響正弦函數的橫向位移--相位:φ
那還等什麼,ValueAnimator走起,從0~2 * Math.PIcanvas
//數字時間流
mAnimator = ValueAnimator.ofFloat(0, (float) (2 * Math.PI));
mAnimator.setDuration(1000);
mAnimator.setRepeatMode(ValueAnimator.RESTART);
mAnimator.setInterpolator(new LinearInterpolator());
mAnimator.addUpdateListener(a -> {
φ = (float) a.getAnimatedValue();
invalidate();
});
複製代碼
就這麼簡單?---是的bash
不就是對A值進行漸變嘛...很是簡單微信
mAnimator.addUpdateListener(a -> {
φ = (float) a.getAnimatedValue();
A = (float) (mMaxHeight* (1 - (float) a.getAnimatedValue() / (2 * Math.PI)));
invalidate();
});
複製代碼
雖然有那麼點感受,可是仍是差不少,關鍵在對應法則,提及來也簡單
可是操做起來挺費勁,衰減函數湊了好一會...app
/**
* 對應法則
*
* @param x 原像(自變量)
* @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;
}
複製代碼
什麼顏色好呢,好吧,我計較懶,搭條彩虹吧(之前實現過)dom
int[] colors = new int[]{
Color.parseColor("#F60C0C"),//紅
Color.parseColor("#F3B913"),//橙
Color.parseColor("#E7F716"),//黃
Color.parseColor("#3DF30B"),//綠
Color.parseColor("#0DF6EF"),//青
Color.parseColor("#0829FB"),//藍
Color.parseColor("#B709F4"),//紫
};
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
));
複製代碼
會了一個,另外一個Y鏡像一下就好了(y座標邊-y)ide
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);
}
}
複製代碼
onDraw
第二條淡一點函數
mPaint.setAlpha(255);
canvas.drawPath(mPath, mPaint);
mPaint.setAlpha(66);
canvas.drawPath(mReflexPath, mPaint);
複製代碼
個人用意是在錄音是監聽音量大小,而後讓圖象波動
暴漏設置高度的方法,在設置時執行動畫,下面是點擊設置隨機高度效果
/**
* 設置高度
* @param maxHeight
*/
public void setMaxHeight(double maxHeight) {
mMaxHeight = maxHeight;
mAnimator.start();
invalidate();
}
複製代碼
4、掃尾--封裝
該dp的dp,該刪的刪,該封裝的封裝,該優化的優化,直接貼代碼
/**
* 做者:張風捷特烈<br/>
* 時間:2018/11/16 0016:9:04<br/>
* 郵箱:1981462002@qq.com<br/>
* 說明:貝塞爾三次曲線--旋律視圖
*/
public class RhythmView extends View {
private double mMaxHeight = 0;//最到點
private double mPerHeight = 0;//最到點
private double min;//最小x
private double max;//最大x
private double φ = 0;//初相
private double A = mMaxHeight;//振幅
private double ω;//角頻率
private Paint mPaint;//主畫筆
private Path mPath;//主路徑
private Path mReflexPath;//鏡像路徑
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();//初始化
}
private void init() {
//初始化主畫筆
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(dp(2));
//初始化主路徑
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 -> {
φ = (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"),//紅
Color.parseColor("#F3B913"),//橙
Color.parseColor("#E7F716"),//黃
Color.parseColor("#3DF30B"),//綠
Color.parseColor("#0DF6EF"),//青
Color.parseColor("#0829FB"),//藍
Color.parseColor("#33B709F4"),//紫
};
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();
}
/**
* 對應法則
*
* @param x 原像(自變量)
* @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) {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
}
複製代碼
第一天用
AudioTrack
實現了錄音,MediaRecode能夠錄音也能夠錄視頻
二者的區別AudioTrack
麻煩一點,須要本身去操做字節流,但能夠精緻操做
MediaRecode至關於給你封裝好了,你一步步走,給個文件就好了
/**
* 做者:張風捷特烈
* 時間:2018/4/16:10:33
* 郵箱:1981462002@qq.com
* 說明:MediaRecorder錄音幫助類
*/
public class MediaRecorderTask {
private MediaRecorder mRecorder;
private long mStartTime;//開始的時間
private int mAllTime;//總共耗時
private boolean isRecording;//是否正在錄音
private File mFile;//文件
private Timer mTimer;
private final Handler mHandler;
public MediaRecorderTask() {
mTimer = new Timer();//建立Timer
mHandler = new Handler();//建立Handler
}
/**
* 開始錄音
*/
public void start(File file) {
mAllTime = 0;
mFile = file;
if (mRecorder == null) {
// [1]獲取MediaRecorder類的實例
mRecorder = new MediaRecorder();
}
//配置MediaRecorder
// [2]設置音頻的來源
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
// [3]設置音頻的輸出格式
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
// [4]採樣頻率
mRecorder.setAudioSamplingRate(44100);
// [5]設置音頻的編碼方式
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
//[6]音質編碼頻率:96Kbps
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();
}
}
/**
* 每隔1秒回調一次音量
*/
private void cbkVolume() {
mTimer.schedule(new TimerTask() {
@Override
public void run() {
if (isRecording) {
float per;
try {
//獲取音量大小
per = mRecorder.getMaxAmplitude() / 32767f;//最大32767
} catch (IllegalStateException e) {
e.printStackTrace();
per = (float) Math.random();
}
if (mOnVolumeChangeListener != null) {
float finalPer = per;
mHandler.post(() -> {
mOnVolumeChangeListener.volumeChange(finalPer);
});
}
}
}
}, 0, 1000);
}
public void pause() {
mAllTime += System.currentTimeMillis() - mStartTime;
mRecorder.pause(); // [7]暫停錄
isRecording = false;
mStartTime = System.currentTimeMillis();
}
public void resume() {
mRecorder.resume(); // [8]恢復錄
isRecording = true;
}
/**
* 中止錄音
*/
public void stop() {
try {
mAllTime += System.currentTimeMillis() - mStartTime;
mRecorder.stop(); // [7]中止錄
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() {
return mAllTime / 1000;
}
//---------設置音量改變監聽-------------
public interface OnVolumeChangeListener {
void volumeChange(float per);
}
private OnVolumeChangeListener mOnVolumeChangeListener;
public void setOnVolumeChangeListener(OnVolumeChangeListener onVolumeChangeListener) {
mOnVolumeChangeListener = onVolumeChangeListener;
}
}
複製代碼
Activity中
基本套路和第一篇的錄音一致,下面只給出核心的步驟
不明白參見第一篇或源碼
//初始化MediaRecorderTask
mMediaRecorderTask = new MediaRecorderTask();
//設置監聽---效果的核心
mMediaRecorderTask.setOnVolumeChangeListener(per -> {
mIdRth.setPerHeight(per);
});
/**
* 開啓錄音
*/
private void startRecord() {
//建立錄音文件---這裏建立文件不是重點,我直接用了
mFile = FileHelper.get().createFile("MediaRecorder錄音/" + StrUtil.getCurrentTime_yyyyMMddHHmmss() + ".m4a");
mMediaRecorderTask.start(mFile);
}
/**
* 中止錄製
*/
private void stopRecode() {
mMediaRecorderTask.stop();
mIdTvState.setText("錄製" + mMediaRecorderTask.getAllTime() + "秒");
}
複製代碼
昨天已經實現了MediaPlayer播放音頻,不廢話了,直接拿來用
mMusicPlayer = new MusicPlayer();
mMusicPlayer.start("/sdcard/MediaRecorder錄音/20190104195319.m4a");
複製代碼
關於音頻的編碼,壓縮,格式這三者感受挺煩人的,下一篇把它們捋一下
再玩一下音頻的變速和變聲操做,今天就到這裏
項目源碼 | 日期 | 備註 |
---|---|---|
V0.1-github | 2018-1-5 | Android自定義控件(神級)+MediaRecode錄音 |
筆名 | 微信 | 愛好 | |
---|---|---|---|
張風捷特烈 | 1981462002 | zdl1994328 | 語言 |
個人github | 個人簡書 | 個人掘金 | 我的網站 |
1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大編程愛好者共同交流
3----我的能力有限,若有不正之處歡迎你們批評指證,一定虛心改正
4----看到這裏,我在此感謝你的喜歡與支持