編輯:關於Android編程
參考於:Android模仿微信語音聊天功能,這代碼跑起來有問題,自己改動了一下,基本上沒什麼大問題
先貼下效果圖



<framelayout android:layout_height="wrap_content" android:layout_width="match_parent"> </framelayout>
<framelayout android:background="@drawable/chatto_bg_focused" android:id="@+id/recorder_length" android:layout_centervertical="true" android:layout_height="wrap_content" android:layout_toleftof="@id/item_icon" android:layout_width="wrap_content"> </framelayout>
package com.nickming.view;
import com.example.weixin_record.R;
import android.app.Dialog;
import android.content.Context;
import android.graphics.drawable.AnimationDrawable;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
/**
*
* @ClassName: DialogManager
* @Description:對話框管理類
* @author: 張 維
* @date: 2016-5-23 下午4:56:03
*
*/
public class DialogManager {
/**
* 以下為dialog的初始化控件,包括其中的布局文件
*/
private Dialog mDialog;
private ImageView mIcon;
private ImageView mVoice;
private TextView mLable;
private Context mContext;
public DialogManager(Context context) {
mContext = context;
}
public void showRecordingDialog() {
mDialog = new Dialog(mContext,R.style.Theme_audioDialog);
// 用layoutinflater來引用布局
LayoutInflater inflater = LayoutInflater.from(mContext);
View view = inflater.inflate(R.layout.dialog_manager, null);
mDialog.setContentView(view);
mIcon = (ImageView) mDialog.findViewById(R.id.dialog_icon);
mVoice = (ImageView) mDialog.findViewById(R.id.dialog_voice);
mVoice.setBackgroundResource(R.drawable.play02);
AnimationDrawable drawable = (AnimationDrawable) mVoice
.getBackground();
drawable.start();
mLable = (TextView) mDialog.findViewById(R.id.recorder_dialogtext);
mDialog.show();
}
/**
* 設置正在錄音時的dialog界面
*/
public void recording() {
if (mDialog != null && mDialog.isShowing()) {
mIcon.setVisibility(View.VISIBLE);
mVoice.setVisibility(View.VISIBLE);
mLable.setVisibility(View.VISIBLE);
mIcon.setImageResource(R.drawable.recorder);
mLable.setText(R.string.shouzhishanghua);
}
}
/**
* 取消界面
*/
public void wantToCancel() {
// TODO Auto-generated method stub
if (mDialog != null && mDialog.isShowing()) {
mIcon.setVisibility(View.VISIBLE);
mVoice.setVisibility(View.GONE);
mLable.setVisibility(View.VISIBLE);
mIcon.setImageResource(R.drawable.cancel);
mLable.setText(R.string.want_to_cancle);
}
}
// 時間過短
public void timeShort() {
// TODO Auto-generated method stub
if (mDialog != null && mDialog.isShowing()) {
mIcon.setVisibility(View.VISIBLE);
mVoice.setVisibility(View.GONE);
mLable.setVisibility(View.VISIBLE);
mIcon.setImageResource(R.drawable.voice_to_short);
mLable.setText(R.string.timeshort);
}
}
// 隱藏dialog
public void dimissDialog() {
// TODO Auto-generated method stub
if (mDialog != null && mDialog.isShowing()) {
mDialog.dismiss();
mDialog = null;
}
}
public void updateVoiceLevel(int level) {
// TODO Auto-generated method stub
if (mDialog != null && mDialog.isShowing()) {
//先不改變它的默認狀態
// mIcon.setVisibility(View.VISIBLE);
// mVoice.setVisibility(View.VISIBLE);
// mLable.setVisibility(View.VISIBLE);
//通過level來找到圖片的id,也可以用switch來尋址,但是代碼可能會比較長
int resId = mContext.getResources().getIdentifier("v" + level,
"drawable", mContext.getPackageName());
mVoice.setImageResource(resId);
}
}
}
(2)AudioRecordButton
package com.nickming.view;
import com.example.weixin_record.R;
import com.example.weixin_record.R.string;
import com.nickming.view.AudioManager.AudioStageListener;
import android.R.bool;
import android.content.Context;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
/**
*
* @ClassName: AudioRecordButton
* @Description:自定義的button按鈕
* @author: 張 維
* @date: 2016-5-23 下午2:13:20
*
*/
public class AudioRecordButton extends Button implements AudioStageListener {
private static final int STATE_NORMAL = 1;
private static final int STATE_RECORDING = 2;
private static final int STATE_WANT_TO_CANCEL = 3;
private static final int DISTANCE_Y_CANCEL = 50;
private int mCurrentState = STATE_NORMAL;
// 已經開始錄音
private boolean isRecording = false;
private DialogManager mDialogManager;
private AudioManager mAudioManager;
private float mTime = 0;
// 是否觸發了onlongclick,准備好了
private boolean mReady;
/**
* 先實現兩個參數的構造方法,布局會默認引用這個構造方法,
* 用一個 構造參數的構造方法來引用這個方法 * @param context
*/
public AudioRecordButton(Context context) {
this(context, null);
// TODO Auto-generated constructor stub
}
public AudioRecordButton(Context context, AttributeSet attrs) {
super(context, attrs);
mDialogManager = new DialogManager(getContext());
// 這裡沒有判斷儲存卡是否存在,有空要判斷
String dir = Environment.getExternalStorageDirectory()
+ "/temp";
mAudioManager = AudioManager.getInstance(dir);
mAudioManager.setOnAudioStageListener(this);
setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
// TODO Auto-generated method
mReady = true;
mAudioManager.prepareAudio();
return false;
}
});
}
/**
* 錄音完成後的回調,回調給activiy,可以獲得mtime和文件的路徑
* @author nickming
*
*/
public interface AudioFinishRecorderListener{
void onFinished(float mtime,String filePath);
}
private AudioFinishRecorderListener mListener;
public void setAudioFinishRecorderListener(AudioFinishRecorderListener listener)
{
mListener=listener;
}
// 獲取音量大小的runnable
private Runnable mGetVoiceLevelRunnable = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while (isRecording) {
try {
Thread.sleep(100);
mTime += 0.1f;
mhandler.sendEmptyMessage(MSG_VOICE_CHANGE);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
// 准備三個常量
private static final int MSG_AUDIO_PREPARED = 0X110;
private static final int MSG_VOICE_CHANGE = 0X111;
private static final int MSG_DIALOG_DIMISS = 0X112;
private Handler mhandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case MSG_AUDIO_PREPARED:
// 顯示應該是在audio end prepare之後回調
mDialogManager.showRecordingDialog();
isRecording = true;
new Thread(mGetVoiceLevelRunnable).start();
// 需要開啟一個線程來變換音量
break;
case MSG_VOICE_CHANGE:
mDialogManager.updateVoiceLevel(mAudioManager.getVoiceLevel(7));
break;
case MSG_DIALOG_DIMISS:
break;
}
};
};
// 在這裡面發送一個handler的消息
@Override
public void wellPrepared() {
// TODO Auto-generated method stub
mhandler.sendEmptyMessage(MSG_AUDIO_PREPARED);
}
/**
* 直接復寫這個監聽函數
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
int action = event.getAction();
int x = (int) event.getX();
int y = (int) event.getY();
switch (action) {
case MotionEvent.ACTION_DOWN://表示用戶開始觸摸.
changeState(STATE_RECORDING);
break;
case MotionEvent.ACTION_MOVE://表示用戶在移動(手指或者其他)
if (isRecording) {
// 根據x,y來判斷用戶是否想要取消
if (wantToCancel(x, y)) {
changeState(STATE_WANT_TO_CANCEL);
} else {
changeState(STATE_RECORDING);
}
}
break;
case MotionEvent.ACTION_UP://表示用戶抬起了手指
// 首先判斷是否有觸發onlongclick事件,沒有的話直接返回reset
if (!mReady) {
reset();
return super.onTouchEvent(event);
}
// 如果按的時間太短,還沒准備好或者時間錄制太短,就離開了,則顯示這個dialog
if (!isRecording || mTime < 0.6f) {
mDialogManager.timeShort();//取消對話框
mAudioManager.cancel();
mhandler.sendEmptyMessageDelayed(MSG_DIALOG_DIMISS, 1300);// 持續1.3s
} else if (mCurrentState == STATE_RECORDING) {//正常錄制結束
mDialogManager.dimissDialog();
mAudioManager.release();// release釋放一個mediarecorder
if (mListener!=null) {// 並且callbackActivity,保存錄音
mListener.onFinished(mTime, mAudioManager.getCurrentFilePath());
}
} else if (mCurrentState == STATE_WANT_TO_CANCEL) {
// cancel
mAudioManager.cancel();
mDialogManager.dimissDialog();
}
reset();// 恢復標志位
break;
}
return super.onTouchEvent(event);
}
/**
* 回復標志位以及狀態
*/
private void reset() {
// TODO Auto-generated method stub
isRecording = false;
changeState(STATE_NORMAL);
mReady = false;
mTime = 0;
}
private boolean wantToCancel(int x, int y) {
// 超過按鈕的寬度
if (x < 0 || x > getWidth()) {// 判斷是否在左邊,右邊,上邊,下邊
return true;
}
// 超過按鈕的高度
if (y < -DISTANCE_Y_CANCEL || y > getHeight() + DISTANCE_Y_CANCEL) {
return true;
}
return false;
}
private void changeState(int state) {
if (mCurrentState != state) {
mCurrentState = state;
switch (mCurrentState) {
case STATE_NORMAL:
setBackgroundResource(R.drawable.button_recordnormal);
setText(R.string.normal);
break;
case STATE_RECORDING:
setBackgroundResource(R.drawable.button_recording);
setText(R.string.recording);
if (isRecording) {
mDialogManager.recording();
// 復寫dialog.recording();
}
break;
case STATE_WANT_TO_CANCEL:
setBackgroundResource(R.drawable.button_recording);
setText(R.string.want_to_cancle);
// 取消
mDialogManager.wantToCancel();
break;
}
}
}
@Override
public boolean onPreDraw() {
return false;
}
}
(3)MediaRecorder
package com.nickming.view;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
import android.media.MediaRecorder;
/**
*
* @ClassName: AudioManager
* @Description: 錄音的管理類(准備工作)
* @author: 張 維
* @date: 2016-5-23 下午2:10:35
*
*/
public class AudioManager {
private MediaRecorder mRecorder;
private String mDirString;
private String mCurrentFilePath;
private boolean isPrepared;// 是否准備好了
/**
* 單例化的方法
* 1 先聲明一個static 類型的變量a
* 2 在聲明默認的構造函數
* 3 再用public synchronized static
* 類名 getInstance() { if(a==null) { a=new 類();} return a; } 或者用以下的方法
*/
/**
* 單例化這個類
*/
private static AudioManager mInstance;
private AudioManager(String dir) {
mDirString=dir;
}
public static AudioManager getInstance(String dir) {
if (mInstance == null) {
synchronized (AudioManager.class) {
if (mInstance == null) {
mInstance = new AudioManager(dir);
}
}
}
return mInstance;
}
/**
* 回調函數,准備完畢,准備好後,button才會開始顯示錄音框
*
* @author nickming
*
*/
public interface AudioStageListener {
void wellPrepared();
}
public AudioStageListener mListener;
public void setOnAudioStageListener(AudioStageListener listener) {
mListener = listener;
}
// 准備方法
public void prepareAudio() {
try {
// 一開始應該是false的
isPrepared = false;
File dir = new File(mDirString);
//判斷對象file是否存在
if (!dir.exists()) {
//創建此抽象路徑指定的目錄,包括所有必須但不存在的父目錄。(及可以創建多級目錄,無論是否存在父目錄)
dir.mkdirs();
}
String fileNameString = generalFileName();
File file = new File(dir, fileNameString);
mCurrentFilePath = file.getAbsolutePath();
mRecorder = new MediaRecorder();
// 設置輸出文件
mRecorder.setOutputFile(file.getAbsolutePath());
// 設置meidaRecorder的音頻源是麥克風
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
// 設置文件音頻的輸出格式為amr
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR);
// 設置音頻的編碼格式為amr
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
// 嚴格遵守google官方api給出的mediaRecorder的狀態流程圖
mRecorder.prepare();
mRecorder.start();
// 准備結束
isPrepared = true;
// 已經准備好了,可以錄制了
if (mListener != null) {
mListener.wellPrepared();
}
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 隨機生成文件的名稱
*
* @return
*/
private String generalFileName() {
//UUID.randomUUID().toString()是javaJDK提供的一個自動生成主鍵的方法。
return UUID.randomUUID().toString() + ".amr";
}
// 獲得聲音的大小
public int getVoiceLevel(int maxLevel) {
// mRecorder.getMaxAmplitude()這個是音頻的振幅范圍,值域是1-32767
if (isPrepared) {
try {
// 取證+1,否則去不到7
return maxLevel * mRecorder.getMaxAmplitude() / 32768 + 1;
} catch (Exception e) {
}
}
return 1;
}
// 釋放資源
public void release() {
// 嚴格按照api流程進行
mRecorder.stop();
mRecorder.release();
mRecorder = null;
}
// 取消,因為prepare時產生了一個文件,所以cancel方法應該要刪除這個文件,
// 這是與release的方法的區別
public void cancel() {
release();
if (mCurrentFilePath != null) {
File file = new File(mCurrentFilePath);
file.delete();//刪除文件
mCurrentFilePath = null;
}
}
public String getCurrentFilePath() {
return mCurrentFilePath;
}
}
package com.example.weixin_record; import java.util.List; import com.example.weixin_record.MainActivity.Recorder; import android.content.Context; import android.util.DisplayMetrics; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.ArrayAdapter; import android.widget.TextView; /** * * @ClassName: RecorderAdapter * @Description:list的適配器 * @author: 張 維 * @date: 2016-5-23 下午4:04:02 * */ public class RecorderAdapter extends ArrayAdapter{ private LayoutInflater inflater; private int mMinItemWith;// 設置對話框的最大寬度和最小寬度 private int mMaxItemWith; public RecorderAdapter(Context context, List dataList) { super(context, -1, dataList); inflater = LayoutInflater.from(context); // 獲取系統寬度 WindowManager wManager = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); wManager.getDefaultDisplay().getMetrics(outMetrics); mMaxItemWith = (int) (outMetrics.widthPixels * 0.7f); mMinItemWith = (int) (outMetrics.widthPixels * 0.15f); } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder = null; if (convertView == null) { convertView = inflater.inflate(R.layout.item_layout, parent, false); viewHolder=new ViewHolder(); viewHolder.seconds=(TextView) convertView.findViewById(R.id.recorder_time); viewHolder.length=convertView.findViewById(R.id.recorder_length); convertView.setTag(viewHolder); }else { viewHolder=(ViewHolder) convertView.getTag(); } viewHolder.seconds.setText(Math.round(getItem(position).time)+"\""); ViewGroup.LayoutParams lParams=viewHolder.length.getLayoutParams(); lParams.width=(int) (mMinItemWith+mMaxItemWith/60f*getItem(position).time); viewHolder.length.setLayoutParams(lParams); return convertView; } class ViewHolder { TextView seconds;// 時間 View length;// 對話框長度 } }
package com.example.weixin_record;
import java.io.IOException;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.util.Log;
/**
*
* @ClassName: MediaManager
* @Description:播放的管理類
* @author: 張 維
* @date: 2016-5-23 下午4:04:43
*
*/
public class MediaManager {
private static MediaPlayer mPlayer;
private static boolean isPause;
public static void playSound(String filePathString,
OnCompletionListener onCompletionListener) {
if (mPlayer==null) {
mPlayer=new MediaPlayer();
//保險起見,設置報錯監聽
mPlayer.setOnErrorListener(new OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
Log.i("info", "");
mPlayer.reset();
return false;
}
});
}else {
mPlayer.reset();//就回復
}
try {
mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mPlayer.setOnCompletionListener(onCompletionListener);
mPlayer.setDataSource(filePathString);
mPlayer.prepare();
mPlayer.start();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//停止函數
public static void pause(){
if (mPlayer!=null&&mPlayer.isPlaying()) {
mPlayer.pause();
isPause=true;
}
}
//繼續
public static void resume()
{
if (mPlayer!=null&&isPause) {
mPlayer.start();
isPause=false;
}
}
public static void release()
{
if (mPlayer!=null) {
mPlayer.release();
mPlayer=null;
}
}
}
package com.example.weixin_record;
import java.util.ArrayList;
import java.util.List;
import com.nickming.view.AudioRecordButton;
import com.nickming.view.AudioRecordButton.AudioFinishRecorderListener;
import android.app.Activity;
import android.graphics.drawable.AnimationDrawable;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
public class MainActivity extends Activity {
AudioRecordButton button;
private ListView mlistview;
private ArrayAdapter mAdapter;
private View viewanim;
private List mDatas = new ArrayList();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mlistview = (ListView) findViewById(R.id.listview);
button = (AudioRecordButton) findViewById(R.id.recordButton);
button.setAudioFinishRecorderListener(new AudioFinishRecorderListener() {
@Override
public void onFinished(float seconds, String filePath) {
// TODO Auto-generated method stub
Recorder recorder = new Recorder(seconds, filePath);
mDatas.add(recorder);
mAdapter.notifyDataSetChanged();
mlistview.setSelection(mDatas.size() - 1);
}
});
mAdapter = new RecorderAdapter(this, mDatas);
mlistview.setAdapter(mAdapter);
mlistview.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView parent, View view,
int position, long id) {
// 播放動畫
if (viewanim!=null) {//讓第二個播放的時候第一個停止播放
viewanim.setBackgroundResource(R.drawable.adj);
viewanim=null;
}
viewanim = view.findViewById(R.id.show_anim01);
viewanim.setBackgroundResource(R.drawable.play);
AnimationDrawable drawable = (AnimationDrawable) viewanim
.getBackground();
drawable.start();
// 播放音頻
Log.i("info", "position==="+position);
MediaManager.playSound(mDatas.get(position).filePathString,
new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
viewanim.setBackgroundResource(R.drawable.adj);
}
});
}
});
}
@Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
MediaManager.pause();
}
@Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
MediaManager.resume();
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
MediaManager.release();
}
class Recorder {
float time;
String filePathString;
public Recorder(float time, String filePathString) {
super();
this.time = time;
this.filePathString = filePathString;
}
public float getTime() {
return time;
}
public void setTime(float time) {
this.time = time;
}
public String getFilePathString() {
return filePathString;
}
public void setFilePathString(String filePathString) {
this.filePathString = filePathString;
}
}
}
Android系統聯系人全特效實現(上)分組導航和擠壓動畫(附源碼)
記得在我剛接觸Android的時候對系統聯系人中的特效很感興趣,它會根據手機中聯系人姓氏的首字母進行分組,並在界面的最頂端始終顯示一個當前的分組。如下圖所示:  
Android版九連環NingRings
喜歡九連環以及想玩九連環的小伙伴們,送福利了,Android版本的九連環小游戲上線了!!!快來嘗鮮吧,如果覺得好玩,請幫忙轉發。游戲演示及下載地址http://onest
MSM8909+Android5.1.1的USB連接方式介紹
默認是采用WIN7電腦測試的。 USB連接方式總結:(1) 作為HOST,連接鼠標和U盤可正常使用功能。(2) 作為Client,在WIN7電腦上不需要安裝驅動
Android開發中WebView的簡單使用小結
前言WebView(網絡視圖)在Andorid中就是用來顯示網頁的,下面我們來一起看看它是如何使用的。一、基本使用1.聲明權限,WebView不可避免地要用到網絡,我們要