Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 全仿To圈兒錄音界面實現

Android 全仿To圈兒錄音界面實現

編輯:關於Android編程

我們先來看看To圈(QQ,微信等其他大部分軟件也是大同小異)的注冊錄音界面運行截圖:

\

(⊙o⊙)…為了實現這個效果還是花了一番功夫的,

主要難點有以下方面:

1、跟隨音量變化的話筒。

這個話筒一開始感覺是最頭痛的部分,完全不知道從何開始實現。首先直接用圖片肯定是不行的,想實現我最後達到的效果需要12張圖片,這太占資源了直接GG。然後又想到用遮罩實現,但是仔細觀察可以發現話筒的圓形進度條周圍是透明的,也就是說如果用遮罩,除非完美重合(重合就得考慮屏幕適配問題了,這就是個大難題了)。最後我想出的辦法有點取巧吧:用一個豎向進度條實現話筒的進度部分,然後下面的Y字形直接用canvas畫(為了使其看起來像個話筒花費了大量代碼進行計算...)。這樣一來看起來差不多,而且規避了屏幕適配問題,也不需要加載那麼多圖片然後輪著換了。但這肯定不是最好的方法,所以跪求更高雅的實現的方法!!!

2、圈內的藍色圓弧計時部分。

3、手勢判斷。

這麼一總結感覺1比2和3加起來都難得多..

 

實現過程:

首先是界面實現:

\

activity_register.xml

 


    

 


不重要的部分沒有貼上去,按下完成注冊會出現黑色錄音框的布局在標簽裡引用。這裡暫時設置為不可見。

 

下面是黑色錄音框布局:

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類的各種調用,比較簡單就不再贅述了。

 

最後實現的效果如下:

\

總的來說功能還是比較好實現的,就是目前經驗還是不是太足做起來比較費力。

需要源碼的留評論哈~

繼續加油~

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved