博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android 全仿To圈儿录音界面实现
阅读量:2020 次
发布时间:2019-04-28

本文共 14089 字,大约阅读时间需要 46 分钟。

我们先来看看To圈(QQ,微信等其他大部分软件也是大同小异)的注册录音界面运行截图:

(⊙o⊙)…为了实现这个效果还是花了一番功夫的,

主要难点有以下方面:

1、跟随音量变化的话筒。

这个话筒一开始感觉是最头痛的部分,完全不知道从何开始实现。首先直接用图片肯定是不行的,想实现我最后达到的效果需要12张图片,这太占资源了直接GG。然后又想到用遮罩实现,但是仔细观察可以发现话筒的圆形进度条周围是透明的,也就是说如果用遮罩,除非完美重合(重合就得考虑屏幕适配问题了,这就是个大难题了)。最后我想出的办法有点取巧吧:用一个竖向进度条实现话筒的进度部分,然后下面的Y字形直接用canvas画(为了使其看起来像个话筒花费了大量代码进行计算...)。这样一来看起来差不多,而且规避了屏幕适配问题,也不需要加载那么多图片然后轮着换了。但这肯定不是最好的方法,所以跪求更高雅的实现的方法!!!

2、圈内的蓝色圆弧计时部分。

3、手势判断。

这么一总结感觉1比2和3加起来都难得多..

实现过程:

首先是界面实现

activity_register.xml

    
<!-- 录音显示UI层 --> <include android:id="@+id/layout_register_popup" android:layout_width="fill_parent" android:layout_height="fill_parent" layout="@layout/layout_recode_popwindow" android:gravity="center" android:visibility="gone" /></RelativeLayout>

 不重要的部分没有贴上去,按下完成注册会出现黑色录音框的布局在<include>标签里引用。这里暂时设置为不可见。 

下面是黑色录音框布局:

layout_recode_popwindow.xml

实现效果如下:

代码实现

从上面的界面中可以看到有一个自定义的View:RecodePopWindowCircle.java

