android自定義相機(帶邊框和按鈕)
阿新 • • 發佈:2018-12-12
前兩個月專案要求不能呼叫系統的相機,那就只能用自定義的了,查了一些資料,自己再研究了一下,自定義的相機還是有點複雜的,佈局和程式碼中都要用到一個重要的SurfaceView。
一、建立佈局,佈局的背景框可以讓美工給出,這裡姑且就是一個藍色的邊框,然後下面有三個按鈕,我里布局檔案activity_custom_camera.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <RelativeLayout android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_weight="1"> <FrameLayout android:id="@+id/layout_camera" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="visible"> <SurfaceView android:id="@+id/surfaceView" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="visible"/> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/view_text_top" android:layout_width="match_parent" android:layout_height="50dp" android:gravity="center" android:text="請將證件對準方框" android:textSize="18dp" android:visibility="visible" android:textColor="@color/blue" android:background="#000000" android:alpha="0.5"/> <LinearLayout android:id="@+id/view_main_content" android:layout_marginTop="50dp" android:layout_above="@+id/layout_button_btn" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"> <View android:id="@+id/view_left" android:layout_width="30dp" android:layout_height="match_parent" android:background="#000000" android:alpha="0.5" android:visibility="visible"/> <!--中心佈局,取景處--> <View android:id="@+id/bg_center_view" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_weight="1" android:layout_gravity="center" android:adjustViewBounds="true" android:scaleType="fitXY" android:background="@drawable/qr_code_bg_take_photo"/> <TextView android:id="@+id/view_right" android:textColor="@color/blue" android:gravity="center" android:layout_width="30dp" android:layout_height="match_parent" android:background="#000000" android:alpha="0.5" android:visibility="visible" /> </LinearLayout> <RelativeLayout android:id="@+id/layout_button_btn" android:layout_width="match_parent" android:layout_height="80dp" android:layout_alignParentBottom="true" android:background="#000000" android:alpha="0.5"> <Button android:id="@+id/btn_cancel" android:layout_marginLeft="30dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/ic_cancel_take" android:layout_alignParentLeft="true" android:layout_centerVertical="true"/> <Button android:id="@+id/btn_take_photo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:background="@drawable/ic_take_photo_large" /> <Button android:id="@+id/btn_finish" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="30dp" android:background="@drawable/ic_finish_take" android:layout_alignParentRight="true" android:layout_centerVertical="true"/> </RelativeLayout> </RelativeLayout> </FrameLayout> </RelativeLayout> </LinearLayout>
效果圖:左邊是取消,右邊的按鈕當點選拍照按鈕後才會出來
二、在Activity中實現SurfaceView和拍照,這裡給出相機的主要程式碼,初始化View就省略了。
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setBaseContentLayoutWithoutTitle(R.layout.activity_custom_camera); //初始化相機 initCamera(); //初始化照片儲存路徑 getFileSavePath(); }
方法initCamera():
SurfaceHolder holder; private void initCamera(){ surfaceview.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { if(!status && camera != null){ camera.autoFocus(autoFocusCallback); } return false; } }); MySurfaceCallback mySurfaceCallback = new MySurfaceCallback(); holder = surfaceview.getHolder(); holder.setKeepScreenOn(true);// 螢幕常亮 holder.addCallback(mySurfaceCallback); holder.lockCanvas(); } class MySurfaceCallback implements SurfaceHolder.Callback { @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { initCameraParamsAndOpen(); } @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) { //當surface的格式或大小發生改變,這個方法就被呼叫,或者View被隱藏 status = false; btnTakePhoto.setVisibility(View.VISIBLE); //拍照按鈕顯示 btnFinish.setVisibility(View.GONE); //拍照完成按鈕隱藏 camera.release(); camera = null; initCameraParamsAndOpen(); //重新初始化相機 } @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { if (camera != null) { camera.stopPreview(); camera.release(); camera = null; } } } private void initCameraParamsAndOpen(){ try { // surfaceview建立之後,就去開啟相機 camera = getCameraInstance(); camera.setPreviewDisplay(holder); camera.setDisplayOrientation(90); updateCameraParameters(); camera.startPreview(); } catch (Exception e) { if(camera != null){ camera.release(); } e.printStackTrace(); } } private void updateCameraParameters() { if (camera != null) { Camera.Parameters mParameters = camera.getParameters(); Camera.Size picSize = mParameters.getPreviewSize(); Camera.Size previewSize = getOptimalPreviewSize(mParameters.getSupportedPreviewSizes(), (double) picSize.width / picSize.height); if (previewSize != null) { mParameters.setPreviewSize(previewSize.width, previewSize.height); } picSize = mParameters.getPictureSize(); Camera.Size pictureSize = getOptimalPictureSize(mParameters.getSupportedPictureSizes(), (double) picSize.width / picSize.height); if (pictureSize != null) { mParameters.setPictureSize(pictureSize.width, pictureSize.height); } mParameters.setRotation(90);//防止儲存的圖片旋轉 camera.setParameters(mParameters); } } public static Camera getCameraInstance(){ Camera c = null; try { c = Camera.open(0); // attempt to get a Camera instance } catch (Exception e){ // Camera is not available (in use or does not exist) } return c; // returns null if camera is unavailable }
/** * 設定的螢幕的比例不是圖片的比例 *@date 建立時間 2017/4/15 *@author *@company *@name:zhongshuiping *@Description 匹配解析度 */ private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, double targetRatio) { if (sizes == null) return null; Camera.Size optimalSize = null; Collections.sort(sizes, new Comparator<Camera.Size>() { @Override public int compare(Camera.Size lhs, Camera.Size rhs) { return new Double(lhs.width).compareTo(new Double(rhs.width)); } }); for (int i=sizes.size()-1;i>=0;i--) { Camera.Size size = sizes.get(i); if ((( Constants.EIGHT_HUNDRED < size.width && size.width < Constants.TWO_THOUSAND) || (Constants.EIGHT_HUNDRED< size.height && size.height < Constants.TWO_THOUSAND)) && ((size.width * 9) == (size.height * 16) )) { optimalSize = size; break; } } return optimalSize; }
/** * 設定的是拍照的圖片的比例 *@date 建立時間 2017/4/15 *@author *@company *@name:zhongshuiping *@Description 匹配解析度 */ private Camera.Size getOptimalPictureSize(List<Camera.Size> sizes, double targetRatio) { if (sizes == null) return null; Camera.Size optimalSize = null; Collections.sort(sizes, new Comparator<Camera.Size>() { @Override public int compare(Camera.Size lhs, Camera.Size rhs) { return new Double(lhs.width).compareTo(new Double(rhs.width)); } }); for (int i=sizes.size()-1;i>=0;i--) { Camera.Size size = sizes.get(i); if (((Constants.NUMBER_ONE_THOUSAN < size.width && size.width < Constants.NUMBER_TWO_THOUSAND) || (Constants.NUMBER_ONE_THOUSAN < size.height && size.height < Constants.NUMBER_TWO_THOUSAND)) && ((size.width * 10) ==(size.height * 16) )) { optimalSize = size; break; } } /**如果沒找到16/9的就選擇最接近的*/ if(optimalSize == null) { double dMin = 100.0; Camera.Size RightSize = null; for (Camera.Size size : sizes) { double fRate = size.width/(float)size.height; double fDistance = Math.abs(fRate - 16.0/10); //找最接近16比9的size; if(fDistance < dMin) { dMin = fDistance; RightSize = size; } } //最接近的值賦給變數optimalSize optimalSize = RightSize; } return optimalSize; }
Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() { @Override public void onAutoFocus(boolean b, Camera camera) {
} } ;
儲存路徑getFileSavePath(), File tempFile為拍照預留的儲存位置檔案,最後拍照完畢將會把照片流寫入該檔案中。
private File mediaStorageDir;
private void getFileSavePath(){
try
{
mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "test");
} catch (Exception e) {
e.printStackTrace();
showShortToastCenter("建立test失敗");
LogUtils.LogError("lenita", "Error in Creating mediaStorageDir: " + mediaStorageDir);
}
if (!mediaStorageDir.exists())
{
if (!mediaStorageDir.mkdirs())
{
// 在SD卡上建立資料夾需要許可權:
// <uses-permission
// android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
showShortToastCenter("建立test失敗");
LogUtils.LogError("lenita", "failed to create directory, check if you have the WRITE_EXTERNAL_STORAGE permission");
}
}
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss")
.format(new Date());
tempFile = new File(mediaStorageDir.getPath() + File.separator
+ "IMG_" + timeStamp + ".jpg");
}
相機呼叫,點選btnTakePhoto按鈕(R.id.btn_take_photo),此處對拍照後的圖片進行壓縮,免得太大導致上傳伺服器出錯:
//點選拍照
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_take_photo:
if(camera != null){
camera.takePicture(null, null, myPictureCallback);
status = true;
btnTakePhoto.setVisibility(View.GONE);
btnFinish.setVisibility(View.VISIBLE);
}else {
showShortToastCenter("請重新拍攝");
status = false;
btnTakePhoto.setVisibility(View.VISIBLE);
btnFinish.setVisibility(View.GONE);
camera.release();
camera = null;
initCameraParamsAndOpen();
}
break;
case R.id.btn_finish: //點選完成,進行圖片的上傳,上傳到伺服器程式碼省略
if(tempBitmap == null || tempFile == null){
showShortToastCenter("拍照失敗,請重新拍攝");
status = false;
btnFinish.setVisibility(View.GONE);
btnTakePhoto.setVisibility(View.VISIBLE);
camera.release();
camera = null;
initCameraParamsAndOpen();
return;
}
LogUtils.LogError("lenita","tempFile path = "+tempFile.getPath());
formatFile(); //TODO 壓縮檢視
//上傳到伺服器的程式碼省略。。。File tempFile為圖片轉換成的檔案流
break;
}
}
private File mImageFile;
private void formatFile(){
try {
//當大於2MB時用到
String lRealFilePath = tempFile.getPath();
String stringAll[] = lRealFilePath.split("\\.");
int length = stringAll.length;
String imageSuffix = stringAll[length - 1];
String string[] = lRealFilePath.split("\\."+imageSuffix);
String newFileSavePath = string[0] + "_compress.jpg";
long fileLong = FileSizeUtil.getFileSize(tempFile);
String fileString = FileSizeUtil.formatFileSizeUnder2MB(fileLong);
if (TextUtils.isEmpty(fileString)) { //TODO 證明是大於2MB的,要進行壓縮
Toast.Long(this,"正在壓縮圖片,請稍等片刻...");
compressImageUtilbelow2MB(lRealFilePath,newFileSavePath);
//壓縮後替換要上傳的路徑
lRealFilePath = newFileSavePath;
mImageFile = new File(lRealFilePath);
tempFile = mImageFile;
}
}catch (Exception e) {
Log.e("lenita","e e = "+e.toString());
}
}
private void compressImageUtilbelow2MB(String filePath,String newFilePath){
//TODO 大於2MB的進行壓縮
ImageFactory imageFactory = new ImageFactory();
try {
imageFactory.compressAndGenImage(filePath,newFilePath,1024,false);
} catch (IOException e) {
e.printStackTrace();
}
}
Camera.PictureCallback myPictureCallback = new Camera.PictureCallback(){
@Override
public void onPictureTaken(byte[] data, Camera camera) {
LogUtils.LogError("lenita"," onPictureTaken");
//卡住預覽頁面,讓使用者看
camera.stopPreview();
//儲存圖片
tempBitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
saveBitmap(tempBitmap);
}
};
public void saveBitmap(Bitmap bitmap) {
if(tempFile != null){
try {
FileOutputStream out = new FileOutputStream(tempFile);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
out.flush();
out.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
LogUtils.LogError("lenita","saveBitmap e = "+e);
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
LogUtils.LogError("lenita","saveBitmap e = "+e);
e.printStackTrace();
}
}else {
LogUtils.LogError("lenita","tempFile == null");
}
}
以上就是拍照的相關的程式碼,這裡要注意的是,拍照之後最好呼叫camera.stopPreview();這個方法,否則有些手機會一直在preview,導致無法定在當前的拍照中,當然,如果你是想拍照完直接存下,而不要定下來檢視的話,就不需要camera.stopPreview();來定住畫面了,效果圖如下所示:
儲存的檔案就會在Picture/test資料夾下,圖片如下圖:
最後給個參考文章,還是值得借鑑一下的:
http://blog.csdn.net/ming_csdn_/article/details/70154381