最近项目里面又要加一个拍照搜题的功能,也就是用户对着不会做的题目拍一张照片,将照片的文字使用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 @Override 30 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 changes 113 try { 114 camera.stopPreview(); 115 } catch (Exception e){ 116 // ignore: tried to stop a non-existent preview 117 } 118 // set preview size and make any resize, rotate or 119 // reformatting changes here 120 updateCameraParameters(); 121 // start preview with new settings 122 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 @Override 138 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 * @return 155 */ 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 number 162 163 for (int camIdx = 0; camIdx < cameraCount; camIdx++) { 164 Camera.getCameraInfo(camIdx, cameraInfo); // get camerainfo 165 // 代表摄像头的方位,目前有定义值两个分别为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 instance 176 } 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 p 202 */ 203 private void setParameters(Camera.Parameters p) { 204 List<String> focusModes = p.getSupportedFocusModes(); 205 if (focusModes 206 .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 @Override 241 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 @Override 266 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 parameters 277 * @return 278 */ 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 parameters 292 * @return 293 */ 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 event 360 */ 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 focusView 403 */ 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 data 167 */ 168 @Override 169 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 @Override 254 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 @Override 278 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里面去。
Demo源码下载:https://github.com/liuling07/CustomCameraDemo