SurfaceView绘图、调用Camera
一、使用surfaceview绘图
* <p> * Android系统提供了View进行绘图处理,我们通过自定义的View可以满足大部分的绘图需求,但是这有个问题就 * 是我们通常自定义的View是用于主动更新情况的,用户无法控制其绘制的速度,由于View是通过invalidate方法 * 通知系统去调用view.onDraw方法进行重绘,而Android系统是通过发出VSYNC信号来进行屏幕的重绘,刷新的时间 * 是16ms,如果在16ms内View完成不了执行的操作,用户就会看着卡顿,比如当draw方法里执行的逻辑过多,需要频 * 繁刷新的界面上,例如游戏界面,那么就会不断的阻塞主线程,从而导致画面卡顿。而SurfaceView相当于是另一 * 个绘图线程,它是不会阻碍主线程,并且它在底层实现机制中实现了双缓冲机制。 * </p> * * <p> * 1、获取surfaceview 实例对象 * 2、添加SurfaceHolderCallback回调 * 3、在surfaceCreated回调方法中调用绘图逻辑 * </p> */ public class SurfaceViewActivity extends AppCompatActivity implements View.OnTouchListener { private SurfaceView mSurfaceView; private SurfaceHolder mSurfaceHolder; private boolean isSurfaceCreated; private SurfaceViewActivity mActivity; private int width,height; private Canvas canvas; private Paint mPaint; // 路径 private Path mPath; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_surface_view); mSurfaceView=findViewById(R.id.surface_sv); mActivity=this; mSurfaceHolder=mSurfaceView.getHolder(); mSurfaceHolder.addCallback(new MySurfaceHolderCallback()); mSurfaceView.setFocusable(true); mSurfaceView.setFocusableInTouchMode(true); mSurfaceView.setKeepScreenOn(true); initCanvasAndPaint(); } @Override public boolean onTouch(View v, MotionEvent event) { return false; } //添加surfaceview的holder回调 private class MySurfaceHolderCallback implements SurfaceHolder.Callback{ @Override public void surfaceCreated(SurfaceHolder holder) { isSurfaceCreated = true; width = mSurfaceView.getWidth(); height = mSurfaceView.getHeight(); //开启子线程绘图 new MyThread().start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {} @Override public void surfaceDestroyed(SurfaceHolder holder) { isSurfaceCreated=false; } } private class MyThread extends Thread{ @Override public void run() { super.run(); while (isSurfaceCreated){ drawing(); } } } private void initCanvasAndPaint(){ mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mPaint.setStrokeWidth(3); mPaint.setStyle(Paint.Style.FILL); /**Paint.Style.STROKE*/ mPaint.setStrokeJoin(Paint.Join.ROUND); mPaint.setStrokeCap(Paint.Cap.ROUND); mPath = new Path(); } private void drawing(){ float x= (float) (width/2.0); float y= (float) (height/2.0); try { //获取画布 canvas=mSurfaceHolder.lockCanvas(); // 画圆 mPaint.setColor(ContextCompat.getColor(mActivity,android.R.color.holo_blue_dark)); canvas.drawCircle(x,y,y,mPaint); //贝赛尔曲线 mPaint.setColor(ContextCompat.getColor(mActivity,android.R.color.holo_red_dark)); mPath.moveTo(x,y-40); mPath.quadTo(x-100,y-120,x,y+80); mPath.close(); canvas.drawPath(mPath,mPaint); mPath.moveTo(x,y-40); mPath.quadTo(x+100,y-120,x,y+80); mPath.close(); canvas.drawPath(mPath,mPaint); //绘制箭头 mPaint.setColor(ContextCompat.getColor(mActivity,android.R.color.holo_green_dark)); canvas.drawLine(0,y,x+y,y,mPaint); mPath.moveTo(x+y,y-40); mPath.lineTo(x+y,y+40); mPath.lineTo(x+y+60,y); mPath.close(); canvas.drawPath(mPath,mPaint); //异步更新画布 mSurfaceHolder.unlockCanvasAndPost(canvas); }catch (Exception e){ if (canvas!=null) mSurfaceHolder.unlockCanvasAndPost(canvas); } } }
二、调用Camera
1、自定义属性
<declare-styleable name="CustomCamera"> <attr name="custom_camera_id" format="integer" > <enum name="back" value="99" /> <enum name="front" value="98" /> </attr> </declare-styleable>
在xml中引用配置前置后置摄像头
2、自定义相机的java类
public class CustomCamera extends SurfaceView implements SurfaceHolder.Callback{ private String TAG="CustomCamera"; private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); public static final int CAMERA_ID[]={98,99}; private int mWith,mHeight;// private CameraCaptureSession mCameraCaptureSession; private CameraDevice mCameraDevice; private int mCameraId=CAMERA_ID[1]; //98前置摄像头 99后置摄像头 private ImageReader mImageReader; private Handler mWorkHandler, mUiHandler; private SurfaceHolder mSurfaceHolder; private CaptureRequest.Builder previewRequestBuilder; private Activity mContext; @RequiresApi(api = Build.VERSION_CODES.KITKAT) public CustomCamera(Context context,int cameraId) { super(context); this.mContext= (Activity) context; this.mCameraId=cameraId; init(); } @RequiresApi(api = Build.VERSION_CODES.KITKAT) public CustomCamera(Context context, AttributeSet attrs) { super(context, attrs); this.mContext= (Activity) context; TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomCamera); mCameraId=typedArray.getInt(R.styleable.CustomCamera_custom_camera_id,99); typedArray.recycle(); init(); } @RequiresApi(api = Build.VERSION_CODES.KITKAT) public CustomCamera(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mContext= (Activity) context; TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomCamera); mCameraId=typedArray.getInt(R.styleable.CustomCamera_custom_camera_id,99); typedArray.recycle(); init(); } static { ORIENTATIONS.append(Surface.ROTATION_0, 90); ORIENTATIONS.append(Surface.ROTATION_90, 0); ORIENTATIONS.append(Surface.ROTATION_180, 270); ORIENTATIONS.append(Surface.ROTATION_270, 180); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mWith=MeasureSpec.getSize(widthMeasureSpec); mHeight=MeasureSpec.getSize(heightMeasureSpec); Log.i(TAG,"mWith="+mWith+"---------------mHeight="+mHeight); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override public void surfaceCreated(SurfaceHolder holder) { initCamera2(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override public void surfaceDestroyed(SurfaceHolder holder) { if (null != mCameraDevice) { mCameraDevice.close(); this.mCameraDevice = null; } } @RequiresApi(api = Build.VERSION_CODES.KITKAT) private void init(){ mSurfaceHolder= this.getHolder(); mSurfaceHolder.setKeepScreenOn(true); mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); mSurfaceHolder.addCallback(this); HandlerThread mHandlerThread = new HandlerThread("Camera2"); mHandlerThread.start(); mUiHandler = new Handler(mContext.getMainLooper()); mWorkHandler = new Handler(mHandlerThread.getLooper()); } /** * 初始化Camera2 */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private void initCamera2() { try { if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { Toast.makeText(mContext,"摄像头未开启",Toast.LENGTH_LONG).show(); } else { String mCameraID =mCameraId==99?String.valueOf(CameraCharacteristics.LENS_FACING_FRONT) :String.valueOf(CameraCharacteristics.LENS_FACING_BACK); CameraManager mCameraManager = (CameraManager) getContext().getSystemService(Context.CAMERA_SERVICE); assert mCameraManager != null; mCameraManager.openCamera(mCameraID, stateCallback, mUiHandler); } } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 摄像头创建监听 */ private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() { @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override public void onOpened(@NonNull CameraDevice camera) { try { //打开摄像头 配置自动对焦、打开闪光灯、打开闪光灯 mCameraDevice = camera; previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); previewRequestBuilder.addTarget(mSurfaceHolder.getSurface()); mImageReader = ImageReader.newInstance(mWith, mHeight, ImageFormat.JPEG, 1); mCameraDevice.createCaptureSession(Arrays.asList(mSurfaceHolder.getSurface(), mImageReader.getSurface()),callback,mWorkHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override public void onDisconnected(@NonNull CameraDevice camera) { //关闭摄像头 if (null != mCameraDevice) { mCameraDevice.close(); mCameraDevice = null; } } @Override public void onError(@NonNull CameraDevice camera, int error) { Toast.makeText(getContext(), "开启摄像头失败", Toast.LENGTH_SHORT).show(); } }; /** * 配置摄像头,预览回调 */ private CameraCaptureSession.StateCallback callback=new CameraCaptureSession.StateCallback() { @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { try { mCameraCaptureSession = cameraCaptureSession; mCameraCaptureSession.setRepeatingRequest(previewRequestBuilder.build(), null, mWorkHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { Toast.makeText(getContext(), "配置失败", Toast.LENGTH_SHORT).show(); } }; /** * 回调处理拿到拍照照片数据 */ private ImageReader.OnImageAvailableListener mImageAvailableListener=new ImageReader.OnImageAvailableListener() { @RequiresApi(api = Build.VERSION_CODES.KITKAT) @Override public void onImageAvailable(ImageReader reader) { Image image = reader.acquireNextImage(); ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); if (bitmap != null&&showPhotoListener!=null) showPhotoListener.show(bitmap); } }; /** * 调用拍照,执行回调将拍到的照片显示到指定的控件上 */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public void takePhoto() { try { previewRequestBuilder.addTarget(mImageReader.getSurface()); // 获取手机横屏竖屏方向,根据设备方向计算设置照片的方向 previewRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS. get(mContext.getWindowManager().getDefaultDisplay().getRotation())); mCameraCaptureSession.capture(previewRequestBuilder.build(), null, mWorkHandler); mImageReader.setOnImageAvailableListener(mImageAvailableListener, mUiHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 暴漏接口,回调处理拍照结果 */ public interface ShowPhotoListener{ void show(Bitmap bitmap); } public ShowPhotoListener showPhotoListener; public void setShowPhotoListener(ShowPhotoListener showPhotoListener) { this.showPhotoListener = showPhotoListener; } public static int getScreenWidth(Context context) { WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics metrics = new DisplayMetrics(); assert manager != null; manager.getDefaultDisplay().getMetrics(metrics); return metrics.widthPixels; } public static int getScreenHeight(Context context) { WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics metrics = new DisplayMetrics(); assert manager != null; manager.getDefaultDisplay().getMetrics(metrics); return metrics.heightPixels; } }
3、调用方法
public class TakePhotoActivity extends AppCompatActivity { private LinearLayout mParent; private CustomCamera customCamera; @RequiresApi(api = Build.VERSION_CODES.KITKAT) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_take_photo); mParent=findViewById(R.id.parent_ll); customCamera=new CustomCamera(this,99); LinearLayout.LayoutParams lp1= new LinearLayout.LayoutParams(CustomCamera.getScreenWidth(this),CustomCamera.getScreenHeight(this)/2); customCamera.setLayoutParams(lp1); mParent.addView(customCamera); final ImageView imageView=new ImageView(this); mParent.addView(imageView); LinearLayout.LayoutParams lp= (LinearLayout.LayoutParams) imageView.getLayoutParams(); lp.width=CustomCamera.getScreenWidth(this)/2; lp.width=CustomCamera.getScreenHeight(this)/4; lp.topMargin=20; imageView.setLayoutParams(lp); TextView textView=new TextView(this); textView.setText("拍照"); textView.setTextSize(20); mParent.addView(textView); textView.setOnClickListener(new View.OnClickListener() { @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override public void onClick(View v) { customCamera.takePhoto(); } }); customCamera.setShowPhotoListener(new CustomCamera.ShowPhotoListener() { @Override public void show(Bitmap bitmap) { imageView.setImageBitmap(bitmap); if (bitmap.isRecycled()) bitmap.recycle(); } }); } }
三、使用SurfaceView播放网络视频
public class CustomVideoPlayer extends FrameLayout implements SurfaceHolder.Callback, View.OnClickListener { private String TAG = getClass().getSimpleName(); private Context mContext; private int mWith, mHeight; @SuppressLint("StaticFieldLeak") protected static SeekBar mSeekBar; private Button mPlayerBtn; private SurfaceHolder mSurfaceHolder; @SuppressLint("StaticFieldLeak") private static MediaPlayer mMediaPlayer; private String mPath; //視頻地址 private static boolean isPause = true;//暂停 private ExecutorService mExecutorService; private static int curSeekPosition = 0;//进度条进度 private static int mSeekBarWidth; private SeekHandler mSeekHandler; private static int PROGRESS_UPDATE = 1; private static int PROGRESS_MAX = 100; private static int mTotalTime; private static final SparseIntArray orientations = new SparseIntArray();//手机旋转对应的调整角度 static { orientations.append(Surface.ROTATION_0, 90); orientations.append(Surface.ROTATION_90, 0); orientations.append(Surface.ROTATION_180, 270); orientations.append(Surface.ROTATION_270, 180); } public CustomVideoPlayer(Context context, String mPath) { super(context); mContext = context; this.mPath = mPath; init(); } public CustomVideoPlayer(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; init(); } public CustomVideoPlayer(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mContext = context; init(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mWith = MeasureSpec.getSize(widthMeasureSpec); mHeight = MeasureSpec.getSize(heightMeasureSpec); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); int margin = dp2px(mContext, 10); int wh = dp2px(mContext, 30); int marginTop = dp2px(mContext, 50); for (int i = 0, len = getChildCount(); i < len; i++) { View view = getChildAt(i); if (view instanceof SurfaceView) { view.layout(0, 0, mWith, mHeight - marginTop); } else if (view instanceof RelativeLayout) { RelativeLayout rel = (RelativeLayout) view; view.setBackgroundColor(Color.parseColor("#3385ff")); rel.layout(0, mHeight - marginTop, mWith, mHeight); for (int j = 0, cLen = rel.getChildCount(); j < cLen; j++) { View childView = rel.getChildAt(j); if (childView instanceof SeekBar) { childView.layout(2 * margin, marginTop - wh, mWith - margin, wh); mSeekBarWidth = mWith - 2 * margin - wh; } else if (childView instanceof ImageView) { childView.layout(margin, margin, margin + wh, margin + wh); } } } } } @Override public void surfaceCreated(SurfaceHolder holder) { initPlayer(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { if (mMediaPlayer != null && mMediaPlayer.isPlaying()) stop(); } @Override public void onClick(View v) { if ("player_btn".equals(v.getTag())) pause(); } private void init() { mSeekHandler = new SeekHandler(); mExecutorService = Executors.newSingleThreadExecutor(); SurfaceView mSurfaceView = new SurfaceView(mContext); this.addView(mSurfaceView); mSurfaceHolder = mSurfaceView.getHolder(); mSurfaceHolder.addCallback(this); mSurfaceHolder.setKeepScreenOn(true); mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); addView(); mMediaPlayer = new MediaPlayer(); mMediaPlayer.setOnPreparedListener(mPreparedListener); mMediaPlayer.setOnCompletionListener(mCompletionListener); mMediaPlayer.setOnErrorListener(mErrorListener); mMediaPlayer.setOnSeekCompleteListener(mSeekCompleteListener); } private void addView() { RelativeLayout mPlayerRl = new RelativeLayout(mContext); this.addView(mPlayerRl); mPlayerBtn = new Button(mContext); mPlayerBtn.setTag("player_btn"); mPlayerRl.addView(mPlayerBtn); mPlayerBtn.setOnClickListener(this); mSeekBar = new SeekBar(mContext); mPlayerRl.addView(mSeekBar); mSeekBar.setOnSeekBarChangeListener(mSeekBarChangeListener); } private void initPlayer() { try { mMediaPlayer.reset(); // 指定装载uri mMediaPlayer.setDataSource(mContext, Uri.parse(mPath)); mMediaPlayer.prepareAsync(); //将所播放的视频图像输出到指定的SurfaceView组件 mMediaPlayer.setDisplay(mSurfaceHolder); } catch (IOException e) { e.printStackTrace(); } } //MediaPlayer调用prepare()方法时触发该监听器 private MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { mMediaPlayer.start(); isPause = false; mTotalTime = mMediaPlayer.getDuration(); mPlayerBtn.setBackgroundResource(R.drawable.v_play_bg); mSeekHandler.sendEmptyMessage(PROGRESS_MAX); mExecutorService.execute(mRunnable); } }; //MediaPlayer的播放完成事件绑定事件监听器 private MediaPlayer.OnCompletionListener mCompletionListener = new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { isPause = true; mPlayerBtn.setBackgroundResource(R.drawable.v_stop_bg); } }; //播放错误事件绑定事件监听器 private MediaPlayer.OnErrorListener mErrorListener = new MediaPlayer.OnErrorListener() { @Override public boolean onError(MediaPlayer mp, int what, int extra) { isPause = true; mPlayerBtn.setBackgroundResource(R.drawable.v_stop_bg); return false; } }; //当MediaPlayer调用seek()方法时触发该监听器 private MediaPlayer.OnSeekCompleteListener mSeekCompleteListener = new MediaPlayer.OnSeekCompleteListener() { @Override public void onSeekComplete(MediaPlayer mp) { // mMediaPlayer.seekTo(curSeekPosition); } }; //通知SeekBar进度被修改SeekBar监听 private SeekBar.OnSeekBarChangeListener mSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }; private Runnable mRunnable = new Runnable() { @Override public void run() { if (mMediaPlayer != null) { while (mMediaPlayer.getCurrentPosition()<mTotalTime) { if (mMediaPlayer.isPlaying()){ Message msg = mSeekHandler.obtainMessage(); msg.what = PROGRESS_UPDATE; mSeekHandler.sendMessageDelayed(msg, 500); } } } } }; private static class SeekHandler extends Handler { @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (msg.what == PROGRESS_MAX) { mSeekBar.setMax((int) (mTotalTime/100.0)); } else if (msg.what == PROGRESS_UPDATE) { curSeekPosition= (int) (mMediaPlayer.getCurrentPosition()/100.0); mSeekBar.setProgress(curSeekPosition); } } } private void pause() { if (mMediaPlayer != null) { if (!isPause) { isPause = true; curSeekPosition = (int) (mMediaPlayer.getCurrentPosition()/100.0); mPlayerBtn.setBackgroundResource(R.drawable.v_stop_bg); mMediaPlayer.pause(); } else { isPause = false; mPlayerBtn.setBackgroundResource(R.drawable.v_play_bg); mMediaPlayer.start(); } } } //停止播放, 释放资源 private void stop() { if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { isPause = true; mMediaPlayer.stop(); mMediaPlayer.release(); } } public static int dp2px(Context context, float dpVal) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, context.getResources().getDisplayMetrics()); } }
使用方法:
public class VideoActivity extends AppCompatActivity { private LinearLayout mParent; private CustomVideoPlayer customVideoPlayer; private String path="http://jzvd.nathen.cn/c6e3dc12a1154626b3476d9bf3bd7266/6b56c5f0dc31428083757a45764763b0-5287d2089db37e62345123a1be272f8b.mp4"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_video); mParent = findViewById(R.id.parent_ll); customVideoPlayer=new CustomVideoPlayer(this,path); LinearLayout.LayoutParams lp1= new LinearLayout.LayoutParams(CustomCamera.getScreenWidth(this), CustomCamera.getScreenHeight(this)/2); customVideoPlayer.setLayoutParams(lp1); mParent.addView(customVideoPlayer); } }