1. 程式人生 > >android自定義相機(帶邊框和按鈕)

android自定義相機(帶邊框和按鈕)

前兩個月專案要求不能呼叫系統的相機,那就只能用自定義的了,查了一些資料,自己再研究了一下,自定義的相機還是有點複雜的,佈局和程式碼中都要用到一個重要的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