零、前言
總算想到一個神級的自定義控制元件了
前方高能預警,萌新自帶零食飲料
本文的前置知識你需簡單瞭解:Android繪製函式圖象及正弦函式的介紹
沒錯,今天玩自定義控制元件,和函式、錄音有什麼關係?用腳趾頭稍微想一下就知道了...
廢話不多說,看待仿
效果:
別激動...這只是
待仿
的效果(OPPOR15X錄音自帶),至於能仿成什麼樣我心裡也沒底
二、正式開戰
1.截兩張圖分析一下
[1]--上下映象有沒有,做一條,另一條映象一下就行了
[2]--顏色漸變色,Paint支援顏色漸變
[3]--一條深,一條淺,就拿深的開刀吧
[4]--兩端線較細,這個得琢磨一下
[5]--算上駐點兩條線一共有5個交點,一共兩個週期
複製程式碼
2.正弦函式的繪製
先別看別的,先畫一個正弦函式再說
那一篇用點拼的,現在想想可以用path,這篇用path來畫
2.1--分析
A:振幅---預設200
φ:初相,預設0
曲線總長:測試階段:-600~600 共1200
T:週期--600
ω:2π/T
複製程式碼
2.2--繪製正弦函式
/**
* 作者:張風捷特烈<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;
}
}
複製程式碼
3.正弦函式的動起來
什麼影響正弦函式的橫向位移--相位:φ
那還等什麼,ValueAnimator走起,從0~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 -> {
φ = (float) a.getAnimatedValue();
invalidate();
});
複製程式碼
就這麼簡單?---是的
4.讓凸起的部分漸漸平息
不就是對A值進行漸變嘛...非常簡單
mAnimator.addUpdateListener(a -> {
φ = (float) a.getAnimatedValue();
A = (float) (mMaxHeight* (1 - (float) a.getAnimatedValue() / (2 * Math.PI)));
invalidate();
});
複製程式碼
二、加入衰減函式與漸變色
1.加入衰減函式
雖然有那麼點感覺,但是還是差很多,關鍵在對應法則,說起來也簡單
但是操作起來挺費勁,衰減函式湊了好一會...
/**
* 對應法則
*
* @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;
}
複製程式碼
2.加漸變色
什麼顏色好呢,好吧,我計較懶,搭條彩虹吧(以前實現過)
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
));
複製程式碼
三、第二條曲線的繪製
1.路徑的形成
會了一個,另一個Y映象一下就行了(y座標邊-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);
}
}
複製程式碼
2.繪製第二條曲線:onDraw
第二條淡一點
mPaint.setAlpha(255);
canvas.drawPath(mPath, mPaint);
mPaint.setAlpha(66);
canvas.drawPath(mReflexPath, mPaint);
複製程式碼
3.高度設定
我的用意是在錄音是監聽音量大小,然後讓圖象波動
暴漏設定高度的方法,在設定時執行動畫,下面是點選設定隨機高度效果
/**
* 設定高度
* @param maxHeight
*/
public void setMaxHeight(double maxHeight) {
mMaxHeight = maxHeight;
mAnimator.start();
invalidate();
}
複製程式碼
四、掃尾--封裝
該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());
}
}
複製程式碼
五、MediaRecode實現錄音
第一天用
AudioTrack
實現了錄音,MediaRecode可以錄音也可以錄視訊
兩者的區別AudioTrack
麻煩一點,需要自己去操作位元組流,但可以精緻操作
MediaRecode相當於給你封裝好了,你一步步走,給個檔案就行了
1.錄音的輔助類
/**
* 作者:張風捷特烈
* 時間: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;
}
}
複製程式碼
2.使用--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() + "秒");
}
複製程式碼
3.播放音訊
昨天已經實現了MediaPlayer播放音訊,不廢話了,直接拿來用
mMusicPlayer = new MusicPlayer();
mMusicPlayer.start("/sdcard/MediaRecorder錄音/20190104195319.m4a");
複製程式碼
關於音訊的編碼,壓縮,格式這三者感覺挺煩人的,下一篇把它們捋一下
再玩一下音訊的變速和變聲操作,今天就到這裡
後記:捷文規範
1.本文成長記錄及勘誤表
專案原始碼 | 日期 | 備註 |
---|---|---|
V0.1-github | 2018-1-5 | Android自定義控制元件(神級)+MediaRecode錄音 |
2.更多關於我
筆名 | 微信 | 愛好 | |
---|---|---|---|
張風捷特烈 | 1981462002 | zdl1994328 | 語言 |
我的github | 我的簡書 | 我的掘金 | 個人網站 |
3.宣告
1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大程式設計愛好者共同交流
3----個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4----看到這裡,我在此感謝你的喜歡與支援