标签:
手机直播一般都会通过移动屏幕来调节音量的大小,本篇只实现了图例,并不能改变音量。
先看效果:
需要的素材:小喇叭图片,点击这里获取
如果你对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
标签:
原文地址:http://blog.csdn.net/qq_26411333/article/details/52383186