安卓下自定義相機簡單實現

關於安卓自定義相機,網上有不少的源碼。但是功能實現上一般都還會略有不足比如對焦方式,camera資源的釋放等等。還有的自定義相機是基於opnCV實現,應用於AR場景,在這裏我們暫時用不到這樣高大上的技術。
具體實現爲:自定義一個CameraSurfaceView,繼承於SurfaceView.實現了在activity中自定義的CameraFocusListener接口用於相機對焦時對焦指示器的顯示與隱藏,和OnTouchListener接口用於監聽觸摸對焦的觸摸操作。
具體代碼:

public class CameraSurfaceView extends SurfaceView implements CameraFocusListener,OnTouchListener {
    private static final String TAG = "CameraSurfaceView";
    //4:3的比例
    public static double RATIO = 3.0 / 5.0;
    private ImageView mIVIndicator;
    private PeportedCameraActivity mPeportedCameraActivity;

    public void setPeportedCameraActivity(PeportedCameraActivity mPeportedCameraActivity) {
        this.mPeportedCameraActivity = mPeportedCameraActivity;
    }

    public void setIVIndicator(ImageView IVIndicator) {
        this.mIVIndicator = IVIndicator;
        setOnTouchListener(this);
        Log.e("", "had executed  setOnTouchListener");
    }
    /**
     * 對焦的時候把對焦指示器顯示出來
     */
    @Override
    public void onFocusBegin(float x,float y) {
        mIVIndicator.setX(x-mIVIndicator.getWidth()/2);
        mIVIndicator.setY(y-mIVIndicator.getHeight()/2);
        mIVIndicator.setAlpha(1.0f);        

    }
/**
 * 對焦結束,隱藏
 */
    @Override
    public void onFocusEnd() {
        mIVIndicator.setAlpha(0.0f);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mPeportedCameraActivity.startFocus(event.getX(), event.getY());
            Log.e("onTouch", "檢測到觸摸操作");
            break;
        default:
            break;
        }
        return false;
    }

    public CameraSurfaceView(Context context) {
        super(context);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int height = MeasureSpec.getSize(heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        Log.d("Measured", "before width" + width + "height" + height);

        boolean isWidthLonger;
        int longSide;
        int shortSide;
        // 以短邊爲準確定一下長度?
        if (width < height) {
            height = (int) (width / RATIO);
            isWidthLonger = false;

        } else {
            width = (int) (height / RATIO);
            isWidthLonger = true;
        }

        Log.d("Measured", "after width" + width + "height" + height);
        setMeasuredDimension(width, height);

    }
}

Activity代碼如下:

public class PeportedCameraActivity extends Activity  implements OnClickListener,AutoFocusCallback,SurfaceHolder.Callback, Camera.PictureCallback{

    private SurfaceHolder mSurfaceHolder;
    private CameraSurfaceView preview;
    private int mFrontCameraId = -1;
    private int mBackCameraId = -1;

    private CameraFocusListener focusListener;

