首頁  >  文章  >  微信小程式  >  使用Android實現微信小錄影功能詳細介紹

使用Android實現微信小錄影功能詳細介紹

高洛峰
高洛峰原創
2017-03-23 13:39:442201瀏覽

這篇文章主要介紹了Android 微信小視頻錄製功能實現詳解的相關資料,這裡提供了具體的實現思路及代碼,需要的朋友可以參考下

Android 微信小視頻錄製功能

開發之前

這幾天接觸了一下和視頻相關的控件, 所以, 繼之前的微信搖一搖, 我想到了來實現一下微信小視頻錄製的功能, 它的功能點比較多, 我每天都抽出點時間來寫寫, 說實話, 有些東西還是比較費勁, 希望大家認真看看, 說得不對的地方還請大家在評論中指正. 廢話不多說, 進入正題.

開發環境

最近剛更新的, 沒更新的小夥伴們抓緊了

  1. Android Studio 2.2.2

  2. JDK1.7

  3. #API 24

  4. Gradle 2.2 .2

相關知識點

  1. #影片錄製介面SurfaceView 的使用

## Camera的使用

相機的對焦, 變焦

  1. #影片錄製控制項MediaRecorder的使用

  2. 簡單自訂View

  3. GestureDetector(手勢偵測)的使用

  4. 用到的東西真不少, 不過別急, 咱們一個一個來.

    開始開發
  5. 案例分析

  6. #大家可以打開自己微信裡面的小影片, 一塊簡單的分析一下它的功能點有哪些?

基本的影片預覽功能

# 長按「按住拍」實現影片的錄製

錄製過程中的進度條從兩側向中間變短


#當鬆手或進度條走到盡頭影片停止錄製並保存

從「按住拍攝」 上滑取消影片的錄製


雙擊螢幕變焦放大


根據上述的分析, 我們一步一步的完成

搭建佈局

佈局介面的實作還可以, 難度不大

<?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控制項, 設定基本屬性和對應監聽(該控制項的建立是異步的, 只有在真正」準備」好之後才能呼叫)


mSurfaceView = (SurfaceView) findViewById(R.id.main_surface_view);
 //设置屏幕分辨率
mSurfaceHolder.setFixedSize(videoWidth, videoHeight);
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
mSurfaceHolder.addCallback(this);

step2: 實作介面的方法, surfaceCreated方法中開啟影片的預覽, 在surfaceDestroyed中銷毀


//////////////////////////////////////////////
// SurfaceView回调
/////////////////////////////////////////////
@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: ");
    //停止预览并释放摄像头资源
    mCamera.stopPreview();
    mCamera.release();
    mCamera = null;
  }
  if (mMediaRecorder != null) {
    mMediaRecorder.release();
    mMediaRecorder = null;
  }
}

step3: 實作影片預覽的方法


/**
 * 开启预览
 *
 * @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();
      //实现Camera自动对焦
      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: 上面新增了自動對焦的程式碼, 但是部分手機可能不支援


#自訂雙向縮減的進度條

有些像我一樣的初學者一看到自訂某某View, 就覺得比較牛X. 其實呢, Google已經替我們寫好了很多程式碼, 所以我們用就行了.而且咱們的這個進度條也沒啥, 不就是一根線, 今天咱就來說說.

step1: 繼承View, 完成初始化
  1. private static final String TAG = "BothWayProgressBar";
    //取消状态为红色bar, 反之为绿色bar
    private boolean isCancel = false;
    private Context mContext;
    //正在录制的画笔
    private Paint mRecordPaint;
    //上滑取消时的画笔
    private Paint mCancelPaint;
    //是否显示
    private int mVisibility;
    // 当前进度
    private int progress;
    //进度条结束的监听
    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);
    }
  2. Note: OnProgressEndListener, 主要用於當進度條走到中間了, 好通知相機停止錄製, 介面如下:
  3. public interface OnProgressEndListener{
      void onProgressEndListener();
    }
    /**
     * 当进度条结束后的 监听
     * @param onProgressEndListener
     */
    public void setOnProgressEndListener(OnProgressEndListener onProgressEndListener) {
      mOnProgressEndListener = onProgressEndListener;
    }
  4. step2 :設定Setter方法用於通知我們的Progress改變狀態

  5. #
    /**
     * 设置进度
     * @param progress
     */
    public void setProgress(int progress) {
      this.progress = progress;
      invalidate();
    }
    
    /**
     * 设置录制状态 是否为取消状态
     * @param isCancel
     */
    public void setCancel(boolean isCancel) {
      this.isCancel = isCancel;
      invalidate();
    }
    /**
     * 重写是否可见方法
     * @param visibility
     */
    @Override
    public void setVisibility(int visibility) {
      mVisibility = visibility;
      //重新绘制
      invalidate();
    }

    step3 :最重要的一步, 畫出我們的進度條,使用的就是View中的onDraw(Canvas canvas)方法


