Cet article présente principalement des informations pertinentes sur la mise en œuvre de la fonction d'enregistrement vidéo courte d'Android WeChat. Des idées et des codes de mise en œuvre spécifiques sont fournis ici. Les amis dans le besoin peuvent se référer à
Enregistrement vidéo courte d'Android WeChat. fonction
Avant le développement
J'ai été exposé à des contrôles liés à la vidéo ces derniers jours, donc, suite au précédent tremblement de WeChat, j'ai pensé à implémenter une courte vidéo WeChat. La fonction d'enregistrement a de nombreuses fonctions. Je prends le temps d'écrire à ce sujet chaque jour. Pour être honnête, certaines choses sont assez difficiles. J'espère que tout le monde y jettera un œil attentif. Veuillez me corriger dans les commentaires si je me trompe. . Sans plus tarder, entrons dans le vif du sujet.
Environnement de développement
Il vient d'être mis à jour récemment, amis qui n'ont pas mis à jour, dépêchez-vous
Android Studio 2.2.2
JDK1.7
API 24
Gradle 2.2 .2
Points de connaissances associés
Utilisation de l'interface d'enregistrement vidéo SurfaceView
L'utilisation de la caméra
La mise au point et le zoom de la caméra
L'utilisation du contrôle d'enregistrement vidéo MediaRecorder
Vue personnalisée simple
Utilisation de GestureDetector (détection de gestes)
Il y a beaucoup de choses utilisées , mais ne vous inquiétez pas, allons-y un par un Venez.
Commencer le développement
Analyse de cas
Vous pouvez ouvrir la courte vidéo dans votre WeChat et analysez brièvement ses fonctions : laquelle ?
Fonction de prévisualisation vidéo de base
Appuyez longuement sur « appuyez et maintenez pour filmer » pour enregistrer une vidéo
La barre de progression pendant l'enregistrement devient plus courte des deux côtés jusqu'au milieu
Lorsque vous lâchez prise ou que la barre de progression atteint la fin , la vidéo arrête l'enregistrement et est enregistrée
Faites glisser votre doigt vers le haut depuis « Maintenir pour filmer » pour annuler l'enregistrement vidéo
Appuyez deux fois sur l'écran pour zoomer
Selon l'analyse ci-dessus, nous l'avons complété étape par étape
Construire la mise en page
La mise en œuvre de l'interface de mise en page est correcte et pas difficile
<?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>
Mise en œuvre de l'aperçu vidéo
étape 1 : obtenir le SurfaceView contrôle, définir les propriétés de base et la surveillance correspondante (la création du contrôle est asynchrone, seulement lorsqu'il est vraiment "préparé" » ne peut être appelé qu'une fois qu'il est prêt)
mSurfaceView = (SurfaceView) findViewById(R.id.main_surface_view); //设置屏幕分辨率 mSurfaceHolder.setFixedSize(videoWidth, videoHeight); mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); mSurfaceHolder.addCallback(this);
étape 2 : implémentez la méthode d'interface, ouvrez l'aperçu de la vidéo dans la méthode surfaceCreated et détruisez-la dans 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; } }
étape 3 : méthode d'implémentation de la vidéo aperçu
/** * 开启预览 * * @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(); } } }
Remarque : le code de mise au point automatique est ajouté ci-dessus, mais certains téléphones mobiles peuvent
Rétrécissement bidirectionnel personnalisé la barre de progression n'est pas prise en charge
Certains débutants comme moi se sentent bien lorsqu'ils voient personnaliser une certaine vue X. En fait, Google a déjà écrit beaucoup de code pour nous, nous pouvons donc simplement l'utiliser. . Et notre barre de progression n'est rien, juste une ligne, parlons-en aujourd'hui
étape 1 : Hériter la vue et terminer l'initialisation
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); }
Remarque. : OnProgressEndListener, principalement utilisé lorsque la barre de progression atteint le milieu pour avertir la caméra d'arrêter l'enregistrement. L'interface est la suivante :
public interface OnProgressEndListener{ void onProgressEndListener(); } /** * 当进度条结束后的 监听 * @param onProgressEndListener */ public void setOnProgressEndListener(OnProgressEndListener onProgressEndListener) { mOnProgressEndListener = onProgressEndListener; }
étape 2 : Définissez la méthode Setter. pour informer notre progression du changement de statut
/** * 设置进度 * @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(); }
étape 3 : L'étape la plus importante consiste à dessiner notre barre de progression, en utilisant la méthode onDraw (Canvas Canvas) dans View
@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)); } }
Gestion des événements d'enregistrement
Les événements déclenchés lors de l'enregistrement sont au nombre de quatre :
Long appuyez pour enregistrer
Soulevez pour enregistrer
Faites glisser vers le haut pour annuler
Double-cliquez pour zoomer (zoom)
Analysez maintenant ces 4 événements un par un :
J'ai placé les trois premiers événements dans une méthode de rappel onTouch()
Pour le quatrième un, nous en reparlerons plus tard
Listons d'abord les variables locales dans 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; // ... }
Appuyez longuement pour enregistrer
Appuyez longuement pour enregistrer, nous devons écouter l'événement ACTION_DOWN et utiliser le fil de discussion pour retarder l'envoi du gestionnaire pour mettre à jour la barre de progression
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; }
Remarque : ne parlons pas de la méthode startRecord(). Il suffit de savoir qu'elle peut être enregistrée après son exécution, mais il faut quand même mentionner l'événement Handler. Seul responsable de la mise à jour de la progression de la barre de progression<🎜. >
//////////////////////////////////////////////////// // 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; } } }
Soulevez et enregistrez
De même, nous devons écouter l'événement ACTION_UP ici, mais considérez cela lorsque l'utilisateur soulève trop rapidement (la durée d'enregistrement est trop courte), il n'est pas nécessaire de sauvegarder. De plus, cet événement inclut le levage dans l'état d'annulation. Expliquez : c'est le moment où l'utilisateur se lève pour annuler l'enregistrement. , veuillez regarder le codecase 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;
glissez vers le haut pour annuler
coopérer Une partie dit que vous devez déclencher l'événement d'annulation et réaliser un glissement vers le haut pour annulercase 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: 这个停止不保存是我自己的一种想法, 如果大家有更好的想法, 欢迎大家到评论中指出, 不胜感激
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!