關於安卓自定義相機,網上有不少的源碼。但是功能實現上一般都還會略有不足比如對焦方式,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中以相機是否爲空進行判斷,務必將資源的釋放與鏈接控制好。