前言
最近支援bos端新app,接到一个自定义相机的需求; 因为需要适配大部分机型预览不变形,历时良久,特此记录。
1、核心实现类
核心实现类CameraPreview
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
static final String TAG = "CameraPreview";
private Context context;
/**
* 预览尺寸集合
*/
private final SizeMap mPreviewSizes = new SizeMap();
/**
* 图片尺寸集合
*/
private final SizeMap mPictureSizes = new SizeMap();
/**
* 屏幕旋转显示角度
*/
private int mDisplayOrientation;
/**
* 设备屏宽比
*/
private AspectRatio mAspectRatio;
private Camera mCamera;
private boolean fitPreviewAndPicture = false;
public CameraPreview(Context context) {
super(context);
initPreview(context);
}
public CameraPreview(Context context, AttributeSet attrs) {
super(context, attrs);
initPreview(context);
}
public CameraPreview(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPreview(context);
}
private void initPreview(Context context) {
this.context = context;
getHolder().addCallback(this);
mDisplayOrientation = ((Activity) context).getWindowManager().getDefaultDisplay().getRotation();
mAspectRatio = AspectRatio.of(16, 9);
}
public Camera getCamera() {
return mCamera;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
HiLogger.i(TAG, ",surfaceCreated...");
try {
initCamera();
Activity activity = (Activity) context;
//设置设备高宽比
mAspectRatio = getDeviceAspectRatio(activity);
//设置预览方向
Camera.Parameters parameters = mCamera.getParameters();
mCamera.setDisplayOrientation(90);
//获取所有支持的预览尺寸
mPreviewSizes.clear();
//获取所有支持的图片尺寸
mPictureSizes.clear();
Size previewSize = new Size(1280, 720);
Size pictureSize = new Size(1280, 720);
for (Camera.Size size : parameters.getSupportedPreviewSizes()) {
Log.i(TAG, "mPreviewSizes,size.width = "
+ size.width + ":size.height = " + size.height);
mPreviewSizes.add(new Size(size.width, size.height));
if (activity.getWindow().getDecorView().getWidth() <= size.height) {
if (previewSize.getHeight() < size.height) {
previewSize = new Size(size.width, size.height);
}
}
}
for (Camera.Size size : parameters.getSupportedPictureSizes()) {
Log.i(TAG, "mPictureSizes,size.width = "
+ size.width + ":size.height = " + size.height);
mPictureSizes.add(new Size(size.width, size.height));
if (fitPreviewAndPicture){
break;
}
if (activity.getWindow().getDecorView().getWidth() <= size.height) {
if (pictureSize.getHeight() < size.height) {
pictureSize = new Size(size.width, size.height);
}
}else if (previewSize.getWidth() * pictureSize.getHeight()
!= previewSize.getHeight() * pictureSize.getWidth()){
Log.i(TAG, "mPictureSizes,!= :pictureSize.getWidth() = "
+ pictureSize.getWidth() +
":pictureSize.getHeight() = " + pictureSize.getHeight());
if (previewSize.getWidth() * size.height ==
previewSize.getHeight() * size.width){
fitPreviewAndPicture = true;
Log.i(TAG, "better,mPictureSizes,== :size.width = "
+ size.width+
":size.height = " + size.height);
pictureSize = new Size(size.width, size.height);
}
}
}
Size betterPreviewSize = chooseOptimalSize(mPreviewSizes.sizes(mAspectRatio));
if (betterPreviewSize != null) {
// previewSize = betterPreviewSize;
}
SortedSet<Size> sortedSet = mPictureSizes.sizes(mAspectRatio);
if (sortedSet != null && sortedSet.size() > 0) {
// pictureSize = sortedSet.last();
}
Log.d(TAG, "confirm,previewSize.getWidth() = " + previewSize.getWidth()
+ ",previewSize.getHeight() = " + previewSize.getHeight());
Log.d(TAG, "confirm,pictureSize.getWidth() = " + pictureSize.getWidth()
+ ",pictureSize.getHeight() = " + pictureSize.getHeight());
//设置相机参数
parameters.setPreviewSize(previewSize.getWidth(), previewSize.getHeight());
parameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight());
parameters.setPictureFormat(ImageFormat.JPEG);
parameters.setRotation(90);
mCamera.setParameters(parameters);
//把这个预览效果展示在SurfaceView上面
mCamera.setPreviewDisplay(holder);
//开启预览效果
mCamera.startPreview();
} catch (IOException e) {
HiLogger.e("CameraPreview", "相机预览错误: " + e.getMessage());
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
HiLogger.i(TAG, ",surfaceChanged...000");
}
@Override
public boolean onCapturedPointerEvent(MotionEvent event) {
HiLogger.i(TAG, "onCapturedPointerEvent");
return super.onCapturedPointerEvent(event);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
doFocus(event);
return super.onTouchEvent(event);
}
private void doFocus(MotionEvent event) {
if (mOnFocusListener != null) {
mOnFocusListener.onFocus(mCamera, event);
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
HiLogger.i(TAG, ",surfaceDestroyed...");
releaseCamera();
}
private void initCamera() {
if (mCamera == null) {
mCamera = Camera.open();
}
}
private void releaseCamera() {
if (mCamera != null) {
mCamera.release();
mCamera = null;
}
}
private AspectRatio getDeviceAspectRatio(Activity activity) {
int width = activity.getWindow().getDecorView().getWidth();
int height = activity.getWindow().getDecorView().getHeight();
Log.i(TAG, "getDeviceAspectRatio,width = "
+ width + ":height = " + height);
return AspectRatio.of(height, width);
}
@SuppressWarnings("SuspiciousNameCombination")
private Size chooseOptimalSize(SortedSet<Size> sizes) {
if (sizes == null || sizes.size() == 0) return null;
int desiredWidth;
int desiredHeight;
final int surfaceWidth = getWidth();
final int surfaceHeight = getHeight();
if (isLandscape(mDisplayOrientation)) {
desiredWidth = surfaceHeight;
desiredHeight = surfaceWidth;
} else {
desiredWidth = surfaceWidth;
desiredHeight = surfaceHeight;
}
Size result = null;
for (Size size : sizes) {
if (desiredWidth <= size.getWidth() && desiredHeight <= size.getHeight()) {
return size;
}
result = size;
}
return result;
}
/**
* @param orientationDegrees Orientation in degrees (0,90,180,270)
* @return True if in landscape, false if portrait
*/
private boolean isLandscape(int orientationDegrees) {
return (orientationDegrees == 90 ||
orientationDegrees == 270);
}
private OnFocusListener mOnFocusListener;
public void setOnFocusListener(OnFocusListener listener) {
mOnFocusListener = listener;
}
}
2、辅助类
记录坐标AspectRatio
public class AspectRatio implements Comparable<AspectRatio>, Parcelable {
private final static SparseArrayCompat<SparseArrayCompat<AspectRatio>> sCache
= new SparseArrayCompat<>(16);
private final int mX;
private final int mY;
/**
* @param x The width
* @param y The height
* @return An instance of {@link AspectRatio}
*/
public static AspectRatio of(int x, int y) {
int gcd = gcd(x, y);
x /= gcd;
y /= gcd;
SparseArrayCompat<AspectRatio> arrayX = sCache.get(x);
if (arrayX == null) {
AspectRatio ratio = new AspectRatio(x, y);
arrayX = new SparseArrayCompat<>();
arrayX.put(y, ratio);
sCache.put(x, arrayX);
return ratio;
} else {
AspectRatio ratio = arrayX.get(y);
if (ratio == null) {
ratio = new AspectRatio(x, y);
arrayX.put(y, ratio);
}
return ratio;
}
}
/**
* Parse an {@link AspectRatio} from a {@link String} formatted like "4:3".
*
* @param s The string representation of the aspect ratio
* @return The aspect ratio
* @throws IllegalArgumentException when the format is incorrect.
*/
public static AspectRatio parse(String s) {
int position = s.indexOf(':');
if (position == -1) {
throw new IllegalArgumentException("Malformed aspect ratio: " + s);
}
try {
int x = Integer.parseInt(s.substring(0, position));
int y = Integer.parseInt(s.substring(position + 1));
return AspectRatio.of(x, y);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Malformed aspect ratio: " + s, e);
}
}
private AspectRatio(int x, int y) {
mX = x;
mY = y;
}
public int getX() {
return mX;
}
public int getY() {
return mY;
}
public boolean matches(Size size) {
int gcd = gcd(size.getWidth(), size.getHeight());
int x = size.getWidth() / gcd;
int y = size.getHeight() / gcd;
return mX == x && mY == y;
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (this == o) {
return true;
}
if (o instanceof AspectRatio) {
AspectRatio ratio = (AspectRatio) o;
return mX == ratio.mX && mY == ratio.mY;
}
return false;
}
@Override
public String toString() {
return mX + ":" + mY;
}
public float toFloat() {
return (float) mX / mY;
}
@Override
public int hashCode() {
// assuming most sizes are <2^16, doing a rotate will give us perfect hashing
return mY ^ ((mX << (Integer.SIZE / 2)) | (mX >>> (Integer.SIZE / 2)));
}
@Override
public int compareTo(@NonNull AspectRatio another) {
if (equals(another)) {
return 0;
} else if (toFloat() - another.toFloat() > 0) {
return 1;
}
return -1;
}
private static int gcd(int a, int b) {
while (b != 0) {
int c = b;
b = a % b;
a = c;
}
return a;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mX);
dest.writeInt(mY);
}
public static final Creator<AspectRatio> CREATOR
= new Creator<AspectRatio>() {
@Override
public AspectRatio createFromParcel(Parcel source) {
int x = source.readInt();
int y = source.readInt();
return AspectRatio.of(x, y);
}
@Override
public AspectRatio[] newArray(int size) {
return new AspectRatio[size];
}
};
}
记录宽高的操作的Size
public class Size implements Comparable<Size> {
private final int mWidth;
private final int mHeight;
/**
* Create a new immutable Size instance.
*
* @param width The width of the size, in pixels
* @param height The height of the size, in pixels
*/
public Size(int width, int height) {
mWidth = width;
mHeight = height;
}
public int getWidth() {
return mWidth;
}
public int getHeight() {
return mHeight;
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (this == o) {
return true;
}
if (o instanceof Size) {
Size size = (Size) o;
return mWidth == size.mWidth && mHeight == size.mHeight;
}
return false;
}
@Override
public int hashCode() {
// assuming most sizes are <2^16, doing a rotate will give us perfect hashing
return mHeight ^ ((mWidth << (Integer.SIZE / 2)) | (mWidth >>> (Integer.SIZE / 2)));
}
@Override
public int compareTo(@NonNull Size another) {
return mWidth * mHeight - another.mWidth * another.mHeight;
}
}
联合AspectRatio和Size操作的集合
public class SizeMap {
private final ArrayMap<AspectRatio, SortedSet<Size>> mRatios = new ArrayMap<>();
/**
* Add a new {@link Size} to this collection.
*
* @param size The size to add.
* @return {@code true} if it is added, {@code false} if it already exists and is not added.
*/
public void add(Size size) {
for (AspectRatio ratio : mRatios.keySet()) {
if (ratio.matches(size)) {
final SortedSet<Size> sizes = mRatios.get(ratio);
if (sizes != null) {
sizes.add(size);
}
}
}
SortedSet<Size> sizes = new TreeSet<>();
sizes.add(size);
mRatios.put(AspectRatio.of(size.getWidth(), size.getHeight()), sizes);
}
SortedSet<Size> sizes(AspectRatio ratio) {
return mRatios.get(ratio);
}
void clear() {
mRatios.clear();
}
}
3、总结心得
- 在适配的问题上来来回回磨了一个月,要耐心点再耐心点;
- 对Camera和SurfaceView温习一遍,温故知新。