Android麦克风录音带音量大小动态显示的圆形自定义View

1、所谓无图无真相,先上效果图。我们要实现的就是中间那个录音的按钮,周边会显示一圈音量大小的波形

2、VolumCircleBar继承自View,我们进行了自定义,代码如下

package com.rdinfo.ccenglish.ui.ccprofile.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;

import com.panshi.xuexiao.R;
/**
 * 麦克风音量圆形按钮
 * @author hiphonezhu@gmail.com
 * @version [CCEnglish, 2014-7-25]
 */
public class VolumCircleBar extends View
{
    private double volumRate; // 音量百分比
    private boolean isRecording; // 录音标志
    private Object lock = new Object();
    private Thread uiThread;
    private Paint mPaint;
    private RectF arcRect;
    private Matrix matrix = new Matrix();
    private final int VOLUM_INDICATE_LENGTH = 5; // 音量大小线长度
    private final int CIRCLE_INNER_DISTANCE_TO_OUTSIDE = 5; // 内切圆距离外圆的距离
    public VolumCircleBar(Context context)
    {
        this(context, null);
    }
    
    public VolumCircleBar(Context context, AttributeSet attrs)
    {
        this(context, attrs, 0);
    }
    
    public VolumCircleBar(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.VolumCircleBar, defStyle, 0); 
        init(typedArray);
    }
    
    private int recordingColor; // 录音背景色
    private int stoppedColor; // 停止背景色
    private Bitmap centerRes; // 中间麦克风图片
    private int totalBlockCount; // 块数量
    private int spliteAngle; // 块之间的间隔角度大小
    private int circleWidth; // 直径
    /**
     * 初始化
     */
    private void init(TypedArray typedArray)
    {
        for (int i = 0; i < typedArray.length(); i++)  
        {  
            int attr = typedArray.getIndex(i);  
            switch (attr)
            {
                case R.styleable.VolumCircleBar_recordingColor:
                    recordingColor = typedArray.getColor(i, Color.GREEN);
                    break;
                case R.styleable.VolumCircleBar_stoppedColor:
                    stoppedColor = typedArray.getColor(i, Color.GRAY);
                    break;
                case R.styleable.VolumCircleBar_centerRes:
                    centerRes = BitmapFactory.decodeResource(getContext().getResources(), typedArray.getResourceId(i, R.drawable.ic_launcher));
                    break;
                case R.styleable.VolumCircleBar_blockCount:
                    totalBlockCount = typedArray.getInt(i, 50);
                    break;
                case R.styleable.VolumCircleBar_splitAngle:
                    spliteAngle = typedArray.getInt(i, 2);
                    break;
            }
        }  
        typedArray.recycle();  
        uiThread = Thread.currentThread();
        mPaint = new Paint();
        if (spliteAngle * totalBlockCount > 360)
        {
            throw new IllegalArgumentException("spliteAngle * blockCount > 360, while the result should be less than 360.");
        }
        
        // debug for test
        isRecording = true;
        volumRate = 0.5;
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        // 直径
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        circleWidth = width > height? width : height;
        if (arcRect == null)
        {
            arcRect = new RectF(CIRCLE_INNER_DISTANCE_TO_OUTSIDE, CIRCLE_INNER_DISTANCE_TO_OUTSIDE, 
                    circleWidth - CIRCLE_INNER_DISTANCE_TO_OUTSIDE, circleWidth - CIRCLE_INNER_DISTANCE_TO_OUTSIDE); // 音量显示区域, 内偏移几个像素
            // 图片处理矩阵
            initBitmapMatrix();
        }
        // 强制设置view大小
        setMeasuredDimension(circleWidth, circleWidth);
    }
    
    /**
     * 中间图片压缩处理
     */
    private void initBitmapMatrix()
    {
        float innerCircleRadius = (circleWidth - 2 * (VOLUM_INDICATE_LENGTH + CIRCLE_INNER_DISTANCE_TO_OUTSIDE)) / 2f; // 内圆的半径
        float innerRectangleWidth = (float)Math.cos((Math.PI / 180) * 45) * innerCircleRadius * 2; // 内圆的内切正方形的边长
        float translateOffset = VOLUM_INDICATE_LENGTH + CIRCLE_INNER_DISTANCE_TO_OUTSIDE + innerCircleRadius - innerRectangleWidth / 2; // 偏移的offset
        if (centerRes.getWidth() > (innerRectangleWidth) || centerRes.getHeight() > (innerRectangleWidth))
        {
            // 图片宽度或高度大于(直径-内偏移), 等比压缩
            if (centerRes.getWidth() > centerRes.getHeight())
            {
                // 按照宽度压缩
                float ratio = innerRectangleWidth / centerRes.getWidth();
                matrix.postScale(ratio, ratio);
                float translateY = (innerRectangleWidth - (centerRes.getHeight() * ratio)) / 2f;
                // 在纵坐标方向上进行偏移,以保证图片居中显示
                matrix.postTranslate(translateOffset, translateY + translateOffset);
            }
            else
            {
                // 按照高度压缩
                float ratio = innerRectangleWidth / (centerRes.getHeight() * 1.0f);
                matrix.postScale(ratio, ratio);
                float translateX = (innerRectangleWidth - (centerRes.getWidth() * ratio)) / 2f;
                // 在横坐标方向上进行偏移,以保证图片居中显示
                matrix.postTranslate(translateX + translateOffset, translateOffset);
            }
        }
        else
        {
            // 当图片的宽高都小于屏幕宽高时,直接让图片居中显示
            float translateX = (innerRectangleWidth - centerRes.getWidth()) / 2f;
            float translateY = (innerRectangleWidth - centerRes.getHeight()) / 2f;
            matrix.postTranslate(translateX + translateOffset, translateY + translateOffset);
        }
    }
    
    /**
     * 设置音量百分比
     * @param rate
     */
    public void updateVolumRate(double rate)
    {
        synchronized (lock)
        {
            this.volumRate = rate;
            if (Thread.currentThread() != uiThread)
            {
                postInvalidate();
            }
            else
            {
                invalidate();
            }
        }
    }
    
    /**
     * 开始、停止录音
     */
    public void toggleRecord()
    {
        synchronized (lock)
        {
            isRecording = !isRecording;
            if (Thread.currentThread() != uiThread)
            {
                postInvalidate();
            }
            else
            {
                invalidate();
            }
        }
    }
    
    @Override
    protected void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);
        canvas.drawColor(Color.TRANSPARENT); 
        synchronized (lock)
        {
            if (isRecording) // 正在录音
            {
                //1.绘制绿色圆圈
                mPaint.setAntiAlias(true); //消除锯齿   
                mPaint.setColor(recordingColor);
                mPaint.setStrokeWidth(1);
                mPaint.setStyle(Paint.Style.FILL); // 填充
                canvas.drawCircle(circleWidth / 2f, circleWidth / 2f, circleWidth / 2f, mPaint);
                //2.根据音量百分比、块数量、块间隔大小计算角度动态绘制音量大小
                // 计算块的角度
                float blockAngle = (360 * 1.0f - spliteAngle * totalBlockCount) / totalBlockCount;
                int drawBlockCount = (int)(totalBlockCount * volumRate); // 绘制的block数量
                mPaint.setStrokeWidth(VOLUM_INDICATE_LENGTH);
                mPaint.setColor(stoppedColor);
                mPaint.setStyle(Paint.Style.STROKE); // 空心
                for (int i = 0; i < drawBlockCount; i++)
                {
                    canvas.drawArc(arcRect, i * (blockAngle + spliteAngle) - 90, blockAngle, false, mPaint);
                }
            } 
            else // 录音停止
            {
                //1.绘制灰色圆圈
                mPaint.setColor(stoppedColor);
                mPaint.setStrokeWidth(1);
                mPaint.setStyle(Paint.Style.FILL); // 填充
                canvas.drawCircle(circleWidth / 2f, circleWidth / 2f, circleWidth / 2f, mPaint);
            }
        }
        // 绘制中间话筒
        canvas.drawBitmap(centerRes, matrix, null);
    }
}