@Override
protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);
  if (mVisibility == View.VISIBLE) {
    int height = getHeight();
    int width = getWidth();
    int mid = width / 2;


    //画出进度条
    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));
  }
}

錄製事件的處理

錄製中觸發的事件包括四個:


長按錄製

抬起儲存


上滑取消






####雙擊放大(變焦) ############ 現在對這4個事件逐一分析: ###前三這個事件, 我都放在了一個onTouch()回呼方法中了###對於第4個, 我們待會談###我們先把onTouch()中局部變數列舉一下:############
@Override
public boolean onTouch(View v, MotionEvent event) {
  boolean ret = false;
  int action = event.getAction();
  float ey = event.getY();
  float ex = event.getX();
  //只监听中间的按钮处
  int vW = v.getWidth();
  int left = LISTENER_START;
  int right = vW - LISTENER_START;
  float downY = 0;
  // ...
}
######長按錄製#########長按錄製我們需要監聽ACTION_DOWN事件, 使用執行緒延遲發送Handler來實現進度列的更新###############
switch (action) {
 case MotionEvent.ACTION_DOWN:
   if (ex > left && ex < right) {
     mProgressBar.setCancel(false);
     //显示上滑取消
     mTvTip.setVisibility(View.VISIBLE);
     mTvTip.setText("↑ 上滑取消");
     //记录按下的Y坐标
     downY = ey;
     // TODO: 2016/10/20 开始录制视频, 进度条开始走
     mProgressBar.setVisibility(View.VISIBLE);
     //开始录制
     Toast.makeText(this, "开始录制", 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()這個方法先不說, 我們只需要知道執行了它就可以錄製了, 但是Handler事件還是要說的, 它只負責更新進度條的進度############
////////////////////////////////////////////////////
// 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;
    }
  }
}
######抬起保存#########同樣我們這兒需要監聽ACTION_UP事件, 但是要考慮當用戶抬起過快時(錄製的時間過短), 不需要保存. 而且,在這個事件中包含了取消狀態的抬起, 解釋一下: 就是當上滑取消時抬起的一瞬間取消錄製, 大家看代碼###############
case MotionEvent.ACTION_UP:
 if (ex > left && ex < right) {
   mTvTip.setVisibility(View.INVISIBLE);
   mProgressBar.setVisibility(View.INVISIBLE);
   //判断是否为录制结束, 或者为成功录制(时间过短)
   if (!isCancel) {
     if (mProgress < 50) {
       //时间太短不保存
       stopRecordUnSave();
       Toast.makeText(this, "时间太短", Toast.LENGTH_SHORT).show();
       break;
     }
     //停止录制
     stopRecordSave();
   } else {
     //现在是取消状态,不保存
     stopRecordUnSave();
     isCancel = false;
     Toast.makeText(this, "取消录制", Toast.LENGTH_SHORT).show();
     mProgressBar.setCancel(false);
   }

   ret = false;
 }
 break;
