关于安卓自定义相机,网上有不少的源码。但是功能实现上一般都还会略有不足比如对焦方式,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 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);
}
/**
* 让预览跟照片符合正确的方向。
* 因为预览默认是横向的。如果是一个竖向的应用,就需要把预览转90度
* 比如横着时1280*960的尺寸时,1280是宽.
* 竖着的时候1280就是高了
* 这段代码来自官方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中以相机是否为空进行判断,务必将资源的释放与链接控制好。