android之利用surfaceView实现自定义水印相机
知识点
1、自定义相机+预览相机
2、截屏拍照加水印
3、关于不使用intent来传输图片
4、关于大家说要demo的,因为这里是项目里头的,不方便弄出来,大家可以直接复制代码过去,就差不多的了。
俗话说,有图有真相。很多人都是喜欢直接看图,不像我,比较喜欢文字多点,经常看看散文什么的陶冶一下情操。
好了,说到这里,就引出我们今天要做的这个功能,那就是水印相机。水印相机说白了,就是在拍照的图片上面加上自己想要的各种信息,包括文字,图片或者其它你想要的信息。
在这里,我自己定义了一个类WaterCameraActivity,是自定义的相机的,然后还有一个类ViewPhoto,是用来查看你拍照后的图片的,有使用图片和取消/重新拍照功能。
3、关于不使用intent来传输图片
因为intent最大的传输数据为1m,一张图片随便都有3,4m,再加之读取到内存中,就可能变成2倍3倍大了,很容易造成oom。所以我们还是利用本地储存来进行,只要传输一个路径就OK了,这样做的问题就是,老是要读取本地图片,性能不是很好呢。关于这个,如果各位有好的建议可以提出来一起探讨。
下面我们直接上代码,毕竟还是代码说事比较清楚,代码里面都注释好了,各位可以认真去看。
WaterCameraActivity的布局:布局很简单,就是一个SurfaceView+需要加入的水印信息,如下图
布局
WaterCameraActivity类
/**
* 启动自定义水印相机
*
* Created by tanksu on 16/6/28.
*/
public class WaterCameraActivity extends BaseActivity implements SurfaceHolder.Callback {
private Context mContext;
private SurfaceView mSurfaceView;
private ImageButton imgvBtn_takePic, imgvBtn_switchFlash, imgvBtn_switchCamera;
private Button btn_back;
private TextView tv_time, tv_username, tv_address, tv_date, tv_operation;
private SurfaceHolder mSurfaceHolder;
private Camera mCamera;
private String curDate = "", curTime = "", curAddress = "", userName = "", userOperation = "";
private final int REQUEST_CODE = 1001;
private Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
private int mCameraId;
private long currentTimeMillis = 0;
private Intent waterIntent;
/**
* 这是点击surfaceview聚焦所调用的方法
*/
private Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback(){
@Override
public void onAutoFocus(boolean success, Camera camera) {
//success = true,聚焦成功,否则聚焦失败
//在这里我们可以在点击相机后是否聚焦成功,然后做我们的一些操作,这里我就省略了,大家自行根据需要添加
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.watercamera_layout);
initViews();
initData();
initListener();
}
/**
* 初始化控件
*/
private void initViews() {
mSurfaceView = (SurfaceView) findViewById(R.id.sfv_camera);
imgvBtn_takePic = (ImageButton) findViewById(R.id.btn_takePic);
tv_time = (TextView) findViewById(R.id.tv_time);
tv_username = (TextView) findViewById(R.id.tv_username);
tv_address = (TextView) findViewById(R.id.tv_address);
tv_date = (TextView) findViewById(R.id.tv_date);
tv_operation = (TextView) findViewById(R.id.tv_operation);
imgvBtn_switchFlash = (ImageButton) findViewById(R.id.imgvBtn_switchFlash);
imgvBtn_switchFlash.setImageResource(R.drawable.camera_setting_flash_off_normal);
imgvBtn_switchCamera = (ImageButton) findViewById(R.id.imgvBtn_switchCamera);
imgvBtn_switchCamera.setImageResource(R.drawable.changing_camera_normal);
btn_back = (Button) findViewById(R.id.imgvBtn_back);
mContext = this;
}
/**
* 初始化数据
*/
private void initData() {
mSurfaceView.setFocusable(true);
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
mSurfaceHolder.setKeepScreenOn(true);
mSurfaceHolder.setFormat(PixelFormat.TRANSPARENT);
mSurfaceHolder.addCallback(this);
SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd"); //获取当前时间,作为图片的命名,再转换为常用时间格式
currentTimeMillis = System.currentTimeMillis();
curDate = formatter.format(currentTimeMillis);
tv_date.setText(curDate);
SimpleDateFormat format = new SimpleDateFormat("HH:mm", Locale.getDefault()); //获取24小时制的时间
curTime = format.format(currentTimeMillis);
tv_time.setText(curTime);
Intent intent = getIntent(); //我写的这个类,是要用startActivityForResult来启动的,传入的参数可以根据自己需求来定,我这里传过来的信息有
//地址CUR_ADDRESS,用户名USER_NAME,用户操作USER_OPERATION,然后把信息设置到空间里面去,同时还要保存intent。
//而时间和日期,则是在本类中自己获取,同样设置入控件里面去
if (intent != null) {
waterIntent = intent;
curAddress = intent.getStringExtra(StaticParam.CUR_ADDRESS);
userName = intent.getStringExtra(StaticParam.USER_NAME);
userOperation = intent.getStringExtra(StaticParam.USER_OPERATION);
tv_operation.setText(userOperation);
tv_address.setText(curAddress);
tv_username.setText(userName);
}else {
toast("intent equals null,please try again!");
}
mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
}
/**
* 初始化监听器
*/
private void initListener() {
//这个方法是点击拍照的方法
imgvBtn_takePic.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mCamera.takePicture(null, null, new PicCallBacKImpl(WaterCameraActivity.this));
}
});
//设置闪光灯的模式,有禁止,自动和打开闪光灯三种模式
imgvBtn_switchFlash.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
CameraUtil.setFlashMode(mCamera, imgvBtn_switchFlash);
}
});
//这个是切换前后摄像头的操作,因为时间关系没有做
imgvBtn_switchCamera.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
//取消按钮,finish本页面
btn_back.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
WaterCameraActivity.this.finish();
}
});
mSurfaceView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mCamera.autoFocus(autoFocusCallback); //设置相机为自动对焦模式,就不用认为去点击了
return false;
}
});
}
/**
* 我们在此周期方法里面打开摄像头
*/
@Override
protected void onStart() {
if (this.checkCameraHardware(this) && (mCamera == null)) {
openCamera();//打开后置摄像头
}
super.onStart();
}
/**
* 拍照回调类
*/
class PicCallBacKImpl implements Camera.PictureCallback {
private Activity mActivity;
public PicCallBacKImpl(Activity activity) {
this.mActivity = activity;
}
@Override
public void onPictureTaken(byte[] data, Camera camera) {
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
bitmap = ImageUtil.matrixImageView(bitmap, 90);
String path = ImageUtil.saveBitmap(null, String.valueOf(currentTimeMillis), bitmap);
if (path != null && path.length() > 0) {
waterIntent.setClass(mActivity, ViewPhoto.class);
waterIntent.putExtra(StaticParam.PIC_PATH, path);
waterIntent.putExtra(StaticParam.CUR_DATE, curDate);
waterIntent.putExtra(StaticParam.CUR_TIME, curTime);
waterIntent.putExtra(StaticParam.CUR_TIME_MILLIS, currentTimeMillis);
mActivity.startActivityForResult(waterIntent, REQUEST_CODE);
} else {
toast("can't save the picture");
camera.stopPreview();
camera.release();
camera = null;
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if(RESULT_OK == resultCode){
switch (requestCode){
case REQUEST_CODE: //处理返回结果
setResult(RESULT_OK, data); //将结果直接给设置为,启动水印相机的返回结果
break;
default:
break;
}
WaterCameraActivity.this.finish();//结束本页面,就会将结果返回到调用本页的那个activity了
}
super.onActivityResult(requestCode, resultCode, data);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
mCamera = Camera.open(mCameraId);
Camera.getCameraInfo(mCameraId, cameraInfo);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
startPreview(mCamera, mSurfaceHolder);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
}
/**
* 检查设备是否有摄像头
*
* @param context context
* @return boolean
*/
private boolean checkCameraHardware(Context context) {
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
return true;
}
return false;
}
/**
* 打开后置摄像头
*/
private void openCamera() {
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
Camera.getCameraInfo(mCameraId, cameraInfo);
this.cameraInfo = cameraInfo;
if (mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) { //后置摄像头 CAMERA_FACING_FRONT
mCamera = Camera.open();
mCamera.startPreview();//开始预览相机
}
}
/**
* 开始预览相机
*
* @param camera camera
* @param surfaceHolder surfaceHolder
*/
private void startPreview(Camera camera, SurfaceHolder surfaceHolder) {
camera.setDisplayOrientation(CameraUtil.getPreviewDegree(WaterCameraActivity.this));
try {
camera.setPreviewDisplay(surfaceHolder);
} catch (IOException e) {
e.printStackTrace();
} finally {
}
camera.startPreview();//调用此方法,然后真正的预览相机
}
/**
* 停止相机预览
*/
private void stopPreview() {
if (mCamera != null) {
mCamera.release();
mCamera.release();
mCamera = null;
}
}
@Override
protected void onDestroy() {
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
super.onDestroy();
}
}
ViewPhoto类的布局,布局入下图所示
ViewPhoto类代码
/**
* 查看拍照的类
*
* Created by tanksu on 16/6/29.
*/
public class ViewPhoto extends BaseActivity {
private ImageView imgv_photo;
private TextView tv_cancel, tv_ok;
private int width, height;
private RelativeLayout rl_layout;
private String picPath = "", curDate = "", curTime = "", curAddress = "", userName = "", userOperation = "";
private TextView tv_time, tv_date, tv_userName, tv_address, tv_operation;
private CheckBox cb_savePic;
private long currentTimeMillis;
private String signal;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);//去掉标题栏
setContentView(R.layout.viewphoto_layout);
initViews();
initData();
initListener();
}
/**
* 初始化控件
*/
private void initViews() {
imgv_photo = (ImageView) findViewById(R.id.imgv_photo);
tv_cancel = (TextView) findViewById(R.id.tv_cancel);
tv_ok = (TextView) findViewById(R.id.tv_ok);
rl_layout = (RelativeLayout) findViewById(R.id.rl_layout);
tv_time = (TextView) findViewById(R.id.tv_time);
tv_date = (TextView) findViewById(R.id.tv_date);
tv_userName = (TextView) findViewById(R.id.tv_userName);
tv_address = (TextView) findViewById(R.id.tv_address);
cb_savePic = (CheckBox) findViewById(R.id.cb_savePic);
tv_operation = (TextView) findViewById(R.id.tv_operation);
}
/**
* 初始化数据
*/
private void initData() {
WindowManager windowManager = getWindowManager();
Display display = windowManager.getDefaultDisplay();
width = display.getWidth();
height = display.getHeight();
Intent intent = getIntent();
if (intent != null) {
//这里的目标是,将所有传过来的的信息都去取出来,设置到每个相应的空间里面去
//有人会问我为什么要这样做,其实我在拍照的时候,还没有真正的拿到一张具有水印的照片
//我这里采用的是截屏的方式,所以呢,就要重新吧信息展现出来
//其实还有很多的方法可以做水印相机,例如用位图来“画”信息等,但是有简单的方法,为什么不用呢,非要去弄一些很复杂的方法?!
picPath = intent.getStringExtra(StaticParam.PIC_PATH);
curDate = intent.getStringExtra(StaticParam.CUR_DATE);
curTime = intent.getStringExtra(StaticParam.CUR_TIME);
userName = intent.getStringExtra(StaticParam.USER_NAME);
curAddress = intent.getStringExtra(StaticParam.CUR_ADDRESS);
userOperation = intent.getStringExtra(StaticParam.USER_OPERATION);
signal = intent.getStringExtra(StaticParam.TS_HUB_OP_SIGNAL);
currentTimeMillis = intent.getLongExtra(StaticParam.CUR_TIME_MILLIS, System.currentTimeMillis());
tv_time.setText(curTime);
tv_date.setText(curDate);
tv_userName.setText(userName);
tv_address.setText(curAddress);
tv_operation.setText(userOperation);
BitmapFactory.Options options = new BitmapFactory.Options();
options.outWidth = width;
options.outHeight = height;
Bitmap bitmap = ImageUtil.getPressedBitmap(picPath, width, height);//方法在下面,根据路径,获取第一步拍照存本地的图片
/**
*
*
* 根据图片路径,得到压缩过的位图
*
* @param path
* @param width
* @param height
* @return returnBitmap
public static Bitmap getPressedBitmap(String path, int width, int height) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeFile(path, options);
options.inSampleSize = getBitmapSampleSize(options, width, height);//getBitmapSampleSize(options, width, height)
options.inJustDecodeBounds = false;
Bitmap returnBitmap = BitmapFactory.decodeFile(path, options);
return returnBitmap;
}
* 根据要去的宽高,压缩图片
*
* @param options options
* @param reqWidth reqWidth
* @param reqHeight reqHeight
* @return inSimpleSize
public static int getBitmapSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
int imgWidth = options.outWidth;
int imgHeight = options.outHeight;
int inSimpleSize = 1;
if (imgWidth > imgHeight || imgWidth < imgHeight) {
final int heightRatio = imgWidth / reqWidth;
final int widthRatio = imgHeight / reqHeight;
inSimpleSize = widthRatio < heightRatio ? widthRatio : heightRatio;
}
return inSimpleSize;
}
*/
imgv_photo.setImageBitmap(bitmap);
} else {
toast("intent equals null,please try again!");
}
}
/**
* 初始化监听器
*/
private void initListener() {
//点击使用图片按钮,就可以在启动水印相机的onactivityresult回调里面,获取到图片的路径,然后获取图片即可使用了
tv_ok.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final Bitmap bitmap = getScreenPhoto(rl_layout);
ImageUtil.saveBitmap(picPath, String.valueOf(currentTimeMillis), bitmap);//根据路径保存图片
/**
* 根据路径和名字保存图片
*
* @param path path
* @param imgName imgName
* @param bitmap bitmap
* @return createPath
public static String saveBitmap(String path, String imgName, Bitmap bitmap) {
String savePath = null;
if (path == null) { //if path is null
File fileSDCardDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
String imgPath = fileSDCardDir.getAbsolutePath() + "/s/waterCamera/";
File fileDir = new File(imgPath);
if (!fileDir.exists()) {
fileDir.mkdirs();
}
String photoName = imgName + ".JPG";
imgPath = imgPath + photoName;
File fileIphoto = new File(imgPath);
if (!fileIphoto.exists()) {
try {
fileIphoto.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
savePath = fileIphoto.getPath();
saveBitmap(bitmap, fileIphoto);
return savePath;
} else { //if path isn't null, override the photo
File oldFile = new File(path);
if (oldFile.exists()) {
oldFile.delete();
}
File newFile = new File(path);
if (!newFile.exists()) {
try {
newFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
saveBitmap(bitmap, newFile);
savePath = newFile.getPath();
return savePath;
}
}
*/
Intent intent = new Intent(ViewPhoto.this, TsDrActivity.class);
intent.putExtra(StaticParam.PIC_PATH, picPath);//这里最主要的,就是将储存在本地的图片的路径作为结果返回
intent.putExtra(StaticParam.IS_SAVE_PIC, cb_savePic.isChecked());//这里就是是否用户要保存这张图片的选项
intent.putExtra(StaticParam.TS_HUB_OP_SIGNAL, signal);
setResult(RESULT_OK, intent); //如果是OK,就设置为OK结果
ViewPhoto.this.finish();
}
});
tv_cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
ImageUtil.deleteImageFromSDCard(picPath);//重新拍照,就将本地的图片给删除掉,然后重新拍照
}
}).start();
ViewPhoto.this.finish();
}
});
}
/**
* 截屏,这里就是截屏的地方了,我这里是截屏RelativeLayout,
* 只要你将需要的信息放到这个RelativeLayout里面去就可以截取下来了
*
* @param waterPhoto waterPhoto
* @return Bitmap
*/
public Bitmap getScreenPhoto(RelativeLayout waterPhoto) {
View view = waterPhoto;
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
Bitmap bitmap = view.getDrawingCache();
int width = view.getWidth();
int height = view.getHeight();
Bitmap bitmap1 = Bitmap.createBitmap(bitmap, 0, 0, width, height);
view.destroyDrawingCache();
bitmap = null;
return bitmap1;
}
//例如:启动水印相机的代码
Intent intent = new Intent(TsHubActivity.this, WaterCameraActivity.class);
intent.putExtra(StaticParam.CUR_ADDRESS, curAddress);
intent.putExtra(StaticParam.USER_NAME, stationName);
Button btn_recv = (Button) findViewById(R.id.batchRecv);
intent.putExtra(StaticParam.USER_OPERATION, btn_recv.getText().toString());
startActivityForResult(intent, JustOneOrder);
回调
//拍照回调方法
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
//以下是需要拍照的图片参数
final String imgPath = data.getStringExtra(StaticParam.PIC_PATH);//图片路径
Bitmap bitmap = BitmapFactory.decodeFile(imgPath);//获取到图片了
}
上面这两个类,直接复制来就可以用了,可以根据你的需求进行修改,不难,很简单。关于前后摄像头切换的,因为时间紧任务重,就没有时间去做了。
如有任何问题,请及时与我联系,谢谢。