首页 > 代码库 > Android 音量调节View

Android 音量调节View

导语

手机直播一般都会通过移动屏幕来调节音量的大小,本篇只实现了图例,并不能改变音量
先看效果:
技术分享

需要的素材:小喇叭图片,点击这里获取
技术分享

预热

如果你对Path,PathMeasure,RectF,Canvas等不适很了解的话,强烈建议看这位哥们的教程:
点击这里查看教程
如果你将这哥们的十几篇帖子都看完了的话,这个View实际上是非常简单的

步骤介绍

用动态图来介绍:

技术分享

这里用文字翻译下:

  1. 将小喇叭画到中心位置
  2. 围绕着喇叭画一个圆圈,浅色的
  3. 画一个圆弧,深色的
  4. 根据触摸的位置来改变圆弧的大小

分解之后,发现并没有什么难度(可能本身就没有什么难度),下面来看每一步的操作,最后会将整个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

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    Android 音量调节View