    TextView camera_cancle;
    TextView local_image;
    Camera camera;
    TextView fotoButton;
    String savatar;
    private File favatar;
    public static int REQUEST_CODE_PICK_IMAGE = 0;
    private LinearLayout rl_content;
    private ImageView iv_show;
    private TextView top_back;
    private TextView cancle;
    private LinearLayout fl;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.reported_camera);
        initview();
    }

    private void initview() {
        //拍照按鈕
        fotoButton = (TextView) findViewById(R.id.imageView_foto);
        //查看相冊
        local_image = (TextView) findViewById(R.id.imageView_localpic);
        //取消按鈕
        camera_cancle = (TextView) findViewById(R.id.imageView_cancle);
        rl_content = (LinearLayout) findViewById(R.id.rl_upload);
        iv_show = (ImageView) findViewById(R.id.iv_show);
        top_back = (TextView) findViewById(R.id.top_back);
        cancle = (TextView) findViewById(R.id.imageView_cancle);

        fl = (LinearLayout) findViewById(R.id.KutCameraFragment);
        preview = new CameraSurfaceView(this);
        preview.getHolder().addCallback(this);
        focusListener=preview;

        ImageView imageView=new ImageView(this);
        Bitmap bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.focus_on_touch);
        imageView.setImageBitmap(bitmap);
        imageView.setAlpha(0.0f);
        preview.setIVIndicator(imageView);
        preview.setPeportedCameraActivity(this);

        RelativeLayout layout = new RelativeLayout(this);
        layout.addView(preview);
        layout.addView(imageView);
        fl.addView(layout);

        preview.setKeepScreenOn(true);
        fotoButton.setOnClickListener(this);
        local_image.setOnClickListener(this);
        top_back.setOnClickListener(this);
        cancle.setOnClickListener(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        // TODO Auto-generated method stub
        if(camera==null){
            camera = Camera.open();
            camera.startPreview();
            //preview.setCamera(camera);
            findAvailableCameras();
        }
    }


    /**
     * 獲得可用的相機,並設置前後攝像機的ID
     */
    private void findAvailableCameras() {

        Camera.CameraInfo info = new CameraInfo();
        int numCamera = Camera.getNumberOfCameras();
        for (int i = 0; i < numCamera; i++) {
            Camera.getCameraInfo(i, info);
            // 找到了前置攝像頭
            if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                mFrontCameraId = info.facing;
            }
            // 招到了後置攝像頭
            if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                mBackCameraId = info.facing;
            }

        }
        getCorrectOrientation();
    }

    public void takePicture() {
        camera.takePicture(null, null, this);
    }

    private void startPreView() {
        try {
            camera.setPreviewDisplay(mSurfaceHolder);
            setPreviewSize();
            setDisplayOrientation();
            camera.startPreview();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    Camera.ShutterCallback mShutterCallback = new ShutterCallback() {

        @Override
        public void onShutter() {

        }
    };

    PictureCallback rawCallback = new PictureCallback() {
        public void onPictureTaken(byte[] data, Camera camera) {
            // Log.d(TAG, "onPictureTaken - raw");
        }
    };



    public static Bitmap rotate(Bitmap source, float angle) {
        Matrix matrix = new Matrix();
        matrix.postRotate(angle);
        return Bitmap.createBitmap(source, 0, 0, source.getWidth(),
                source.getHeight(), matrix, false);
    }


    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.imageView_cancle:
            finish();
            break;

        case R.id.top_back:
            finish();
            break;

        case R.id.imageView_foto:
            takePicture();
            break;

        case R.id.imageView_localpic:
            Intent localIntent = new Intent(Intent.ACTION_GET_CONTENT);
            localIntent.setType("image/*");
            startActivityForResult(localIntent, REQUEST_CODE_PICK_IMAGE);
            //camera = null;
            break;
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_CODE_PICK_IMAGE && resultCode == Activity.RESULT_OK) {
            FileOutputStream out = null;
            savatar = Environment.getExternalStorageDirectory().toString()+ "/guixue/tmp/";
            favatar = new File(savatar);
            if (!favatar.exists()) {
                favatar.mkdirs();
            }
            new DateFormat();
            String name = DateFormat.format("yyyyMMdd_hhmmss",Calendar.getInstance(Locale.CHINA))+ ".jpg";
            savatar = savatar +"avatar.jpg";
            favatar = new File(savatar);
            Uri uri = data.getData();
            ContentResolver contentResolver = getContentResolver();
            try {
                Bitmap bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri));
                out = new FileOutputStream(favatar);
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
                iv_show.setImageBitmap(bitmap);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }finally{
                try {
                    out.flush();
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            rl_content.setVisibility(View.VISIBLE);
        }
    }

    public void startFocus(float x,float y){
        focusListener.onFocusBegin(x,y);
        camera.autoFocus(this);
        Log.e("startFocus", "startFocus(float x,float y) is executed !");
    }

    @Override
    public void onAutoFocus(boolean success, Camera camera) {
        focusListener.onFocusEnd();
    }

    @Override
    public void onPictureTaken(byte[] data, Camera camera) {
        savatar = Environment.getExternalStorageDirectory().toString()+"/guixue/tmp/";
        FileOutputStream outStream = null;
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        Calendar c = Calendar.getInstance();
        favatar = new File(savatar);

        if (!favatar.exists()) {
            favatar.mkdirs();
        }
        savatar = savatar +"avatar.jpg";
        favatar = new File(savatar);
        try {
            // Write to SD Card
            outStream = new FileOutputStream(favatar);
            outStream.write(data);
            outStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

        }

        Bitmap realImage;
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = 5;
        options.inPurgeable=true;                   //Tell to gc that whether it needs free memory, the Bitmap can be cleared
        options.inInputShareable=true;              //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future
        realImage = BitmapFactory.decodeByteArray(data,0,data.length,options);
        ExifInterface exif = null;
        try {
            exif = new ExifInterface(savatar);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            Log.d("EXIF value",
                    exif.getAttribute(ExifInterface.TAG_ORIENTATION));
            if (exif.getAttribute(ExifInterface.TAG_ORIENTATION)
                    .equalsIgnoreCase("1")) {
                realImage = rotate(realImage, 90);
            } else if (exif.getAttribute(ExifInterface.TAG_ORIENTATION)
                    .equalsIgnoreCase("8")) {
                realImage = rotate(realImage, 90);
            } else if (exif.getAttribute(ExifInterface.TAG_ORIENTATION)
                    .equalsIgnoreCase("3")) {
                realImage = rotate(realImage, 90);
            } else if (exif.getAttribute(ExifInterface.TAG_ORIENTATION)
                    .equalsIgnoreCase("0")) {
                realImage = rotate(realImage, 90);
            }
        } catch (Exception e) {

        }

        fotoButton.setClickable(true);
        rl_content.setVisibility(View.VISIBLE);
        iv_show.setImageBitmap(realImage);
    }


    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {

    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mSurfaceHolder = holder;

        startPreView();

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (camera != null) {
            camera.setPreviewCallback(null);
            camera.stopPreview();
            camera.release();
            camera=null;
        }
    }

    /**
     * 我們用4比3的比例設置預覽圖片
     */
    private void setPreviewSize() {
        Camera.Parameters params = camera.getParameters();
        List<Size> sizes = params.getSupportedPreviewSizes();
        for (Size size : sizes) {
            Log.d("previewSize", "width:" + size.width + " height " + size.height);
        }
        for (Size size : sizes) {
            if (size.width / 5 == size.height / 3) {
                params.setPreviewSize(size.width, size.height);
                Log.d("previewSize", "SET width:" + size.width + " height " + size.height);
                break;
            }
        }
        // params一定要記得寫回Camera
        camera.setParameters(params);
    }

    private void setDisplayOrientation() {
        int displayOrientation = getCorrectOrientation();

        camera.setDisplayOrientation(displayOrientation);
    }

    /**
     * 讓預覽跟照片符合正確的方向。<br/>
     * 因爲預覽默認是橫向的。如果是一個豎向的應用,就需要把預覽轉90度<br/>
     * 比如橫着時1280*960的尺寸時,1280是寬.<br/>
     * 豎着的時候1280就是高了<br/>
     * 這段代碼來自官方API。意思就是讓拍出照片的方向和預覽方向正確的符合設備當前的方向(有可能是豎向的也可能使橫向的)
     * 
     */
    private int getCorrectOrientation() {
        android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
        android.hardware.Camera.getCameraInfo(mBackCameraId, info);
        int rotation = this.getWindowManager().getDefaultDisplay().getRotation();
        int degrees = 0;
        switch (rotation) {
        case Surface.ROTATION_0:
            degrees = 0;
            break;
        case Surface.ROTATION_90:       
            degrees = 90;
            break;
        case Surface.ROTATION_180:
            degrees = 180;
            break;
        case Surface.ROTATION_270:
            degrees = 270;
            break;
        }

        int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360; 
            result = (360 - result) % 360; // compensate the mirror
        } else { // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }
        Log.d("orientationResult", result + "");
        return result;
    }
    public interface CameraFocusListener {
        public void onFocusBegin(float x,float y);
        public void onFocusEnd();
    }
}

