最近项目里面又要加一个拍照搜题的功能,也就是用户对着不会做的题目拍一张照片,将照片的文字使用ocr识别出来,再调用题库搜索接口搜索出来展示给用户,类似于小猿搜题、学霸君等app。

  其实Android提供Intent让我们打开系统的相机,但是系统相机跟自己app风格不搭,而且用起来体验不好。所以我使用了SDK提供的camera API自定义了一个相机,并且在相机界面上面添加了参考线,有助于用户将题目拍正,提高ocr的识别率。

  1、绘制参考线的代码

 public class ReferenceLine extends View {

     private Paint mLinePaint;

     public ReferenceLine(Context context) {
         super(context);
         init();
     }

     public ReferenceLine(Context context, AttributeSet attrs) {
         super(context, attrs);
         init();
     }

     public ReferenceLine(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         init();
     }

     private void init() {
         mLinePaint = new Paint();
         mLinePaint.setAntiAlias(true);
         mLinePaint.setColor(Color.parseColor("#45e0e0e0"));
         mLinePaint.setStrokeWidth(1);
     }

     @Override
     protected void onDraw(Canvas canvas) {
         int screenWidth = Utils.getScreenWH(getContext()).widthPixels;
         int screenHeight = Utils.getScreenWH(getContext()).heightPixels;

         int width = screenWidth/3;
         int height = screenHeight/3;

         for (int i = width, j = 0;i < screenWidth && j<2;i += width, j++) {
             canvas.drawLine(i, 0, i, screenHeight, mLinePaint);
         }
         for (int j = height,i = 0;j < screenHeight && i < 2;j += height,i++) {
             canvas.drawLine(0, j, screenWidth, j, mLinePaint);
         }
     }

 }

  2、自定义相机代码

  这里主要是要创建一个SurfaceView,将摄像头的预览界面放到SurfaceView中显示。

 package com.bbk.lling.camerademo.camare;

 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.hardware.Camera;
 import android.hardware.Camera.AutoFocusCallback;
 import android.hardware.Camera.PictureCallback;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.View;
 import android.widget.RelativeLayout;
 import android.widget.Toast;

 import com.bbk.lling.camerademo.utils.Utils;

 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;

 /**
  * @Class: CameraPreview
  * @Description: 自定义相机
  * @author: lling(www.cnblogs.com/liuling)
  * @Date: 2015/10/25
  */
 public class CameraPreview extends SurfaceView implements
         SurfaceHolder.Callback, AutoFocusCallback {
     private static final String TAG = "CameraPreview";

     private int viewWidth = 0;
     private int viewHeight = 0;

     /** 监听接口 */
     private OnCameraStatusListener listener;

     private SurfaceHolder holder;
     private Camera camera;
     private FocusView mFocusView;

     //创建一个PictureCallback对象,并实现其中的onPictureTaken方法
     private PictureCallback pictureCallback = new PictureCallback() {

         // 该方法用于处理拍摄后的照片数据
         @Override
         public void onPictureTaken(byte[] data, Camera camera) {
             // 停止照片拍摄
             try {
                 camera.stopPreview();
             } catch (Exception e) {
             }
             // 调用结束事件
             if (null != listener) {
                 listener.onCameraStopped(data);
             }
         }
     };

     // Preview类的构造方法
     public CameraPreview(Context context, AttributeSet attrs) {
         super(context, attrs);
         // 获得SurfaceHolder对象
         holder = getHolder();
         // 指定用于捕捉拍照事件的SurfaceHolder.Callback对象
         holder.addCallback(this);
         // 设置SurfaceHolder对象的类型
         holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
         setOnTouchListener(onTouchListener);
     }

     // 在surface创建时激发
     public void surfaceCreated(SurfaceHolder holder) {
         Log.e(TAG, "==surfaceCreated==");
         if(!Utils.checkCameraHardware(getContext())) {
             Toast.makeText(getContext(), "摄像头打开失败!", Toast.LENGTH_SHORT).show();
             return;
         }
         // 获得Camera对象
         camera = getCameraInstance();
         try {
             // 设置用于显示拍照摄像的SurfaceHolder对象
             camera.setPreviewDisplay(holder);
         } catch (IOException e) {
             e.printStackTrace();
             // 释放手机摄像头
             camera.release();
             camera = null;
         }
         updateCameraParameters();
         if (camera != null) {
             camera.startPreview();
         }
         setFocus();
     }

     // 在surface销毁时激发
     public void surfaceDestroyed(SurfaceHolder holder) {
         Log.e(TAG, "==surfaceDestroyed==");
         // 释放手机摄像头
         camera.release();
         camera = null;
     }

     // 在surface的大小发生改变时激发
     public void surfaceChanged(final SurfaceHolder holder, int format, int w,
             int h) {
         // stop preview before making changes
         try {
             camera.stopPreview();
         } catch (Exception e){
             // ignore: tried to stop a non-existent preview
         }
         // set preview size and make any resize, rotate or
         // reformatting changes here
         updateCameraParameters();
         // start preview with new settings
         try {
             camera.setPreviewDisplay(holder);
             camera.startPreview();

         } catch (Exception e){
             Log.d(TAG, "Error starting camera preview: " + e.getMessage());
         }
         setFocus();
     }

     /**
      * 点击显示焦点区域
      */
     OnTouchListener onTouchListener = new OnTouchListener() {
         @SuppressWarnings("deprecation")
         @Override
         public boolean onTouch(View v, MotionEvent event) {
             if (event.getAction() == MotionEvent.ACTION_DOWN) {
                 int width = mFocusView.getWidth();
                 int height = mFocusView.getHeight();
                 mFocusView.setX(event.getX() - (width / 2));
                 mFocusView.setY(event.getY() - (height / 2));
                 mFocusView.beginFocus();
             } else if (event.getAction() == MotionEvent.ACTION_UP) {
                 focusOnTouch(event);
             }
             return true;
         }
     };

     /**
      * 获取摄像头实例
      * @return
      */
     private Camera getCameraInstance() {
         Camera c = null;
         try {
             int cameraCount = 0;
             Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
             cameraCount = Camera.getNumberOfCameras(); // get cameras number

             for (int camIdx = 0; camIdx < cameraCount; camIdx++) {
                 Camera.getCameraInfo(camIdx, cameraInfo); // get camerainfo
                 // 代表摄像头的方位,目前有定义值两个分别为CAMERA_FACING_FRONT前置和CAMERA_FACING_BACK后置
                 if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                     try {
                         c = Camera.open(camIdx);   //打开后置摄像头
                     } catch (RuntimeException e) {
                         Toast.makeText(getContext(), "摄像头打开失败!", Toast.LENGTH_SHORT).show();
                     }
                 }
             }
             if (c == null) {
                 c = Camera.open(0); // attempt to get a Camera instance
             }
         } catch (Exception e) {
             Toast.makeText(getContext(), "摄像头打开失败!", Toast.LENGTH_SHORT).show();
         }
         return c;
     }

     private void updateCameraParameters() {
         if (camera != null) {
             Camera.Parameters p = camera.getParameters();

             setParameters(p);

             try {
                 camera.setParameters(p);
             } catch (Exception e) {
                 Camera.Size previewSize = findBestPreviewSize(p);
                 p.setPreviewSize(previewSize.width, previewSize.height);
                 p.setPictureSize(previewSize.width, previewSize.height);
                 camera.setParameters(p);
             }
         }
     }

     /**
      * @param p
      */
     private void setParameters(Camera.Parameters p) {
         List<String> focusModes = p.getSupportedFocusModes();
         if (focusModes
                 .contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
             p.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
         }

         long time = new Date().getTime();
         p.setGpsTimestamp(time);
         // 设置照片格式
         p.setPictureFormat(PixelFormat.JPEG);
         Camera.Size previewSize = findPreviewSizeByScreen(p);
         p.setPreviewSize(previewSize.width, previewSize.height);
         p.setPictureSize(previewSize.width, previewSize.height);
         p.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
         if (getContext().getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
             camera.setDisplayOrientation(90);
             p.setRotation(90);
         }
     }

     // 进行拍照,并将拍摄的照片传入PictureCallback接口的onPictureTaken方法
     public void takePicture() {
         if (camera != null) {
             try {
                 camera.takePicture(null, null, pictureCallback);
             } catch (Exception e) {
                 e.printStackTrace();
             }
         }
     }

     // 设置监听事件
     public void setOnCameraStatusListener(OnCameraStatusListener listener) {
         this.listener = listener;
     }

     @Override
     public void onAutoFocus(boolean success, Camera camera) {

     }

     public void start() {
         if (camera != null) {
             camera.startPreview();
         }
     }

     public void stop() {
         if (camera != null) {
             camera.stopPreview();
         }
     }

     /**
      * 相机拍照监听接口
      */
     public interface OnCameraStatusListener {
         // 相机拍照结束事件
         void onCameraStopped(byte[] data);
     }

     @Override
     protected void onMeasure(int widthSpec, int heightSpec) {
         viewWidth = MeasureSpec.getSize(widthSpec);
         viewHeight = MeasureSpec.getSize(heightSpec);
         super.onMeasure(
                 MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY),
                 MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY));
     }

     /**
      * 将预览大小设置为屏幕大小
      * @param parameters
      * @return
      */
     private Camera.Size findPreviewSizeByScreen(Camera.Parameters parameters) {
         if (viewWidth != 0 && viewHeight != 0) {
             return camera.new Size(Math.max(viewWidth, viewHeight),
                     Math.min(viewWidth, viewHeight));
         } else {
             return camera.new Size(Utils.getScreenWH(getContext()).heightPixels,
                     Utils.getScreenWH(getContext()).widthPixels);
         }
     }

     /**
      * 找到最合适的显示分辨率 (防止预览图像变形)
      * @param parameters
      * @return
      */
     private Camera.Size findBestPreviewSize(Camera.Parameters parameters) {

         // 系统支持的所有预览分辨率
         String previewSizeValueString = null;
         previewSizeValueString = parameters.get("preview-size-values");

         if (previewSizeValueString == null) {
             previewSizeValueString = parameters.get("preview-size-value");
         }

         if (previewSizeValueString == null) { // 有些手机例如m9获取不到支持的预览大小 就直接返回屏幕大小
             return camera.new Size(Utils.getScreenWH(getContext()).widthPixels,
                     Utils.getScreenWH(getContext()).heightPixels);
         }
         float bestX = 0;
         float bestY = 0;

         float tmpRadio = 0;
         float viewRadio = 0;

         if (viewWidth != 0 && viewHeight != 0) {
             viewRadio = Math.min((float) viewWidth, (float) viewHeight)
                     / Math.max((float) viewWidth, (float) viewHeight);
         }

         String[] COMMA_PATTERN = previewSizeValueString.split(",");
         for (String prewsizeString : COMMA_PATTERN) {
             prewsizeString = prewsizeString.trim();

             int dimPosition = prewsizeString.indexOf('x');
             if (dimPosition == -1) {
                 continue;
             }

             float newX = 0;
             float newY = 0;

             try {
                 newX = Float.parseFloat(prewsizeString.substring(0, dimPosition));
                 newY = Float.parseFloat(prewsizeString.substring(dimPosition + 1));
             } catch (NumberFormatException e) {
                 continue;
             }

             float radio = Math.min(newX, newY) / Math.max(newX, newY);
             if (tmpRadio == 0) {
                 tmpRadio = radio;
                 bestX = newX;
                 bestY = newY;
             } else if (tmpRadio != 0 && (Math.abs(radio - viewRadio)) < (Math.abs(tmpRadio - viewRadio))) {
                 tmpRadio = radio;
                 bestX = newX;
                 bestY = newY;
             }
         }

         if (bestX > 0 && bestY > 0) {
             return camera.new Size((int) bestX, (int) bestY);
         }
         return null;
     }

     /**
      * 设置焦点和测光区域
      *
      * @param event
      */
     public void focusOnTouch(MotionEvent event) {

         int[] location = new int[2];
         RelativeLayout relativeLayout = (RelativeLayout)getParent();
         relativeLayout.getLocationOnScreen(location);

         Rect focusRect = Utils.calculateTapArea(mFocusView.getWidth(),
                 mFocusView.getHeight(), 1f, event.getRawX(), event.getRawY(),
                 location[0], location[0] + relativeLayout.getWidth(), location[1],
                 location[1] + relativeLayout.getHeight());
         Rect meteringRect = Utils.calculateTapArea(mFocusView.getWidth(),
                 mFocusView.getHeight(), 1.5f, event.getRawX(), event.getRawY(),
                 location[0], location[0] + relativeLayout.getWidth(), location[1],
                 location[1] + relativeLayout.getHeight());

         Camera.Parameters parameters = camera.getParameters();
         parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);

         if (parameters.getMaxNumFocusAreas() > 0) {
             List<Camera.Area> focusAreas = new ArrayList<Camera.Area>();
             focusAreas.add(new Camera.Area(focusRect, 1000));

             parameters.setFocusAreas(focusAreas);
         }

         if (parameters.getMaxNumMeteringAreas() > 0) {
             List<Camera.Area> meteringAreas = new ArrayList<Camera.Area>();
             meteringAreas.add(new Camera.Area(meteringRect, 1000));

             parameters.setMeteringAreas(meteringAreas);
         }

         try {
             camera.setParameters(parameters);
         } catch (Exception e) {
         }
         camera.autoFocus(this);
     }

     /**
      * 设置聚焦的图片
      * @param focusView
      */
     public void setFocusView(FocusView focusView) {
         this.mFocusView = focusView;
     }

     /**
      * 设置自动聚焦,并且聚焦的圈圈显示在屏幕中间位置
      */
     public void setFocus() {
         if(!mFocusView.isFocusing()) {
             try {
                 camera.autoFocus(this);
                 mFocusView.setX((Utils.getWidthInPx(getContext())-mFocusView.getWidth()) / 2);
                 mFocusView.setY((Utils.getHeightInPx(getContext())-mFocusView.getHeight()) / 2);
                 mFocusView.beginFocus();
             } catch (Exception e) {
             }
         }
     }

 }

  3、Activity中使用自定义相机

 public class TakePhoteActivity extends Activity implements CameraPreview.OnCameraStatusListener,
         SensorEventListener {
     private static final String TAG = "TakePhoteActivity";
     public static final Uri IMAGE_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
     public static final String PATH = Environment.getExternalStorageDirectory()
             .toString() + "/AndroidMedia/";
     CameraPreview mCameraPreview;
     CropImageView mCropImageView;
     RelativeLayout mTakePhotoLayout;
     LinearLayout mCropperLayout;
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         // 设置横屏
 //        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
         // 设置全屏
         requestWindowFeature(Window.FEATURE_NO_TITLE);
         getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                 WindowManager.LayoutParams.FLAG_FULLSCREEN);
         setContentView(R.layout.activity_take_phote);
         // Initialize components of the app
         mCropImageView = (CropImageView) findViewById(R.id.CropImageView);
         mCameraPreview = (CameraPreview) findViewById(R.id.cameraPreview);
         FocusView focusView = (FocusView) findViewById(R.id.view_focus);
         mTakePhotoLayout = (RelativeLayout) findViewById(R.id.take_photo_layout);
         mCropperLayout = (LinearLayout) findViewById(R.id.cropper_layout);

         mCameraPreview.setFocusView(focusView);
         mCameraPreview.setOnCameraStatusListener(this);
         mCropImageView.setGuidelines(2);

         mSensorManager = (SensorManager) getSystemService(Context.
                 SENSOR_SERVICE);
         mAccel = mSensorManager.getDefaultSensor(Sensor.
                 TYPE_ACCELEROMETER);

     }

     boolean isRotated = false;

     @Override
     protected void onResume() {
         super.onResume();
         if(!isRotated) {
             TextView hint_tv = (TextView) findViewById(R.id.hint);
             ObjectAnimator animator = ObjectAnimator.ofFloat(hint_tv, "rotation", 0f, 90f);
             animator.setStartDelay(800);
             animator.setDuration(1000);
             animator.setInterpolator(new LinearInterpolator());
             animator.start();
             View view =  findViewById(R.id.crop_hint);
             AnimatorSet animSet = new AnimatorSet();
             ObjectAnimator animator1 = ObjectAnimator.ofFloat(view, "rotation", 0f, 90f);
             ObjectAnimator moveIn = ObjectAnimator.ofFloat(view, "translationX", 0f, -50f);
             animSet.play(animator1).before(moveIn);
             animSet.setDuration(10);
             animSet.start();
             isRotated = true;
         }
         mSensorManager.registerListener(this, mAccel, SensorManager.SENSOR_DELAY_UI);
     }

     @Override
     protected void onPause() {
         super.onPause();
         mSensorManager.unregisterListener(this);
     }

     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         Log.e(TAG, "onConfigurationChanged");
         super.onConfigurationChanged(newConfig);
     }

     public void takePhoto(View view) {
         if(mCameraPreview != null) {
             mCameraPreview.takePicture();
         }
     }

     public void close(View view) {
         finish();
     }

     /**
      * 关闭截图界面
      * @param view
      */
     public void closeCropper(View view) {
         showTakePhotoLayout();
     }

     /**
      * 开始截图,并保存图片
      * @param view
      */
     public void startCropper(View view) {
         //获取截图并旋转90度
         CropperImage cropperImage = mCropImageView.getCroppedImage();
         Log.e(TAG, cropperImage.getX() + "," + cropperImage.getY());
         Log.e(TAG, cropperImage.getWidth() + "," + cropperImage.getHeight());
         Bitmap bitmap = Utils.rotate(cropperImage.getBitmap(), -90);
 //        Bitmap bitmap = mCropImageView.getCroppedImage();
         // 系统时间
         long dateTaken = System.currentTimeMillis();
         // 图像名称
         String filename = DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken)
                 .toString() + ".jpg";
         Uri uri = insertImage(getContentResolver(), filename, dateTaken, PATH,
                 filename, bitmap, null);
         cropperImage.getBitmap().recycle();
         cropperImage.setBitmap(null);
         Intent intent = new Intent(this, ShowCropperedActivity.class);
         intent.setData(uri);
         intent.putExtra("path", PATH + filename);
         intent.putExtra("width", bitmap.getWidth());
         intent.putExtra("height", bitmap.getHeight());
         intent.putExtra("cropperImage", cropperImage);
         startActivity(intent);
         bitmap.recycle();
         finish();
         super.overridePendingTransition(R.anim.fade_in,
                 R.anim.fade_out);
 //        doAnimation(cropperImage);
     }

     private void doAnimation(CropperImage cropperImage) {
         ImageView imageView = new ImageView(this);
         View view = LayoutInflater.from(this).inflate(
                 R.layout.image_view_layout, null);
         ((RelativeLayout) view.findViewById(R.id.root_layout)).addView(imageView);
         RelativeLayout relativeLayout = ((RelativeLayout) findViewById(R.id.root_layout));
 //        relativeLayout.addView(imageView);
         imageView.setX(cropperImage.getX());
         imageView.setY(cropperImage.getY());
         ViewGroup.LayoutParams lp = imageView.getLayoutParams();
         lp.width = (int)cropperImage.getWidth();
         lp.height = (int) cropperImage.getHeight();
         imageView.setLayoutParams(lp);
         imageView.setImageBitmap(cropperImage.getBitmap());
         try {
             getWindow().addContentView(view, lp);
         } catch (Exception e) {
             e.printStackTrace();
         }
         /*AnimatorSet animSet = new AnimatorSet();
         ObjectAnimator translationX = ObjectAnimator.ofFloat(this, "translationX", cropperImage.getX(), 0);
         ObjectAnimator translationY = ObjectAnimator.ofFloat(this, "translationY", cropperImage.getY(), 0);*/

         TranslateAnimation translateAnimation = new TranslateAnimation(
                 0, -cropperImage.getX(), 0, -(Math.abs(cropperImage.getHeight() - cropperImage.getY())));// 当前位置移动到指定位置
         RotateAnimation rotateAnimation = new RotateAnimation(0, -90,
                 Animation.ABSOLUTE, cropperImage.getX() ,Animation.ABSOLUTE, cropperImage.getY());
         AnimationSet animationSet = new AnimationSet(true);
         animationSet.addAnimation(translateAnimation);
         animationSet.addAnimation(rotateAnimation);
         animationSet.setFillAfter(true);
         animationSet.setDuration(2000L);
         imageView.startAnimation(animationSet);
 //        finish();
     }

     /**
      * 拍照成功后回调
      * 存储图片并显示截图界面
      * @param data
      */
     @Override
     public void onCameraStopped(byte[] data) {
         Log.i("TAG", "==onCameraStopped==");
         // 创建图像
         Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
         // 系统时间
         long dateTaken = System.currentTimeMillis();
         // 图像名称
         String filename = DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken)
                 .toString() + ".jpg";
         // 存储图像(PATH目录)
         Uri source = insertImage(getContentResolver(), filename, dateTaken, PATH,
                 filename, bitmap, data);
         //准备截图
         try {
             mCropImageView.setImageBitmap(MediaStore.Images.Media.getBitmap(this.getContentResolver(), source));
 //            mCropImageView.rotateImage(90);
         } catch (IOException e) {
             Log.e(TAG, e.getMessage());
         }
         showCropperLayout();
     }

     /**
      * 存储图像并将信息添加入媒体数据库
      */
     private Uri insertImage(ContentResolver cr, String name, long dateTaken,
                             String directory, String filename, Bitmap source, byte[] jpegData) {
         OutputStream outputStream = null;
         String filePath = directory + filename;
         try {
             File dir = new File(directory);
             if (!dir.exists()) {
                 dir.mkdirs();
             }
             File file = new File(directory, filename);
             if (file.createNewFile()) {
                 outputStream = new FileOutputStream(file);
                 if (source != null) {
                     source.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
                 } else {
                     outputStream.write(jpegData);
                 }
             }
         } catch (FileNotFoundException e) {
             Log.e(TAG, e.getMessage());
             return null;
         } catch (IOException e) {
             Log.e(TAG, e.getMessage());
             return null;
         } finally {
             if (outputStream != null) {
                 try {
                     outputStream.close();
                 } catch (Throwable t) {
                 }
             }
         }
         ContentValues values = new ContentValues(7);
         values.put(MediaStore.Images.Media.TITLE, name);
         values.put(MediaStore.Images.Media.DISPLAY_NAME, filename);
         values.put(MediaStore.Images.Media.DATE_TAKEN, dateTaken);
         values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
         values.put(MediaStore.Images.Media.DATA, filePath);
         return cr.insert(IMAGE_URI, values);
     }

     private void showTakePhotoLayout() {
         mTakePhotoLayout.setVisibility(View.VISIBLE);
         mCropperLayout.setVisibility(View.GONE);
     }

     private void showCropperLayout() {
         mTakePhotoLayout.setVisibility(View.GONE);
         mCropperLayout.setVisibility(View.VISIBLE);
         mCameraPreview.start();   //继续启动摄像头
     }

     private float mLastX = 0;
     private float mLastY = 0;
     private float mLastZ = 0;
     private boolean mInitialized = false;
     private SensorManager mSensorManager;
     private Sensor mAccel;
     @Override
     public void onSensorChanged(SensorEvent event) {

         float x = event.values[0];
         float y = event.values[1];
         float z = event.values[2];
         if (!mInitialized){
             mLastX = x;
             mLastY = y;
             mLastZ = z;
             mInitialized = true;
         }
         float deltaX  = Math.abs(mLastX - x);
         float deltaY = Math.abs(mLastY - y);
         float deltaZ = Math.abs(mLastZ - z);

         if(deltaX > 0.8 || deltaY > 0.8 || deltaZ > 0.8){
             mCameraPreview.setFocus();
         }
         mLastX = x;
         mLastY = y;
         mLastZ = z;
     }

     @Override
     public void onAccuracyChanged(Sensor sensor, int accuracy) {
     }
 }

  actiity中注册了SensorEventListener,也就是使用传感器监听用户手机的移动,如果有一定距离的移动,则自动聚焦,这样体验好一点。

  我对比了一下小猿搜题和学霸君两款app的拍照功能,个人感觉小猿搜题的体验要好一些,因为从主界面进入拍照界面,连个界面没有一个旋转的过渡,而学霸君就有一个过渡,有一丝丝的影响体验。也就是说学霸君的拍照界面是横屏的,在activity的onCreate方法里面调用了setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)来设置全屏,而切换界面的时候又从竖屏切换为横屏,就会有个过渡的效果,影响了体验。

  个人猜测小猿搜题是将拍照界面的activity设置为竖屏,而将摄像头直接旋转90度,这样就强制用户横屏拍摄,当然,拍完之后还要将图片旋转回来。所以我参考小猿搜题来实现的,毕竟体验为王嘛。

  如上图(其实是竖屏),红色圈起来的其实是放到底部,然后将屏幕中间的文字旋转90度(带有动画,起了提示用户横屏拍照的作用),就给人的感觉是横屏的。了。

  还有一点就是小猿搜题拍完照到截图过渡的很自然,感觉很流畅,估计是拍照和截图放在同一个activity中的,如果是两个activty,涉及到界面切换,肯定不会那么自然。所以我也将拍照和截图放在一个界面,拍照完就将自定义相机隐藏,将截图界面显示出来,这样切换就很流畅了。

  项目中截图的功能我是从github上面找的一个开源库cropper:https://github.com/edmodo/cropper

    因为ocr图片识别的代码是公司的,所以识别的功能没有添加到demo里面去。

  Demo源码下载:https://github.com/liuling07/CustomCameraDemo