## #Note: 同樣的, 內部的stopRecordUnSave()和stopRecordSave();大家先不要考慮, 我們會在後面介紹, 他倆從名字就能看出前者用來停止錄製但不保存, 後者停止錄製並保存############上滑取消#########配合上一部分說得抬起取消事件, 實作上滑取消############
case MotionEvent.ACTION_MOVE:
 if (ex > left && ex < right) {
   float currentY = event.getY();
   if (downY - currentY > 10) {
     isCancel = true;
     mProgressBar.setCancel(true);
   }
 }
 break;

Note: 主要原理不难, 只要按下并且向上移动一定距离 就会触发,当手抬起时视频录制取消

双击放大(变焦)

这个事件比较特殊, 使用了Google提供的GestureDetector手势检测 来判断双击事件

step1: 对SurfaceView进行单独的Touch事件监听, why? 因为GestureDetector需要Touch事件的完全托管, 如果只给它传部分事件会造成某些事件失效


mDetector = new GestureDetector(this, new ZoomGestureListener());
/**
 * 单独处理mSurfaceView的双击事件
 */
mSurfaceView.setOnTouchListener(new View.OnTouchListener() {
  @Override
  public boolean onTouch(View v, MotionEvent event) {
    mDetector.onTouchEvent(event);
    return true;
  }
});

step2: 重写GestureDetector.SimpleOnGestureListener, 实现双击事件


///////////////////////////////////////////////////////////////////////////
// 变焦手势处理类
///////////////////////////////////////////////////////////////////////////
class ZoomGestureListener extends GestureDetector.SimpleOnGestureListener {
  //双击手势事件
  @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: 实现相机的变焦的方法


/**
 * 相机变焦
 *
 * @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: 至此我们已经完成了对所有事件的监听, 看到这里大家也许有些疲惫了, 不过不要灰心, 现在完成我们的核心部分, 实现视频的录制

实现视频的录制

说是核心功能, 也只不过是我们不知道某些API方法罢了, 下面代码中我已经加了详细的注释, 部分不能理解的记住就好^v^


/**
 * 开始录制
 */
private void startRecord() {
  if (mMediaRecorder != null) {
    //没有外置存储, 直接停止录制
    if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
      return;
    }
    try {
      //mMediaRecorder.reset();
      mCamera.unlock();
      mMediaRecorder.setCamera(mCamera);
      //从相机采集视频
      mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
      // 从麦克采集音频信息
      mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
      // TODO: 2016/10/20 设置视频格式
      mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
      mMediaRecorder.setVideoSize(videoWidth, videoHeight);
      //每秒的帧数
      mMediaRecorder.setVideoFrameRate(24);
      //编码格式
      mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
      mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
      // 设置帧频率,然后就清晰了
      mMediaRecorder.setVideoEncodingBitRate(1 * 1024 * 1024 * 100);
      // TODO: 2016/10/20 临时写个文件地址, 稍候该!!!
      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();
    }

  }
}

实现视频的停止

大家可能会问, 视频的停止为什么单独抽出来说呢? 仔细的同学看上面代码会看到这两个方法: stopRecordSave和stopRecordUnSave, 一个停止保存, 一个是停止不保存, 接下来我们就补上这个坑

停止并保存


private void stopRecordSave() {
  if (isRecording) {
    isRunning = false;
    mMediaRecorder.stop();
    isRecording = false;
    Toast.makeText(this, "视频已经放至" + mTargetFile.getAbsolutePath(), Toast.LENGTH_SHORT).show();
  }
}

停止不保存


private void stopRecordUnSave() {
  if (isRecording) {
    isRunning = false;
    mMediaRecorder.stop();
    isRecording = false;
    if (mTargetFile.exists()) {
      //不保存直接删掉
      mTargetFile.delete();
    }
  }
}

Note: 这个停止不保存是我自己的一种想法, 如果大家有更好的想法, 欢迎大家到评论中指出, 不胜感激

以上是使用Android實現微信小錄影功能詳細介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn