編輯:關於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;
}
}
}
最後實現的效果如下:

總的來說功能還是比較好實現的,就是目前經驗還是不是太足做起來比較費力。
需要源碼的留評論哈~
繼續加油~
Android_仿支付寶賬單列表(頭部停留及分頁數據加載)
沒有辦法,米公設計的一個UI是stickyheaderlist(頭部停留)和分頁加載數據功能的整合,筆者原以為是米工自己拍著腦袋想出來的,還想進一步討論一下,後來才發現支
Android實現圖片上傳功能
最近在開發中,涉及到用戶的意見反饋功能這一方面的開發,需要用戶輸入的文字或者提交的圖片,效果大概類似於微信朋友圈那樣的圖片選擇器,一開始自己找了個用universal-i
Android中讓圖片自適應控件的大小的方法
這就需要把.png格式的圖片轉成.9.png格式,.9.png就是後綴名。在安裝Android-SDK時自帶了<draw9patch.bat>可以把.png格
Android實現分享微信好友及出現閃退的解決辦法
1.申請微信APPID要實現分享到微信的功能,首先要到微信開放平台申請一個APPID。但在申請APPID的時候需要填寫一個應用簽名和應用包名。需要注意的是包名