因爲網上大部分自定義相機的實現,都是耦合性比較強的,不方便從此的複用,因此我本身實現了一套自定義相機,方便之後的擴展。自定義相機分爲如下3個部分。java
package com.focustech.xyz.baselibrary.camera; import android.app.Activity; import android.content.Context; import android.graphics.ImageFormat; import android.hardware.Camera; import android.view.SurfaceHolder; import android.view.SurfaceView; import com.focustech.xyz.baselibrary.common.XyzLogger; import java.io.IOException; import java.util.SortedSet; /** * @author 郭翰林 * @date 2019/2/28 0028 17:06 * 註釋:相機預覽視圖 */ public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback { private SurfaceHolder mHolder; private Camera mCamera; private boolean isPreview; private Context context; /** * 預覽尺寸集合 */ private final SizeMap mPreviewSizes = new SizeMap(); /** * 圖片尺寸集合 */ private final SizeMap mPictureSizes = new SizeMap(); /** * 屏幕旋轉顯示角度 */ private int mDisplayOrientation; /** * 設備屏寬比 */ private AspectRatio mAspectRatio; /** * 註釋:構造函數 * 時間:2019/2/28 0028 17:10 * 做者:郭翰林 * * @param context * @param mCamera */ public CameraPreview(Context context, Camera mCamera) { super(context); this.context = context; this.mCamera = mCamera; this.mHolder = getHolder(); this.mHolder.addCallback(this); mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); mDisplayOrientation = ((Activity) context).getWindowManager().getDefaultDisplay().getRotation(); mAspectRatio = AspectRatio.of(16, 9); } @Override public void surfaceCreated(SurfaceHolder holder) { try { //設置設備高寬比 mAspectRatio = getDeviceAspectRatio((Activity) context); //設置預覽方向 mCamera.setDisplayOrientation(90); Camera.Parameters parameters = mCamera.getParameters(); //獲取全部支持的預覽尺寸 mPreviewSizes.clear(); for (Camera.Size size : parameters.getSupportedPreviewSizes()) { mPreviewSizes.add(new Size(size.width, size.height)); } //獲取全部支持的圖片尺寸 mPictureSizes.clear(); for (Camera.Size size : parameters.getSupportedPictureSizes()) { mPictureSizes.add(new Size(size.width, size.height)); } Size previewSize = chooseOptimalSize(mPreviewSizes.sizes(mAspectRatio)); Size pictureSize = mPictureSizes.sizes(mAspectRatio).last(); //設置相機參數 parameters.setPreviewSize(previewSize.getWidth(), previewSize.getHeight()); parameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight()); parameters.setPictureFormat(ImageFormat.JPEG); parameters.setRotation(90); mCamera.setParameters(parameters); //把這個預覽效果展現在SurfaceView上面 mCamera.setPreviewDisplay(holder); //開啓預覽效果 mCamera.startPreview(); isPreview = true; } catch (IOException e) { XyzLogger.e("CameraPreview", "相機預覽錯誤: " + e.getMessage()); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { if (holder.getSurface() == null) { return; } //中止預覽效果 mCamera.stopPreview(); //從新設置預覽效果 try { mCamera.setPreviewDisplay(mHolder); } catch (IOException e) { e.printStackTrace(); } mCamera.startPreview(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { if (mCamera != null) { if (isPreview) { //正在預覽 mCamera.stopPreview(); mCamera.release(); } } } /** * 註釋:獲取設備屏寬比 * 時間:2019/3/4 0004 12:55 * 做者:郭翰林 */ private AspectRatio getDeviceAspectRatio(Activity activity) { int width = activity.getWindow().getDecorView().getWidth(); int height = activity.getWindow().getDecorView().getHeight(); return AspectRatio.of(height, width); } /** * 註釋:選擇合適的預覽尺寸 * 時間:2019/3/4 0004 11:25 * 做者:郭翰林 * * @param sizes * @return */ @SuppressWarnings("SuspiciousNameCombination") private Size chooseOptimalSize(SortedSet<Size> sizes) { int desiredWidth; int desiredHeight; final int surfaceWidth = getWidth(); final int surfaceHeight = getHeight(); if (isLandscape(mDisplayOrientation)) { desiredWidth = surfaceHeight; desiredHeight = surfaceWidth; } else { desiredWidth = surfaceWidth; desiredHeight = surfaceHeight; } Size result = null; for (Size size : sizes) { if (desiredWidth <= size.getWidth() && desiredHeight <= size.getHeight()) { return size; } result = size; } return result; } /** * Test if the supplied orientation is in landscape. * * @param orientationDegrees Orientation in degrees (0,90,180,270) * @return True if in landscape, false if portrait */ private boolean isLandscape(int orientationDegrees) { return (orientationDegrees == 90 || orientationDegrees == 270); } } 複製代碼
這裏有2處地方須要注意,相機要設置正確的預覽尺寸和正確的圖片的尺寸。若是預覽尺寸設置錯誤,則預覽佈局會被拉伸或者收縮。若是圖片尺寸設置錯誤,部分機型會致使閃退或者拍出的照片很不清晰。android
這裏適配預覽尺寸和圖片尺寸,是根據設備的屏寬比和Carme拿到的說支持的預覽尺寸和圖片尺寸計算得出應有的預覽尺寸和圖片尺寸,代碼以下。ios
//設置設備高寬比 mAspectRatio = getDeviceAspectRatio((Activity) context);. Camera.Parameters parameters = mCamera.getParameters(); //獲取全部支持的預覽尺寸 mPreviewSizes.clear(); for (Camera.Size size : parameters.getSupportedPreviewSizes()) { mPreviewSizes.add(new Size(size.width, size.height)); } //獲取全部支持的圖片尺寸 mPictureSizes.clear(); for (Camera.Size size : parameters.getSupportedPictureSizes()) { mPictureSizes.add(new Size(size.width, size.height)); } Size previewSize = chooseOptimalSize(mPreviewSizes.sizes(mAspectRatio)); Size pictureSize = mPictureSizes.sizes(mAspectRatio).last(); //設置相機參數 parameters.setPreviewSize(previewSize.getWidth(), previewSize.getHeight()); parameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight()); 複製代碼
/** * 註釋:獲取設備屏寬比 * 時間:2019/3/4 0004 12:55 * 做者:郭翰林 */ private AspectRatio getDeviceAspectRatio(Activity activity) { int width = activity.getWindow().getDecorView().getWidth(); int height = activity.getWindow().getDecorView().getHeight(); return AspectRatio.of(height, width); } 複製代碼
/** * 註釋:選擇合適的預覽尺寸 * 時間:2019/3/4 0004 11:25 * 做者:郭翰林 * * @param sizes * @return */ @SuppressWarnings("SuspiciousNameCombination") private Size chooseOptimalSize(SortedSet<Size> sizes) { int desiredWidth; int desiredHeight; final int surfaceWidth = getWidth(); final int surfaceHeight = getHeight(); if (isLandscape(mDisplayOrientation)) { desiredWidth = surfaceHeight; desiredHeight = surfaceWidth; } else { desiredWidth = surfaceWidth; desiredHeight = surfaceHeight; } Size result = null; for (Size size : sizes) { if (desiredWidth <= size.getWidth() && desiredHeight <= size.getHeight()) { return size; } result = size; } return result; } 複製代碼
package com.focustech.xyz.baselibrary.camera; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.v4.util.SparseArrayCompat; /** * @author 郭翰林 * @date 2019/3/4 0004 11:11 * 註釋:屏寬比 */ public class AspectRatio implements Comparable<AspectRatio>, Parcelable { private final static SparseArrayCompat<SparseArrayCompat<AspectRatio>> sCache = new SparseArrayCompat<>(16); private final int mX; private final int mY; /** * Returns an instance of {@link AspectRatio} specified by {@code x} and {@code y} values. * The values {@code x} and {@code} will be reduced by their greatest common divider. * * @param x The width * @param y The height * @return An instance of {@link AspectRatio} */ public static AspectRatio of(int x, int y) { int gcd = gcd(x, y); x /= gcd; y /= gcd; SparseArrayCompat<AspectRatio> arrayX = sCache.get(x); if (arrayX == null) { AspectRatio ratio = new AspectRatio(x, y); arrayX = new SparseArrayCompat<>(); arrayX.put(y, ratio); sCache.put(x, arrayX); return ratio; } else { AspectRatio ratio = arrayX.get(y); if (ratio == null) { ratio = new AspectRatio(x, y); arrayX.put(y, ratio); } return ratio; } } /** * Parse an {@link AspectRatio} from a {@link String} formatted like "4:3". * * @param s The string representation of the aspect ratio * @return The aspect ratio * @throws IllegalArgumentException when the format is incorrect. */ public static AspectRatio parse(String s) { int position = s.indexOf(':'); if (position == -1) { throw new IllegalArgumentException("Malformed aspect ratio: " + s); } try { int x = Integer.parseInt(s.substring(0, position)); int y = Integer.parseInt(s.substring(position + 1)); return AspectRatio.of(x, y); } catch (NumberFormatException e) { throw new IllegalArgumentException("Malformed aspect ratio: " + s, e); } } private AspectRatio(int x, int y) { mX = x; mY = y; } public int getX() { return mX; } public int getY() { return mY; } public boolean matches(Size size) { int gcd = gcd(size.getWidth(), size.getHeight()); int x = size.getWidth() / gcd; int y = size.getHeight() / gcd; return mX == x && mY == y; } @Override public boolean equals(Object o) { if (o == null) { return false; } if (this == o) { return true; } if (o instanceof AspectRatio) { AspectRatio ratio = (AspectRatio) o; return mX == ratio.mX && mY == ratio.mY; } return false; } @Override public String toString() { return mX + ":" + mY; } public float toFloat() { return (float) mX / mY; } @Override public int hashCode() { // assuming most sizes are <2^16, doing a rotate will give us perfect hashing return mY ^ ((mX << (Integer.SIZE / 2)) | (mX >>> (Integer.SIZE / 2))); } @Override public int compareTo(@NonNull AspectRatio another) { if (equals(another)) { return 0; } else if (toFloat() - another.toFloat() > 0) { return 1; } return -1; } /** * @return The inverse of this {@link AspectRatio}. */ public AspectRatio inverse() { //noinspection SuspiciousNameCombination return AspectRatio.of(mY, mX); } private static int gcd(int a, int b) { while (b != 0) { int c = b; b = a % b; a = c; } return a; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mX); dest.writeInt(mY); } public static final Parcelable.Creator<AspectRatio> CREATOR = new Parcelable.Creator<AspectRatio>() { @Override public AspectRatio createFromParcel(Parcel source) { int x = source.readInt(); int y = source.readInt(); return AspectRatio.of(x, y); } @Override public AspectRatio[] newArray(int size) { return new AspectRatio[size]; } }; } 複製代碼
/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.focustech.xyz.baselibrary.camera; import android.support.annotation.NonNull; /** * 註釋:尺寸對象 * 時間:2019/3/4 0004 11:14 * 做者:郭翰林 */ public class Size implements Comparable<Size> { private final int mWidth; private final int mHeight; /** * Create a new immutable Size instance. * * @param width The width of the size, in pixels * @param height The height of the size, in pixels */ public Size(int width, int height) { mWidth = width; mHeight = height; } public int getWidth() { return mWidth; } public int getHeight() { return mHeight; } @Override public boolean equals(Object o) { if (o == null) { return false; } if (this == o) { return true; } if (o instanceof Size) { Size size = (Size) o; return mWidth == size.mWidth && mHeight == size.mHeight; } return false; } @Override public String toString() { return mWidth + "x" + mHeight; } @Override public int hashCode() { // assuming most sizes are <2^16, doing a rotate will give us perfect hashing return mHeight ^ ((mWidth << (Integer.SIZE / 2)) | (mWidth >>> (Integer.SIZE / 2))); } @Override public int compareTo(@NonNull Size another) { return mWidth * mHeight - another.mWidth * another.mHeight; } } 複製代碼
package com.focustech.xyz.baselibrary.camera; import android.support.v4.util.ArrayMap; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; /** * @author 郭翰林 * @date 2019/3/4 0004 11:13 * 註釋:尺寸集合 */ public class SizeMap { private final ArrayMap<AspectRatio, SortedSet<Size>> mRatios = new ArrayMap<>(); /** * Add a new {@link Size} to this collection. * * @param size The size to add. * @return {@code true} if it is added, {@code false} if it already exists and is not added. */ public boolean add(Size size) { for (AspectRatio ratio : mRatios.keySet()) { if (ratio.matches(size)) { final SortedSet<Size> sizes = mRatios.get(ratio); if (sizes.contains(size)) { return false; } else { sizes.add(size); return true; } } } // None of the existing ratio matches the provided size; add a new key SortedSet<Size> sizes = new TreeSet<>(); sizes.add(size); mRatios.put(AspectRatio.of(size.getWidth(), size.getHeight()), sizes); return true; } /** * Removes the specified aspect ratio and all sizes associated with it. * * @param ratio The aspect ratio to be removed. */ public void remove(AspectRatio ratio) { mRatios.remove(ratio); } Set<AspectRatio> ratios() { return mRatios.keySet(); } SortedSet<Size> sizes(AspectRatio ratio) { return mRatios.get(ratio); } void clear() { mRatios.clear(); } boolean isEmpty() { return mRatios.isEmpty(); } } 複製代碼
package com.focustech.xyz.baselibrary.camera; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.hardware.Camera; import android.support.v7.widget.AppCompatImageView; import android.util.AttributeSet; import android.view.WindowManager; import com.focustech.xyz.baselibrary.common.XyzLogger; import java.util.ArrayList; import java.util.List; /** * @author 郭翰林 * @date 2019/3/1 0001 9:21 * 註釋:對焦框 */ public class OverCameraView extends AppCompatImageView { private Context context; //焦點附近設置矩形區域做爲對焦區域 private Rect touchFocusRect; private Paint touchFocusPaint; //是否正在對焦 private boolean isFoucuing; public OverCameraView(Context context) { this(context, null, 0); } public OverCameraView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public OverCameraView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context) { this.context = context; //畫筆設置 touchFocusPaint = new Paint(); touchFocusPaint.setColor(Color.GREEN); touchFocusPaint.setStyle(Paint.Style.STROKE); touchFocusPaint.setStrokeWidth(3); } public boolean isFoucuing() { return isFoucuing; } public void setFoucuing(boolean foucuing) { isFoucuing = foucuing; } /** * 註釋:對焦並繪製對焦矩形框 * 時間:2019/3/1 0001 9:28 * 做者:郭翰林 * * @param camera * @param autoFocusCallback * @param x * @param y */ public void setTouchFoucusRect(Camera camera, Camera.AutoFocusCallback autoFocusCallback, float x, float y) { //以焦點爲中心,寬度爲200的矩形框 touchFocusRect = new Rect((int) (x - 100), (int) (y - 100), (int) (x + 100), (int) (y + 100)); //對焦光感區域 int left = touchFocusRect.left * 2000 / getWindowWidth(context) - 1000; int top = touchFocusRect.top * 2000 / getWindowHeight(context) - 1000; int right = touchFocusRect.right * 2000 / getWindowWidth(context) - 1000; int bottom = touchFocusRect.bottom * 2000 / getWindowHeight(context) - 1000; // 若是超出了(-1000,1000)到(1000, 1000)的範圍,則會致使相機崩潰 left = left < -1000 ? -1000 : left; top = top < -1000 ? -1000 : top; right = right > 1000 ? 1000 : right; bottom = bottom > 1000 ? 1000 : bottom; final Rect targetFocusRect = new Rect(left, top, right, bottom); //對焦 doTouchFocus(camera, autoFocusCallback, targetFocusRect); //刷新界面,調用onDraw(Canvas canvas)函數繪製矩形框 postInvalidate(); } /** * 註釋:設置camera參數,並完成對焦 * 時間:2019/3/1 0001 9:27 * 做者:郭翰林 * * @param camera * @param autoFocusCallback * @param tfocusRect */ public void doTouchFocus(Camera camera, Camera.AutoFocusCallback autoFocusCallback, final Rect tfocusRect) { if (camera == null || isFoucuing) { return; } try { final List<Camera.Area> focusList = new ArrayList<>(); Camera.Area focusArea = new Camera.Area(tfocusRect, 1000); focusList.add(focusArea); Camera.Parameters para = camera.getParameters(); para.setFocusAreas(focusList); para.setMeteringAreas(focusList); para.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); camera.cancelAutoFocus(); camera.setParameters(para); camera.autoFocus(autoFocusCallback); isFoucuing = true; } catch (Exception e) { XyzLogger.e("設置相機參數異常", e.getMessage()); } } /** * 註釋:對焦完成後,清除對焦矩形框 * 時間:2019/3/1 0001 9:28 * 做者:郭翰林 */ public void disDrawTouchFocusRect() { //將對焦區域設置爲null,刷新界面後對焦框消失 touchFocusRect = null; //刷新界面,調用onDraw(Canvas canvas)函數 postInvalidate(); } @Override protected void onDraw(Canvas canvas) { //在畫布上繪圖,postInvalidate()後自動調用 drawTouchFocusRect(canvas); super.onDraw(canvas); } /** * 獲取屏幕高度 */ @SuppressWarnings("deprecation") public static int getWindowHeight(Context cxt) { WindowManager wm = (WindowManager) cxt .getSystemService(Context.WINDOW_SERVICE); return wm.getDefaultDisplay().getHeight(); } /** * 獲取屏幕寬度 */ @SuppressWarnings("deprecation") public static int getWindowWidth(Context cxt) { WindowManager wm = (WindowManager) cxt .getSystemService(Context.WINDOW_SERVICE); return wm.getDefaultDisplay().getWidth(); } private void drawTouchFocusRect(Canvas canvas) { if (null != touchFocusRect) { //根據對焦區域targetFocusRect,繪製本身想要的對焦框樣式,本文在矩形四個角取L形狀 //左下角 canvas.drawRect(touchFocusRect.left - 2, touchFocusRect.bottom, touchFocusRect.left + 20, touchFocusRect.bottom + 2, touchFocusPaint); canvas.drawRect(touchFocusRect.left - 2, touchFocusRect.bottom - 20, touchFocusRect.left, touchFocusRect.bottom, touchFocusPaint); //左上角 canvas.drawRect(touchFocusRect.left - 2, touchFocusRect.top - 2, touchFocusRect.left + 20, touchFocusRect.top, touchFocusPaint); canvas.drawRect(touchFocusRect.left - 2, touchFocusRect.top, touchFocusRect.left, touchFocusRect.top + 20, touchFocusPaint); //右上角 canvas.drawRect(touchFocusRect.right - 20, touchFocusRect.top - 2, touchFocusRect.right + 2, touchFocusRect.top, touchFocusPaint); canvas.drawRect(touchFocusRect.right, touchFocusRect.top, touchFocusRect.right + 2, touchFocusRect.top + 20, touchFocusPaint); //右下角 canvas.drawRect(touchFocusRect.right - 20, touchFocusRect.bottom, touchFocusRect.right + 2, touchFocusRect.bottom + 2, touchFocusPaint); canvas.drawRect(touchFocusRect.right, touchFocusRect.bottom - 20, touchFocusRect.right + 2, touchFocusRect.bottom, touchFocusPaint); } } } 複製代碼
@Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { if (!isFoucing) { float x = event.getX(); float y = event.getY(); isFoucing = true; if (mCamera != null && !isTakePhoto) { mOverCameraView.setTouchFoucusRect(mCamera, autoFocusCallback, x, y); } mRunnable = () -> { ToastUtil.showToast(this, "自動聚焦超時,請調整合適的位置拍攝!"); isFoucing = false; mOverCameraView.setFoucuing(false); mOverCameraView.disDrawTouchFocusRect(); }; //設置聚焦超時 mHandler.postDelayed(mRunnable, 3000); } } return super.onTouchEvent(event); } 複製代碼
/** * 註釋:自動對焦回調 * 時間:2019/3/1 0001 10:02 * 做者:郭翰林 */ private Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() { @Override public void onAutoFocus(boolean success, Camera camera) { isFoucing = false; mOverCameraView.setFoucuing(false); mOverCameraView.disDrawTouchFocusRect(); //中止聚焦超時回調 mHandler.removeCallbacks(mRunnable); } }; 複製代碼
package com.focustech.xyz.baselibrary.camera; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.hardware.Camera; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.view.MotionEvent; import android.view.View; import android.widget.Button; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.RelativeLayout; import com.bumptech.glide.Glide; import com.focustech.xyz.baselibrary.R; import com.focustech.xyz.baselibrary.utils.PermissionUtils; import com.focustech.xyz.baselibrary.utils.ToastUtil; import com.newland.springdialog.AnimSpring; import com.yanzhenjie.permission.AndPermission; import com.yanzhenjie.permission.Permission; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; /** * @author 郭翰林 * @date 2019/2/28 0028 16:23 * 註釋:Android自定義相機 */ public class CameraActivity extends AppCompatActivity implements View.OnClickListener { public static final String KEY_IMAGE_PATH = "imagePath"; /** * 相機預覽 */ private FrameLayout mPreviewLayout; /** * 拍攝按鈕視圖 */ private RelativeLayout mPhotoLayout; /** * 肯定按鈕視圖 */ private RelativeLayout mConfirmLayout; /** * 閃光燈 */ private ImageView mFlashButton; /** * 拍照按鈕 */ private ImageView mPhotoButton; /** * 取消保存按鈕 */ private ImageView mCancleSaveButton; /** * 保存按鈕 */ private ImageView mSaveButton; /** * 聚焦視圖 */ private OverCameraView mOverCameraView; /** * 相機類 */ private Camera mCamera; /** * Handle */ private Handler mHandler = new Handler(); private Runnable mRunnable; /** * 取消按鈕 */ private Button mCancleButton; /** * 是否開啓閃光燈 */ private boolean isFlashing; /** * 圖片流暫存 */ private byte[] imageData; /** * 拍照標記 */ private boolean isTakePhoto; /** * 是否正在聚焦 */ private boolean isFoucing; /** * 蒙版類型 */ private MongolianLayerType mMongolianLayerType; /** * 蒙版圖片 */ private ImageView mMaskImage; /** * 護照出入境蒙版 */ private ImageView mPassportEntryAndExitImage; /** * 提示文案容器 */ private RelativeLayout rlCameraTip; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_camre_layout); mMongolianLayerType = (MongolianLayerType) getIntent().getSerializableExtra("MongolianLayerType"); PermissionUtils.applicationPermissions(this, new PermissionUtils.PermissionListener() { @Override public void onSuccess(Context context) { initView(); setOnclickListener(); } @Override public void onFailed(Context context) { if (AndPermission.hasAlwaysDeniedPermission(context, Permission.Group.CAMERA) && AndPermission.hasAlwaysDeniedPermission(context, Permission.Group.STORAGE)) { AndPermission.with(context).runtime().setting().start(); } ToastUtil.showToast(context, context.getString(com.focustech.xyz.baselibrary.R.string.permission_camra_storage)); finish(); } }, Permission.Group.STORAGE, Permission.Group.CAMERA); } /** * 啓動拍照界面 * * @param activity * @param requestCode * @param type */ public static void startMe(Activity activity, int requestCode, MongolianLayerType type) { Intent intent = new Intent(activity, CameraActivity.class); intent.putExtra("MongolianLayerType", type); activity.startActivityForResult(intent, requestCode); } /** * 註釋:獲取蒙版圖片 * 時間:2019/3/4 0004 17:19 * 做者:郭翰林 * * @return */ private int getMaskImage() { if (mMongolianLayerType == MongolianLayerType.BANK_CARD) { return R.mipmap.bank_card; } else if (mMongolianLayerType == MongolianLayerType.HK_MACAO_TAIWAN_PASSES_POSITIVE) { return R.mipmap.hk_macao_taiwan_passes_positive; } else if (mMongolianLayerType == MongolianLayerType.HK_MACAO_TAIWAN_PASSES_NEGATIVE) { return R.mipmap.hk_macao_taiwan_passes_negative; } else if (mMongolianLayerType == MongolianLayerType.IDCARD_POSITIVE) { return R.mipmap.idcard_positive; } else if (mMongolianLayerType == MongolianLayerType.IDCARD_NEGATIVE) { return R.mipmap.idcard_negative; } else if (mMongolianLayerType == MongolianLayerType.PASSPORT_PERSON_INFO) { return R.mipmap.passport_person_info; } return 0; } /** * 註釋:設置監聽事件 * 時間:2019/3/1 0001 11:13 * 做者:郭翰林 */ private void setOnclickListener() { mCancleButton.setOnClickListener(this); mCancleSaveButton.setOnClickListener(this); mFlashButton.setOnClickListener(this); mPhotoButton.setOnClickListener(this); mSaveButton.setOnClickListener(this); } @Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { if (!isFoucing) { float x = event.getX(); float y = event.getY(); isFoucing = true; if (mCamera != null && !isTakePhoto) { mOverCameraView.setTouchFoucusRect(mCamera, autoFocusCallback, x, y); } mRunnable = () -> { ToastUtil.showToast(this, "自動聚焦超時,請調整合適的位置拍攝!"); isFoucing = false; mOverCameraView.setFoucuing(false); mOverCameraView.disDrawTouchFocusRect(); }; //設置聚焦超時 mHandler.postDelayed(mRunnable, 3000); } } return super.onTouchEvent(event); } /** * 註釋:自動對焦回調 * 時間:2019/3/1 0001 10:02 * 做者:郭翰林 */ private Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() { @Override public void onAutoFocus(boolean success, Camera camera) { isFoucing = false; mOverCameraView.setFoucuing(false); mOverCameraView.disDrawTouchFocusRect(); //中止聚焦超時回調 mHandler.removeCallbacks(mRunnable); } }; /** * 註釋:拍照並保存圖片到相冊 * 時間:2019/3/1 0001 15:37 * 做者:郭翰林 */ private void takePhoto() { isTakePhoto = true; //調用相機拍照 mCamera.takePicture(null, null, null, (data, camera1) -> { //視圖動畫 mPhotoLayout.setVisibility(View.GONE); mConfirmLayout.setVisibility(View.VISIBLE); AnimSpring.getInstance(mConfirmLayout).startRotateAnim(120, 360); imageData = data; //中止預覽 mCamera.stopPreview(); }); } /** * 註釋:切換閃光燈 * 時間:2019/3/1 0001 15:40 * 做者:郭翰林 */ private void switchFlash() { isFlashing = !isFlashing; mFlashButton.setImageResource(isFlashing ? R.mipmap.flash_open : R.mipmap.flash_close); AnimSpring.getInstance(mFlashButton).startRotateAnim(120, 360); try { Camera.Parameters parameters = mCamera.getParameters(); parameters.setFlashMode(isFlashing ? Camera.Parameters.FLASH_MODE_TORCH : Camera.Parameters.FLASH_MODE_OFF); mCamera.setParameters(parameters); } catch (Exception e) { ToastUtil.showToast(this, "該設備不支持閃光燈"); } } /** * 註釋:取消保存 * 時間:2019/3/1 0001 16:31 * 做者:郭翰林 */ private void cancleSavePhoto() { mPhotoLayout.setVisibility(View.VISIBLE); mConfirmLayout.setVisibility(View.GONE); AnimSpring.getInstance(mPhotoLayout).startRotateAnim(120, 360); //開始預覽 mCamera.startPreview(); imageData = null; isTakePhoto = false; } /** * 解析拍出照片的路徑 * * @param data * @return */ public static String parseResult(Intent data) { return data.getStringExtra(KEY_IMAGE_PATH); } @Override public void onClick(View v) { int id = v.getId(); if (id == R.id.cancle_button) { finish(); } else if (id == R.id.take_photo_button) { if (!isTakePhoto) { takePhoto(); } } else if (id == R.id.flash_button) { switchFlash(); } else if (id == R.id.save_button) { savePhoto(); } else if (id == R.id.cancle_save_button) { cancleSavePhoto(); } } /** * 註釋:蒙版類型 * 時間:2019/2/28 0028 16:26 * 做者:郭翰林 */ public enum MongolianLayerType { /** * 護照我的信息 */ PASSPORT_PERSON_INFO, /** * 護照出入境 */ PASSPORT_ENTRY_AND_EXIT, /** * 身份證正面 */ IDCARD_POSITIVE, /** * 身份證反面 */ IDCARD_NEGATIVE, /** * 港澳通行證正面 */ HK_MACAO_TAIWAN_PASSES_POSITIVE, /** * 港澳通行證反面 */ HK_MACAO_TAIWAN_PASSES_NEGATIVE, /** * 銀行卡 */ BANK_CARD } /** * 註釋:初始化視圖 * 時間:2019/3/1 0001 11:12 * 做者:郭翰林 */ private void initView() { mCancleButton = findViewById(R.id.cancle_button); mPreviewLayout = findViewById(R.id.camera_preview_layout); mPhotoLayout = findViewById(R.id.ll_photo_layout); mConfirmLayout = findViewById(R.id.ll_confirm_layout); mPhotoButton = findViewById(R.id.take_photo_button); mCancleSaveButton = findViewById(R.id.cancle_save_button); mSaveButton = findViewById(R.id.save_button); mFlashButton = findViewById(R.id.flash_button); mMaskImage = findViewById(R.id.mask_img); rlCameraTip = findViewById(R.id.camera_tip); mPassportEntryAndExitImage = findViewById(R.id.passport_entry_and_exit_img); mCamera = Camera.open(); CameraPreview preview = new CameraPreview(this, mCamera); mOverCameraView = new OverCameraView(this); mPreviewLayout.addView(preview); mPreviewLayout.addView(mOverCameraView); if (mMongolianLayerType == null) { mMaskImage.setVisibility(View.GONE); rlCameraTip.setVisibility(View.GONE); return; } //設置蒙版,護照出入境蒙版特殊處理 if (mMongolianLayerType != MongolianLayerType.PASSPORT_ENTRY_AND_EXIT) { Glide.with(this).load(getMaskImage()).into(mMaskImage); } else { mMaskImage.setVisibility(View.GONE); mPassportEntryAndExitImage.setVisibility(View.VISIBLE); } } /** * 註釋:保持圖片 * 時間:2019/3/1 0001 16:32 * 做者:郭翰林 */ private void savePhoto() { FileOutputStream fos = null; String cameraPath = Environment.getExternalStorageDirectory().getPath() + File.separator + "DCIM" + File.separator + "Camera"; //相冊文件夾 File cameraFolder = new File(cameraPath); if (!cameraFolder.exists()) { cameraFolder.mkdirs(); } //保存的圖片文件 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); String imagePath = cameraFolder.getAbsolutePath() + File.separator + "IMG_" + simpleDateFormat.format(new Date()) + ".jpg"; File imageFile = new File(imagePath); try { fos = new FileOutputStream(imageFile); fos.write(imageData); } catch (Exception e) { e.printStackTrace(); } finally { if (fos != null) { try { fos.close(); Intent intent = new Intent(); intent.putExtra(KEY_IMAGE_PATH, imagePath); setResult(RESULT_OK, intent); } catch (IOException e) { setResult(RESULT_FIRST_USER); e.printStackTrace(); } } finish(); } } } 複製代碼
Activity自定義佈局 R.layout.activity_camre_layout
git
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <!--相機預覽視圖--> <FrameLayout android:id="@+id/camera_preview_layout" android:layout_width="match_parent" android:layout_height="match_parent"></FrameLayout> <!--蒙版區域--> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" android:orientation="vertical"> <!--提示文字--> <RelativeLayout android:id="@+id/camera_tip" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:background="@drawable/tip_layout_shape" android:gravity="center" android:text="請參照輔助線進行拍攝" android:textColor="#fff" android:textSize="12sp" /> </RelativeLayout> <!--蒙版圖片--> <ImageView android:id="@+id/mask_img" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="20dp" android:layout_marginTop="25dp" android:layout_marginRight="20dp" android:scaleType="fitCenter" android:src="@mipmap/hk_macao_taiwan_passes_positive" android:visibility="visible" /> <ImageView android:id="@+id/passport_entry_and_exit_img" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="54dp" android:layout_marginTop="20dp" android:layout_marginRight="54dp" android:layout_marginBottom="50dp" android:scaleType="fitCenter" android:src="@mipmap/passport_entry_and_exit" android:visibility="gone" /> </LinearLayout> <!--頂部視圖--> <LinearLayout android:layout_width="match_parent" android:layout_height="80dp" android:gravity="bottom" android:orientation="horizontal" android:padding="15dp"> <ImageView android:id="@+id/flash_button" android:layout_width="25dp" android:layout_height="25dp" android:src="@mipmap/flash_close" /> </LinearLayout> <!--拍照完成肯定視圖--> <RelativeLayout android:id="@+id/ll_confirm_layout" android:layout_width="match_parent" android:layout_height="150dp" android:layout_alignParentBottom="true" android:padding="50dp" android:visibility="gone"> <ImageView android:id="@+id/cancle_save_button" android:layout_width="50dp" android:layout_height="50dp" android:layout_centerVertical="true" android:src="@mipmap/failed" /> <ImageView android:id="@+id/save_button" android:layout_width="50dp" android:layout_height="50dp" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:src="@mipmap/success" /> </RelativeLayout> <!--底部拍照按鈕--> <RelativeLayout android:id="@+id/ll_photo_layout" android:layout_width="match_parent" android:layout_height="150dp" android:layout_alignParentBottom="true" android:padding="15dp" android:visibility="visible"> <Button android:id="@+id/cancle_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:background="@null" android:text="取消" android:textColor="#fff" android:textSize="14sp" /> <ImageView android:id="@+id/take_photo_button" android:layout_width="80dp" android:layout_height="80dp" android:layout_centerInParent="true" android:src="@mipmap/take_button" /> </RelativeLayout> </RelativeLayout> 複製代碼