This article mainly introduces the relevant information on the implementation of the Android WeChat short video recording function. Specific implementation ideas and codes are provided here. Friends in need can refer to
Android WeChat short video Recording function
Before development
I have been exposed to video-related controls in the past few days, so after the previous WeChat shake, I thought of implementing a WeChat short video The recording function has many functions. I take some time to write about it every day. To be honest, some things are quite difficult. I hope everyone will take a look at it carefully. Please correct me in the comments if I am wrong. Nonsense. Without further ado, let’s get to the point.
Development environment
It was just updated recently, friends who haven’t updated please hurry up
Android Studio 2.2.2
- ##JDK1.7
- API 24
- Gradle 2.2 .2
Related knowledge points
- Usage of Video Recording Interface SurfaceView
- The use of Camera
- The focus and zoom of the camera
- The use of video recording control MediaRecorder
- Simple customization of View
- Using GestureDetector
There are a lot of things used, but don’t worry, let’s go one by one Come.
Start development
Case analysis
You can open the short video in your WeChat and briefly analyze its functions: Which?
- Basic video preview function
- Long press "press and hold to shoot" to record video
- The progress bar during recording becomes shorter from both sides to the middle
- When you let go or the progress bar reaches the end, the video stops recording and is saved
- Swipe up from "Hold to shoot" to cancel video recording
- Double-click the screen to zoom in
According to the above Analysis, we complete it step by step
Build the layout
The implementation of the layout interface is okay and not difficult
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | <?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>
|
Copy after login
Video Preview Implementation
#step1: Get the SurfaceView control, set the basic properties and corresponding monitoring (the creation of the control is asynchronous, and it is only when it is truly "ready" It can only be called later)
1 2 3 4 5 | mSurfaceView = (SurfaceView) findViewById(R.id.main_surface_view);
mSurfaceHolder.setFixedSize(videoWidth, videoHeight);
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
mSurfaceHolder.addCallback(this);
|
Copy after login
step2: Implement the interface method, open the preview of the video in the surfaceCreated method, and destroy it in surfaceDestroyed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | @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;
}
}
|
Copy after login
step3: How to implement video preview
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
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();
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();
}
}
}
|
Copy after login
Note: The autofocus code is added above, but some mobile phones may not support it
Customized two-way reduction progress bar
Some beginners like me feel that it is awesome when they see a customized View. In fact, Google has already replaced it We have written a lot of code, so we can just use it. And our progress bar is nothing, just a line, let’s talk about it today.
step1: Inherit View and complete initialization
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | private static final String TAG = "BothWayProgressBar" ;
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);
}
|
Copy after login
Note: OnProgressEndListener, mainly used when the progress bar reaches the middle, so as to notify the camera to stop recording. The interface is as follows:
1 2 3 4 5 6 7 8 9 10 | public interface OnProgressEndListener{
void onProgressEndListener();
}
public void setOnProgressEndListener(OnProgressEndListener onProgressEndListener) {
mOnProgressEndListener = onProgressEndListener;
}
|
Copy after login
step2: Set the Setter method to notify our Progress of changing status
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public void setProgress(int progress) {
this.progress = progress;
invalidate();
}
public void setCancel(boolean isCancel) {
this.isCancel = isCancel;
invalidate();
}
@Override
public void setVisibility(int visibility) {
mVisibility = visibility;
invalidate();
}
|
Copy after login
step3: The most important step, draw our progress bar, use It is the onDraw(Canvas canvas) method in View
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | @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));
}
}
|
Copy after login
Processing of recording events
The events triggered during recording include four:
- Long press to record
- Lift up to save
- Swipe up to cancel
- Double-click to enlarge (zoom)
Now analyze these four events one by one:
For the first three events, I have placed an onTouch() callback method Winning
For the fourth one, we will talk about it later
Let’s first list the local variables in onTouch():
1 2 3 4 5 6 7 8 9 10 11 12 13 | @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;
}
|
Copy after login
Long press to record
We need to listen to the ACTION_DOWN event for long press recording, and use the thread to delay sending the Handler to update the progress bar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | switch (action) {
case MotionEvent.ACTION_DOWN:
if (ex > left && ex < right) {
mProgressBar.setCancel(false);
mTvTip.setVisibility(View.VISIBLE);
mTvTip.setText( "↑ 上滑取消" );
downY = ey;
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;
}
|
Copy after login
Note: Let’s not talk about the startRecord() method. We only need to know that it can be recorded after it is executed, but the Handler event still needs to be mentioned. It is only responsible for updating the progress of the progress bar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 ;
}
}
}
|
Copy after login
Lift up and save
We also need to listen to the ACTION_UP event here, but we must consider that when the user lifts up too quickly (the recording time is too short), there is no need to save. Moreover, This event includes the lifting of the cancellation state. To explain: it cancels the recording the moment it is lifted when swiping up to cancel. Please look at the code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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 ;
|
Copy after login
Note: Similarly, the internal stopRecordUnSave() and stopRecordSave(); don’t think about it for now, we will introduce it later. From their names, we can tell that the former is used to stop recording but not save, and the latter is used to stop recording and save.
Swipe up to cancel
In conjunction with the previous part, raise the cancellation event to realize slide up to cancel
1 2 3 4 5 6 7 8 9 | case MotionEvent.ACTION_MOVE:
if (ex > left && ex < right) {
float currentY = event.getY();
if (downY - currentY > 10) {
isCancel = true;
mProgressBar.setCancel(true);
}
}
break ;
|
Copy after login
Note: 主要原理不难, 只要按下并且向上移动一定距离 就会触发,当手抬起时视频录制取消
双击放大(变焦)
这个事件比较特殊, 使用了Google提供的GestureDetector手势检测 来判断双击事件
step1: 对SurfaceView进行单独的Touch事件监听, why? 因为GestureDetector需要Touch事件的完全托管, 如果只给它传部分事件会造成某些事件失效
1 2 3 4 5 6 7 8 9 10 11 | mDetector = new GestureDetector(this, new ZoomGestureListener());
mSurfaceView.setOnTouchListener( new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mDetector.onTouchEvent(event);
return true;
}
});
|
Copy after login
step2: 重写GestureDetector.SimpleOnGestureListener, 实现双击事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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;
}
}
|
Copy after login
step3: 实现相机的变焦的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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);
}
}
}
|
Copy after login
Note: 至此我们已经完成了对所有事件的监听, 看到这里大家也许有些疲惫了, 不过不要灰心, 现在完成我们的核心部分, 实现视频的录制
实现视频的录制
说是核心功能, 也只不过是我们不知道某些API方法罢了, 下面代码中我已经加了详细的注释, 部分不能理解的记住就好^v^
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
private void startRecord() {
if (mMediaRecorder != null) {
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
return ;
}
try {
mCamera.unlock();
mMediaRecorder.setCamera(mCamera);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
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);
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();
}
}
}
|
Copy after login
实现视频的停止
大家可能会问, 视频的停止为什么单独抽出来说呢? 仔细的同学看上面代码会看到这两个方法: stopRecordSave和stopRecordUnSave, 一个停止保存, 一个是停止不保存, 接下来我们就补上这个坑
停止并保存
1 2 3 4 5 6 7 8 | private void stopRecordSave() {
if (isRecording) {
isRunning = false;
mMediaRecorder.stop();
isRecording = false;
Toast.makeText(this, "视频已经放至" + mTargetFile.getAbsolutePath(), Toast.LENGTH_SHORT).show();
}
}
|
Copy after login
停止不保存
1 2 3 4 5 6 7 8 9 10 11 | private void stopRecordUnSave() {
if (isRecording) {
isRunning = false;
mMediaRecorder.stop();
isRecording = false;
if (mTargetFile.exists()) {
mTargetFile. delete ();
}
}
}
|
Copy after login
Note: 这个停止不保存是我自己的一种想法, 如果大家有更好的想法, 欢迎大家到评论中指出, 不胜感激
The above is the detailed content of Detailed introduction to using Android to implement WeChat short video recording function. For more information, please follow other related articles on the PHP Chinese website!