前言:
證件照相機 |
有一種相機,叫做證件相機!
這邊提供客製化相機的做法。
在網路上找資料很多,但一直沒有確切可以用的code。
不是不完整,不然就是blog寫的當下android版本太低已不適用現今環境。
發現網路還是有一篇能用的。
https://blog.csdn.net/LHBTM/article/details/55505668
這篇不完整,但都把釣竿給你了。
如果你整篇複製貼上,你會發現拍完照後,並不會返回MainActivity,就卡在拍照頁。
仔細看一下Code,其實說明都很完整!
合併一下官方文擋獲取儲存File位置的方式,來修改這篇。
先佈局好畫面:
acitvity_main.xml |
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.hello.kaiser.customcamera.MainActivity"> <ImageView android:id="@+id/show_image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:src="@drawable/ic_launcher_background" android:text="Hello World!" /> <Button android:id="@+id/btn_take_pic" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/show_image" android:layout_centerHorizontal="true" android:text="拍照" /> </RelativeLayout>
然後MainActivity.java
public class MainActivity extends AppCompatActivity { private Button mBtnPic; private ImageView mShowImage; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); } private void initView() { mBtnPic = (Button) findViewById(R.id.btn_take_pic); mShowImage = (ImageView) findViewById(R.id.show_image); } }
沒啥功能。
接下來開始大家都很會的crtl+c 和 crtl+v吧
https://blog.csdn.net/LHBTM/article/details/55505668
把上面這篇全部搬到你個人的專案內。
會動到的地方 |
會動到的地方,大概會長這樣
比較需要注意的,就是xml的View
記得換成自己包名的位置
記得更換View的名稱 |
記得在MainActivity.java製作畫面轉移事件
mBtnPic.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startActivity(new Intent(MainActivity.this, TakePicActivity.class)); } });
此時,大致上完成了
開啟後你會發現,事情沒你想得順利XD
至少畫面出來了,是吧!
這像我上一篇講得不太一樣,上一篇甚至連權限都不用。
這邊因為是客製化,就必須加權限進來。
此時要加入三個權限:
寫入相機權限、寫入SDCard權限、和相機權限
<!--寫入手機權限--> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!--相機權限--> <uses-permission android:name="android.permission.CAMERA" />
權限的部分就請參考:
http://nikeru8.blogspot.tw/2017/04/android-permission.html
權限加入 MainActivity.java
import android.Manifest; import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import android.widget.ImageView; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private Activity activity; public static final int PermissionCode = 1000; public static final int GetPhotoCode = 1001; private Button mBtnPic; private ImageView mShowImage; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); activity = this; initView(); initListener(); } private void initView() { mBtnPic = (Button) findViewById(R.id.btn_take_pic); mShowImage = (ImageView) findViewById(R.id.show_image); } private void initListener() { mBtnPic.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //檢查是否取得權限 final int permissionCheck = ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA); //沒有權限時 if (permissionCheck != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA, Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, Manifest.permission.WRITE_EXTERNAL_STORAGE}, PermissionCode); } else { //已獲得權限 Toast.makeText(activity, "已經拿到權限囉!", Toast.LENGTH_SHORT).show(); startActivityForResult(new Intent(MainActivity.this, TakePicActivity.class), GetPhotoCode); } } }); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == PermissionCode) { //假如允許了 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { //do something Toast.makeText(this, "感謝賜予權限!", Toast.LENGTH_SHORT).show(); startActivityForResult(new Intent(MainActivity.this, TakePicActivity.class), GetPhotoCode); } //假如拒絕了 else { //do something Toast.makeText(this, "CAMERA權限FAIL", Toast.LENGTH_SHORT).show(); } } super.onRequestPermissionsResult(requestCode, permissions, grantResults); } }
此時,你按相機,已經會出現畫面了吧!
但按下去的時候,他會頓一下(已經拍照ing)然後...就沒有然後了。
為什麼呢,其實已經有拍照的效果,只是沒有把畫面帶回MainActivity內而已
此時就要往takePicture這方法追到底發生什麼事情
仔細看一下作者寫的 CameraSurfaceView 會發現
public void takePicture(){ //设置参数,并拍照 setCameraParams(mCamera, mScreenWidth, mScreenHeight); // 当调用camera.takePiture方法后,camera关闭了预览,这时需要调用startPreview()来重新开启预览 mCamera.takePicture(null,null, jpeg); }
作者在這邊只拍了照,卻沒有結束掉finish();
這邊可以自己加入finish();
我們這邊設計,你按拍照後...
一、檢查相機權限。
二、帶畫面。(創建相片存放位置FilePath並且把filePath帶到拍照頁面的activity,拍完照後存入到filePath的位置)
上面已經增加了權限。
這邊我們來帶畫面吧
在MainActivity先把要存照片的地方位置創建好。
//創造檔案名稱、和存擋路徑 String imageFilePath; private File createImageFile() throws IOException { String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); String imageFileName = "IMG_" + timeStamp + "_"; File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); File image = File.createTempFile( imageFileName, /* prefix */ ".jpg", /* suffix */ storageDir /* directory */ ); imageFilePath = image.getAbsolutePath(); return image; }
此時,我們就獲取到創建的File。
整理一下MainActivity
把權限和帶畫面的Code整理在同一個方法內
MainActivity
public class MainActivity extends AppCompatActivity { private Activity activity; public static final int PermissionCode = 1000; public static final int GetPhotoCode = 1001; private Button mBtnPic; private ImageView mShowImage; String imageFilePath; private boolean isCameraPermission = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); activity = this; initView(); initListener(); } private void initView() { mBtnPic = (Button) findViewById(R.id.btn_take_pic); mShowImage = (ImageView) findViewById(R.id.show_image); } private void initListener() { mBtnPic.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { openCamera(); } }); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == PermissionCode) { //假如允許了 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { isCameraPermission = true; //do something Toast.makeText(this, "感謝賜予權限!", Toast.LENGTH_SHORT).show(); startActivityForResult(new Intent(MainActivity.this, TakePicActivity.class), GetPhotoCode); } //假如拒絕了 else { isCameraPermission = false; //do something Toast.makeText(this, "CAMERA權限FAIL,請給權限", Toast.LENGTH_SHORT).show(); } } super.onRequestPermissionsResult(requestCode, permissions, grantResults); } //創造檔案名稱、和存擋路徑 private File createImageFile() throws IOException { String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); String imageFileName = "IMG_" + timeStamp + "_"; File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); File image = File.createTempFile( imageFileName, /* prefix */ ".jpg", /* suffix */ storageDir /* directory */ ); imageFilePath = image.getAbsolutePath(); return image; } private void openCamera() { //已獲得權限 if (isCameraPermission) { File photoFile = null; try { photoFile = createImageFile(); } catch (IOException e) { Log.d("checkpoint", "error for createImageFile 創建路徑失敗"); } //成功創建路徑的話 if (photoFile != null) { Intent intent = new Intent(MainActivity.this, TakePicActivity.class); Bundle bundle = new Bundle(); bundle.putString("url", photoFile.getAbsolutePath()); intent.putExtras(bundle); startActivityForResult(intent, GetPhotoCode); } } //沒有獲得權限 else { getPermission(); } } private void getPermission() { //檢查是否取得權限 final int permissionCheck = ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA); //沒有權限時 if (permissionCheck != PackageManager.PERMISSION_GRANTED) { isCameraPermission = false; ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA, Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, Manifest.permission.WRITE_EXTERNAL_STORAGE}, PermissionCode); } else { //已獲得權限 isCameraPermission = true; openCamera(); } } }
再來就會動到原作者的Code了
CameraSurfaceView
import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Matrix; import android.hardware.Camera; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.WindowManager; import android.widget.FrameLayout; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.List; /** * Created by Administrator on 2017/2/15 0015.//自定义相机 */ public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Camera.AutoFocusCallback { private static final String TAG = "CameraSurfaceView"; private Context mContext; private SurfaceHolder holder; private Camera mCamera; private int mScreenWidth; private int mScreenHeight; private CameraTopRectView topView; //更動 private String filePath; private Activity activity; public CameraSurfaceView(Context context) { this(context, null); } public CameraSurfaceView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CameraSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mContext = context; getScreenMetrix(context); topView = new CameraTopRectView(context, attrs); initView(); } //拿到手机屏幕大小 private void getScreenMetrix(Context context) { WindowManager WM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); WM.getDefaultDisplay().getMetrics(outMetrics); mScreenWidth = outMetrics.widthPixels; mScreenHeight = outMetrics.heightPixels; } private void initView() { holder = getHolder();//获得surfaceHolder引用 holder.addCallback(this); // holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);//设置类型 } @Override public void surfaceCreated(SurfaceHolder holder) { Log.i(TAG, "surfaceCreated"); if (mCamera == null) { mCamera = Camera.open();//开启相机 try { mCamera.setPreviewDisplay(holder);//摄像头画面显示在Surface上 } catch (IOException e) { e.printStackTrace(); } } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Log.i(TAG, "surfaceChanged"); setCameraParams(mCamera, mScreenWidth, mScreenHeight); mCamera.startPreview(); // mCamera.takePicture(null, null, jpeg); } @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.i(TAG, "surfaceDestroyed"); mCamera.stopPreview();//停止预览 mCamera.release();//释放相机资源 mCamera = null; holder = null; } @Override public void onAutoFocus(boolean success, Camera Camera) { if (success) { Log.i(TAG, "onAutoFocus success=" + success); System.out.println(success); } } private void setCameraParams(Camera camera, int width, int height) { Log.i(TAG, "setCameraParams width=" + width + " height=" + height); Camera.Parameters parameters = mCamera.getParameters(); // 获取摄像头支持的PictureSize列表 ListpictureSizeList = parameters.getSupportedPictureSizes(); for (Camera.Size size : pictureSizeList) { Log.i(TAG, "pictureSizeList size.width=" + size.width + " size.height=" + size.height); } /**从列表中选取合适的分辨率*/ Camera.Size picSize = getProperSize(pictureSizeList, ((float) height / width)); if (null == picSize) { Log.i(TAG, "null == picSize"); picSize = parameters.getPictureSize(); } Log.i(TAG, "picSize.width=" + picSize.width + " picSize.height=" + picSize.height); // 根据选出的PictureSize重新设置SurfaceView大小 float w = picSize.width; float h = picSize.height; parameters.setPictureSize(picSize.width, picSize.height); this.setLayoutParams(new FrameLayout.LayoutParams((int) (height * (h / w)), height)); // 获取摄像头支持的PreviewSize列表 List previewSizeList = parameters.getSupportedPreviewSizes(); for (Camera.Size size : previewSizeList) { Log.i(TAG, "previewSizeList size.width=" + size.width + " size.height=" + size.height); } Camera.Size preSize = getProperSize(previewSizeList, ((float) height) / width); if (null != preSize) { Log.i(TAG, "preSize.width=" + preSize.width + " preSize.height=" + preSize.height); parameters.setPreviewSize(preSize.width, preSize.height); } parameters.setJpegQuality(100); // 设置照片质量 if (parameters.getSupportedFocusModes().contains(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { parameters.setFocusMode(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);// 连续对焦模式 } mCamera.cancelAutoFocus();//自动对焦。 mCamera.setDisplayOrientation(90);// 设置PreviewDisplay的方向,效果就是将捕获的画面旋转多少度显示 mCamera.setParameters(parameters); } /** * 从列表中选取合适的分辨率 * 默认w:h = 4:3 * 注意:这里的w对应屏幕的height * h对应屏幕的width */ private Camera.Size getProperSize(List pictureSizeList, float screenRatio) { Log.i(TAG, "screenRatio=" + screenRatio); Camera.Size result = null; for (Camera.Size size : pictureSizeList) { float currentRatio = ((float) size.width) / size.height; if (currentRatio - screenRatio == 0) { result = size; break; } } if (null == result) { for (Camera.Size size : pictureSizeList) { float curRatio = ((float) size.width) / size.height; if (curRatio == 4f / 3) {// 默认w:h = 4:3 result = size; break; } } } return result; } // 拍照瞬间调用 private Camera.ShutterCallback shutter = new Camera.ShutterCallback() { @Override public void onShutter() { Log.i(TAG, "shutter"); System.out.println("执行了吗+1"); } }; // 获得没有压缩过的图片数据 private Camera.PictureCallback raw = new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera Camera) { Log.i(TAG, "raw"); System.out.println("执行了吗+2"); } }; //创建jpeg图片回调数据对象 private Camera.PictureCallback jpeg = new Camera.PictureCallback() { private Bitmap bitmap; @Override public void onPictureTaken(byte[] data, Camera Camera) { topView.draw(new Canvas()); BufferedOutputStream bos = null; Bitmap bm = null; if (data != null) { } try { // 获得图片 bm = BitmapFactory.decodeByteArray(data, 0, data.length); Log.d("checkpoint", "checkpoint - " + bm); // if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { // String filePath = "/sdcard/dyk" + System.currentTimeMillis() + ".JPEG";//照片保存路径 // //图片存储前旋转 Matrix m = new Matrix(); int height = bm.getHeight(); int width = bm.getWidth(); m.setRotate(90); //旋转后的图片 bitmap = Bitmap.createBitmap(bm, 0, 0, width, height, m, true); System.out.println("执行了吗+3"); File file = new File(filePath); if (!file.exists()) { file.createNewFile(); } bos = new BufferedOutputStream(new FileOutputStream(file)); Bitmap sizeBitmap = Bitmap.createScaledBitmap(bitmap, topView.getViewWidth(), topView.getViewHeight(), true); bm = Bitmap.createBitmap(sizeBitmap, topView.getRectLeft(), topView.getRectTop(), topView.getRectRight() - topView.getRectLeft(), topView.getRectBottom() - topView.getRectTop());// 截取 bm.compress(Bitmap.CompressFormat.JPEG, 100, bos);//将图片压缩到流中 } catch (Exception e) { e.printStackTrace(); } finally { try { bos.flush();//输出 bos.close();//关闭 bm.recycle();// 回收bitmap空间 mCamera.stopPreview();// 关闭预览 activity.setResult(Activity.RESULT_OK); activity.finish(); // mCamera.startPreview();// 开启预览 } catch (IOException e) { e.printStackTrace(); } } } }; public void takePicture(Activity activity, String filePath) { this.filePath = filePath; this.activity = activity; //设置参数,并拍照 setCameraParams(mCamera, mScreenWidth, mScreenHeight); // 当调用camera.takePiture方法后,camera关闭了预览,这时需要调用startPreview()来重新开启预览 mCamera.takePicture(null, null, jpeg); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } }
我主要把activity和filePath帶入這邊的takePicture方法。
註解掉做者獲取filePath的方法,帶入自己的filePath。
並且增加activity關掉的finish()
再回到TakePicActivity,接收在MainActivity創建的filePath。
import android.app.Activity; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.Button; public class TakePicActivity extends AppCompatActivity { private Button button; private CameraSurfaceView mCameraSurfaceView; private Activity activity; String filePath; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); activity = this; getBundleData(); initSet(); initView(); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mCameraSurfaceView.takePicture(activity, filePath); } }); } private void initSet() { requestWindowFeature(Window.FEATURE_NO_TITLE); // 全屏显示 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.activity_take_pic); } private void initView() { mCameraSurfaceView = (CameraSurfaceView) findViewById(R.id.cameraSurfaceView); button = (Button) findViewById(R.id.takePic); } private void getBundleData() { Bundle bundle = getIntent().getExtras(); if (bundle != null) { filePath = bundle.getString("url"); } Log.d("checkpoint", "check filePath - " + filePath); } }
再回到MainActivity做一次整理
多增加onActivityResult 和 自寫的方法setPic
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == GetPhotoCode) { setPic(imageFilePath); } } private void setPic(String mCurrentPhotoPath) { // Get the dimensions of the View int targetW = mShowImage.getWidth(); int targetH = mShowImage.getHeight(); // Get the dimensions of the bitmap BitmapFactory.Options bmOptions = new BitmapFactory.Options(); bmOptions.inJustDecodeBounds = true; BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions); int photoW = bmOptions.outWidth; int photoH = bmOptions.outHeight; // Determine how much to scale down the image int scaleFactor = Math.min(photoW / targetW, photoH / targetH); // Decode the image file into a Bitmap sized to fill the View bmOptions.inJustDecodeBounds = false; bmOptions.inSampleSize = scaleFactor; bmOptions.inPurgeable = true; Bitmap bitmap = BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions); mShowImage.setImageBitmap(bitmap); }
就完成囉!
有仔細看的朋友一定會想知道...
原作者filePath寫得好好的為啥要動?
拍照會延遲,可能照片還沒拍好、圖片都還沒存到路徑TackPicActivity就被關掉了。
所以我才改動filePath的取得方式。
屁話講了那麼多,重點大家就是要程式碼吧 哈哈哈
Demo:
https://github.com/nikeru8/CustomCamera
如果有錯誤或者不懂的地方歡迎提問!
文獻:
https://developer.android.com/training/camera/photobasics.html#TaskScalePhoto
https://blog.csdn.net/LHBTM/article/details/55505668