在做IM的需求中,难免要支持语音,那么肯定会有监听麦克风动态的改变话筒的音量的大小的view. 市面上大同小异都是仿微信的样子,而且实现方式也是序列帧动画.
这次根据效果图靠我们勤劳的双手来撸一个出来岂不是快哉…
1秒 ~ 2秒~ 3秒…来开撸.

  • 先看效果图

Android 怎么实现以指定语音模拟手机麦克风输入作为通话上行语音 安卓模拟麦克风输入_自定义view

浓浓的简约黑白风,ins风格…跑题了.我们这期就是要画上面那个话筒,和那个取消的回车键…
少啰嗦先看东西

Android 怎么实现以指定语音模拟手机麦克风输入作为通话上行语音 安卓模拟麦克风输入_canvas绘制_02

接下来是取消状态的样子

Android 怎么实现以指定语音模拟手机麦克风输入作为通话上行语音 安卓模拟麦克风输入_canvas绘制_03

大概就是这个样子,一只画笔就搞定了…(建议:如果赶工期,直接找设计切序列帧图,不敢工期也要找设计切图…?)
此时来一个GIF吧,但是清晰度就凑乎看吧,嫌不清晰的,把代码导入项目自己运行也行.

Android 怎么实现以指定语音模拟手机麦克风输入作为通话上行语音 安卓模拟麦克风输入_自定义话筒_04


好了,到此结束了,谢谢观看…(?对了还没撸呢…那么开始吧)
流程如下
  • 观察分析,抽取自定义属性
  • 考虑适配的问题,根据效果图的尺寸计算所有用到的绘制rectF的约束比例
  • 生成所有的绘制约束rectF
  • 由于有话筒动态的变化,根据画笔PorterDuffXfermode 找合适的来进行绘制

  1. 自定义属性抽取(根据效果图和你的业务扩展)
<declare-styleable name="VchatAudioStateView">
	        <attr name="audioTextColor" format="color" />
	        <attr name="audioTextCancelColor" format="color" />
	        <attr name="audioTubeColor" format="color" />
	        <attr name="audioTextSize" format="dimension" />
	        <attr name="audioBackgroundColor" format="color" />
	        <attr name="audioCorners" format="dimension" />
	        <attr name="audioStrokeColor" format="color" />
	        <attr name="audioStrokeWidth" format="dimension" />
	        <attr name="audioTubeFillColor" format="color" />
	        <attr name="audioNormalDesc" format="string" />
	        <attr name="audioCancelDesc" format="string" />
	        <attr name="audioTubeLineWidth" format="dimension"/>
    </declare-styleable>
  1. 计算约束比例
这个我是根据效果图的宽度当做基准比例为1,然后计算出我们要绘制的起点以及各个,绘制模块的坐标
比如我们先确定,画笔的下笔点,然后顺藤摸瓜,一次确定
/**
     * 默认宽度(宽位比例基准)
     * 高度为绘制基准(从底部小尾巴开始绘制)
     */
    private static final float VIEW_DEF_WIDTH = 176.F;
    /**
     * 默认高度
     */
    private static final float VIEW_DEF_HEIGHT = 145.F;

    /**
     * 话筒大碗的半径比例
     */
    private static final float SCALE_OF_BIG_CIRCLE_RADIO = 60.F / 580;
    /**
     * 话筒小尾巴的宽度比例
     */
    private static final float SCALE_OF_TAIL_WIDTH = 8.F / 580;
    /**
     * 话筒小尾巴的高度比例
     */
    private static final float SCALE_OF_TAIL_HEIGHT = 20.F / 580;
    /**
     * 话筒的倒角比例
     */
    private static final float SCALE_OF_TUBE_RECT_RADIO = 40.F / 580;
    /**
     * 话筒的高度比例
     */
    private static final float SCALE_OF_TUBE_RECT_HEIGHT = 114.F / 580;
    /**
     * 话筒的宽度比例
     */
    private static final float SCALE_OF_TUBE_RECT_WIDTH = 80.F / 580;
    /**
     * 开始绘制的y坐标比例
     */
    private static final float SCALE_OF_AUDIO_START = 274.F / 580;
    /**
     * 话筒开始绘制的比例起点y坐标
     */
    private static final float SCALE_OF_TUBE_RECT_START = 238.F / 580;
    .......省略........
  1. 确定了要绘制的比例,第三步骤就是绘制了,