activity佈局如下:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    tools:context=".MainActivity" >

    <FrameLayout
        android:id="@+id/preview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1" >

        <LinearLayout
            android:id="@+id/KutCameraFragment"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:orientation="vertical" />

        <RelativeLayout
            android:id="@+id/rel_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >

            <RelativeLayout
                android:id="@+id/RelativeLayout1"
                android:layout_width="match_parent"
                android:layout_height="90dp"
                android:layout_alignParentBottom="true"
                android:background="@android:color/black"
                android:padding="10dp" >

                <TextView
                    android:id="@+id/imageView_foto"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerHorizontal="true"
                    android:layout_centerVertical="true"
                    android:background="@drawable/camera_down" />

                <TextView
                    android:id="@+id/imageView_localpic"
                    android:layout_width="80dp"
                    android:layout_height="100dp"
                    android:gravity="center_vertical"
                    android:padding="5dp"
                    android:paddingLeft="30dp"
                    android:text="相冊"
                    android:textColor="#ffffff"
                    android:textSize="20sp" />

                <TextView
                    android:id="@+id/imageView_cancle"
                    android:layout_width="80dp"
                    android:layout_height="100dp"
                    android:layout_alignParentRight="true"
                    android:gravity="center"
                    android:padding="5dp"
                    android:paddingLeft="30dp"
                    android:text="取消"
                    android:textColor="#ffffff"
                    android:textSize="20sp" />
            </RelativeLayout>
        </RelativeLayout>
    </FrameLayout>

    <LinearLayout
        android:id="@+id/rl_upload"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#ffffff"
        android:orientation="vertical"
        android:visibility="gone" >

        <RelativeLayout
            android:layout_width="fill_parent"
            android:layout_height="55dp"
            android:background="#00aeff" >

            <include layout="@layout/main_tab_top" />
        </RelativeLayout>

        <ImageView
            android:id="@+id/iv_show"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_marginBottom="5dp"
            android:layout_weight="1"
            android:scaleType="fitXY" />

        <Button
            android:id="@+id/upload_btn"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_alignParentBottom="true"
            android:layout_marginBottom="5dp"
            android:layout_marginLeft="40dp"
            android:layout_marginRight="40dp"
            android:background="@drawable/login_btn_bg"
            android:text="上傳"
            android:textSize="20sp" />
    </LinearLayout>

</FrameLayout>

代碼是項目中先前已經寫完了的,但是後來要加入觸摸對焦,我就在他的基礎上做了修改,部分地方來未來得及改進。

注意幾點:
1、在自定義的surfaceView中,我將預覽比例調至5:3,具體情況可根據自己的實際情況而定。(最一開始寫的4:3,但是實際效果由於比例問題,在預覽的相機畫面下方會多一條灰邊,調至5:3即可)
2、從相機至相冊,surfaceview 停止,釋放相機資源並置空,在onResume中以相機是否爲空進行判斷,務必將資源的釋放與鏈接控制好。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章