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