绘制的时候化繁为简,canvas能绘制基本图形,什么圆,弧形,矩形.还有....(其他的不重要,我也不会其他的,这三个完全能搞定这个话筒)
@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawStrokeBg(canvas);
        drawFillBg(canvas);
        if (cancelMode) {
            drawTubeCancel(canvas);
            drawAudioCancelDesc(canvas, audioCancelDesc);
        } else {
            drawTube(canvas);
            drawAudioDesc(canvas, audioNormalDesc);
        }
    }
/**
     * 绘制话筒填充
     * 利用画布模式.来取交集
     *
     * @param canvas 画布
     */
    private void drawTubeFillRect(Canvas canvas) {
        audioTubePaint.setXfermode(xFermode);
        audioTubePaint.setColor(audioTubeFillColor);
        audioTubePaint.setStyle(Paint.Style.FILL);
        canvas.drawRect(tubeFillRect, audioTubePaint);
        audioTubePaint.setXfermode(null);
        audioTubePaint.setColor(audioTubeColor);
        audioTubePaint.setStyle(Paint.Style.STROKE);
    }

    /**
     * 绘制话筒
     *
     * @param canvas 画布
     */
    private void drawTubeRect(Canvas canvas) {
        canvas.drawRoundRect(tubeRect, tubeRectRadio + tailWidth, tubeRectRadio, audioTubePaint);
    }

    /**
     * 绘制话筒下面 大碗
     *
     * @param canvas 画布
     */
    private void drawBigCircle(Canvas canvas) {
        canvas.drawArc(bigCircleRectF, 0, 180, false, audioTubePaint);
    }

    /**
     * 绘制小尾巴
     *
     * @param canvas 画布
     */
    private void drawTail(Canvas canvas) {
        canvas.drawRect(tailRectF, audioTailPaint);
    }
上面就是绘制话筒的部分代码,我们把话筒分为静态部分和动态部分
静态部分就是那些黑色的框框,分别先绘制
1,小尾巴
2,大碗
3,话筒静态
4,动态背景
着重说下这个动态背景,这里我们就用到了画笔的X模式,那个经典的图,用
到的时候上网搜即可
这里我们用的模式是PorterDuff.Mode.MULTIPLY
我们让话筒里面灰色的矩形背景和静态的话筒相交,留下相交的部分,
把绘制的灰色矩形去掉不消交的角落即可.
经过上面的步骤就完成了话筒样式,解下来就是那个取消箭头的样式了,
这个似乎更简单了,这里只提一下,就是那个箭头的绘制
我们用path 先确定下箭头的点,然后往外面扩展两个点来定位箭头的开角度即可

代码虽然看着多,一共500多行吧,其实有300行是模板代码,还有100行注释吧,所以我们基本上可以用100行来实现这个话筒,下面就把完整的代码粘贴到下面.
/**
 * 文件描述:再次建议一定要UI切图即使你会画?,因为各司其职,你切你的图,
 * 用不用是我的事情?
 *
 * @author :feilong on 2018/10/22
 */
