Android自定义相机拍照、图片裁剪的实现

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

  最近项目里面又要加一个拍照搜题的功能,也就是用户对着不会做的题目拍一张照片,将照片的文字使用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

目录
相关文章
|
1月前
|
Android开发 开发者
安卓应用开发中的自定义视图
【9月更文挑战第37天】在安卓开发的海洋中,自定义视图犹如一座座小岛,等待着勇敢的探索者去发现其独特之处。本文将带领你踏上这段旅程,从浅滩走向深海,逐步揭开自定义视图的神秘面纱。
40 3
|
1月前
|
数据可视化 Android开发 开发者
安卓应用开发中的自定义View组件
【10月更文挑战第5天】在安卓应用开发中,自定义View组件是提升用户交互体验的利器。本篇将深入探讨如何从零开始创建自定义View,包括设计理念、实现步骤以及性能优化技巧,帮助开发者打造流畅且富有创意的用户界面。
80 0
|
16天前
|
搜索推荐 前端开发 Android开发
安卓应用开发中的自定义视图实现
【10月更文挑战第30天】在安卓开发的海洋中,自定义视图是那抹不可或缺的亮色,它为应用界面的个性化和交互体验的提升提供了无限可能。本文将深入探讨如何在安卓平台创建自定义视图,并展示如何通过代码实现这一过程。我们将从基础出发,逐步引导你理解自定义视图的核心概念,然后通过一个实际的代码示例,详细讲解如何将理论应用于实践,最终实现一个美观且具有良好用户体验的自定义控件。无论你是想提高自己的开发技能,还是仅仅出于对安卓开发的兴趣,这篇文章都将为你提供价值。
|
17天前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
28 5
|
2月前
|
存储 缓存 编解码
Android经典面试题之图片Bitmap怎么做优化
本文介绍了图片相关的内存优化方法,包括分辨率适配、图片压缩与缓存。文中详细讲解了如何根据不同分辨率放置图片资源,避免图片拉伸变形;并通过示例代码展示了使用`BitmapFactory.Options`进行图片压缩的具体步骤。此外,还介绍了Glide等第三方库如何利用LRU算法实现高效图片缓存。
65 20
Android经典面试题之图片Bitmap怎么做优化
|
1月前
|
XML 前端开发 Java
安卓应用开发中的自定义View组件
【10月更文挑战第5天】自定义View是安卓应用开发的一块基石,它为开发者提供了无限的可能。通过掌握其原理和实现方法,可以创造出既美观又实用的用户界面。本文将引导你了解自定义View的创建过程,包括绘制技巧、事件处理以及性能优化等关键步骤。
|
2月前
|
Android开发 开发者
安卓开发中的自定义视图:从入门到精通
【9月更文挑战第19天】在安卓开发的广阔天地中,自定义视图是一块充满魔力的土地。它不仅仅是代码的堆砌,更是艺术与科技的完美结合。通过掌握自定义视图,开发者能够打破常规,创造出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战应用,一步步展示如何用代码绘出心中的蓝图。无论你是初学者还是有经验的开发者,这篇文章都将为你打开一扇通往创意和效率的大门。让我们一起探索自定义视图的秘密,将你的应用打造成一件艺术品吧!
61 10
|
2月前
|
XML 编解码 Android开发
安卓开发中的自定义视图控件
【9月更文挑战第14天】在安卓开发中,自定义视图控件是一种高级技巧,它可以让开发者根据项目需求创建出独特的用户界面元素。本文将通过一个简单示例,引导你了解如何在安卓项目中实现自定义视图控件,包括创建自定义控件类、处理绘制逻辑以及响应用户交互。无论你是初学者还是有经验的开发者,这篇文章都会为你提供有价值的见解和技巧。
46 3
|
2月前
|
前端开发 Android开发 开发者
安卓应用开发中的自定义视图基础
【9月更文挑战第13天】在安卓开发的广阔天地中,自定义视图是一块神奇的画布,它允许开发者将想象力转化为用户界面的创新元素。本文将带你一探究竟,了解如何从零开始构建自定义视图,包括绘图基础、触摸事件处理,以及性能优化的实用技巧。无论你是想提升应用的视觉吸引力,还是追求更流畅的交互体验,这里都有你需要的金钥匙。
|
2月前
|
缓存 搜索推荐 Android开发
安卓应用开发中的自定义View组件实践
【9月更文挑战第10天】在安卓开发领域,自定义View是提升用户体验和实现界面个性化的重要手段。本文将通过一个实际案例,展示如何在安卓项目中创建和使用自定义View组件,包括设计思路、实现步骤以及可能遇到的问题和解决方案。文章不仅提供了代码示例,还深入探讨了自定义View的性能优化技巧,旨在帮助开发者更好地掌握这一技能。