今天时间不多,没看明白的留言吧。

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: 在WPF中,可以通过使用麦克风录音的API以及图像处理技术来动态显示音量大小。 首先,需要使用WPF中的MediaCapture类来获取麦克风的录音数据。该类提供了访问音频设备的功能,并可以获取到实时的音频数据。 接下来,可以使用音频处理技术来计算音频数据的音量大小。可以将录制的音频数据转换为时域数据,并对数据进行幅度计算,该计算通常使用Root Mean Square(RMS)方法来度量音量大小。也可以使用其他算法来计算音量大小,如傅里叶变换等。 然后,将计算得到的音量大小映射到WPF界面上合适的控件,例如ProgressBar或者Slider控件。可以根据音量大小来调整控件的显示,从而实现动态显示音量大小的效果。 在每次音频数据更新时,更新对应的控件来显示最新的音量大小。可以使用定时器或者事件来触发更新控件的操作,保证界面能够实时呈现音量的变化。 另外,为了更好地反映音量大小的变化,可以使用颜色渐变或者动画效果来提升用户体验。例如,通过改变控件的背景色或者边框颜色来显示不同的音量范围,或者使用缓慢变化的动画效果来平滑地显示音量的变化。 总之,通过获取麦克风录音的数据,结合音频处理技术和WPF的图形界面特性,可以实现麦克风录音音量大小动态显示。这样用户在录音过程中可以实时了解到音量的变化情况,提升了用户体验和操作的可视化效果。 ### 回答2: 在WPF中,我们可以通过使用麦克风录音功能来动态显示音量大小。首先,我们需要使用WPF中的音频捕获设备类来获取麦克风的音频输入。然后,我们可以使用一个定时器或者后台线程来不断获取当前的音频输入数据,并计算其音量大小。 一种常见的方法是使用WPF中的均方根(RMS)算法来计算音频的音量大小。这个算法可以通过对音频输入数据的平方和进行平均,并取平方根来获得音量大小。我们可以使用WPF中的MediaFoundation框架来获取音频输入数据,并计算其均方根值。 然后,我们可以将计算出的音量大小绑定到一个WPF控件(例如ProgressBar)的Value属性上,从而实现动态显示音量大小。我们可以通过改变ProgressBar的长度或颜色来反映音量大小的变化。 除了实时显示音量大小外,我们还可以将音量大小保存在一个变量中,并在需要时使用该变量进行其他操作,例如调整音量显示的阈值,触发其他事件等。 最后,我们需要确保在应用程序关闭或不再需要录音时,正确释放麦克风录音设备和相关资源,以避免内存泄漏和性能问题。 在WPF中实现麦克风录音的音量大小动态显示并不复杂,只需使用适当的音频捕获类、算法和WPF控件绑定即可。 ### 回答3: 在WPF中实现麦克风录音时,可以通过动态显示录音音量大小来提供更好的用户体验。下面是一个简单的实现步骤: 1. 获取麦克风音量:在录音过程中,可以使用WPF的音频API来获取麦克风录音的音量。可以通过调用相关的API方法,如`AudioMeterInformation`类来获取当前麦克风录音的音量大小。 2. 监听麦克风音量变化:在WPF中,可以使用`DispatcherTimer`来定时监听麦克风音量的变化。可以设置一个合适的时间间隔,如100毫秒,定时刷新录音音量显示。 3. 动态显示录音音量:在WPF中,可以使用ProgressBar或者Slider等控件来显示录音音量大小。可以将获取到的麦克风音量值转换为适合控件显示的值范围,如0-100。然后将转换后的值赋给显示音量的控件的Value属性,即可动态显示录音音量大小。 总结:通过上述步骤,我们可以在WPF中实现麦克风录音音量大小动态显示。用户可以通过该功能直观地了解到当前录音的音量大小,从而更好地控制和调整录音设备。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值