一、使用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); } }














![图形视图(02):【类】QGraphicsScene [官翻]](https://img-blog.csdnimg.cn/img_convert/7d91375d14b1b5500fbd7c61c37fc289.png)