package com.whale.nangua.toquan.view;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Rect;import android.graphics.RectF;import android.util.AttributeSet;import android.view.LayoutInflater;import android.view.View;import android.widget.ProgressBar;import android.widget.RelativeLayout;import com.whale.nangua.toquan.R;/** * Created by nangua on 2016/7/29. */public class RecodePopWindowCircle extends RelativeLayout {    ProgressBar progressbar_register_recode;//进度条    int width; //控件总高    int height; //控件总宽     boolean IS_SHOW_RECODING = true; //默认设置为true    float scale = this.getResources().getDisplayMetrics().density; //获得像素    public RecodePopWindowCircle(Context context, AttributeSet attrs) {        super(context, attrs);        //在构造函数中将Xml中定义的布局解析出来。        LayoutInflater.from(context).inflate(R.layout.layout_recode_circlepopwindow, this, true);        init();    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        super.onLayout(changed, l, t, r, b);        progressbar_register_recode = (ProgressBar) this.findViewById(R.id.progressbar_register_recode);        width = getWidth();        height = getHeight();    }    private void init() {    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        Paint paint = new Paint(); //画笔        if (IS_SHOW_RECODING) {            progressbar_register_recode.setVisibility(View.VISIBLE);            //画话筒下面的Y形,整体下移5个像素点5*scale            paint.setColor(Color.WHITE);            paint.setStrokeWidth(6);    //宽度            paint.setAntiAlias(true);   //抗锯齿            paint.setStyle(Paint.Style.STROKE); //设置空心            RectF oval = new RectF();                     //RectF对象            int xandy = width / 2;            int r = (int) ( 8 * scale); //进度条底部半径            int space = (int) (5 * scale); //圆弧与底部进度条的间隔            oval.left = xandy - r - space;                              //左边            oval.top = xandy - 2 * r - space + 5 * scale;                                   //上边            oval.right = xandy + r + space;                             //右边            oval.bottom = xandy + space + 5 * scale;            //画弧形            canvas.drawArc(oval, 20, 140, false, paint);            //画线            int lineLength = (int) (10 * scale);            canvas.drawLine(xandy, xandy + space + 5 * scale, xandy, xandy + space + lineLength + 5 * scale, paint);            //画弧形的圆点            //因为位置计算没有精确化所以做了些微调            int pointx = (int) (Math.cos(xandy) + r + space);            paint.setStyle(Paint.Style.FILL); //设置实心            //右边缘圆点            canvas.drawCircle(xandy + pointx - 1 * scale, xandy + 2 * scale, 3, paint);            //左边缘圆点            canvas.drawCircle(xandy - pointx + 1 * scale, xandy + 2 * scale, 3, paint);            //直线下边缘圆点            canvas.drawCircle(xandy, xandy + space + lineLength + 5 * scale, 3, paint);        }        //否则显示垃圾桶界面        else {            progressbar_register_recode.setVisibility(View.INVISIBLE);            Bitmap bitmap = BitmapFactory.decodeResource(this.getContext().getResources(), R.drawable.ic_trash);            canvas.drawBitmap(bitmap,null,  new Rect((int) (width/2 - 20*scale),                    (int) (height/2 - 25*scale),                    (int) (width/2 + 20*scale),                    (int) (height/2 + 25*scale)),null);        }        //画外围的蓝色录音圆弧        paint.setColor(Color.parseColor("#0CA6D9"));        paint.setStrokeWidth(2);    //宽度        paint.setAntiAlias(true);   //抗锯齿        paint.setStyle(Paint.Style.STROKE); //设置空心        canvas.drawArc(new RectF(0 + 2, 0 + 2, width - 2, height - 2), -90, endArc, false, paint);    }    /**     * 设置是否显示录音话筒在录音     */    public void setIsShowRecoding(boolean IS_SHOW_RECODING) {        this.IS_SHOW_RECODING = IS_SHOW_RECODING;        postInvalidate();    }    //设置时间圆弧终止角度    public void setEndArc(int endArc) {        this.endArc = endArc;        postInvalidate();    }    //设置进度    public void setProgress(int progress) {        progressbar_register_recode.setProgress(progress);    }    int endArc = 0; //圆弧终止角度}
实现的过程注释说明得很清楚了,这里再概括一下,主要是在试图中绘制Y字形话筒的"把手",以及外围蓝色录音弧,并提供了设置方法以便在Activity中改变视图。

这里再Y字形三个点处加画了白色小圈圈,以使其更加Q弹...

最后就是控制代码的实现了:

RegisterActivity.java

package com.whale.nangua.toquan;import android.app.Activity;import android.media.MediaPlayer;import android.os.Bundle;import android.os.Handler;import android.view.MotionEvent;import android.view.View;import android.widget.Button;import android.widget.ImageButton;import android.widget.RadioButton;import android.widget.RadioGroup;import android.widget.TextView;import com.whale.nangua.toquan.view.RecodePopWindowCircle;import com.whale.nangua.toquan.voice.SoundMeter;import java.io.IOException;/** * Created by nangua on 2016/7/26. */public class RegisterActivity extends Activity implements        View.OnClickListener, RadioGroup.OnCheckedChangeListener, SoundMeter.onStartRecoder {    //性别选择的radiobutton    private RadioButton radiobtn_register_man;    private RadioGroup radiogroup_register_sexcheck;    private View layout_register_popup;    //播放录音按钮    private Button btn_register_play;    //录音按钮    private ImageButton imgbtn_register_recode;    private RecodePopWindowCircle circleview_register_microphone;    private long startVoiceT; //开始录音的时间    private String voiceName; //音频名    //录音组件    private SoundMeter mSensor;    private Handler mHandler = new Handler();    private Runnable ampTask = new Runnable() {        public void run() {            double amp = mSensor.getAmplitude();    //得到音频图            updateDisplay(amp);            mHandler.postDelayed(ampTask, POLL_INTERVAL);        }    };    private int endArc = 0;    private Runnable updateArcTask = new Runnable() {        @Override        public void run() {            endArc += 1;            circleview_register_microphone.setEndArc(endArc);            //更新时间圈圈            mHandler.postDelayed(updateArcTask, UPDATE_ARC_INTERVAL);        }    };    private Runnable mSleepTask = new Runnable() {        public void run() {            stop();        }    };    //录音延迟    private static final int POLL_INTERVAL = 300;    //时间圆弧更新延迟,默认一秒    private static final int UPDATE_ARC_INTERVAL = 200;    //录音提示文字    TextView tv_register_show;    //是否取消发送    private boolean IF_CANCLE_SEND = false;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_register);        initView();    }    float scale; //像素密度    int screenHeight; //屏幕高度    private void initView() {        screenHeight = this.getWindowManager().getDefaultDisplay().getHeight();    //屏幕高度        scale = this.getResources().getDisplayMetrics().density;        //初始化录音提示文字        tv_register_show = (TextView) findViewById(R.id.tv_register_show);        //初始化播放录音按钮        btn_register_play = (Button) findViewById(R.id.btn_register_play);        btn_register_play.setOnClickListener(this);        //初始化录音组件        mSensor = new SoundMeter();        mSensor.setonStartRecoderCallback(this);        circleview_register_microphone = (RecodePopWindowCircle) findViewById(R.id.circleview_register_microphone);        //初始化性别选择rbtn        radiobtn_register_man = (RadioButton) findViewById(R.id.radiobtn_register_man);        radiobtn_register_man.setChecked(true);        radiogroup_register_sexcheck = (RadioGroup) findViewById(R.id.radiogroup_register_sexcheck);        radiogroup_register_sexcheck.setOnCheckedChangeListener(this);        layout_register_popup = findViewById(R.id.layout_register_popup);        layout_register_popup.setVisibility(View.INVISIBLE);        imgbtn_register_recode = (ImageButton) findViewById(R.id.imgbtn_register_recode);        imgbtn_register_recode.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                int Y = (int) event.getRawY();                switch (event.getAction()) {                    case MotionEvent.ACTION_DOWN:                        btn_register_play.setVisibility(View.VISIBLE);                        layout_register_popup.setVisibility(View.VISIBLE);                        circleview_register_microphone.setEndArc(0);//初始化时间圆弧                        startVoiceT = System.currentTimeMillis();                        voiceName = startVoiceT + ".amr";                        /**                         * 开始录音方法,传入音频名字为事件+.amr                         */                        start(voiceName);                        break;                    case MotionEvent.ACTION_UP:                        layout_register_popup.setVisibility(View.GONE);                        endArc = 0;                        stop();                        //如果取消发送                        if (IF_CANCLE_SEND) {                            //TODO 取消发送                        }                        //如果发送                        else {                            //TODO 发送                        }                        break;                    case MotionEvent.ACTION_MOVE:                        //位置判断在方框范围以内                        if (Y <= screenHeight / 2 + 90 * scale) {                            tv_register_show.setText("手指松开,取消发送");                            tv_register_show.setBackground(getResources().getDrawable(R.drawable.shape_register_recodetv));                            circleview_register_microphone.setIsShowRecoding(false);                        } else {                            tv_register_show.setText("手指上划,取消发送");                            tv_register_show.setBackground(null);                            circleview_register_microphone.setIsShowRecoding(true);                        }                        break;                }                return true;            }        });    }    private void stop() {        mHandler.removeCallbacks(mSleepTask);        mHandler.removeCallbacks(ampTask);        mHandler.removeCallbacks(updateArcTask);        mSensor.stop();        circleview_register_microphone.setProgress(0);    }    /**     * 更新显示音频高低图     *     * @param signalEMA     */    private void updateDisplay(double signalEMA) {        int temp = 100 / 12;        switch ((int) signalEMA) {            case 0:                circleview_register_microphone.setProgress(temp);                break;            case 1:                circleview_register_microphone.setProgress(2 * temp);                break;            case 2:                circleview_register_microphone.setProgress(3 * temp);                break;            case 3:                circleview_register_microphone.setProgress(4 * temp);                break;            case 4:                circleview_register_microphone.setProgress(5 * temp);                break;            case 5:                circleview_register_microphone.setProgress(6 * temp);                break;            case 6:                circleview_register_microphone.setProgress(7 * temp);                break;            case 7:                circleview_register_microphone.setProgress(8 * temp);                break;            case 8:                circleview_register_microphone.setProgress(9 * temp);                break;            case 9:                circleview_register_microphone.setProgress(10 * temp);                break;            case 10:                circleview_register_microphone.setProgress(11 * temp);                break;            case 11:                circleview_register_microphone.setProgress(12 * temp);                break;            default:                break;        }    }    /**     * 开始录音     *     * @param name     */    private void start(String name) {        mSensor.start(name);        mHandler.postDelayed(ampTask, POLL_INTERVAL);        mHandler.postDelayed(updateArcTask, UPDATE_ARC_INTERVAL);    }    @Override    public void onClick(View v) {        switch (v.getId()) {            case R.id.btn_register_play:                MediaPlayer mediaPlayer = new MediaPlayer();                try {                    mediaPlayer.setDataSource(soundFilePath);                    mediaPlayer.prepare();                } catch (IOException e) {                    e.printStackTrace();                }                mediaPlayer.start();                break;        }    }    String soundFilePath;//录音文件路径    @Override    public void setVoicePath(String path) {        soundFilePath = path;    }    /**     * 性别选择改变的监听方法     *     * @param group     * @param checkedId     */    @Override    public void onCheckedChanged(RadioGroup group, int checkedId) {        switch (checkedId) {            case R.id.radiobtn_register_women:                //TODO 选择了男性                break;            case R.id.radiobtn_register_man:                //TODO 选择了女性                break;        }    }}
其中主要是对MediaRecoder类的各种调用,比较简单就不再赘述了。

最后实现的效果如下:

总的来说功能还是比较好实现的,就是目前经验还是不是太足做起来比较费力。

需要源码的留评论哈~

继续加油~

你可能感兴趣的文章
LeetCode49. Group Anagrams (思路及python解法)
查看>>
LeetCode41. First Missing Positive(思路及python解法)
查看>>
LeetCode50. Pow(x, n)(思路及python解法)
查看>>
LeetCode55. Jump Game(思路及python解法)
查看>>
LeetCode56. Merge Intervals(思路及python解法)
查看>>
LeetCode73. Set Matrix Zeroes(思路及python解法)
查看>>
LeetCode15. 3Sum(思路及python解法)
查看>>
LeetCode22. Generate Parentheses(思路及python解法)
查看>>
LeetCode179. Largest Number(思路及python解法)
查看>>
LeetCode227. Basic Calculator II(思路及python解法)
查看>>
LeetCode166. Fraction to Recurring Decimal(思路及python解法)
查看>>
LeetCode454. 4Sum II(思路及python解法)
查看>>
LeetCode395. Longest Substring with At Least K Repeating Characters(思路及python解法)
查看>>
LeetCode268. Missing Number
查看>>
【Web前端开发】《零基础入门学习Web开发》(HTML5&CSS3)(小甲鱼)
查看>>
iOS组件化开发一远端私有库建立(二)
查看>>
我们应当怎样做需求分析
查看>>
问题账户需求分析
查看>>
《uml大战需求分析》阅读笔记05
查看>>
ZooKeeper安装部署
查看>>