码迷,mamicode.com
首页 > 移动开发 > 详细

Android 全仿To圈儿录音界面实现

时间:2016-08-01 21:22:20      阅读:237      评论:0      收藏:0      [点我收藏+]

标签:

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

技术分享

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

主要难点有以下方面:

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

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

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

3、手势判断。

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


实现过程:

首先是界面实现

技术分享

activity_register.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
    >

  <span style="white-space:pre"></span><pre name="code" class="html">    <!-- ...不重要的部分... -->
<!-- 录音显示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

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
    >

    <RelativeLayout
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:layout_width="180dp"
        android:layout_height="180dp"
        android:background="@drawable/shape_register_recoderlayout"
        android:orientation="vertical">

        <com.whale.nangua.toquan.view.RecodePopWindowCircle
            android:id="@+id/circleview_register_microphone"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="20dp"
            android:background="@drawable/shape_register_recodepopwindow"
            ></com.whale.nangua.toquan.view.RecodePopWindowCircle>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="50dp"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true">

            <TextView
                android:layout_marginBottom="10dp"
                android:paddingLeft="10dp"
                android:paddingRight="10dp"
                android:id="@+id/tv_register_show"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="center"
                android:text="手指上划,取消发送"
                android:textColor="@android:color/white"
                android:textSize="16sp"/>
        </LinearLayout>
    </RelativeLayout>
</RelativeLayout>
实现效果如下:

技术分享


代码实现

从上面的界面中可以看到有一个自定义的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类的各种调用,比较简单就不再赘述了。

最后实现的效果如下:

技术分享

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

需要源码的留评论哈~

继续加油~





Android 全仿To圈儿录音界面实现

标签:

原文地址:http://blog.csdn.net/qq_22770457/article/details/52088799

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!