public class VchatAudioStateView extends View {
    private PorterDuffXfermode xFermode = new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY);

    /**
     * 默认宽度(宽位比例基准)
     * 高度为绘制基准(从底部小尾巴开始绘制)
     */
    private static final float VIEW_DEF_WIDTH = 176.F;
    /**
     * 默认高度
     */
    private static final float VIEW_DEF_HEIGHT = 145.F;

    /**
     * 话筒大碗的半径比例
     */
    private static final float SCALE_OF_BIG_CIRCLE_RADIO = 60.F / 580;
    /**
     * 话筒小尾巴的宽度比例
     */
    private static final float SCALE_OF_TAIL_WIDTH = 8.F / 580;
    /**
     * 话筒小尾巴的高度比例
     */
    private static final float SCALE_OF_TAIL_HEIGHT = 20.F / 580;
    /**
     * 话筒的倒角比例
     */
    private static final float SCALE_OF_TUBE_RECT_RADIO = 40.F / 580;
    /**
     * 话筒的高度比例
     */
    private static final float SCALE_OF_TUBE_RECT_HEIGHT = 114.F / 580;
    /**
     * 话筒的宽度比例
     */
    private static final float SCALE_OF_TUBE_RECT_WIDTH = 80.F / 580;
    /**
     * 开始绘制的y坐标比例
     */
    private static final float SCALE_OF_AUDIO_START = 274.F / 580;
    /**
     * 话筒开始绘制的比例起点y坐标
     */
    private static final float SCALE_OF_TUBE_RECT_START = 238.F / 580;
    /**
     * 文字开始绘制的y坐标比例
     */
    private static final float SCALE_OF_DESC_START = 370.F / 580;

    /**
     * 取消箭头的总高度 40.F / 140 (高度比例基准dp)
     */
    private static final float SCALE_OF_CANCEL_HEIGHT = 40.F / 140;
    /**
     * 取消箭头的总宽度比例
     */
    private static final float SCALE_OF_CANCEL_WIDTH = 24.F / 140;


    private float bigCircleRadio;
    private float tailWidth;
    private float tailHeight;
    private float tubeRectRadio;
    private float tubeRectHeight;
    private float tubeRectWidth;
    private float cancelTotalHeight;
    private float cancelTotalWidth;
    /**
     * 高度为基准,开始绘制的小尾巴中间点坐标
     */
    private AudioPoint startPoint;
    private AudioPoint audioTextPoint;
    private AudioPoint tuebRectStartPoint;

    private int audioTextColor;
    private int audioTextSize;
    private int audioBackgroundColor;
    private int audioCorners;
    private int audioStrokeColor;
    private int audioStrokeWidth;
    private int audioTubeFillColor;
    private int audioTextCancelColor;
    private int audioTubeColor;
    private int audioViewHeight;
    private int audioViewWidth;
    private int audioTubeLineWidth;

    private Paint audioFillPaint;
    private Paint audioTextPaint;
    private Paint audioTextCancelPaint;
    private Paint audioStrokePaint;
    private Paint audioTubePaint;
    private Paint audioTubeFillPaint;
    private Paint audioTailPaint;

    private RectF fillRoundRect;
    private RectF strokeRoundRect;
    private RectF tailRectF;
    private RectF bigCircleRectF;
    private RectF tubeRect;
    private RectF tubeFillRect;
    private RectF cancelLeftRectF;
    private RectF cancelArcRectF;
    private RectF cancelRightRectF;
    private Path arrowPath;

    private String audioNormalDesc;
    private String audioCancelDesc;

    /**
     * 绘制的模式取消或者正常
     */
    private boolean cancelMode;
    /**
     * 音量输入的大小转变成距离
     */
    private float volumeHeight;
    /**
     * 最大音量的高度
     */
    private float volumeMaxHeight;
    private float tubeFillTop;


    public VchatAudioStateView(Context context) {
        this(context, null);
    }

    public VchatAudioStateView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VchatAudioStateView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttrs(context, attrs, defStyleAttr);
        setBackgroundColor(audioBackgroundColor);
        initPaints();

    }

    /**
     * 初始化画笔
     */
    private void initPaints() {
        audioTextPaint = creatPaint(audioTextColor, audioTextSize, Paint.Style.FILL, 0);
        audioTextCancelPaint = creatPaint(audioTextCancelColor, audioTextSize, Paint.Style.FILL, 0);
        audioStrokePaint = creatPaint(audioStrokeColor, 0, Paint.Style.FILL, 0);
        audioTubePaint = creatTubePaint(audioTubeColor, 0, Paint.Style.STROKE, 0);
        audioTubeFillPaint = creatPaint(audioTubeFillColor, 0, Paint.Style.FILL, 0);
        audioFillPaint = creatPaint(audioBackgroundColor, 0, Paint.Style.FILL, 0);
        audioTailPaint = creatTubePaint(audioTubeColor, 0, Paint.Style.FILL, 0);
    }

    /**
     * 初始化自定义属性
     *
     * @param context      上线文
     * @param attrs        属性
     * @param defStyleAttr 默认style
     */
    private void initAttrs(Context context, AttributeSet attrs, int defStyleAttr) {
        TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.VchatAudioStateView, defStyleAttr, R.style.DefaultVchatAudioStateStyle);
        int count = typedArray.getIndexCount();
        for (int i = 0; i < count; i++) {
            int attr = typedArray.getIndex(i);
            switch (attr) {
                case R.styleable.VchatAudioStateView_audioTextColor:
                    audioTextColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.VchatAudioStateView_audioTextSize:
                    audioTextSize = typedArray.getDimensionPixelSize(attr, 0);
                    break;
                case R.styleable.VchatAudioStateView_audioBackgroundColor:
                    audioBackgroundColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.VchatAudioStateView_audioCorners:
                    audioCorners = typedArray.getDimensionPixelOffset(attr, 0);
                    break;
                case R.styleable.VchatAudioStateView_audioStrokeColor:
                    audioStrokeColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.VchatAudioStateView_audioStrokeWidth:
                    audioStrokeWidth = typedArray.getDimensionPixelOffset(attr, 0);
                    break;
                case R.styleable.VchatAudioStateView_audioTubeFillColor:
                    audioTubeFillColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.VchatAudioStateView_audioTubeColor:
                    audioTubeColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.VchatAudioStateView_audioTextCancelColor:
                    audioTextCancelColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.VchatAudioStateView_audioTubeLineWidth:
                    audioTubeLineWidth = typedArray.getDimensionPixelOffset(attr, Color.BLACK);
                    break;
                case R.styleable.VchatAudioStateView_audioNormalDesc:
                    audioNormalDesc = typedArray.getString(attr);
                    break;
                case R.styleable.VchatAudioStateView_audioCancelDesc:
                    audioCancelDesc = typedArray.getString(attr);
                    break;
                default:
                    break;
            }
        }
        typedArray.recycle();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        audioViewHeight = h;
        audioViewWidth = w;

        bigCircleRadio = w * SCALE_OF_BIG_CIRCLE_RADIO;
        tailWidth = w * SCALE_OF_TAIL_WIDTH;
        tailHeight = w * SCALE_OF_TAIL_HEIGHT;
        tubeRectRadio = w * SCALE_OF_TUBE_RECT_RADIO;
        tubeRectWidth = w * SCALE_OF_TUBE_RECT_WIDTH;
        tubeRectHeight = w * SCALE_OF_TUBE_RECT_HEIGHT;
        cancelTotalHeight = h * SCALE_OF_CANCEL_HEIGHT;
        cancelTotalWidth = h * SCALE_OF_CANCEL_WIDTH;

        startPoint = new AudioPoint(w * 0.5F, w * SCALE_OF_AUDIO_START);
        audioTextPoint = new AudioPoint(w * 0.5F, w * SCALE_OF_DESC_START);
        tuebRectStartPoint = new AudioPoint(w * 0.5F, w * SCALE_OF_TUBE_RECT_START);

        // 给画笔设定宽度
        audioTubePaint.setStrokeWidth(tailWidth);
        audioTubeFillPaint.setXfermode(xFermode);

        strokeRoundRect = new RectF(0, 0, audioViewWidth, audioViewHeight);
        fillRoundRect = new RectF(audioStrokeWidth, audioStrokeWidth, audioViewWidth - audioStrokeWidth, audioViewHeight - audioStrokeWidth);

        float tailLeft = audioViewWidth * 0.5F - tailWidth * 0.5F;
        float tailTop = startPoint.y - tailHeight;
        float tailRight = audioViewWidth * 0.5F + tailWidth * 0.5F;
        float tailBottom = startPoint.y;
        tailRectF = new RectF(tailLeft, tailTop, tailRight, tailBottom);

        //组装外面的大碗
        float bigCircleLeft = audioViewWidth * 0.5F - bigCircleRadio;
        float bigCircleRight = audioViewWidth * 0.5F + bigCircleRadio;
        float bigCircleBottom = tailRectF.top;
        float bigCircleTop = bigCircleBottom - bigCircleRadio * 2;
        bigCircleRectF = new RectF(bigCircleLeft, bigCircleTop, bigCircleRight, bigCircleBottom);

        // 组装话筒的圆角矩形
        float tubeLeft = audioViewWidth * 0.5F - tubeRectWidth * 0.5F;
        float tubeRight = audioViewWidth * 0.5F + tubeRectWidth * 0.5F;
        float tubeBottom = tuebRectStartPoint.y;
        float tubeTop = tubeBottom - tubeRectHeight;
        tubeRect = new RectF(tubeLeft, tubeTop, tubeRight, tubeBottom);

        // 记录最大音量的距离
        volumeMaxHeight = tubeRect.bottom - tubeRect.top;

        // 组装话筒中根据语音音量大小要改变的灰色填充北京,其中要改变的是矩形的高度范围
        float tubeFillLeft = tubeLeft + tailWidth * 0.5F;
        float tubeFillRight = tubeRight - tailWidth * 0.5F;
        float tubeFillBottom = tubeBottom - tailWidth * 0.5F;

        tubeFillTop = (tubeTop + tailWidth * 0.5F) + volumeHeight;
        tubeFillRect = new RectF(tubeFillLeft, tubeFillTop, tubeFillRight, tubeFillBottom);

        // 组装取消模式左边的矩形区域
        float cancelLeft = audioViewWidth * 0.5F - cancelTotalWidth * 0.5F;
        float cancelRight = cancelLeft + tailWidth * 1.F;
        float cancelBottom = audioViewHeight * 0.5F;
        float cancelTop = cancelBottom - cancelTotalHeight + tubeRectRadio;
        cancelLeftRectF = new RectF(cancelLeft, cancelTop, cancelRight, cancelBottom);

        // 组织取消模式上面的弧形区域
        float cancelArcLeft = cancelLeftRectF.left + tailWidth * 0.5F;
        float cancelArcTop = audioViewHeight * 0.5F - cancelTotalHeight;
        float cancelArcRight = cancelArcLeft + tubeRectRadio * 2 + tailWidth * 0.5F;
        float cancelArcBottom = cancelArcTop + tubeRectRadio * 2;
        cancelArcRectF = new RectF(cancelArcLeft, cancelArcTop, cancelArcRight, cancelArcBottom);

        // 组装取消模式右边的矩形区域
        float cancelRightLeft = cancelArcRectF.right - tailWidth * 0.5F;
        float cancelRightTop = cancelLeftRectF.top;
        float cancelRightRight = cancelRightLeft + tailWidth;
        float cancelRightBottom = cancelLeftRectF.bottom - 0.5F * (cancelLeftRectF.bottom - cancelLeftRectF.top);
        cancelRightRectF = new RectF(cancelRightLeft, cancelRightTop, cancelRightRight, cancelRightBottom);

        // 组装箭头数据
        float arrowTopX = cancelRightRectF.left + tailWidth * 0.5F;
        float arrowTopY = cancelRightRectF.bottom + tailWidth * 0.5F;
        float cons = 2.5F;
        float startX = arrowTopX - tailWidth * cons;
        float startY = arrowTopY - tailWidth * cons;
        float endX = arrowTopX + tailWidth * cons;
        float endY = arrowTopY - tailWidth * cons;
        arrowPath = new Path();
        arrowPath.moveTo(startX, startY);
        arrowPath.lineTo(arrowTopX, arrowTopY);
        arrowPath.lineTo(endX, endY);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int widthMeasure = 0;
        int heightMeasure = 0;

        if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
            widthMeasure = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, VIEW_DEF_WIDTH, getContext().getResources().getDisplayMetrics());
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthMeasure, MeasureSpec.EXACTLY);
        }

        if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
            heightMeasure = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, VIEW_DEF_HEIGHT, getResources().getDisplayMetrics());
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightMeasure, MeasureSpec.EXACTLY);
        }

        int viewMeasureSpec = widthMeasure - heightMeasure >= 0 ? heightMeasureSpec : widthMeasureSpec;
        super.onMeasure(viewMeasureSpec, viewMeasureSpec);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawStrokeBg(canvas);
        drawFillBg(canvas);
        if (cancelMode) {
            drawTubeCancel(canvas);
            drawAudioCancelDesc(canvas, audioCancelDesc);
        } else {
            drawTube(canvas);
            drawAudioDesc(canvas, audioNormalDesc);
        }
    }

    /**
     * 创建画笔
     *
     * @return 画笔
     */
    private Paint creatPaint(int paintColor, int textSize, Paint.Style style, int lineWidth) {
        Paint paint = new Paint();
        paint.setColor(paintColor);
        paint.setAntiAlias(true);
        paint.setStrokeWidth(lineWidth);
        paint.setDither(true);
        paint.setTextSize(textSize);
        paint.setStyle(style);
        paint.setStrokeCap(Paint.Cap.ROUND);
        paint.setStrokeJoin(Paint.Join.ROUND);
        return paint;
    }


    private Paint creatTubePaint(int paintColor, int textSize, Paint.Style style, int lineWidth) {
        Paint paint = new Paint();
        paint.setColor(paintColor);
        paint.setAntiAlias(true);
        paint.setStrokeWidth(lineWidth);
        paint.setDither(true);
        paint.setTextSize(textSize);
        paint.setStyle(style);
        return paint;
    }


    /**
     * 绘制描述文案
     *
     * @param canvas    画不
     * @param audioDesc 文案描述
     */
    private void drawAudioDesc(Canvas canvas, String audioDesc) {
        AudioPoint audioPoint = measureTextSize(audioTextPaint, audioDesc);
        float textY = audioTextPoint.y + audioPoint.y;
        float textX = audioViewWidth * 0.5F - audioPoint.x * 0.5F;
        canvas.drawText(audioDesc, textX, textY, audioTextPaint);
    }


    private void drawAudioCancelDesc(Canvas canvas, String audioDesc) {
        AudioPoint audioPoint = measureTextSize(audioTextCancelPaint, audioDesc);
        float textY = audioTextPoint.y + audioPoint.y;
        float textX = audioViewWidth * 0.5F - audioPoint.x * 0.5F;
        canvas.drawText(audioDesc, textX, textY, audioTextCancelPaint);
    }

    /**
     * 绘制取消的图案
     *
     * @param canvas 画布
     */
    private void drawTubeCancel(Canvas canvas) {
        drawCancelLeftRect(canvas);
        drawCancelArc(canvas);
        drawCancelRightRect(canvas);
        drawCancelArrowPath(canvas);
    }

    /**
     * 绘制箭头
     *
     * @param canvas 画布
     */
    private void drawCancelArrowPath(Canvas canvas) {
        audioTailPaint.setStyle(Paint.Style.STROKE);
        audioTailPaint.setStrokeWidth(tailWidth);
        canvas.drawPath(arrowPath, audioTailPaint);
        audioTailPaint.setStrokeWidth(0);
        audioTailPaint.setStyle(Paint.Style.FILL);
    }

    /**
     * 绘制取消录音右边的矩形
     *
     * @param canvas 画布
     */
    private void drawCancelRightRect(Canvas canvas) {
        canvas.drawRect(cancelRightRectF, audioTailPaint);
    }

    /**
     * 绘制取消录音上面的半圆
     *
     * @param canvas 画布
     */
    private void drawCancelArc(Canvas canvas) {
        audioTailPaint.setStyle(Paint.Style.STROKE);
        audioTailPaint.setStrokeWidth(tailWidth);
        canvas.drawArc(cancelArcRectF, -180, 180, false, audioTailPaint);
        audioTailPaint.setStrokeWidth(0);
        audioTailPaint.setStyle(Paint.Style.FILL);

    }

    /**
     * 绘制取消录音左边的矩形
     *
     * @param canvas 画布
     */
    private void drawCancelLeftRect(Canvas canvas) {
        canvas.drawRect(cancelLeftRectF, audioTailPaint);
    }

    /**
     * 绘制话筒
     *
     * @param canvas 画布
     */
    private void drawTube(Canvas canvas) {
        drawTail(canvas);
        drawBigCircle(canvas);
        drawTubeRect(canvas);
        drawTubeFillRect(canvas);

    }

    /**
     * 绘制话筒填充
     * 利用画布模式.来取交集
     *
     * @param canvas 画布
     */
    private void drawTubeFillRect(Canvas canvas) {
        audioTubePaint.setXfermode(xFermode);
        audioTubePaint.setColor(audioTubeFillColor);
        audioTubePaint.setStyle(Paint.Style.FILL);
        canvas.drawRect(tubeFillRect, audioTubePaint);
        audioTubePaint.setXfermode(null);
        audioTubePaint.setColor(audioTubeColor);
        audioTubePaint.setStyle(Paint.Style.STROKE);
    }

    /**
     * 绘制话筒
     *
     * @param canvas 画布
     */
    private void drawTubeRect(Canvas canvas) {
        canvas.drawRoundRect(tubeRect, tubeRectRadio + tailWidth, tubeRectRadio, audioTubePaint);
    }

    /**
     * 绘制话筒下面 大碗
     *
     * @param canvas 画布
     */
    private void drawBigCircle(Canvas canvas) {
        canvas.drawArc(bigCircleRectF, 0, 180, false, audioTubePaint);
    }

    /**
     * 绘制小尾巴
     *
     * @param canvas 画布
     */
    private void drawTail(Canvas canvas) {
        canvas.drawRect(tailRectF, audioTailPaint);
    }

    /**
     * 绘制话筒填充色
     *
     * @param canvas 画布
     */
    private void drawFillBg(Canvas canvas) {
        canvas.drawRoundRect(fillRoundRect, audioCorners, audioCorners, audioFillPaint);
    }

    /**
     * 绘制描边背景
     *
     * @param canvas 画布
     */
    private void drawStrokeBg(Canvas canvas) {
        canvas.drawRoundRect(strokeRoundRect, audioCorners, audioCorners, audioStrokePaint);
    }


    /**
     * 坐标对象
     */
    class AudioPoint {
        public float x;
        public float y;

        AudioPoint() {
        }

        AudioPoint(float x, float y) {
            this.x = x;
            this.y = y;
        }
    }


    /**
     * 返回文字的宽和高,x代表宽,y代表高度
     *
     * @param textPaint 画笔
     * @param text      文字
     * @return 文字的宽和高
     */
    private AudioPoint measureTextSize(Paint textPaint, String text) {
        AudioPoint point = new AudioPoint();
        if (!TextUtils.isEmpty(text)) {
            point.x = textPaint.measureText(text);
            Paint.FontMetrics fm = textPaint.getFontMetrics();
            point.y = (float) Math.ceil(fm.descent - fm.top);
        }
        return point;
    }

    /**
     * 设置绘制的模式,是否为取消模式
     *
     * @param cancelMode 模式
     */
    public void setCancelMode(boolean cancelMode) {
        this.cancelMode = cancelMode;
        invalidate();
    }

    /**
     * 音量大小百分比
     *
     * @param percent 百分比
     */
    public void updateVolume(float percent) {
        volumeHeight = volumeMaxHeight - volumeMaxHeight * percent;
        tubeFillRect.top = tubeFillTop + volumeHeight;
        invalidate();
    }

    public void setAudioNormalDesc(String audioNormalDesc) {
        this.audioNormalDesc = audioNormalDesc;
        invalidate();
    }

    public void setAudioCancelDesc(String audioCancelDesc) {
        this.audioCancelDesc = audioCancelDesc;
    }
}

能坚持到最后说明你耐力真的 可以啊,撸完了,到此结束,荆轲刺秦王…(怀念了可惜节目停播了)