由于camera已经被舍弃,建议使用替代类camera2。下篇文章会讲到camera2。
切入主题。
首先是权限,这个比较容易忘记申请。
<!-- 存储 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!--摄像头--> <uses-permission android:name="android.permission.CAMERA" /> <!--录音--> <uses-permission android:name="android.permission.RECORD_AUDIO" />
视频录制需要存储权限、照相机权限、录音权限和网络权限。网络权限在这里并不是主要的。不过一般都顺手加上。毕竟网络是最常用的。
照相机权限和录音权限从Android6.0开始需要动态申请,申请方式
当然现在框架有很多,可以选择一种自己喜欢的使用。
权限申请完成后,下面我们有就要自定义一个预览照相机拍摄画面的展示类了。有两个类可以选择 SurfaceView、SurfaceTexture 这里使用SurfaceView。
![]()
不管怎么说没有比实现代码实在的东西了。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center_horizontal" > <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1.0" > <SurfaceView android:id="@+id/surfaceview" android:layout_width="match_parent" android:layout_height="match_parent" /> <Button android:id="@+id/btn_lamp" android:layout_width="30dp" android:layout_height="30dp" android:background="@drawable/btn_lamp_bg" android:layout_margin="@dimen/margin_10" android:layout_alignParentBottom="true" /> <Button android:id="@+id/btn_lens" android:layout_width="30dp" android:layout_height="30dp" android:background="@drawable/lens" android:layout_margin="@dimen/margin_10" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" /> </RelativeLayout> <ProgressBar android:id="@+id/progressBar" android:layout_width="match_parent" android:layout_height="5dp" style="@android:style/Widget.ProgressBar.Horizontal" android:progressDrawable="@drawable/progressbar_color" /> </LinearLayout>
package pers.wtt.ui.video.view; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; import android.hardware.Camera; import android.media.MediaRecorder; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.OrientationEventListener; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; import android.widget.ProgressBar; import com.googlecode.mp4parser.util.Matrix; import pers.wtt.R; import pers.wtt.lib_common.utils.LogUtil; import java.io.File; import java.io.IOException; import java.util.Timer; import java.util.TimerTask; /** * Created by 王亭 on 2017/8/3. * 视频录制 */ public class MovieRecorderView extends LinearLayout implements MediaRecorder.OnErrorListener,View.OnClickListener { private Context context; private Camera.Parameters parameters; private SurfaceView mSurfaceView; private SurfaceHolder mSurfaceHolder; private ProgressBar mProgressBar; private Button btn_lamp; private Button btn_lens; private MediaRecorder mMediaRecorder; private Camera mCamera; private Timer mTimer;// 计时器 private OnRecordFinishListener mOnRecordFinishListener;// 录制完成回调接口 private int mWidth;// 视频分辨率宽度 private int mHeight;// 视频分辨率高度 private boolean isOpenCamera;// 是否一开始就打开摄像头 private int mRecordMaxTime;// 一次拍摄最长时间 private int mTimeCount;// 时间计数 private File mVecordFile = null;// 文件 private String ROATE_MP4 = ""; private boolean record = false; private boolean isLamp = false; private int mOrientation = 0; private boolean camF = false; private Matrix matrix = Matrix.ROTATE_90; private OrientationEventListener orientationEventListener; public MovieRecorderView(Context context) { this(context, null); } public MovieRecorderView(Context context, AttributeSet attrs) { this(context, attrs, 0); } @TargetApi(Build.VERSION_CODES.HONEYCOMB) public MovieRecorderView(final Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); this.context = context; mWidth=640; mHeight=480; isOpenCamera=true; mRecordMaxTime=1000; LayoutInflater.from(context).inflate(R.layout.moive_recorder_view, this); mSurfaceView = (SurfaceView) findViewById(R.id.surfaceview); mProgressBar = (ProgressBar) findViewById(R.id.progressBar); btn_lamp = (Button) findViewById(R.id.btn_lamp); btn_lens = (Button) findViewById(R.id.btn_lens); btn_lamp.setOnClickListener(this); btn_lens.setOnClickListener(this); orientationEventListener = new OrientationEventListener(context) { @Override public void onOrientationChanged(int orientation) { if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) { return; } //保证只返回四个方向 int newOrientation = ((orientation + 45) / 90 * 90) % 360; if (newOrientation != mOrientation) { mOrientation = newOrientation; btn_lamp.setRotation(-mOrientation); btn_lens.setRotation(-mOrientation); LogUtil.e("方向","mOrientation:"+mOrientation); //返回的mOrientation就是手机方向,为0°、90°、180°和270°中的一个 } } }; if(orientationEventListener.canDetectOrientation()){ orientationEventListener.enable(); } mProgressBar.setMax(mRecordMaxTime);// 设置进度条最大量 mSurfaceHolder = mSurfaceView.getHolder(); mSurfaceHolder.addCallback(new CustomCallBack()); mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } private void OpenLightOn() { PackageManager pm= context.getPackageManager(); FeatureInfo[] features=pm.getSystemAvailableFeatures(); for(FeatureInfo f : features) { if(PackageManager.FEATURE_CAMERA_FLASH.equals(f.name)) //判断设备是否支持闪光灯 { try { initCamera(true, -1); isLamp = true; btn_lamp.setSelected(true); } catch (IOException e) { e.printStackTrace(); } } } } private void CloseLightOff() { if ( mCamera != null ) { try { initCamera(false, -1); isLamp = false; btn_lamp.setSelected(false); } catch (IOException e) { e.printStackTrace(); } } } public boolean isRecord() { return record; } @Override public void onClick(View v) { switch (v.getId()){ case R.id.btn_lamp: if(isLamp){ CloseLightOff(); }else{ OpenLightOn(); } break; case R.id.btn_lens: if(camF){ camF = false; }else{ camF = true; } changeCamera(); break; } } private void changeCamera() { Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); int cameraCount = Camera.getNumberOfCameras(); // get cameras number LogUtil.e("摄像头数量","cameraCount:"+cameraCount + " camF:"+camF); for (int camIdx = 0; camIdx < cameraCount; camIdx++) { Camera.getCameraInfo(camIdx, cameraInfo); // get camerainfo LogUtil.e("摄像头数量","摄像头:"+cameraInfo.facing+" " + cameraInfo.toString()); if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { // 代表摄像头的方位,目前有定义值两个分别为CAMERA_FACING_FRONT前置和CAMERA_FACING_BACK后置 if(camF) { try { initCamera(false, camIdx); camF = true; LogUtil.e("摄像头数量", "前置摄像头:" + camIdx); break; } catch (IOException e) { e.printStackTrace(); } } } else { if(!camF) { try { initCamera(false, camIdx); camF = false; LogUtil.e("摄像头数量", "后置摄像头:" + camIdx); break; } catch (IOException e) { e.printStackTrace(); } } } } } public void init() { if (!isOpenCamera) {// 如果未打开摄像头,则打开 try { initCamera(isLamp, -1); } catch (IOException e) { e.printStackTrace(); } } } /** * * @author wt * * @date 2015-2-5 */ private class CustomCallBack implements SurfaceHolder.Callback, Camera.PreviewCallback { @Override public void surfaceCreated(SurfaceHolder holder) { if (!isOpenCamera) return; try { initCamera(false, -1); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { if (!isOpenCamera) return; freeCameraResource(); } @Override public void onPreviewFrame(byte[] data, Camera camera) { } } /** * 初始化摄像头 * * @author lip * @date 2015-3-16 * @throws IOException * @param lamp */ private void initCamera(boolean lamp, int camIdx) throws IOException { if (mCamera != null) { freeCameraResource(); } try { if(camIdx!=-1){ mCamera = Camera.open(camIdx); }else { mCamera = Camera.open(); } } catch (Exception e) { e.printStackTrace(); freeCameraResource(); } if (mCamera == null) return; setCameraParams(lamp); mCamera.setDisplayOrientation(90); mCamera.setPreviewDisplay(mSurfaceHolder); mCamera.startPreview(); mCamera.unlock(); isOpenCamera = true; } /** * 设置摄像头为竖屏 * * @author lip * @date 2015-3-16 * @param lamp */ private void setCameraParams(boolean lamp) { if (mCamera != null) { parameters = mCamera.getParameters(); if(lamp) parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH); parameters.set("orientation", "portrait"); parameters.setPreviewSize(mWidth, mHeight); mCamera.setParameters(parameters); } } /** * 释放摄像头资源 * * @author wt * @date 2015-2-5 */ private void freeCameraResource() { if (mCamera != null) { mCamera.setPreviewCallback(null); mCamera.stopPreview(); mCamera.lock(); mCamera.release(); mCamera = null; isOpenCamera = false; } } private void createRecordDir() { // File sampleDir = new File(Environment.getExternalStorageDirectory() + File.separator + "im/video/"); File vecordDir = new File(Environment.getExternalStorageDirectory() + File.separator+"RecordVideo/"); //File sampleDir = new File("/video/"); if (!vecordDir.exists()) { vecordDir.mkdirs(); } // 创建文件 try { mVecordFile = File.createTempFile("recording", ".mp4", vecordDir);//mp4格式 //LogUtilUtils.i(mVecordFile.getAbsolutePath()); LogUtil.d("Path:",mVecordFile.getAbsolutePath()); } catch (IOException e) { } } /** * 初始化 * * @author lip * @date 2015-3-16 * @throws IOException */ private void initRecord() throws IOException { try { mMediaRecorder = new MediaRecorder(); mMediaRecorder.reset(); if (mCamera != null) mMediaRecorder.setCamera(mCamera); mMediaRecorder.setOnErrorListener(this); mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface()); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);// 视频源 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 音频源 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);// 视频输出格式 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);// 音频格式 mMediaRecorder.setVideoSize(mWidth, mHeight);// 设置分辨率: // mMediaRecorder.setVideoFrameRate(18);// 帧率 mMediaRecorder.setVideoEncodingBitRate(5 * 1024 * 512);// 设置帧频率,然后就清晰了 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);// 视频录制格式 // mediaRecorder.setMaxDuration(Constant.MAXVEDIOTIME * 1000); mMediaRecorder.setOutputFile(mVecordFile.getAbsolutePath()); if(camF){ if(mOrientation==90){ matrix = Matrix.ROTATE_180; mMediaRecorder.setOrientationHint(180);// 输出旋转180度,保持竖屏录制 }else if(mOrientation==180) { matrix = Matrix.ROTATE_90; mMediaRecorder.setOrientationHint(90);// 输出旋转90度,保持竖屏录制 }else if(mOrientation==270) { matrix = Matrix.ROTATE_0; mMediaRecorder.setOrientationHint(0);// 输出旋转0度,保持竖屏录制 }else{ matrix = Matrix.ROTATE_270; mMediaRecorder.setOrientationHint(270);// 输出旋转270度,保持竖屏录制 } }else { if(mOrientation==90){ matrix = Matrix.ROTATE_180; mMediaRecorder.setOrientationHint(180);// 输出旋转180度,保持竖屏录制 }else if(mOrientation==180) { matrix = Matrix.ROTATE_270; mMediaRecorder.setOrientationHint(270);// 输出旋转270度,保持竖屏录制 }else if(mOrientation==270) { matrix = Matrix.ROTATE_0; mMediaRecorder.setOrientationHint(0);// 输出旋转0度,保持竖屏录制 }else{ matrix = Matrix.ROTATE_90; mMediaRecorder.setOrientationHint(90);// 输出旋转90度,保持竖屏录制 } } mMediaRecorder.prepare(); mMediaRecorder.start(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (RuntimeException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } /** * 开始录制视频 * * @author wt * @date 2015-2-5 // * @param fileName // * 视频储存位置 * @param onRecordFinishListener * 达到指定时间之后回调接口 */ public void record(final OnRecordFinishListener onRecordFinishListener) { this.mOnRecordFinishListener = onRecordFinishListener; createRecordDir(); try { if (!isOpenCamera)// 如果未打开摄像头,则打开 initCamera(isLamp, -1); initRecord(); record = true; btn_lamp.setEnabled(false); btn_lens.setEnabled(false); mTimeCount = 0;// 时间计数器重新赋值 mTimer = new Timer(); mTimer.schedule(new TimerTask() { @Override public void run() { // TODO Auto-generated method stub mTimeCount++; mProgressBar.setProgress(mTimeCount);// 设置进度条 if (mTimeCount == mRecordMaxTime) {// 达到指定时间,停止拍摄 stop(); if (mOnRecordFinishListener != null) { Intent scanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); scanIntent.setData(Uri.fromFile(mVecordFile)); getContext().sendBroadcast(scanIntent); mOnRecordFinishListener.onRecordFinish(mVecordFile.getAbsolutePath()); } } } }, 0, 10); } catch (IOException e) { e.printStackTrace(); } } private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); btn_lamp.setEnabled(true); btn_lens.setEnabled(true ); mProgressBar.setProgress(1);// 设置进度条 } }; /** * 停止拍摄 * * @author wt * @date 2015-2-5 */ public void stop() { mOrientation = 0; record = false; stopRecord(); releaseRecord(); freeCameraResource(); handler.sendEmptyMessage(0); // ROATE_MP4 = DisplayUtil.rotateVideo(mVecordFile.getAbsolutePath(), Matrix.ROTATE_90); } /** * 停止录制 * * @author wt * @date 2015-2-5 */ public void stopRecord() { mProgressBar.setProgress(0); if (mTimer != null) mTimer.cancel(); if (mMediaRecorder != null) { // 设置后不会崩 mMediaRecorder.setOnInfoListener(null); mMediaRecorder.setOnErrorListener(null); mMediaRecorder.setPreviewDisplay(null); try { mMediaRecorder.stop(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (RuntimeException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } } /** * 释放资源 * * @author wt * @date 2015-2-5 */ private void releaseRecord() { if (mMediaRecorder != null) { mMediaRecorder.setOnErrorListener(null); try { mMediaRecorder.release(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } mMediaRecorder = null; } public int getTimeCount() { return mTimeCount; } /** * @return the mVecordFile */ public String getVideoPath() { return mVecordFile.getAbsolutePath(); } /** * 录制完成回调接口 * * @author lip * * @date 2015-3-16 */ public interface OnRecordFinishListener { public void onRecordFinish(String absolutePath); } @Override public void onError(MediaRecorder mr, int what, int extra) { try { if (mr != null) mr.reset(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } }
有一点需要注意,录制相关设置的顺序会影响程序运行。如果有异常出现,请检查顺序是否和本例相同。
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <per.wtt.ui.video.view.MovieRecorderView android:id="@+id/moive_rv" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerHorizontal="true" android:layout_weight="1.0" > </pers.wtt.ui.video.view.MovieRecorderView> <LinearLayout android:id="@+id/record_rl" android:layout_width="match_parent" android:layout_height="120dp" android:layout_below="@id/moive_rv" android:gravity="center_horizontal|center_vertical" android:orientation="horizontal" > <View android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1.0" ></View> <LinearLayout android:id="@+id/ll_photo" android:layout_width="60dp" android:layout_height="wrap_content" android:gravity="center_horizontal" android:orientation="vertical" android:visibility="invisible" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/margin_5" android:text="相册" android:textColor="#9f9f9e" android:textSize="@dimen/text_12sp" android:visibility="invisible" /> <ImageView android:id="@+id/iv_cvit_icon" android:layout_width="40dp" android:layout_height="40dp" android:background="@drawable/ic_album" /> <TextView android:id="@+id/tv_cvit_value" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/text_white" android:textSize="@dimen/text_16sp" android:visibility="gone" /> <TextView android:id="@+id/tv_cvit_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/margin_5" android:text="相册" android:textColor="#9f9f9e" android:textSize="@dimen/text_12sp" /> </LinearLayout> <View android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1.0" ></View> <Button android:id="@+id/start_btn" android:layout_width="80dp" android:layout_height="80dp" android:background="@drawable/btn_rec_bg" /> <View android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1.0" ></View> <View android:layout_width="60dp" android:layout_height="60dp" android:background="@drawable/ic_btn_rec" android:visibility="invisible" /> <View android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1.0" ></View> </LinearLayout> </LinearLayout> <ImageView android:id="@+id/iv_close" android:layout_width="30dp" android:layout_height="30dp" android:src="@mipmap/close_w" android:layout_margin="@dimen/margin_10" /> </RelativeLayout>
package pers.wtt.ui.video.fragment; import android.content.Intent; import android.net.Uri; import android.view.View; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import pers.wtt.R; import pers.wtt.base.BaseFragmentNew; import pers.wtt.ui.video.activity.VideoCoverActivity; import pers.wtt.ui.video.view.MovieRecorderView; import pers.wtt.lib_common.utils.LogUtil; import java.io.File; /** * Created by 王亭 on 2017/4/13. */ public class VideoRECFragment extends BaseFragmentNew implements View.OnClickListener{ private MovieRecorderView movieRV; private Button startBtn; private LinearLayout ll_photo; private ImageView iv_close; public static VideoRECFragment getInstance(String title) { VideoRECFragment videoRECFragment = new VideoRECFragment(); return videoRECFragment; } @Override public int getLayoutID() { return R.layout.activity_video_rec; } @Override public void initView() { initViews(); initEvents(); } /* @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); *//*set it to be no title*//* requestWindowFeature(Window.FEATURE_NO_TITLE); *//*set it to be full screen*//* getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.activity_video_rec); initViews(); initEvents(); }*/ private void initViews() { movieRV = (MovieRecorderView) view.findViewById(R.id.moive_rv); //录制 startBtn = (Button)view.findViewById(R.id.start_btn); ll_photo = (LinearLayout) view.findViewById(R.id.ll_photo); iv_close = (ImageView) view.findViewById(R.id.iv_close); } private void initEvents() { //开始录制 startBtn.setOnClickListener(this); ll_photo.setOnClickListener(this); iv_close.setOnClickListener(this); } public boolean isRec(){ return movieRV.isRecord(); } public void initRec(){ movieRV.init(); } public void stopRec(){ LogUtil.e("stopRec"); if(movieRV.isRecord()) { startBtn.setSelected(false); movieRV.stop(); } } @Override public void onPause() { super.onPause(); LogUtil.e("onPause"); if(movieRV.isRecord()) { movieRV.stop(); } } @Override public void onClick(View v) { switch (v.getId()){ case R.id.start_btn: if(!movieRV.isRecord()) { startBtn.setSelected(true); movieRV.record(new MovieRecorderView.OnRecordFinishListener() { @Override public void onRecordFinish(String absolutePath) { Intent intent = new Intent(); intent.putExtra("filePath", absolutePath); toTarget(VideoCoverActivity.class, intent, true); getActivity().runOnUiThread(new Runnable() { @Override public void run() { startBtn.setSelected(false); } }); } }); }else{ startBtn.setSelected(false); movieRV.stop(); String vecordFile = movieRV.getVideoPath(); Intent scanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); scanIntent.setData(Uri.fromFile(new File(vecordFile))); getContext().sendBroadcast(scanIntent); if(vecordFile!=null){ Intent intent = new Intent(); intent.putExtra("filePath", vecordFile); toTarget(VideoCoverActivity.class, intent, true); } } break; case R.id.ll_photo: break; case R.id.iv_close: showInfoDialog("操作确认", "作品尚未保存,确认要取消吗?", new CallBack() { @Override public void cancel() { } @Override public void confirm() { getActivity().finish(); } }); break; } } }
视频录制后跳转到获取封面Activity
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" > <include layout="@layout/bar_title_back_rt" ></include> <ImageView android:id="@+id/iv_video_cover" android:layout_margin="@dimen/margin_10" android:layout_width="320dp" android:layout_height="240dp" android:layout_weight="1.0" android:scaleType="fitXY" /> <android.support.v7.widget.RecyclerView android:id="@+id/rv_video_cover" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/margin_10" > </android.support.v7.widget.RecyclerView> </LinearLayout>
package pers.wtt.ui.video.activity; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.media.MediaMetadataRetriever; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import pers.wtt.R; import pers.wtt.base.BaseActivity; import pers.wtt.ui.interfaces.IRecyclerViewItemClick; import pers.wtt.utils.DisplayUtil; import java.util.ArrayList; import java.util.List; /** * Created by Administrator on 2017/4/18. */ public class VideoCoverActivity extends BaseActivity implements IRecyclerViewItemClick,View.OnClickListener { private ImageView iv_back; private TextView tv_title; private TextView tv_btn_name; private ImageView iv_video_cover; private RecyclerView rv_video_cover; private LinearLayoutManager linearLayoutManager; private VideoCoverAdapter videoCoverAdapter; private String filePath = ""; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_video_cover); iv_back = (ImageView) findViewById(R.id.iv_back); tv_title = (TextView) findViewById(R.id.tv_title); tv_btn_name = (TextView) findViewById(R.id.tv_btn_name); tv_btn_name.setVisibility(View.VISIBLE); tv_btn_name.setText(getResources().getString(R.string.next)); tv_title.setText(getResources().getString(R.string.cover)); iv_back.setOnClickListener(this); tv_btn_name.setOnClickListener(this); iv_video_cover = (ImageView) findViewById(R.id.iv_video_cover); rv_video_cover = (RecyclerView) findViewById(R.id.rv_video_cover); linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); rv_video_cover.setLayoutManager(linearLayoutManager); videoCoverAdapter = new VideoCoverAdapter(this); videoCoverAdapter.setItemClickListener(this); rv_video_cover.setAdapter(videoCoverAdapter); filePath = getIntent().getStringExtra("filePath"); new Thread(){ @Override public void run() { super.run(); MediaMetadataRetriever mmr = new MediaMetadataRetriever(); mmr.setDataSource(filePath); String durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); // 播放时长单位为毫秒 int duration = 10 * 1000 * 1000; int current = duration/10; if(!TextUtils.isEmpty(durationStr)){ duration = Integer.valueOf(durationStr)*1000; current = duration/10; } for (int i=0; i<10; i++){ try { // final Bitmap bitmap = DisplayUtil.toGrayScale(mmr.getFrameAtTime(i*current, MediaMetadataRetriever.OPTION_CLOSEST)); final Bitmap bitmap = mmr.getFrameAtTime(i*current, MediaMetadataRetriever.OPTION_CLOSEST); if(bitmap!=null) { if (i == 0) { runOnUiThread(new Runnable() { @Override public void run() { if (iv_video_cover != null) { iv_video_cover.setImageBitmap(bitmap); iv_video_cover.setTag(bitmap); } } }); } try { runOnUiThread(new Runnable() { @Override public void run() { if (videoCoverAdapter != null) { videoCoverAdapter.addData(bitmap); videoCoverAdapter.notifyDataSetChanged(); } } }); Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } catch (Exception e) { e.printStackTrace(); showToast("获取封面失败"); finish(); } } /* Bitmap bitmap = mmr.getFrameAtTime(); iv_video_cover.setImageBitmap(bitmap); System.out.println(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE)); System.out.println(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));*/ mmr.release(); } }.start(); } @Override public void onItemClick(View view, Object obj) { Bitmap bitmap = (Bitmap)obj; iv_video_cover.setImageBitmap(bitmap); iv_video_cover.setTag(bitmap); } @Override public void onDeleteClick(int position, Object obj) { } @Override public void onClick(View v) { switch (v.getId()){ case R.id.iv_back: showInfoDialog("操作确认", "作品尚未保存,确认要取消吗?", new CallBack() { @Override public void cancel() { } @Override public void confirm() { finish(); } }); break; case R.id.tv_btn_name: Bitmap bitmap = (Bitmap) iv_video_cover.getTag(); String coverPath = DisplayUtil.saveBitmap(this, bitmap); if(!TextUtils.isEmpty(coverPath)) { Intent intent = new Intent(); intent.putExtra("filePath", filePath); intent.putExtra("coverPath", coverPath); toTarget(VideoShareActivity.class, intent, true); }else{ showToast("保存封面图片失败"); } break; } } class VideoCoverAdapter extends RecyclerView.Adapter{ private List<Bitmap> coverLst = null; private IRecyclerViewItemClick itemClickListener; private Context context; private int currentSel = 0; public VideoCoverAdapter(Context context) { super(); this.context = context; this.coverLst = new ArrayList<Bitmap>(); } public void setDatas(List<Bitmap> lst){ coverLst = lst; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(context).inflate(R.layout.item_cover, parent, false); ConverViewHolder converViewHolder = new ConverViewHolder(view); return converViewHolder; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { ConverViewHolder converViewHolder = (ConverViewHolder)holder; converViewHolder.itemView.setTag(coverLst.get(position)); converViewHolder.iv_conver.setImageBitmap(coverLst.get(position)); if(position==getCurrentSel()) { ColorMatrix cm = new ColorMatrix(); cm.setSaturation(1); // 设置饱和度 ColorMatrixColorFilter grayColorFilter = new ColorMatrixColorFilter(cm); converViewHolder.iv_conver.setColorFilter(grayColorFilter); // 如果想恢复彩色显示,设置为null即可 int padding = DisplayUtil.dip2px(context, 2); converViewHolder.iv_conver.setPadding(padding,padding,padding,padding); converViewHolder.iv_status.setVisibility(View.VISIBLE); }else{ converViewHolder.iv_conver.setPadding(0,0,0,0); converViewHolder.iv_status.setVisibility(View.GONE); converViewHolder.iv_conver.setColorFilter(null); // 如果想恢复彩色显示,设置为null即可 } converViewHolder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { setCurrentSel(position); notifyDataSetChanged(); if(itemClickListener!=null){ itemClickListener.onItemClick(v, v.getTag()); } } }); } @Override public int getItemCount() { if(coverLst!=null){ return coverLst.size(); } return 0; } public void setItemClickListener(IRecyclerViewItemClick itemClickListener) { this.itemClickListener = itemClickListener; } public int getCurrentSel() { return currentSel; } public void setCurrentSel(int currentSel) { this.currentSel = currentSel; } public void addData(Bitmap bitmap) { coverLst.add(bitmap); } } class ConverViewHolder extends RecyclerView.ViewHolder{ private ImageView iv_conver; private ImageView iv_status; public ConverViewHolder(View itemView) { super(itemView); iv_conver = (ImageView) itemView.findViewById(R.id.iv_cover); iv_status = (ImageView) itemView.findViewById(R.id.iv_status); } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if(keyCode==KeyEvent.KEYCODE_BACK){ showInfoDialog("操作确认", "作品尚未保存,确认要取消吗?", new CallBack() { @Override public void cancel() { } @Override public void confirm() { finish(); } }); return true; } return super.onKeyDown(keyCode, event); } }
最新布局,上面是ImageView 下面按钮是自定义View。
录制按钮 ,实现。
package pers.wtt.videocorder.view; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.support.annotation.Nullable; import android.support.annotation.Px; import android.util.AttributeSet; import android.view.View; import com.orhanobut.logger.Logger; import java.util.Timer; import java.util.TimerTask; import pers.wtt.videocorder.interfaces.OnCustomCirleListener; /** * Created by 王亭 on 2017/12/18. */ public class CustomCircleButton extends View implements View.OnClickListener{ private int mWidth; private int mHeight; //中心圆画笔 private Paint mCirclePaint; //默认灰色描边/进度画笔 private Paint mDefStrokePaint; //描边/进度画笔 private Paint mStrokePaint; //按钮状态 private Status currentStatus = Status.NORMAL; //总进度 private int maxProgress = 360; //当前进度 private int currentProgress = 0; //完成时间 private int mComplete = 10; //定时器 private Timer timer; //定时任务 private TimerTask timerTask; //中心圆颜色 private int mCircleColor = Color.parseColor("#129aee"); //边框颜色 private int mStrokeColor = Color.parseColor("#eeeeee"); //进度颜色 private int mProgressColor = Color.parseColor("#129aee"); //进度完成接口 private OnCustomCirleListener onCustomCirleListener; //更新UI private Handler handler = new Handler(Looper.getMainLooper()){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); invalidate(); } }; public void setOnCustomCirleListener(OnCustomCirleListener onCustomCirleListener) { this.onCustomCirleListener = onCustomCirleListener; } public int getmCircleColor() { return mCircleColor; } public void setmCircleColor(int mCircleColor) { this.mCircleColor = mCircleColor; } public int getmStrokeColor() { return mStrokeColor; } public void setmStrokeColor(int mStrokeColor) { this.mStrokeColor = mStrokeColor; } public int getmProgressColor() { return mProgressColor; } public void setmProgressColor(int mProgressColor) { this.mProgressColor = mProgressColor; } public int getmComplete() { return mComplete; } /** * 设置进度完成时间 * @param mComplete 单位秒 */ public void setmComplete(int mComplete) { this.mComplete = mComplete; } /** * 10秒为例 360度/10+1秒=36度每秒 1000毫秒/36度每秒≈28毫秒每度 * 从0s开始的所以需要+1秒否则会少一秒 * @return */ private int getSpeed(){ return Math.round(1000.00f/(360.00f/(mComplete+1))); } private void startTimer(){ stopTimer(); timer = new Timer(); timerTask = new TimerTask() { @Override public void run() { if(currentProgress>maxProgress){ currentStatus = Status.NORMAL; currentProgress = 0; stopTimer(); if(onCustomCirleListener!=null) onCustomCirleListener.complete(); handler.sendEmptyMessage(0); }else{ currentProgress+=1; handler.sendEmptyMessage(0); } } }; timer.schedule(timerTask, 0,getSpeed()); } private void stopTimer(){ if(timer!=null){ timer.cancel(); timer =null; } if(timerTask!=null){ timerTask.cancel(); timerTask=null; } } public enum Status{ START,//开始 STOP,//暂停 NORMAL//停止 } public CustomCircleButton(Context context) { this(context, null); } public CustomCircleButton(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public CustomCircleButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mCirclePaint = new Paint(); mCirclePaint.setAntiAlias(true);//消除锯齿 mCirclePaint.setColor(mCircleColor); mDefStrokePaint = new Paint(); mDefStrokePaint.setStyle(Paint.Style.STROKE); mDefStrokePaint.setAntiAlias(true);//消除锯齿 mDefStrokePaint.setColor(mStrokeColor); mDefStrokePaint.setStrokeWidth(20); mStrokePaint = new Paint(); mStrokePaint.setStyle(Paint.Style.STROKE); mStrokePaint.setAntiAlias(true);//消除锯齿 mStrokePaint.setColor(mProgressColor); mStrokePaint.setStrokeWidth(10); setOnClickListener(this); } @Override public void layout(@Px int l, @Px int t, @Px int r, @Px int b) { super.layout(l, t, r, b); } @Override protected void onDraw(Canvas canvas) { int r = getWidth() / 2;//也可以是getMeasuredHeight()/2,本例中我们已经将宽高设置相等了 //圆心的横坐标为当前的View的左边起始位置+半径 int centerX = r; //圆心的纵坐标为当前的View的顶部起始位置+半径 int centerY = r; if(currentStatus==Status.NORMAL) { canvas.drawCircle(centerX, centerY, r - mDefStrokePaint.getStrokeWidth(), mCirclePaint); canvas.drawCircle(centerX, centerY, r - mDefStrokePaint.getStrokeWidth(), mDefStrokePaint); }else{ canvas.drawCircle(centerX, centerY, r - mDefStrokePaint.getStrokeWidth(), mCirclePaint); canvas.drawCircle(centerX, centerY, r - mDefStrokePaint.getStrokeWidth(), mDefStrokePaint); RectF oval = new RectF(); oval.set(15,15,r*2 - 15,r * 2 - 15); canvas.drawArc(oval, -90, ((float) currentProgress / maxProgress) * 360, false, mStrokePaint); } super.onDraw(canvas); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, mHeight); } else if (widthSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, heightSpecSize); } else if (heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSpecSize, mHeight); } else { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } @Override public void onClick(View v) { Logger.e("CustomCircleButton Click!"); if(currentStatus==Status.START) { currentStatus = Status.STOP; stopTimer(); }else if(currentStatus==Status.STOP){ currentStatus = Status.START; startTimer(); }else{ currentStatus = Status.START; startTimer(); } invalidate(); onCustomCirleListener.onClick(v); } }