這篇文章主要介紹了Android 微信小視頻錄製功能實(shí)現(xiàn)詳解的相關(guān)資料,這裡提供了具體的實(shí)現(xiàn)思路及代碼,需要的朋友可以參考下
Android 微信小視頻錄製功能
開(kāi)發(fā)之前
這幾天接觸了一下和視頻相關(guān)的控件, 所以, 繼之前的微信搖一搖, 我想到了來(lái)實(shí)現(xiàn)一下微信小視頻錄製的功能, 它的功能點(diǎn)比較多, 我每天都抽出點(diǎn)時(shí)間來(lái)寫(xiě)寫(xiě), 說(shuō)實(shí)話, 有些東西還是比較費(fèi)勁, 希望大家認(rèn)真看看, 說(shuō)得不對(duì)的地方還請(qǐng)大家在評(píng)論中指正. 廢話不多說(shuō), 進(jìn)入正題.
開(kāi)發(fā)環(huán)境
最近剛更新的, 沒(méi)更新的小夥伴們抓緊了
Android Studio 2.2.2
JDK1.7
#API 24
Gradle 2.2 .2
相關(guān)知識(shí)點(diǎn)
#影片錄製介面SurfaceView 的使用
## Camera的使用
相機(jī)的對(duì)焦, 變焦
- #影片錄製控制項(xiàng)MediaRecorder的使用
- 簡(jiǎn)單自訂View
- GestureDetector(手勢(shì)偵測(cè))的使用
- 用到的東西真不少, 不過(guò)別急, 咱們一個(gè)一個(gè)來(lái).開(kāi)始開(kāi)發(fā)
案例分析
- #大家可以打開(kāi)自己微信裡面的小影片, 一塊簡(jiǎn)單的分析一下它的功能點(diǎn)有哪些?
基本的影片預(yù)覽功能
# 長(zhǎng)按「按住拍」實(shí)現(xiàn)影片的錄製
錄製過(guò)程中的進(jìn)度條從兩側(cè)向中間變短#當(dāng)鬆手或進(jìn)度條走到盡頭影片停止錄製並保存
從「按住拍攝」 上滑取消影片的錄製
根據(jù)上述的分析, 我們一步一步的完成
搭建佈局
佈局介面的實(shí)作還可以, 難度不大
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/main_tv_tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="150dp"
android:elevation="1dp"
android:text="雙擊放大"
android:textColor="#FFFFFF"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<SurfaceView
android:id="@+id/main_surface_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="3"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@color/colorApp"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/main_press_control"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.lulu.weichatsamplevideo.BothWayProgressBar
android:id="@+id/main_progress_bar"
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="#000"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="按住拍"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:textColor="#00ff00"/>
</RelativeLayout>
</LinearLayout>
</LinearLayout>
</FrameLayout>
step1: 得到SufaceView控制項(xiàng), 設(shè)定基本屬性和對(duì)應(yīng)監(jiān)聽(tīng)(該控制項(xiàng)的建立是異步的, 只有在真正」準(zhǔn)備」好之後才能呼叫)
mSurfaceView = (SurfaceView) findViewById(R.id.main_surface_view); //設(shè)置屏幕分辨率 mSurfaceHolder.setFixedSize(videoWidth, videoHeight); mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); mSurfaceHolder.addCallback(this);
step2: 實(shí)作介面的方法, surfaceCreated方法中開(kāi)啟影片的預(yù)覽, 在surfaceDestroyed中銷毀
////////////////////////////////////////////// // SurfaceView回調(diào) ///////////////////////////////////////////// @Override public void surfaceCreated(SurfaceHolder holder) { mSurfaceHolder = holder; startPreView(holder); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { if (mCamera != null) { Log.d(TAG, "surfaceDestroyed: "); //停止預(yù)覽并釋放攝像頭資源 mCamera.stopPreview(); mCamera.release(); mCamera = null; } if (mMediaRecorder != null) { mMediaRecorder.release(); mMediaRecorder = null; } }
step3: 實(shí)作影片預(yù)覽的方法
/** * 開(kāi)啟預(yù)覽 * * @param holder */ private void startPreView(SurfaceHolder holder) { Log.d(TAG, "startPreView: "); if (mCamera == null) { mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); } if (mMediaRecorder == null) { mMediaRecorder = new MediaRecorder(); } if (mCamera != null) { mCamera.setDisplayOrientation(90); try { mCamera.setPreviewDisplay(holder); Camera.Parameters parameters = mCamera.getParameters(); //實(shí)現(xiàn)Camera自動(dòng)對(duì)焦 List<String> focusModes = parameters.getSupportedFocusModes(); if (focusModes != null) { for (String mode : focusModes) { mode.contains("continuous-video"); parameters.setFocusMode("continuous-video"); } } mCamera.setParameters(parameters); mCamera.startPreview(); } catch (IOException e) { e.printStackTrace(); } } }
Note: 上面新增了自動(dòng)對(duì)焦的程式碼, 但是部分手機(jī)可能不支援
#自訂雙向縮減的進(jìn)度條
private static final String TAG = "BothWayProgressBar"; //取消狀態(tài)為紅色bar, 反之為綠色bar private boolean isCancel = false; private Context mContext; //正在錄制的畫(huà)筆 private Paint mRecordPaint; //上滑取消時(shí)的畫(huà)筆 private Paint mCancelPaint; //是否顯示 private int mVisibility; // 當(dāng)前進(jìn)度 private int progress; //進(jìn)度條結(jié)束的監(jiān)聽(tīng) private OnProgressEndListener mOnProgressEndListener; public BothWayProgressBar(Context context) { super(context, null); } public BothWayProgressBar(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; init(); } private void init() { mVisibility = INVISIBLE; mRecordPaint = new Paint(); mRecordPaint.setColor(Color.GREEN); mCancelPaint = new Paint(); mCancelPaint.setColor(Color.RED); }
Note: OnProgressEndListener, 主要用於當(dāng)進(jìn)度條走到中間了, 好通知相機(jī)停止錄製, 介面如下:- step2 :設(shè)定Setter方法用於通知我們的Progress改變狀態(tài)
- #
/** * 設(shè)置進(jìn)度 * @param progress */ public void setProgress(int progress) { this.progress = progress; invalidate(); } /** * 設(shè)置錄制狀態(tài) 是否為取消狀態(tài) * @param isCancel */ public void setCancel(boolean isCancel) { this.isCancel = isCancel; invalidate(); } /** * 重寫(xiě)是否可見(jiàn)方法 * @param visibility */ @Override public void setVisibility(int visibility) { mVisibility = visibility; //重新繪制 invalidate(); }
step3 :最重要的一步, 畫(huà)出我們的進(jìn)度條,使用的就是View中的onDraw(Canvas canvas)方法
public interface OnProgressEndListener{ void onProgressEndListener(); } /** * 當(dāng)進(jìn)度條結(jié)束后的 監(jiān)聽(tīng) * @param onProgressEndListener */ public void setOnProgressEndListener(OnProgressEndListener onProgressEndListener) { mOnProgressEndListener = onProgressEndListener; }
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mVisibility == View.VISIBLE) { int height = getHeight(); int width = getWidth(); int mid = width / 2; //畫(huà)出進(jìn)度條 if (progress < mid){ canvas.drawRect(progress, 0, width-progress, height, isCancel ? mCancelPaint : mRecordPaint); } else { if (mOnProgressEndListener != null) { mOnProgressEndListener.onProgressEndListener(); } } } else { canvas.drawColor(Color.argb(0, 0, 0, 0)); } }
錄製事件的處理
錄製中觸發(fā)的事件包括四個(gè):
長(zhǎng)按錄製
抬起儲(chǔ)存
上滑取消
@Override public boolean onTouch(View v, MotionEvent event) { boolean ret = false; int action = event.getAction(); float ey = event.getY(); float ex = event.getX(); //只監(jiān)聽(tīng)中間的按鈕處 int vW = v.getWidth(); int left = LISTENER_START; int right = vW - LISTENER_START; float downY = 0; // ... }######長(zhǎng)按錄製#########長(zhǎng)按錄製我們需要監(jiān)聽(tīng)ACTION_DOWN事件, 使用執(zhí)行緒延遲發(fā)送Handler來(lái)實(shí)現(xiàn)進(jìn)度列的更新###############
switch (action) { case MotionEvent.ACTION_DOWN: if (ex > left && ex < right) { mProgressBar.setCancel(false); //顯示上滑取消 mTvTip.setVisibility(View.VISIBLE); mTvTip.setText("↑ 上滑取消"); //記錄按下的Y坐標(biāo) downY = ey; // TODO: 2016/10/20 開(kāi)始錄制視頻, 進(jìn)度條開(kāi)始走 mProgressBar.setVisibility(View.VISIBLE); //開(kāi)始錄制 Toast.makeText(this, "開(kāi)始錄制", Toast.LENGTH_SHORT).show(); startRecord(); mProgressThread = new Thread() { @Override public void run() { super.run(); try { mProgress = 0; isRunning = true; while (isRunning) { mProgress++; mHandler.obtainMessage(0).sendToTarget(); Thread.sleep(20); } } catch (InterruptedException e) { e.printStackTrace(); } } }; mProgressThread.start(); ret = true; } break; // ... return true; }####Note: startRecord()這個(gè)方法先不說(shuō), 我們只需要知道執(zhí)行了它就可以錄製了, 但是Handler事件還是要說(shuō)的, 它只負(fù)責(zé)更新進(jìn)度條的進(jìn)度############
//////////////////////////////////////////////////// // Handler處理 ///////////////////////////////////////////////////// private static class MyHandler extends Handler { private WeakReference<MainActivity> mReference; private MainActivity mActivity; public MyHandler(MainActivity activity) { mReference = new WeakReference<MainActivity>(activity); mActivity = mReference.get(); } @Override public void handleMessage(Message msg) { switch (msg.what) { case 0: mActivity.mProgressBar.setProgress(mActivity.mProgress); break; } } }######抬起保存#########同樣我們這兒需要監(jiān)聽(tīng)ACTION_UP事件, 但是要考慮當(dāng)用戶抬起過(guò)快時(shí)(錄製的時(shí)間過(guò)短), 不需要保存. 而且,在這個(gè)事件中包含了取消狀態(tài)的抬起, 解釋一下: 就是當(dāng)上滑取消時(shí)抬起的一瞬間取消錄製, 大家看代碼###############
case MotionEvent.ACTION_UP: if (ex > left && ex < right) { mTvTip.setVisibility(View.INVISIBLE); mProgressBar.setVisibility(View.INVISIBLE); //判斷是否為錄制結(jié)束, 或者為成功錄制(時(shí)間過(guò)短) if (!isCancel) { if (mProgress < 50) { //時(shí)間太短不保存 stopRecordUnSave(); Toast.makeText(this, "時(shí)間太短", Toast.LENGTH_SHORT).show(); break; } //停止錄制 stopRecordSave(); } else { //現(xiàn)在是取消狀態(tài),不保存 stopRecordUnSave(); isCancel = false; Toast.makeText(this, "取消錄制", Toast.LENGTH_SHORT).show(); mProgressBar.setCancel(false); } ret = false; } break;## #Note: 同樣的, 內(nèi)部的stopRecordUnSave()和stopRecordSave();大家先不要考慮, 我們會(huì)在後面介紹, 他倆從名字就能看出前者用來(lái)停止錄製但不保存, 後者停止錄製並保存############上滑取消#########配合上一部分說(shuō)得抬起取消事件, 實(shí)作上滑取消############
case MotionEvent.ACTION_MOVE: if (ex > left && ex < right) { float currentY = event.getY(); if (downY - currentY > 10) { isCancel = true; mProgressBar.setCancel(true); } } break;
Note: 主要原理不難, 只要按下并且向上移動(dòng)一定距離 就會(huì)觸發(fā),當(dāng)手抬起時(shí)視頻錄制取消
雙擊放大(變焦)
這個(gè)事件比較特殊, 使用了Google提供的GestureDetector手勢(shì)檢測(cè) 來(lái)判斷雙擊事件
step1: 對(duì)SurfaceView進(jìn)行單獨(dú)的Touch事件監(jiān)聽(tīng), why? 因?yàn)镚estureDetector需要Touch事件的完全托管, 如果只給它傳部分事件會(huì)造成某些事件失效
mDetector = new GestureDetector(this, new ZoomGestureListener()); /** * 單獨(dú)處理mSurfaceView的雙擊事件 */ mSurfaceView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { mDetector.onTouchEvent(event); return true; } });
step2: 重寫(xiě)GestureDetector.SimpleOnGestureListener, 實(shí)現(xiàn)雙擊事件
/////////////////////////////////////////////////////////////////////////// // 變焦手勢(shì)處理類 /////////////////////////////////////////////////////////////////////////// class ZoomGestureListener extends GestureDetector.SimpleOnGestureListener { //雙擊手勢(shì)事件 @Override public boolean onDoubleTap(MotionEvent e) { super.onDoubleTap(e); Log.d(TAG, "onDoubleTap: 雙擊事件"); if (mMediaRecorder != null) { if (!isZoomIn) { setZoom(20); isZoomIn = true; } else { setZoom(0); isZoomIn = false; } } return true; } }
step3: 實(shí)現(xiàn)相機(jī)的變焦的方法
/** * 相機(jī)變焦 * * @param zoomValue */ public void setZoom(int zoomValue) { if (mCamera != null) { Camera.Parameters parameters = mCamera.getParameters(); if (parameters.isZoomSupported()) {//判斷是否支持 int maxZoom = parameters.getMaxZoom(); if (maxZoom == 0) { return; } if (zoomValue > maxZoom) { zoomValue = maxZoom; } parameters.setZoom(zoomValue); mCamera.setParameters(parameters); } } }
Note: 至此我們已經(jīng)完成了對(duì)所有事件的監(jiān)聽(tīng), 看到這里大家也許有些疲憊了, 不過(guò)不要灰心, 現(xiàn)在完成我們的核心部分, 實(shí)現(xiàn)視頻的錄制
實(shí)現(xiàn)視頻的錄制
說(shuō)是核心功能, 也只不過(guò)是我們不知道某些API方法罷了, 下面代碼中我已經(jīng)加了詳細(xì)的注釋, 部分不能理解的記住就好^v^
/** * 開(kāi)始錄制 */ private void startRecord() { if (mMediaRecorder != null) { //沒(méi)有外置存儲(chǔ), 直接停止錄制 if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { return; } try { //mMediaRecorder.reset(); mCamera.unlock(); mMediaRecorder.setCamera(mCamera); //從相機(jī)采集視頻 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); // 從麥克采集音頻信息 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); // TODO: 2016/10/20 設(shè)置視頻格式 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mMediaRecorder.setVideoSize(videoWidth, videoHeight); //每秒的幀數(shù) mMediaRecorder.setVideoFrameRate(24); //編碼格式 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); // 設(shè)置幀頻率,然后就清晰了 mMediaRecorder.setVideoEncodingBitRate(1 * 1024 * 1024 * 100); // TODO: 2016/10/20 臨時(shí)寫(xiě)個(gè)文件地址, 稍候該!!! File targetDir = Environment. getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES); mTargetFile = new File(targetDir, SystemClock.currentThreadTimeMillis() + ".mp4"); mMediaRecorder.setOutputFile(mTargetFile.getAbsolutePath()); mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface()); mMediaRecorder.prepare(); //正式錄制 mMediaRecorder.start(); isRecording = true; } catch (Exception e) { e.printStackTrace(); } } }
實(shí)現(xiàn)視頻的停止
大家可能會(huì)問(wèn), 視頻的停止為什么單獨(dú)抽出來(lái)說(shuō)呢? 仔細(xì)的同學(xué)看上面代碼會(huì)看到這兩個(gè)方法: stopRecordSave和stopRecordUnSave, 一個(gè)停止保存, 一個(gè)是停止不保存, 接下來(lái)我們就補(bǔ)上這個(gè)坑
停止并保存
private void stopRecordSave() { if (isRecording) { isRunning = false; mMediaRecorder.stop(); isRecording = false; Toast.makeText(this, "視頻已經(jīng)放至" + mTargetFile.getAbsolutePath(), Toast.LENGTH_SHORT).show(); } }
停止不保存
private void stopRecordUnSave() { if (isRecording) { isRunning = false; mMediaRecorder.stop(); isRecording = false; if (mTargetFile.exists()) { //不保存直接刪掉 mTargetFile.delete(); } } }
Note: 這個(gè)停止不保存是我自己的一種想法, 如果大家有更好的想法, 歡迎大家到評(píng)論中指出, 不勝感激
以上是使用Android實(shí)現(xiàn)微信小錄影功能詳細(xì)介紹的詳細(xì)內(nèi)容。更多資訊請(qǐng)關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

熱AI工具

Undress AI Tool
免費(fèi)脫衣圖片

Undresser.AI Undress
人工智慧驅(qū)動(dòng)的應(yīng)用程序,用於創(chuàng)建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費(fèi)的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門(mén)文章

熱工具

記事本++7.3.1
好用且免費(fèi)的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強(qiáng)大的PHP整合開(kāi)發(fā)環(huán)境

Dreamweaver CS6
視覺(jué)化網(wǎng)頁(yè)開(kāi)發(fā)工具

SublimeText3 Mac版
神級(jí)程式碼編輯軟體(SublimeText3)