最近项目里面又要加一个拍照搜题的功能,也就是用户对着不会做的题目拍一张照片,将照片的文字使用ocr识别出来,再调用题库搜索接口搜索出来展示给用户,类似于小猿搜题、学霸君等app。
其实Android提供Intent让我们打开系统的相机,但是系统相机跟自己app风格不搭,而且用起来体验不好。所以我使用了SDK提供的camera API自定义了一个相机,并且在相机界面上面添加了参考线,有助于用户将题目拍正,提高ocr的识别率。
1、绘制参考线的代码
1 public class ReferenceLine extends View { 2 3 private Paint mLinePaint; 4 5 public ReferenceLine(Context context) { 6 super(context); 7 init(); 8 } 9 10 public ReferenceLine(Context context, AttributeSet attrs) {11 super(context, attrs);12 init();13 }14 15 public ReferenceLine(Context context, AttributeSet attrs, int defStyleAttr) {16 super(context, attrs, defStyleAttr);17 init();18 }19 20 private void init() {21 mLinePaint = new Paint();22 mLinePaint.setAntiAlias(true);23 mLinePaint.setColor(Color.parseColor("#45e0e0e0"));24 mLinePaint.setStrokeWidth(1);25 }26 27 28 29 @Override30 protected void onDraw(Canvas canvas) {31 int screenWidth = Utils.getScreenWH(getContext()).widthPixels;32 int screenHeight = Utils.getScreenWH(getContext()).heightPixels;33 34 int width = screenWidth/3;35 int height = screenHeight/3;36 37 for (int i = width, j = 0;i < screenWidth && j<2;i += width, j++) {38 canvas.drawLine(i, 0, i, screenHeight, mLinePaint);39 }40 for (int j = height,i = 0;j < screenHeight && i < 2;j += height,i++) {41 canvas.drawLine(0, j, screenWidth, j, mLinePaint);42 }43 }44 45 46 }
2、自定义相机代码
这里主要是要创建一个SurfaceView,将摄像头的预览界面放到SurfaceView中显示。
1 package com.bbk.lling.camerademo.camare; 2 3 import android.content.Context; 4 import android.content.res.Configuration; 5 import android.graphics.PixelFormat; 6 import android.graphics.Rect; 7 import android.hardware.Camera; 8 import android.hardware.Camera.AutoFocusCallback; 9 import android.hardware.Camera.PictureCallback; 10 import android.util.AttributeSet; 11 import android.util.Log; 12 import android.view.MotionEvent; 13 import android.view.SurfaceHolder; 14 import android.view.SurfaceView; 15 import android.view.View; 16 import android.widget.RelativeLayout; 17 import android.widget.Toast; 18 19 import com.bbk.lling.camerademo.utils.Utils; 20 21 import java.io.IOException; 22 import java.util.ArrayList; 23 import java.util.Date; 24 import java.util.List; 25 26 /** 27 * @Class: CameraPreview 28 * @Description: 自定义相机 29 * @author: lling(www.cnblogs.com/liuling) 30 * @Date: 2015/10/25 31 */ 32 public class CameraPreview extends SurfaceView implements 33 SurfaceHolder.Callback, AutoFocusCallback { 34 private static final String TAG = "CameraPreview"; 35 36 private int viewWidth = 0; 37 private int viewHeight = 0; 38 39 /** 监听接口 */ 40 private OnCameraStatusListener listener; 41 42 private SurfaceHolder holder; 43 private Camera camera; 44 private FocusView mFocusView; 45 46 //创建一个PictureCallback对象,并实现其中的onPictureTaken方法 47 private PictureCallback pictureCallback = new PictureCallback() { 48 49 // 该方法用于处理拍摄后的照片数据 50 @Override 51 public void onPictureTaken(byte[] data, Camera camera) { 52 // 停止照片拍摄 53 try { 54 camera.stopPreview(); 55 } catch (Exception e) { 56 } 57 // 调用结束事件 58 if (null != listener) { 59 listener.onCameraStopped(data); 60 } 61 } 62 }; 63 64 // Preview类的构造方法 65 public CameraPreview(Context context, AttributeSet attrs) { 66 super(context, attrs); 67 // 获得SurfaceHolder对象 68 holder = getHolder(); 69 // 指定用于捕捉拍照事件的SurfaceHolder.Callback对象 70 holder.addCallback(this); 71 // 设置SurfaceHolder对象的类型 72 holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 73 setOnTouchListener(onTouchListener); 74 } 75 76 // 在surface创建时激发 77 public void surfaceCreated(SurfaceHolder holder) { 78 Log.e(TAG, "==surfaceCreated=="); 79 if(!Utils.checkCameraHardware(getContext())) { 80 Toast.makeText(getContext(), "摄像头打开失败!", Toast.LENGTH_SHORT).show(); 81 return; 82 } 83 // 获得Camera对象 84 camera = getCameraInstance(); 85 try { 86 // 设置用于显示拍照摄像的SurfaceHolder对象 87 camera.setPreviewDisplay(holder); 88 } catch (IOException e) { 89 e.printStackTrace(); 90 // 释放手机摄像头 91 camera.release(); 92 camera = null; 93 } 94 updateCameraParameters(); 95 if (camera != null) { 96 camera.startPreview(); 97 } 98 setFocus(); 99 }100 101 // 在surface销毁时激发102 public void surfaceDestroyed(SurfaceHolder holder) {103 Log.e(TAG, "==surfaceDestroyed==");104 // 释放手机摄像头105 camera.release();106 camera = null;107 }108 109 // 在surface的大小发生改变时激发110 public void surfaceChanged(final SurfaceHolder holder, int format, int w,111 int h) {112 // stop preview before making changes113 try {114 camera.stopPreview();115 } catch (Exception e){116 // ignore: tried to stop a non-existent preview117 }118 // set preview size and make any resize, rotate or119 // reformatting changes here120 updateCameraParameters();121 // start preview with new settings122 try {123 camera.setPreviewDisplay(holder);124 camera.startPreview();125 126 } catch (Exception e){127 Log.d(TAG, "Error starting camera preview: " + e.getMessage());128 }129 setFocus();130 }131 132 /**133 * 点击显示焦点区域134 */135 OnTouchListener onTouchListener = new OnTouchListener() {136 @SuppressWarnings("deprecation")137 @Override138 public boolean onTouch(View v, MotionEvent event) {139 if (event.getAction() == MotionEvent.ACTION_DOWN) {140 int width = mFocusView.getWidth();141 int height = mFocusView.getHeight();142 mFocusView.setX(event.getX() - (width / 2));143 mFocusView.setY(event.getY() - (height / 2));144 mFocusView.beginFocus();145 } else if (event.getAction() == MotionEvent.ACTION_UP) {146 focusOnTouch(event);147 }148 return true;149 }150 };151 152 /**153 * 获取摄像头实例154 * @return155 */156 private Camera getCameraInstance() {157 Camera c = null;158 try {159 int cameraCount = 0;160 Camera.CameraInfo cameraInfo = new Camera.CameraInfo();161 cameraCount = Camera.getNumberOfCameras(); // get cameras number162 163 for (int camIdx = 0; camIdx < cameraCount; camIdx++) {164 Camera.getCameraInfo(camIdx, cameraInfo); // get camerainfo165 // 代表摄像头的方位,目前有定义值两个分别为CAMERA_FACING_FRONT前置和CAMERA_FACING_BACK后置166 if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {167 try {168 c = Camera.open(camIdx); //打开后置摄像头169 } catch (RuntimeException e) {170 Toast.makeText(getContext(), "摄像头打开失败!", Toast.LENGTH_SHORT).show();171 }172 }173 }174 if (c == null) {175 c = Camera.open(0); // attempt to get a Camera instance176 }177 } catch (Exception e) {178 Toast.makeText(getContext(), "摄像头打开失败!", Toast.LENGTH_SHORT).show();179 }180 return c;181 }182 183 private void updateCameraParameters() {184 if (camera != null) {185 Camera.Parameters p = camera.getParameters();186 187 setParameters(p);188 189 try {190 camera.setParameters(p);191 } catch (Exception e) {192 Camera.Size previewSize = findBestPreviewSize(p);193 p.setPreviewSize(previewSize.width, previewSize.height);194 p.setPictureSize(previewSize.width, previewSize.height);195 camera.setParameters(p);196 }197 }198 }199 200 /**201 * @param p202 */203 private void setParameters(Camera.Parameters p) {204 List<String> focusModes = p.getSupportedFocusModes();205 if (focusModes206 .contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {207 p.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);208 }209 210 long time = new Date().getTime();211 p.setGpsTimestamp(time);212 // 设置照片格式213 p.setPictureFormat(PixelFormat.JPEG);214 Camera.Size previewSize = findPreviewSizeByScreen(p);215 p.setPreviewSize(previewSize.width, previewSize.height);216 p.setPictureSize(previewSize.width, previewSize.height);217 p.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);218 if (getContext().getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {219 camera.setDisplayOrientation(90);220 p.setRotation(90);221 }222 }223 224 // 进行拍照,并将拍摄的照片传入PictureCallback接口的onPictureTaken方法225 public void takePicture() {226 if (camera != null) {227 try {228 camera.takePicture(null, null, pictureCallback);229 } catch (Exception e) {230 e.printStackTrace();231 }232 }233 }234 235 // 设置监听事件236 public void setOnCameraStatusListener(OnCameraStatusListener listener) {237 this.listener = listener;238 }239 240 @Override241 public void onAutoFocus(boolean success, Camera camera) {242 243 }244 245 public void start() {246 if (camera != null) {247 camera.startPreview();248 }249 }250 251 public void stop() {252 if (camera != null) {253 camera.stopPreview();254 }255 }256 257 /**258 * 相机拍照监听接口259 */260 public interface OnCameraStatusListener {261 // 相机拍照结束事件262 void onCameraStopped(byte[] data);263 }264 265 @Override266 protected void onMeasure(int widthSpec, int heightSpec) {267 viewWidth = MeasureSpec.getSize(widthSpec);268 viewHeight = MeasureSpec.getSize(heightSpec);269 super.onMeasure(270 MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY),271 MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY));272 }273 274 /**275 * 将预览大小设置为屏幕大小276 * @param parameters277 * @return278 */279 private Camera.Size findPreviewSizeByScreen(Camera.Parameters parameters) {280 if (viewWidth != 0 && viewHeight != 0) {281 return camera.new Size(Math.max(viewWidth, viewHeight),282 Math.min(viewWidth, viewHeight));283 } else {284 return camera.new Size(Utils.getScreenWH(getContext()).heightPixels,285 Utils.getScreenWH(getContext()).widthPixels);286 }287 }288 289 /**290 * 找到最合适的显示分辨率 (防止预览图像变形)291 * @param parameters292 * @return293 */294 private Camera.Size findBestPreviewSize(Camera.Parameters parameters) {295 296 // 系统支持的所有预览分辨率297 String previewSizeValueString = null;298 previewSizeValueString = parameters.get("preview-size-values");299 300 if (previewSizeValueString == null) {301 previewSizeValueString = parameters.get("preview-size-value");302 }303 304 if (previewSizeValueString == null) { // 有些手机例如m9获取不到支持的预览大小 就直接返回屏幕大小305 return camera.new Size(Utils.getScreenWH(getContext()).widthPixels,306 Utils.getScreenWH(getContext()).heightPixels);307 }308 float bestX = 0;309 float bestY = 0;310 311 float tmpRadio = 0;312 float viewRadio = 0;313 314 if (viewWidth != 0 && viewHeight != 0) {315 viewRadio = Math.min((float) viewWidth, (float) viewHeight)316 / Math.max((float) viewWidth, (float) viewHeight);317 }318 319 String[] COMMA_PATTERN = previewSizeValueString.split(",");320 for (String prewsizeString : COMMA_PATTERN) {321 prewsizeString = prewsizeString.trim();322 323 int dimPosition = prewsizeString.indexOf('x');324 if (dimPosition == -1) {325 continue;326 }327 328 float newX = 0;329 float newY = 0;330 331 try {332 newX = Float.parseFloat(prewsizeString.substring(0, dimPosition));333 newY = Float.parseFloat(prewsizeString.substring(dimPosition + 1));334 } catch (NumberFormatException e) {335 continue;336 }337 338 float radio = Math.min(newX, newY) / Math.max(newX, newY);339 if (tmpRadio == 0) {340 tmpRadio = radio;341 bestX = newX;342 bestY = newY;343 } else if (tmpRadio != 0 && (Math.abs(radio - viewRadio)) < (Math.abs(tmpRadio - viewRadio))) {344 tmpRadio = radio;345 bestX = newX;346 bestY = newY;347 }348 }349 350 if (bestX > 0 && bestY > 0) {351 return camera.new Size((int) bestX, (int) bestY);352 }353 return null;354 }355 356 /**357 * 设置焦点和测光区域358 *359 * @param event360 */361 public void focusOnTouch(MotionEvent event) {362 363 int[] location = new int[2];364 RelativeLayout relativeLayout = (RelativeLayout)getParent();365 relativeLayout.getLocationOnScreen(location);366 367 Rect focusRect = Utils.calculateTapArea(mFocusView.getWidth(),368 mFocusView.getHeight(), 1f, event.getRawX(), event.getRawY(),369 location[0], location[0] + relativeLayout.getWidth(), location[1],370 location[1] + relativeLayout.getHeight());371 Rect meteringRect = Utils.calculateTapArea(mFocusView.getWidth(),372 mFocusView.getHeight(), 1.5f, event.getRawX(), event.getRawY(),373 location[0], location[0] + relativeLayout.getWidth(), location[1],374 location[1] + relativeLayout.getHeight());375 376 Camera.Parameters parameters = camera.getParameters();377 parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);378 379 if (parameters.getMaxNumFocusAreas() > 0) {380 List<Camera.Area> focusAreas = new ArrayList<Camera.Area>();381 focusAreas.add(new Camera.Area(focusRect, 1000));382 383 parameters.setFocusAreas(focusAreas);384 }385 386 if (parameters.getMaxNumMeteringAreas() > 0) {387 List<Camera.Area> meteringAreas = new ArrayList<Camera.Area>();388 meteringAreas.add(new Camera.Area(meteringRect, 1000));389 390 parameters.setMeteringAreas(meteringAreas);391 }392 393 try {394 camera.setParameters(parameters);395 } catch (Exception e) {396 }397 camera.autoFocus(this);398 }399 400 /**401 * 设置聚焦的图片402 * @param focusView403 */404 public void setFocusView(FocusView focusView) {405 this.mFocusView = focusView;406 }407 408 /**409 * 设置自动聚焦,并且聚焦的圈圈显示在屏幕中间位置410 */411 public void setFocus() {412 if(!mFocusView.isFocusing()) {413 try {414 camera.autoFocus(this);415 mFocusView.setX((Utils.getWidthInPx(getContext())-mFocusView.getWidth()) / 2);416 mFocusView.setY((Utils.getHeightInPx(getContext())-mFocusView.getHeight()) / 2);417 mFocusView.beginFocus();418 } catch (Exception e) {419 }420 }421 }422 423 }
3、Activity中使用自定义相机
1 public class TakePhoteActivity extends Activity implements CameraPreview.OnCameraStatusListener, 2 SensorEventListener { 3 private static final String TAG = "TakePhoteActivity"; 4 public static final Uri IMAGE_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 5 public static final String PATH = Environment.getExternalStorageDirectory() 6 .toString() + "/AndroidMedia/"; 7 CameraPreview mCameraPreview; 8 CropImageView mCropImageView; 9 RelativeLayout mTakePhotoLayout; 10 LinearLayout mCropperLayout; 11 @Override 12 protected void onCreate(Bundle savedInstanceState) { 13 super.onCreate(savedInstanceState); 14 // 设置横屏 15 // setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 16 // 设置全屏 17 requestWindowFeature(Window.FEATURE_NO_TITLE); 18 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 19 WindowManager.LayoutParams.FLAG_FULLSCREEN); 20 setContentView(R.layout.activity_take_phote); 21 // Initialize components of the app 22 mCropImageView = (CropImageView) findViewById(R.id.CropImageView); 23 mCameraPreview = (CameraPreview) findViewById(R.id.cameraPreview); 24 FocusView focusView = (FocusView) findViewById(R.id.view_focus); 25 mTakePhotoLayout = (RelativeLayout) findViewById(R.id.take_photo_layout); 26 mCropperLayout = (LinearLayout) findViewById(R.id.cropper_layout); 27 28 mCameraPreview.setFocusView(focusView); 29 mCameraPreview.setOnCameraStatusListener(this); 30 mCropImageView.setGuidelines(2); 31 32 mSensorManager = (SensorManager) getSystemService(Context. 33 SENSOR_SERVICE); 34 mAccel = mSensorManager.getDefaultSensor(Sensor. 35 TYPE_ACCELEROMETER); 36 37 } 38 39 boolean isRotated = false; 40 41 @Override 42 protected void onResume() { 43 super.onResume(); 44 if(!isRotated) { 45 TextView hint_tv = (TextView) findViewById(R.id.hint); 46 ObjectAnimator animator = ObjectAnimator.ofFloat(hint_tv, "rotation", 0f, 90f); 47 animator.setStartDelay(800); 48 animator.setDuration(1000); 49 animator.setInterpolator(new LinearInterpolator()); 50 animator.start(); 51 View view = findViewById(R.id.crop_hint); 52 AnimatorSet animSet = new AnimatorSet(); 53 ObjectAnimator animator1 = ObjectAnimator.ofFloat(view, "rotation", 0f, 90f); 54 ObjectAnimator moveIn = ObjectAnimator.ofFloat(view, "translationX", 0f, -50f); 55 animSet.play(animator1).before(moveIn); 56 animSet.setDuration(10); 57 animSet.start(); 58 isRotated = true; 59 } 60 mSensorManager.registerListener(this, mAccel, SensorManager.SENSOR_DELAY_UI); 61 } 62 63 @Override 64 protected void onPause() { 65 super.onPause(); 66 mSensorManager.unregisterListener(this); 67 } 68 69 @Override 70 public void onConfigurationChanged(Configuration newConfig) { 71 Log.e(TAG, "onConfigurationChanged"); 72 super.onConfigurationChanged(newConfig); 73 } 74 75 public void takePhoto(View view) { 76 if(mCameraPreview != null) { 77 mCameraPreview.takePicture(); 78 } 79 } 80 81 public void close(View view) { 82 finish(); 83 } 84 85 /** 86 * 关闭截图界面 87 * @param view 88 */ 89 public void closeCropper(View view) { 90 showTakePhotoLayout(); 91 } 92 93 /** 94 * 开始截图,并保存图片 95 * @param view 96 */ 97 public void startCropper(View view) { 98 //获取截图并旋转90度 99 CropperImage cropperImage = mCropImageView.getCroppedImage();100 Log.e(TAG, cropperImage.getX() + "," + cropperImage.getY());101 Log.e(TAG, cropperImage.getWidth() + "," + cropperImage.getHeight());102 Bitmap bitmap = Utils.rotate(cropperImage.getBitmap(), -90);103 // Bitmap bitmap = mCropImageView.getCroppedImage();104 // 系统时间105 long dateTaken = System.currentTimeMillis();106 // 图像名称107 String filename = DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken)108 .toString() + ".jpg";109 Uri uri = insertImage(getContentResolver(), filename, dateTaken, PATH,110 filename, bitmap, null);111 cropperImage.getBitmap().recycle();112 cropperImage.setBitmap(null);113 Intent intent = new Intent(this, ShowCropperedActivity.class);114 intent.setData(uri);115 intent.putExtra("path", PATH + filename);116 intent.putExtra("width", bitmap.getWidth());117 intent.putExtra("height", bitmap.getHeight());118 intent.putExtra("cropperImage", cropperImage);119 startActivity(intent);120 bitmap.recycle();121 finish();122 super.overridePendingTransition(R.anim.fade_in,123 R.anim.fade_out);124 // doAnimation(cropperImage);125 }126 127 private void doAnimation(CropperImage cropperImage) {128 ImageView imageView = new ImageView(this);129 View view = LayoutInflater.from(this).inflate(130 R.layout.image_view_layout, null);131 ((RelativeLayout) view.findViewById(R.id.root_layout)).addView(imageView);132 RelativeLayout relativeLayout = ((RelativeLayout) findViewById(R.id.root_layout));133 // relativeLayout.addView(imageView);134 imageView.setX(cropperImage.getX());135 imageView.setY(cropperImage.getY());136 ViewGroup.LayoutParams lp = imageView.getLayoutParams();137 lp.width = (int)cropperImage.getWidth();138 lp.height = (int) cropperImage.getHeight();139 imageView.setLayoutParams(lp);140 imageView.setImageBitmap(cropperImage.getBitmap());141 try {142 getWindow().addContentView(view, lp);143 } catch (Exception e) {144 e.printStackTrace();145 }146 /*AnimatorSet animSet = new AnimatorSet();147 ObjectAnimator translationX = ObjectAnimator.ofFloat(this, "translationX", cropperImage.getX(), 0);148 ObjectAnimator translationY = ObjectAnimator.ofFloat(this, "translationY", cropperImage.getY(), 0);*/149 150 TranslateAnimation translateAnimation = new TranslateAnimation(151 0, -cropperImage.getX(), 0, -(Math.abs(cropperImage.getHeight() - cropperImage.getY())));// 当前位置移动到指定位置152 RotateAnimation rotateAnimation = new RotateAnimation(0, -90,153 Animation.ABSOLUTE, cropperImage.getX() ,Animation.ABSOLUTE, cropperImage.getY());154 AnimationSet animationSet = new AnimationSet(true);155 animationSet.addAnimation(translateAnimation);156 animationSet.addAnimation(rotateAnimation);157 animationSet.setFillAfter(true);158 animationSet.setDuration(2000L);159 imageView.startAnimation(animationSet);160 // finish();161 }162 163 /**164 * 拍照成功后回调165 * 存储图片并显示截图界面166 * @param data167 */168 @Override169 public void onCameraStopped(byte[] data) {170 Log.i("TAG", "==onCameraStopped==");171 // 创建图像172 Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);173 // 系统时间174 long dateTaken = System.currentTimeMillis();175 // 图像名称176 String filename = DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken)177 .toString() + ".jpg";178 // 存储图像(PATH目录)179 Uri source = insertImage(getContentResolver(), filename, dateTaken, PATH,180 filename, bitmap, data);181 //准备截图182 try {183 mCropImageView.setImageBitmap(MediaStore.Images.Media.getBitmap(this.getContentResolver(), source));184 // mCropImageView.rotateImage(90);185 } catch (IOException e) {186 Log.e(TAG, e.getMessage());187 }188 showCropperLayout();189 }190 191 /**192 * 存储图像并将信息添加入媒体数据库193 */194 private Uri insertImage(ContentResolver cr, String name, long dateTaken,195 String directory, String filename, Bitmap source, byte[] jpegData) {196 OutputStream outputStream = null;197 String filePath = directory + filename;198 try {199 File dir = new File(directory);200 if (!dir.exists()) {201 dir.mkdirs();202 }203 File file = new File(directory, filename);204 if (file.createNewFile()) {205 outputStream = new FileOutputStream(file);206 if (source != null) {207 source.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);208 } else {209 outputStream.write(jpegData);210 }211 }212 } catch (FileNotFoundException e) {213 Log.e(TAG, e.getMessage());214 return null;215 } catch (IOException e) {216 Log.e(TAG, e.getMessage());217 return null;218 } finally {219 if (outputStream != null) {220 try {221 outputStream.close();222 } catch (Throwable t) {223 }224 }225 }226 ContentValues values = new ContentValues(7);227 values.put(MediaStore.Images.Media.TITLE, name);228 values.put(MediaStore.Images.Media.DISPLAY_NAME, filename);229 values.put(MediaStore.Images.Media.DATE_TAKEN, dateTaken);230 values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");231 values.put(MediaStore.Images.Media.DATA, filePath);232 return cr.insert(IMAGE_URI, values);233 }234 235 private void showTakePhotoLayout() {236 mTakePhotoLayout.setVisibility(View.VISIBLE);237 mCropperLayout.setVisibility(View.GONE);238 }239 240 private void showCropperLayout() {241 mTakePhotoLayout.setVisibility(View.GONE);242 mCropperLayout.setVisibility(View.VISIBLE);243 mCameraPreview.start(); //继续启动摄像头244 }245 246 247 private float mLastX = 0;248 private float mLastY = 0;249 private float mLastZ = 0;250 private boolean mInitialized = false;251 private SensorManager mSensorManager;252 private Sensor mAccel;253 @Override254 public void onSensorChanged(SensorEvent event) {255 256 float x = event.values[0];257 float y = event.values[1];258 float z = event.values[2];259 if (!mInitialized){260 mLastX = x;261 mLastY = y;262 mLastZ = z;263 mInitialized = true;264 }265 float deltaX = Math.abs(mLastX - x);266 float deltaY = Math.abs(mLastY - y);267 float deltaZ = Math.abs(mLastZ - z);268 269 if(deltaX > 0.8 || deltaY > 0.8 || deltaZ > 0.8){270 mCameraPreview.setFocus();271 }272 mLastX = x;273 mLastY = y;274 mLastZ = z;275 }276 277 @Override278 public void onAccuracyChanged(Sensor sensor, int accuracy) {279 }280 }
actiity中注册了SensorEventListener,也就是使用传感器监听用户手机的移动,如果有一定距离的移动,则自动聚焦,这样体验好一点。
我对比了一下小猿搜题和学霸君两款app的拍照功能,个人感觉小猿搜题的体验要好一些,因为从主界面进入拍照界面,连个界面没有一个旋转的过渡,而学霸君就有一个过渡,有一丝丝的影响体验。也就是说学霸君的拍照界面是横屏的,在activity的onCreate方法里面调用了setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)来设置全屏,而切换界面的时候又从竖屏切换为横屏,就会有个过渡的效果,影响了体验。
个人猜测小猿搜题是将拍照界面的activity设置为竖屏,而将摄像头直接旋转90度,这样就强制用户横屏拍摄,当然,拍完之后还要将图片旋转回来。所以我参考小猿搜题来实现的,毕竟体验为王嘛。
如上图(其实是竖屏),红色圈起来的其实是放到底部,然后将屏幕中间的文字旋转90度(带有动画,起了提示用户横屏拍照的作用),就给人的感觉是横屏的。了。
还有一点就是小猿搜题拍完照到截图过渡的很自然,感觉很流畅,估计是拍照和截图放在同一个activity中的,如果是两个activty,涉及到界面切换,肯定不会那么自然。所以我也将拍照和截图放在一个界面,拍照完就将自定义相机隐藏,将截图界面显示出来,这样切换就很流畅了。
项目中截图的功能我是从github上面找的一个开源库cropper:https://github.com/edmodo/cropper
因为ocr图片识别的代码是公司的,所以识别的功能没有添加到demo里面去。