一、相机的基本使用
1,获取SurfaceView控件的初始化
surfaceView = (SurfaceView) findViewById(R.id.surface_sv);
holder = surfaceView.getHolder();
surfaceView.setBackgroundColor(0);
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
holder.addCallback(this);
2,添加CallBack事件监听
@Override
public void surfaceCreated(SurfaceHolder holder) {
startPreview(camera, holder);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
camera.stopPreview();
startPreview(camera, holder);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
releaseCamera();
}
3,预览相机并设置参数
/**
* 预览相机
*/
private void startPreview(Camera camera, SurfaceHolder holder) {
try {
setupCamera(camera);
camera.setPreviewDisplay(holder);
//亲测的一个方法 基本覆盖所有手机 将预览矫正
CameraUtil.getInstance().setCameraDisplayOrientation(this, 0, camera);
camera.startPreview();
if (camera.getParameters() != null) {
Camera.Size size = camera.getParameters().getPreviewSize(); // 获取预览大小
if (Integer.parseInt(Build.VERSION.SDK) >= 8) {
initSurfaceView(size.height, size.width);
} else {
initSurfaceView(size.width, size.height);
}
} else {
Toast.makeText(MainActivity.this, "无法启动相机", Toast.LENGTH_SHORT).show();
finish();
}
} catch (Exception e) {
Toast.makeText(MainActivity.this, "无法启动相机", Toast.LENGTH_SHORT).show();
e.printStackTrace();
finish();
}
}
/**
* 设置
*/
private void setupCamera(Camera camera) {
try {
Camera.Parameters parameters = camera.getParameters();
if (parameters.getSupportedFocusModes().contains(
Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
}
//这里第三个参数为最小尺寸 getPropPreviewSize方法会对从最小尺寸开始升序排列 取出所有支持尺寸的最小尺寸
Camera.Size previewSize = CameraUtil.getInstance().getPropSizeForHeight(parameters.getSupportedPreviewSizes(), 800);
parameters.setPreviewSize(previewSize.width, previewSize.height);
Camera.Size pictrueSize = CameraUtil.getInstance().getPropSizeForHeight(parameters.getSupportedPictureSizes(), 800);
parameters.setPictureSize(pictrueSize.width, pictrueSize.height);
camera.setParameters(parameters);
/**
* 设置surfaceView的尺寸 因为camera默认是横屏,所以取得支持尺寸也都是横屏的尺寸
* 我们在startPreview方法里面把它矫正了过来,但是这里我们设置设置surfaceView的尺寸的时候要注意 previewSize.height<previewSize.width
* previewSize.width才是surfaceView的高度
* 一般相机都是屏幕的宽度 这里设置为屏幕宽度 高度自适应 你也可以设置自己想要的大小
*
*/
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(screenWidth, screenHeight);
surfaceView.setLayoutParams(params);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(MainActivity.this, "无法启动相机", Toast.LENGTH_SHORT).show();
finish();
}
}
4,纠正和矫正Surfaceview
/**
* 初始化SurfaceView
*
* @param camera_w
* @param camera_h
*/
private void initSurfaceView(int camera_w, int camera_h) {
int height = MainApplication.getInstance().getHeight()
- ScreenUtil.getStatusBarHeight();
int width = MainApplication.getInstance().getWidth();
int w = (int) ((height * camera_w) / camera_h);
int h = (int) ((camera_h * width) / camera_w);
int h_outer = 0;
int w_outer = 0;
if (w <= width) {
Log.e("", "高度为屏幕高度");
h_outer = height;
w_outer = w;
} else if (h <= height) {
Log.e("", "宽度为屏幕宽度");// ..
h_outer = h;
w_outer = width;
} else {
Log.e("", "都不合适========================");
}
if (w_outer > 0) {
if (w_outer < width || h_outer < height) {
//无法铺满全屏
float scale_w = (float) width / (float) w_outer;
float scale_h = (float) height / (float) h_outer;
float scale = ((scale_h - scale_w) > 0) ? scale_h : scale_w;
w_outer = (int) (w_outer * scale);
h_outer = (int) (h_outer * scale);
}
preViewSize[0] = w_outer;
preViewSize[1] = h_outer;
FrameLayout.LayoutParams lpsurface = new FrameLayout.LayoutParams(w_outer, h_outer);
// lpsurface.setMargins((width-w_outer)/2,(height-h_outer)/2,0,0);
surfaceView.setLayoutParams(lpsurface);
Log.e("111", "相机预览:" + camera_w + "x" + camera_h);//960x1280
Log.e("111", "surfaceview :" + w_outer + "x" + h_outer);//1305x1740 1080x1440
}
}
5,合适的地方释放相机资源
/**
* 释放相机资源
*/
private void releaseCamera() {
if (camera != null) {
camera.setPreviewCallback(null);
camera.stopPreview();
camera.release();
camera = null;
}
}
此时,相机应该可以正常使用。
【在4中,适配不同的相机预览,便于获取图片不变形】
6,生命周期中的维护
@Override
protected void onResume() {
super.onResume();
if (camera == null) {
camera = getCamera();
if (holder != null) {
startPreview(camera, holder);
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
releaseCamera();
}
/**
* 获取Camera实例
*
* @return
*/
private Camera getCamera() {
Camera camera = null;
try {
camera = Camera.open(0);
} catch (Exception e) {
}
return camera;
}
7,拍照
private void captrue() {
camera.takePicture(null, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
//将data 转换为位图 或者你也可以直接保存为文件使用 FileOutputStream
//这里我相信大部分都有其他用处把 比如加个水印 后续再讲解
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
Bitmap saveBitmap = CameraUtil.getInstance().setTakePicktrueOrientation(0, bitmap);
bitmap.recycle();
bitmap = Bitmap.createScaledBitmap(saveBitmap, preViewSize[0], preViewSize[1], true);
saveBitmap.recycle();
rotateMIV.setmSrc(bitmap);
changeToShowPic();
}
});
}
二、图片处理 --- 旋转,缩放,截图
1,图片处理的主要实现类,由以下自定义控件实现
@SuppressLint("AppCompatCustomView")
public class MatrixImageView extends ImageView {
private static final int MODE_NONE = 0x00123;// 默认的触摸模式
private static final int MODE_DRAG = 0x00321;// 拖拽模式
private static final int MODE_ZOOM = 0x00132;// 缩放or旋转模式
private int mode;// 当前的触摸模式
private float preMove = 1F;// 上一次手指移动的距离
private float saveRotate = 0F;// 保存了的角度值
private float rotate = 0F;// 旋转的角度
private float[] preEventCoor;// 上一次各触摸点的坐标集合
private PointF startPointF, midPointF;// 起点、中点对象
private Matrix currentMatrix, savedMatrix;// 当前和保存了的Matrix对象
/**
* 新增截图保存功能
* 旋转展示的图片和页面上截图的矩形运算,形成新的图片,将图片保存成为新图片即可!
* 1,触发事件
* 2,截图矩形获取
* 3,图片保存 -- 保存路径
*/
/**
* 保存文件的路径
*/
private String fileURL;
/**
* 单字Code码 -- 文件保存名称
*/
private String fontCode;
/**
* 截图矩形
*/
private Rect cutRect;
//原始图片
private Bitmap mSrc;
//控件的宽度
private int mWidth;
// 控件的高度
private int mHeight;
private PaintFlagsDrawFilter mDrawFilter;
int i = 0;
private Context context;
public MatrixImageView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
// 初始化
init();
}
/**
* 初始化
*/
private void init() {
// 实例化对象
currentMatrix = new Matrix();
savedMatrix = new Matrix();
startPointF = new PointF();
midPointF = new PointF();
mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
// 模式初始化
mode = MODE_NONE;
Drawable drawable = getDrawable();
try {
mSrc = drawableToBitamp(drawable);
} catch (Exception e) {
mSrc = null;
}
if (mSrc == null) {
//如果为空,赋默认值
mSrc = BitmapFactory.decodeResource(getResources(), R.mipmap.desc6, null);
}
}
/**
* 计算控件的高度和宽度
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 设置宽度
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
//match_parent或者设置的精确值获取
//MeasureSpec.EXACTLY
if (specMode == MeasureSpec.EXACTLY) {
mWidth = specSize;
} else {
// 由图片决定的宽
//getPaddingLeft(),getPaddingRight()这两个值是控件属性的向内偏移的距离值,所以的一起计算
//区别于layout_marginLeft,两个控件的左间距值设置
int desireByImg = getPaddingLeft() + getPaddingRight()
+ mSrc.getWidth();
// wrap_content
if (specMode == MeasureSpec.AT_MOST) {
//所以最小的值,宽度的话是左右内偏移距离之和
mWidth = Math.min(desireByImg, specSize);
} else
mWidth = desireByImg;
}
// 设置高度,部分解释同上
specMode = MeasureSpec.getMode(heightMeasureSpec);
specSize = MeasureSpec.getSize(heightMeasureSpec);
//match_parent或者设置的精确值获取
//MeasureSpec.EXACTLY
if (specMode == MeasureSpec.EXACTLY) {
mHeight = specSize;
} else {
int desire = getPaddingTop() + getPaddingBottom()
+ mSrc.getHeight();
// wrap_content
if (specMode == MeasureSpec.AT_MOST) {
mHeight = Math.min(desire, specSize);
} else
mHeight = desire;
}
//计算好的宽度以及高度是值,设置进去
setMeasuredDimension(mWidth, mHeight);
}
//drawable转bitmap
private Bitmap drawableToBitamp(Drawable drawable) {
//从控件的src获取背景,也是drawable文件获取
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bd = (BitmapDrawable) drawable;
return bd.getBitmap();
}
//如果没有绘图一个,只不过是空白的图片
// int w = drawable.getIntrinsicWidth();
// int h = drawable.getIntrinsicHeight();
int w = MainApplication.getInstance().getWidth();
int h = MainApplication.getInstance().getHeight();
Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ALPHA_8);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, w, h);
drawable.draw(canvas);
return bitmap;
}
final Paint paint = new Paint();
@Override
protected void onDraw(Canvas canvas) {
//消除锯齿, 图片旋转后的锯齿消除不成功,实在不行图片边缘加一些白色像素点
canvas.setDrawFilter(mDrawFilter);
//画经过Matrix变化后的图
canvas.drawBitmap(mSrc, currentMatrix, null);
/**
* 测试切图区域和显示区域一致
*/
if (cutRect != null) {
paint.setColor(Color.RED);
canvas.drawRect(cutRect, paint);
}
}
/**
* 裁剪图片并保存到本地
*/
public boolean cutBitmapAndSave() {
try {
Bitmap out = Bitmap.createBitmap(cutRect.width(), cutRect.height(), Bitmap.Config.ARGB_8888);
//画布在右上角
Canvas outC = new Canvas(out);
//平移画布到和截图区域一致【注意反向】
outC.translate(-cutRect.left, -cutRect.top);
outC.drawBitmap(mSrc, currentMatrix, null);
String fileParent = Environment.getExternalStorageDirectory().toString() + "/camera/demo/";
File fileDirectory = new File(fileParent);
if (!fileDirectory.exists()) {
boolean mkdirs = fileDirectory.mkdirs();
Log.i("parent", mkdirs + "");
if (mkdirs) {
File file = new File(fileParent + System.currentTimeMillis() + ".png");
if (!file.exists()) {
boolean newFile = file.createNewFile();
if (newFile) {
BitmapUtil.savePNG_After(MainApplication.getInstance(), out, file.toString(), 20);
return true;
} else {
Toast.makeText(context, "创建文件失败", Toast.LENGTH_SHORT).show();
return false;
}
} else {
BitmapUtil.savePNG_After(MainApplication.getInstance(), out, file.toString(), 20);
return true;
}
} else {
Toast.makeText(context, "创建文件夹失败", Toast.LENGTH_SHORT).show();
return false;
}
} else {
File file = new File(fileParent + System.currentTimeMillis() + ".png");
if (!file.exists()) {
boolean newFile = file.createNewFile();
if (newFile) {
BitmapUtil.savePNG_After(MainApplication.getInstance(), out, file.toString(), 20);
return true;
} else {
Toast.makeText(context, "创建文件失败", Toast.LENGTH_SHORT).show();
return false;
}
} else {
BitmapUtil.savePNG_After(MainApplication.getInstance(), out, file.toString(), 20);
return true;
}
}
// return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:// 单点接触屏幕时
savedMatrix.set(currentMatrix);
startPointF.set(event.getX(), event.getY());
//单点触摸是移动模式
mode = MODE_DRAG;
preEventCoor = null;
break;
case MotionEvent.ACTION_POINTER_DOWN:// 第二个点接触屏幕时
preMove = calSpacing(event);
if (preMove > 10F) {
savedMatrix.set(currentMatrix);
// 计算两个触摸点的中点坐标
calMidPoint(midPointF, event);
//两点是旋转或者缩放模式
mode = MODE_ZOOM;
}
preEventCoor = new float[4];
preEventCoor[0] = event.getX(0);
preEventCoor[1] = event.getX(1);
preEventCoor[2] = event.getY(0);
preEventCoor[3] = event.getY(1);
saveRotate = calRotation(event);
break;
case MotionEvent.ACTION_UP:// 单点离开屏幕时
case MotionEvent.ACTION_POINTER_UP:// 第二个点离开屏幕时
mode = MODE_NONE;
preEventCoor = null;
break;
case MotionEvent.ACTION_MOVE:// 触摸点移动时
/*
* 单点触控拖拽平移
*/
if (mode == MODE_DRAG) {
currentMatrix.set(savedMatrix);
float dx = event.getX() - startPointF.x;
float dy = event.getY() - startPointF.y;
currentMatrix.postTranslate(dx, dy);
}
/*
* 两点触控拖放旋转
*/
else if (mode == MODE_ZOOM && event.getPointerCount() == 2) {
float currentMove = calSpacing(event);
currentMatrix.set(savedMatrix);
/*
* 指尖移动距离大于10F缩放
*/
if (currentMove > 10F) {
float scale = currentMove / preMove;
currentMatrix.postScale(scale, scale, midPointF.x, midPointF.y);
}
/*
* 保持两点时旋转
*/
if (preEventCoor != null) {
rotate = calRotation(event);
r = rotate - saveRotate;
currentMatrix.postRotate(r, getMeasuredWidth() / 2, getMeasuredHeight() / 2);
}
}
break;
}
setImageMatrix(currentMatrix);
return true;
}
float r;
/**
* 计算两个触摸点间的距离
*/
private float calSpacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
/**
* 计算两个触摸点的中点坐标
*/
private void calMidPoint(PointF point, MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
point.set(x / 2, y / 2);
}
/**
* 计算旋转角度
*
* @param
* @return 角度值
*/
private float calRotation(MotionEvent event) {
double deltaX = (event.getX(0) - event.getX(1));
double deltaY = (event.getY(0) - event.getY(1));
double radius = Math.atan2(deltaY, deltaX);
return (float) Math.toDegrees(radius);
}
/**
* 设置原始图片
*
* @param mSrc
*/
public void setmSrc(Bitmap mSrc) {
this.mSrc = mSrc;
currentMatrix = new Matrix();
requestLayout();
postInvalidate();
}
public void setFileURL(String fileURL) {
this.fileURL = fileURL;
}
public void setFontCode(String fontCode) {
this.fontCode = fontCode;
}
public void setCutRect(Rect cutRect) {
this.cutRect = cutRect;
}
/**
* 更新当前Matrix
*/
public void setCurrentMatrix() {
this.currentMatrix = new Matrix();
}
}
2,控件的正常使用,需要设置原始图片的默认值
if (mSrc == null) {
//如果为空,赋默认值
mSrc = BitmapFactory.decodeResource(getResources(), R.mipmap.desc6, null);
}
/**
* 设置原始图片
*
* @param mSrc
*/
public void setmSrc(Bitmap mSrc) {
this.mSrc = mSrc;
currentMatrix = new Matrix();
requestLayout();
postInvalidate();
}
在切换到显示图片时,对控件设置原始图片,并刷新显示,绘制。
3,在Ondraw()中绘制裁剪区域
/**
* 测试切图区域和显示区域一致
*/
if (cutRect != null) {
paint.setColor(Color.RED);
canvas.drawRect(cutRect, paint);
}
在Activity中获取裁剪区域大小:
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
cutRectIV.post(new Runnable() {
@Override
public void run() {
Rect viewRect = new Rect();
int[] location = new int[2];
int[] location1 = new int[2];
// cutRectIV.getLocalVisibleRect(viewRect);
cutRectIV.getLocationOnScreen(location);
contentTakePicRL.getLocationOnScreen(location1);
/**
* getLeft() /getTop() /getBottom() /getRight()
* 都是相对父控件的位置
*/
//设置截图区域
viewRect.left = cutRectIV.getLeft();
viewRect.top = contentTakePicRL.getTop();
viewRect.bottom = cutRectIV.getBottom() + contentTakePicRL.getTop();
viewRect.right = cutRectIV.getRight();
rotateMIV.setCutRect(viewRect);
}
});
}
特别注意:gettop(),getLeft()都是相对父布局,在计算时,父布局与实际所在的位置一定要清晰。
三、相册导入
1,相册导入一般使用系统相册导入
注意点:
(1)自己构建相册需要管理相册内存;
(2)实际倒入效果并不会有很好的提升。
导入时注意对图片进行压缩:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//获取图片路径
if (requestCode == IMAGE && resultCode == Activity.RESULT_OK && data != null) {
Uri selectedImage = data.getData();
String[] filePathColumns = {MediaStore.Images.Media.DATA};
Cursor c = getContentResolver().query(selectedImage, filePathColumns, null, null, null);
c.moveToFirst();
int columnIndex = c.getColumnIndex(filePathColumns[0]);
String imagePath = c.getString(columnIndex);
int[] size = getImageWidthHeight(imagePath);
int width = screenWidth;
//导入进来,让宽适配屏幕宽度
int height = (int) (((float) (screenWidth * size[1])) / ((float) size[0]));
Bitmap bitmap = BitmapUtil.getBitmapBySize(imagePath, width, height);
rotateMIV.setmSrc(bitmap);
changeToShowPic();
c.close();
}
}
现获取图片大小,再依据最后显示大小,压缩图片后再显示。
public static Bitmap getBitmapBySize(String path, int width, int height) {
BitmapFactory.Options option = new BitmapFactory.Options();
option.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, option);
option.inSampleSize = computeSampleSize(option, -1, width * height);
option.inJustDecodeBounds = false;
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeFile(path, option);
if (bitmap.getWidth() != width || bitmap.getHeight() != height) {
Matrix mx = new Matrix();
mx.setScale((float) width / (float) bitmap.getWidth(), (float) height / (float) bitmap.getHeight());
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), mx, true);
}
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
2,保存图片
在图片保存中,存在已经设置权限了,还是创建目录不成功的情形,需要动态设置权限。
onCreate()中添加权限动态访问:
ActivityCompat.requestPermissions(MainActivity.this, new String[]{android
.Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
在权限获取结果中处理文件夹的创建管理:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//创建文件夹
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
File file = new File(Environment.getExternalStorageDirectory().toString() + "/camera/demo/");
if (!file.exists()) {
Log.d("fileCreate", "file create:" + file.mkdirs());
}
}
break;
}
}
}
3,有些手机有虚拟导航,为保持页面的一致,需要管控页面。
@SuppressLint("NewApi")
private void hideVirtualGuideBar() {
//隐藏不显示
Window window = getWindow();
WindowManager.LayoutParams params = window.getAttributes();
params.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE;
window.setAttributes(params);
}
因为需求的关系,获取到的图片,还需要做二值化。
且听下回分解。
这里是简化版本呐
传送门 -- 源码哦
偶尔会依旧很迷茫,偶尔会找不到自身的定位。总是在跌跌撞撞中找寻。。。