導語
手機直播一般都會通過移動屏幕來調節音量的大小,本篇只實現了圖例,並不能改變音量。
先看效果:
需要的素材:小喇叭圖片,點擊這裏獲取
預熱
如果你對Path,PathMeasure,RectF,Canvas等不適很瞭解的話,強烈建議看這位哥們的教程:
點擊這裏查看教程
如果你將這哥們的十幾篇帖子都看完了的話,這個View實際上是非常簡單的
步驟介紹
用動態圖來介紹:
這裏用文字翻譯下:
- 將小喇叭畫到中心位置
- 圍繞着喇叭畫一個圓圈,淺色的
- 畫一個圓弧,深色的
- 根據觸摸的位置來改變圓弧的大小
分解之後,發現並沒有什麼難度(可能本身就沒有什麼難度),下面來看每一步的操作,最後會將整個View的代碼貼出來
繪製小喇叭
相關代碼片(不要複製,只是看的)
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//座標移動到中心
canvas.translate(mViewWidth / 2, mViewHeight / 2);
//拿到小喇叭圖片
Bitmap voice = BitmapFactory.decodeResource(getResources(), R.mipmap.voice);
//獲取圖片的寬高
int bWidth = voice.getWidth();
int bHeight = voice.getHeight();
//移動座標到中心位置
canvas.drawBitmap(voice, -bWidth / 2, -bHeight / 2, outerCirclePaint);
}
分析
核心:將圖片放到中心位置:
將圖片向上移動高度的一半,向左移動寬度的一半,就可以移動到中心如圖所示:
繪製淺色的圓環
相關代碼片(不要複製,只是看的)
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
initPaint();
//座標移動到中心
canvas.translate(mViewWidth / 2, mViewHeight / 2);
//底層圓圈
if (cirPath == null)
cirPath = new Path();
if (rectF == null) {
//半徑選取爲圖片寬度一半的1.3倍,可以調節
r = (int) (bWidth / 2 * 1.3f);
rectF = new RectF(-r, -r, r, r);
}
cirPath .addArc(rectF, 0, 360);
//畫底層淺色的圓圈
canvas.drawPath(cirPath, outerCirclePaint);
}
分析:
這步沒有什麼難度,只是繪製一個圓圈
畫一個深色的圓弧
相關代碼片(不要複製,只是看的)
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//座標移動到中心
canvas.translate(mViewWidth / 2, mViewHeight / 2);
//音量大小
if (innerCirclePath == null)
innerCirclePath = new Path();
//繪製外層深色的音量弧形
drawVoicePath(innerCirclePath);
//畫音量強度
canvas.drawPath(voicePath, voicePaint);
}
private void drawVoicePath(Path path) {
if (voiceRectf == null) {
//與底層圓圈保持一致
voiceRectf = new RectF(-r, -r, r, r);
voicePath = new Path();
}
voicePath.reset();
//道聽途說使用359.9可以測量的更準
path.addArc(voiceRectf, -90, 359.9f);
//通過PathMeasure來繪製部分的圓圈,表現出來就是繪製了弧形
PathMeasure measure = new PathMeasure(path, false);
//獲取圓的總長度
float length = measure.getLength();
//根據音量大小來繪製部分的弧形
measure.getSegment(0, voiceNumber * length, voicePath, true);
}
分析
這段代碼需要了解PathMeasure的用法點擊這裏瞭解。
圓弧從上往下繪製,所以:addArc(RectF oval, float startAngle, float sweepAngle)中的第二個參數startAngle爲-90;
圓各部分的對應的Angle:
另外,通過可變參數voiceNumber來控制圓弧的大小,用於之後的修改。
觸摸改變圓弧的大小
相關代碼片(不要複製,只是看的)
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//記錄起始位置的座標和音量大小
startY = (int) event.getY();
oldVoiceNumber = voiceNumber;
break;
case MotionEvent.ACTION_MOVE:
//記錄當前位置的座標
moveY = (int) event.getY();
//與起始位置進行比較來確定音量的大小
changedVoiceNumber();
break;
case MotionEvent.ACTION_UP:
//手指離開屏幕,重置數據
resetData();
break;
}
//繪製圖形
invalidate();
//這裏需要改View處理事件,所以放回true;
return true;
}
最後一步的代碼比較多,這裏沒有貼完,詳細代碼在最後都會貼出來。
分析:
既然要通過滑動來改變深色圓弧的大小,那麼可定是在onTouchEvent()中來進行相關的操作。
1. 通過滑動,圖形改變,需要重新繪製,所以調用invalidate()
2. 滑動事件需要改View來處理,所以返回值爲true
3. 核心部分是通過ACTION_DOWN,ACTION_MOVE,ACTION_UP的相關操作來實現的
核心部分的大致流程圖是這樣的,如果與後面的代碼有出入,以代碼爲準:
完整代碼
這裏可以複製粘貼了
/**
* Created by Kevin on 2016/8/31.
*/
public class VoiceView extends View {
//控件的寬高
private int mViewWidth;
private int mViewHeight;
//小喇叭
private Bitmap voice;
//小喇叭的寬度
private int bWidth;
//小喇叭的高度
private int bHeight;
//表示音量大小
private float voiceNumber = 0.5f;
//調節之前音量的大小
private float oldVoiceNumber;
//開始時的座標
private int startY;
//移動後的座標
private int moveY;
//圓圈的半徑
private int r;
//音量從0-->1所需要移動的距離
private int voiceChangedY;
//音量的畫筆
private Paint voicePaint;
//頂層圓圈的畫筆
private Paint outerCirclePaint;
//音量的Path
private Path voicePath;
//頂層圓圈的的Path
private Path cirPath;
//音量圓圈的Path
private Path innerCirclePath;
//頂層的RectF
private RectF rectF;
//音量的RectF
private RectF voiceRectf;
public VoiceView(Context context) {
super(context);
initBitmap();
}
public VoiceView(Context context, AttributeSet attrs) {
super(context, attrs);
initBitmap();
}
public VoiceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initBitmap();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
initPaint();
//座標移動到中心
canvas.translate(mViewWidth / 2, mViewHeight / 2);
//外層圓圈
if (cirPath == null)
cirPath = new Path();
//音量大小
if (innerCirclePath == null)
innerCirclePath = new Path();
//繪製底層淺色圓圈
drawCirclePath(cirPath);
//繪製外層深色的音量弧形
drawVoicePath(innerCirclePath);
//移動座標到中心位置
canvas.drawBitmap(voice, -bWidth / 2, -bHeight / 2, outerCirclePaint);
//畫頂層淺色的圓圈
canvas.drawPath(cirPath, outerCirclePaint);
//畫音量強度
canvas.drawPath(voicePath, voicePaint);
}
/**
* 通過觸摸來改變音量的大小
*
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//記錄起始位置的座標和音量大小
startY = (int) event.getY();
oldVoiceNumber = voiceNumber;
break;
case MotionEvent.ACTION_MOVE:
//記錄當前位置的座標
moveY = (int) event.getY();
//與起始位置進行比較來確定音量的大小
changedVoiceNumber();
break;
case MotionEvent.ACTION_UP:
//手指離開屏幕,重置數據
resetData();
break;
}
//繪製圖形
invalidate();
//這裏需要改View處理事件,所以放回true;
return true;
}
/**
* 根據起始位置和當前位置來確定音量的大小
* <p>
* 一般情況下:音量大小的改變量 = (當前位置 - 起始位置) / 一個固定的長度
* 註釋:這裏選取的“一個固定的長度”爲高度的一半
* <p>
* 極端情況:如果音量調節到1或者0,仍然以最開始的位置作爲起始位置,感覺會很奇怪(可以將resetData()中的的代碼屏蔽來感覺一下);
* 處理:當爲0或者1時,重置數據
*/
private void changedVoiceNumber() {
int changeY = moveY - startY;
float changedVoice = changeY / (voiceChangedY * 1.0f);
if (changedVoice > 0) {
//音量增加
if (voiceNumber >= 1) {
voiceNumber = 1;
resetData();
return;
} else {
float afterChange = oldVoiceNumber + changedVoice;
if (afterChange >= 1) {
voiceNumber = 1;
resetData();
} else {
voiceNumber = afterChange;
}
}
} else if (changedVoice < 0) {
//音量減少
if (voiceNumber <= 0) {
voiceNumber = 0;
resetData();
return;
} else {
float afterChange = oldVoiceNumber + changedVoice;
if (afterChange <= 0) {
voiceNumber = 0;
resetData();
} else {
voiceNumber = afterChange;
}
}
} else if (changedVoice == 0) {
//音量不變
}
// String print = String.format("startY-->%d,moveY-->%d,voiceNumber-->%f,changeVoice-->%f", startY, moveY, voiceNumber, changedVoice);
// Log.e("ddd", print);
}
/**
* 當音量達到0或者1時,重置數據
*/
private void resetData() {
startY = moveY;
oldVoiceNumber = voiceNumber;
}
/**
* 繪製底層層圓圈
*
* @param path
*/
private void drawCirclePath(Path path) {
if (rectF == null) {
//半徑選取爲圖片寬度一半的1.3倍,可以調節
r = (int) (bWidth / 2 * 1.3f);
rectF = new RectF(-r, -r, r, r);
}
path.addArc(rectF, 0, 360);
}
/**
* 繪製音量
*
* @param path
*/
private void drawVoicePath(Path path) {
if (voiceRectf == null) {
//與底層圓圈保持一致
voiceRectf = new RectF(-r, -r, r, r);
voicePath = new Path();
}
voicePath.reset();
//道聽途說使用359.9可以測量的更準
path.addArc(voiceRectf, -90, 359.9f);
//通過PathMeasure來繪製部分的圓圈,表現出來就是繪製了弧形
PathMeasure measure = new PathMeasure(path, false);
//獲取圓的總長度
float length = measure.getLength();
//根據音量大小來繪製部分的弧形
measure.getSegment(0, voiceNumber * length, voicePath, true);
}
/**
* 小喇叭
*/
private void initBitmap() {
voice = BitmapFactory.decodeResource(getResources(), R.mipmap.voice);
bWidth = voice.getWidth();
bHeight = voice.getHeight();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
//音量從0-->1所需要移動的距離
voiceChangedY = h / 2;
}
/**
* 初始化畫筆
*/
private void initPaint() {
int stroke = 20;
if (outerCirclePaint == null) {
outerCirclePaint = new Paint();
outerCirclePaint.setStrokeWidth(stroke);
outerCirclePaint.setStyle(Paint.Style.STROKE);
outerCirclePaint.setColor(0x8089cff0);
outerCirclePaint.setAntiAlias(true);
}
if (voicePaint == null) {
voicePaint = new Paint();
voicePaint.setStrokeWidth(stroke);
voicePaint.setStyle(Paint.Style.STROKE);
voicePaint.setColor(0xff1d8ffe);
voicePaint.setAntiAlias(true);
}
}
}
結語
該View的繪製難度並不大,涉及了不少基礎,當做練習是一個很好的素材。
轉載請標明出處:http://blog.csdn.net/qq_26411333/article/details/52383186#t6