Android自定义相机拍照、图片裁剪的实现的更多相关文章

  1. iOS开发笔记17:自定义相机拍照

    之前用AVFoundation自定义相机做了拍照与视频相关的东西,为什么要自定义呢?主要是提供更个性化的交互设计,符合app主题,对于视频来说,也便于提供更多丰富有趣的功能.前段时间整理了下拍照部分的 ...

  2. android 自定义相机

    老规矩,先上一下项目地址:GitHub:https://github.com/xiangzhihong/CameraDemo 方式: 调用Camera API 自定义相机 调用系统相机 由于需求不同, ...

  3. 用AVFoundation自定义相机拍照

    自定义拍照或者录视频的功能,就需要用到AVFoundation框架,目前我只用到了拍照,所以记录下自定义拍照用法,视频用法等用上了再补充,应该是大同小异 demo在这里:https://github. ...

  4. android 使用相机拍照,并存储到手机sd卡上,并利用系统录像录像并播放

    //首先声明一个成员变量 String savePath,用来储存文件路径 /** * 保存照片路径 * @return 返回图片的一个文件 * @throws IOException 抛出一个异常 ...

  5. Android自定义ImageView实现图片圆形 ,椭圆和矩形圆角显示

    Android中的ImageView只能显示矩形的图片,为了用户体验更多,Android实现圆角矩形,圆形或者椭圆等图形,一般通过自定义ImageView来实现,首先获取到图片的Bitmap,然后通过 ...

  6. Android 自定义Gallery浏览图片

    之前写的<Android ImageSwitcher和Gallery的使用>一文中提到我在教室一下午为实现那个效果找各种资料.期间在网上找了一个个人觉得比较不错的效果,现在贴图上来: 其实 ...

  7. android 自定义组件-带图片的textView

    1. 定义属性 <?xml version="1.0" encoding="utf-8"?> <resources> <decla ...

  8. android 自定义相机画面倒立解决方案

    有部分手机的影像是倒立的,如何解决这个问题呢? 请看下面 public static void setCameraDisplayOrientation(Activity activity, int c ...

  9. Android—实现自定义相机倒计时拍照

    这篇博客为大家介绍Android自定义相机,并且实现倒计时拍照功能 首先自定义拍照会用到SurfaceView控件显示照片的预览区域,以下是布局文件: 两个TextView是用来显示提示信息和倒计时的 ...

随机推荐

  1. Servlet解决参数乱码问题

    为什么会产生乱码? 之所以会产生乱码,是由于服务器端和客户端的编码方式不一致造成的.客户端与服务器端的交互过程中,存在着两次数据交换:第一次,客户端向服务器端发起请求,第二次数据交换,服务器端响应客户 ...

  2. py2exe使用方法

    一.简介 py2exe是一个将python脚本转换成windows上的可独立执行的可执行程序(*.exe)的工具,这样,你就可以不用装python而在windows系统上运行这个可执行程序. py2e ...

  3. 安装了SQL2005再安装SQL 2008R2,提示此计算机上安装了 Microsoft Visual Studio 2008 的早期版本和检查是否安装了 SQL Server 2005 Express 工具的解决方案

    工作电脑上安装了SQL 2005, 但是客户电脑上安装的是SQL 2008R2,有时候连接他们的库调试没法连接,很不方便.然后又安装了个SQL2008 R2,期间遇到这两个问题,网上搜索了一下收到了解 ...

  4. iOS应用第三方推送的添加

    现在的一些第三方的推送平台挺好用,主要是因为他们有类似微信公众平台一样的管理后台,简单易用,封装了很多开发者需要的推送功能. 下面以个推为例: 1.在个推的应用配置iOS部分设置自己的BounleID ...

  5. Entity FrameWork对有外键关联的数据表的添加操作

    前天做了一个MVC Entity FrameWork项目,遇到有外键关联的数据编辑问题.当你编辑的时候,按照正常的逻辑,把每个字段的数据都对号入座了,然后点击保存按钮,本以为会顺理成章的编辑数据,但是 ...

  6. consul模板配置参数值示例

    参看https://github.com/hashicorp/consul-template#examples // This is the address of the Consul agent. ...

  7. 1.C#中通过委托Action消除重复代码

    阅读目录 一:重复的代码  二:使用委托消除重复代码     一:重复的代码    我们在写一些方法的时候,会在里面可能出现异常的地方使用try catch语句,这样每个方法都会有try catch语 ...

  8. 【现代程序设计】homework-10

    作业地址:http://www.cnblogs.com/xinz/p/3441537.html 进行中...

  9. BestCoder Round #74

    身败名裂啊...... T1WA了半天,30min才A. T2又WA了一发,然后Hack刚2min就被别人叉了. T3做完后最后40min不知所措. 去叉别人,看到一个人写D题判m=0很奇怪,随手把他 ...

  10. Spring boot中使用springfox来生成Swagger Specification小结

    Rest接口对应Swagger Specification路径获取办法: 根据location的值获取api   json描述文件 也许有同学会问,为什么搞的这么麻烦,api json描述文件不就是h ...