先看效果图:
GitHub Demo 地址 :https://github.com/liudeangit/CustomCamera
1.添加依赖
gradle.properties文件下加入android.enableJetifier=true可以混合支持库
android.enableJetifier=true
build.gradle下添加下载库
plugins {
id 'com.android.application'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.axs.camera"
minSdkVersion 26
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
//弹框视图
api 'com.github.RmondJone:SpringDiaLog:1.0.6'
api 'com.github.bumptech.glide:glide:3.7.0'
//权限申请
api 'com.yanzhenjie:permission:2.0.0-rc11'
}
AndroidManifest.xml下加入权限支持
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.axs.camera">
<!-- 增加网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 增加文件存储和访问摄像头的权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" /> <!-- 闪光灯权限 -->
<uses-permission android:name="android.permission.FLASHLIGHT" />
<uses-permission
android:name="android.permission.WRITE_SETTINGS"
tools:ignore="ProtectedPermissions" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" /> <!-- region 适配全面屏 -->
<meta-data
android:name="android.max_aspect"
android:value="2.4" /> <!-- 刘海屏适配 -->
<!--
由于目前市面上刘海屏各家一套,没有按照Android P官方进行适配,所以暂时不做刘海屏适配处理。
大部分厂家都已经对没有适配刘海屏的应用有自己的一套处理逻辑,我们的应用基本可以在大部分刘海屏手机上正常显示!
-->
<!-- 华为刘海区域展示 -->
<meta-data
android:name="android.notch_support"
android:value="true" /> <!-- 小米刘海区域展示 -->
<meta-data
android:name="notch.config"
android:value="portrait" />
<application
android:allowBackup="true"
android:requestLegacyExternalStorage="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Camera">
<activity android:name=".MainActivity3"></activity>
<activity android:name=".MainActivity2" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- <provider-->
<!-- android:name="androidx.core.content.FileProvider"-->
<!-- android:authorities="${applicationId}"-->
<!-- android:exported="false"-->
<!-- android:grantUriPermissions="true">-->
<!-- <meta-data-->
<!-- android:name="android.support.FILE_PROVIDER_PATHS"-->
<!-- android:resource="@xml/file_provider_paths"-->
<!-- tools:replace="android:resource" />-->
<!-- </provider>-->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.cameraalbumtests.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_paths" />
</provider>
</application>
</manifest>
res下新建xml包,包下面创建file_provider_paths文件
<?xml version ="1.0" encoding ="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="my_images"
path=""><!--path为空时表示将整个SD卡进行共享,这里会报错,不影响结果-->
</external-path>
</paths>
2.布局
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/gotoCamare"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="50dp"
android:onClick="gotoCamare"
android:text="点击拍照" />
</LinearLayout>
activity_main2.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity2">
<FrameLayout
android:id="@+id/camera_preview_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!--顶部视图-->
<LinearLayout
android:id="@+id/up_layout"
android:layout_width="match_parent"
android:layout_height="80dp"
android:gravity="bottom"
android:orientation="horizontal"
android:padding="15dp">
<Button
android:id="@+id/cancle_button"
android:layout_width="90dp"
android:layout_height="60dp"
android:layout_centerVertical="true"
android:background="@null"
android:text="取消"
android:textColor="#0A0909"
android:textSize="20sp"/>
<ImageView
android:id="@+id/flash_button"
android:layout_width="50dp"
android:layout_marginLeft="200dp"
android:layout_height="40dp"
android:src="@mipmap/flash_close" />
</LinearLayout>
<!--底部拍照按钮-->
<RelativeLayout
android:id="@+id/ll_photo_layout"
android:layout_width="match_parent"
android:layout_height="150dp"
android:layout_alignParentBottom="true"
android:padding="15dp"
android:visibility="visible">
<ImageView
android:id="@+id/gallerys"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:src="@mipmap/photo" />
<ImageView
android:id="@+id/take_photo_button"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_centerInParent="true"
android:src="@mipmap/take_button" />
</RelativeLayout>
</RelativeLayout>
3.代码
MainActivity
package com.axs.camera;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
import com.axs.camera.crop.view.CropImageView;
import com.yanzhenjie.permission.AndPermission;
import com.yanzhenjie.permission.Permission;
import java.io.File;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* 启动拍照界面
*
*/
public void gotoCamare(View view) {
PermissionUtils.applicationPermissions(MainActivity.this, new PermissionUtils.PermissionListener() {
@Override
public void onSuccess(Context context) {
Intent intent = new Intent(MainActivity.this, MainActivity2.class);
MainActivity.this.startActivity(intent);
}
@Override
public void onFailed(Context context) {
if (AndPermission.hasAlwaysDeniedPermission(context, Permission.Group.CAMERA)
&& AndPermission.hasAlwaysDeniedPermission(context, Permission.Group.STORAGE)) {
AndPermission.with(context).runtime().setting().start();
}
Toast.makeText(context, context.getString(R.string.permission_camra_storage), Toast.LENGTH_SHORT);
}
}, Permission.Group.STORAGE, Permission.Group.CAMERA);
}
}
MainActivity2
package com.axs.camera;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.hardware.Camera;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.text.format.DateFormat;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Toast;
import com.axs.camera.crop.view.CropImageView;
import com.newland.springdialog.AnimSpring;
import com.yanzhenjie.permission.AndPermission;
import com.yanzhenjie.permission.Permission;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
public class MainActivity2 extends AppCompatActivity implements View.OnClickListener{
public static final int CHOOSE_PHOTO = 2;
public static final String KEY_IMAGE_PATH = "imagePath";
/**
* 相机预览
*/
private FrameLayout mPreviewLayout;
/**
* 拍摄按钮视图
*/
private RelativeLayout mPhotoLayout;
/**
* 顶部视图
*/
private LinearLayout upLayout;
/**
* 闪光灯
*/
private ImageView mFlashButton;
/**
* 拍照按钮
*/
private ImageView mPhotoButton;
/**
* 聚焦视图
*/
private OverCameraView mOverCameraView;
/**
* 相机类
*/
private Camera mCamera;
/**
* Handle
*/
private Handler mHandler = new Handler();
private Runnable mRunnable;
/**
* 取消按钮
*/
private Button mCancleButton;
/**
* 是否开启闪光灯
*/
private boolean isFlashing;
/**
* 图片流暂存
*/
private byte[] imageData;
/**
* 拍照标记
*/
private boolean isTakePhoto;
/**
* 是否正在聚焦
*/
private boolean isFoucing;
/**
* 图库按钮
*/
private ImageView gallery;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
initView();
setOnclickListener();
}
/**
* 注释:初始化视图
*/
private void initView() {
mCancleButton = findViewById(R.id.cancle_button);
mPreviewLayout = findViewById(R.id.camera_preview_layout);
mPhotoLayout = findViewById(R.id.ll_photo_layout);
mPhotoButton = findViewById(R.id.take_photo_button);
mFlashButton = findViewById(R.id.flash_button);
gallery = findViewById(R.id.gallerys);
upLayout = findViewById(R.id.up_layout);
}
@Override
protected void onResume() {
super.onResume();
mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
CameraPreview preview = new CameraPreview(this, mCamera);
mOverCameraView = new OverCameraView(this);
mPreviewLayout.addView(preview);
mPreviewLayout.addView(mOverCameraView);
}
/**
* 注释:设置监听事件
*/
private void setOnclickListener() {
mCancleButton.setOnClickListener(this);
mFlashButton.setOnClickListener(this);
mPhotoButton.setOnClickListener(this);
gallery.setOnClickListener(this);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (!isFoucing) {
float x = event.getX();
float y = event.getY();
isFoucing = true;
if (mCamera != null && !isTakePhoto) {
mOverCameraView.setTouchFoucusRect(mCamera, autoFocusCallback, x, y);
}
mRunnable = () -> {
Toast.makeText(MainActivity2.this, "自动聚焦超时,请调整合适的位置拍摄!", Toast.LENGTH_SHORT);
isFoucing = false;
mOverCameraView.setFoucuing(false);
mOverCameraView.disDrawTouchFocusRect();
};
//设置聚焦超时
mHandler.postDelayed(mRunnable, 3000);
}
}
return super.onTouchEvent(event);
}
/**
* 注释:自动对焦回调
*/
private Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
isFoucing = false;
mOverCameraView.setFoucuing(false);
mOverCameraView.disDrawTouchFocusRect();
//停止聚焦超时回调
mHandler.removeCallbacks(mRunnable);
}
};
/**
* 注释:拍照并保存图片到相册
*/
private void takePhoto() {
isTakePhoto = true;
//调用相机拍照
mCamera.takePicture(null, null, null, (data, camera1) -> {
//视图动画
upLayout.setVisibility(View.GONE);//关闭顶部视图
mPhotoLayout.setVisibility(View.GONE);//底部拍照按钮
imageData = data;
//停止预览
mCamera.stopPreview();
//保存后进行裁剪
savePhoto();
});
}
/**
* 注释:切换闪光灯
*/
private void switchFlash() {
isFlashing = !isFlashing;
mFlashButton.setImageResource(isFlashing ? R.mipmap.flash_open : R.mipmap.flash_close);
AnimSpring.getInstance(mFlashButton).startRotateAnim(120, 360);
try {
Camera.Parameters parameters = mCamera.getParameters();
parameters.setFlashMode(isFlashing ? Camera.Parameters.FLASH_MODE_TORCH : Camera.Parameters.FLASH_MODE_OFF);
mCamera.setParameters(parameters);
} catch (Exception e) {
Toast.makeText(this, "该设备不支持闪光灯", Toast.LENGTH_SHORT);
}
}
/**
* 注释:取消保存
*/
private void cancleSavePhoto() {
upLayout.setVisibility(View.VISIBLE);
mPhotoLayout.setVisibility(View.VISIBLE);
AnimSpring.getInstance(mPhotoLayout).startRotateAnim(120, 360);
//开始预览
mCamera.startPreview();
imageData = null;
isTakePhoto = false;
}
@Override
public void onClick(View v) {
int id = v.getId();
System.out.println(id);
if (id == R.id.cancle_button) {
finish();
} else if (id == R.id.take_photo_button) {
if (!isTakePhoto) {
takePhoto();
}
} else if (id == R.id.flash_button) {
switchFlash();
} else if (id == R.id.save_button) {
savePhoto();
} else if (id == R.id.cancle_save_button) {
cancleSavePhoto();
}else if(id == R.id.gallerys){
aa();
}
}
/**
* 注释:保存图片
*/
private void savePhoto() {
FileOutputStream fos = null;
//获取路径
String name = DateFormat.format("yyyyMMdd_hhmmss", Calendar.getInstance(Locale.CHINA)) + ".jpg";
FileOutputStream outputStream = null;
String printTxtPath = getApplicationContext().getFilesDir().getAbsolutePath();//当前程序路径
File file = new File(printTxtPath);
if (!file.exists()) {
file.mkdirs();// 创建文件夹
}
String imagePath = printTxtPath + name;
File imageFile = new File(imagePath);
try {
fos = new FileOutputStream(imageFile);
fos.write(imageData);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
Bitmap retBitmap = BitmapFactory.decodeFile(imagePath);
retBitmap = BitmapUtils.setTakePicktrueOrientation(Camera.CameraInfo.CAMERA_FACING_BACK, retBitmap);
BitmapUtils.saveBitmap(retBitmap, imagePath);
//跳转至裁剪
Intent intent = new Intent(getApplicationContext(),MainActivity3.class);
intent.putExtra(KEY_IMAGE_PATH, imagePath);
startActivity(intent);
} catch (IOException e) {
setResult(RESULT_FIRST_USER);
e.printStackTrace();
}
}
finish();
}
}
/**
* 图库
* */
public void aa(){
//动态申请读取SD卡权限
if (ContextCompat.checkSelfPermission(MainActivity2.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity2.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
} else {
openAlbum();
}
}
private void openAlbum() {
Intent intent = new Intent("android.intent.action.GET_CONTENT");
intent.setType("image/*");
startActivityForResult(intent, CHOOSE_PHOTO); //打开相册
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults){
switch (requestCode){
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
openAlbum();
}else{
Toast.makeText(this,"You denied the permission", Toast.LENGTH_SHORT).show();
}
break;
default:
}
}
//Overriding method should call super.onActivityResult
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
System.out.println("99999999999999999999999999999999999966666666666666");
if (resultCode == RESULT_OK) {
//判断手机系统版本号
if (Build.VERSION.SDK_INT >= 19) {
//4.4及以上系统使用这个方法处理图片
handleImageOnKitKat(data);
} else {
handleImageBeforeKitKat(data);
}
}
}
public byte[] UriToByte(Uri uri){
Bitmap bitmap1 = null;
try {
bitmap1 = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uri);
} catch (IOException e) {
e.printStackTrace();
}
int size = bitmap1.getWidth() * bitmap1.getHeight() * 4;
ByteArrayOutputStream baos = new ByteArrayOutputStream(size);
bitmap1.compress(Bitmap.CompressFormat.PNG, 100, baos);
byte[] imagedata1 = baos.toByteArray();
return imagedata1;
}
@TargetApi(19)
private void handleImageOnKitKat(Intent data){
String imagePath = null;
Uri uri = data.getData();
if(DocumentsContract.isDocumentUri(this,uri)){
/*如果是document类型的Uri,则通过document id处理*/
String docId = DocumentsContract.getDocumentId(uri);
if("com.android.providers.media.documents".equals(uri.getAuthority())){
String id = docId.split(":")[1]; //解析出数字格式的id
String selection = MediaStore.Images.Media._ID + "=" + id;
imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,selection);
}else if("com.android.providers.downloads.documents".equals(uri.getAuthority())){
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),Long.valueOf(docId));
imagePath = getImagePath(contentUri,null);
}
}else if("content".equalsIgnoreCase(uri.getScheme())){
//如果是content类型的uri,则使用普通方式处理
imagePath = getImagePath(uri,null);
}else if("file".equalsIgnoreCase(uri.getScheme())){
//如果是file类型的uri,直接获取图片路径即可
imagePath = uri.getPath();
}
displayImage(imagePath); //根据图片路径显示图片
}
private void handleImageBeforeKitKat(Intent data){
Uri uri = data.getData();
String imagePath = getImagePath(uri,null);
displayImage(imagePath);
}
private String getImagePath(Uri uri,String selection){
String path = null;
//通过Uri和selection来获取真实的图片路径
Cursor cursor = getContentResolver().query(uri,null,selection,null,null);
if(cursor != null){
if (cursor.moveToFirst()){
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
}
cursor.close();
}
return path;
}
private void displayImage(String imagePath) {
if (imagePath != null) {
//跳转裁剪
Intent intent = new Intent(MainActivity2.this,MainActivity3.class);
intent.putExtra(KEY_IMAGE_PATH, imagePath);
startActivity(intent);
} else {
Toast.makeText(this, "failed to get image", Toast.LENGTH_SHORT).show();
}
}
}
工具类
package com.axs.camera;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.collection.SparseArrayCompat;
/**
* 注释:屏宽比
*/
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;
/**
* Returns an instance of {@link AspectRatio} specified by {@code x} and {@code y} values.
* The values {@code x} and {@code} will be reduced by their greatest common divider.
*
* @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(com.axs.camera.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(AspectRatio another) {
if (equals(another)) {
return 0;
} else if (toFloat() - another.toFloat() > 0) {
return 1;
}
return -1;
}
/**
* @return The inverse of this {@link AspectRatio}.
*/
public AspectRatio inverse() {
//noinspection SuspiciousNameCombination
return AspectRatio.of(mY, mX);
}
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];
}
};
}
package com.axs.camera;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.hardware.Camera;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 注释:
*/
public class BitmapUtils {
/**
* 注释:设置拍照图片正确方向
*
* @param id
* @param bitmap
* @return
*/
public static Bitmap setTakePicktrueOrientation(int id, Bitmap bitmap) {
//如果返回的图片宽度小于高度,说明FrameWork层已经做过处理直接返回即可
if (bitmap.getWidth() < bitmap.getHeight()) {
return bitmap;
}
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(id, info);
bitmap = rotaingImageView(id, info.orientation, bitmap);
return bitmap;
}
/**
* 把相机拍照返回照片转正
*
* @param angle 旋转角度
* @return bitmap 图片
*/
private static Bitmap rotaingImageView(int id, int angle, Bitmap bitmap) {
//矩阵
Matrix matrix = new Matrix();
matrix.postRotate(angle);
//加入翻转 把相机拍照返回照片转正
if (id == 1) {
matrix.postScale(-1, 1);
}
// 创建新的图片
Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0,
bitmap.getWidth(), bitmap.getHeight(), matrix, true);
return resizedBitmap;
}
/**
* 注释:保存图片
*
* @param bitmap
* @param path
* @return
*/
public static boolean saveBitmap(Bitmap bitmap, String path) {
try {
File file = new File(path);
File parent = file.getParentFile();
if (!parent.exists()) {
parent.mkdirs();
}
FileOutputStream fos = new FileOutputStream(file);
boolean b = bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
return b;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
}
package com.axs.camera;
import android.app.Activity;
import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.io.IOException;
import java.util.SortedSet;
/**
* 注释:相机预览视图
*/
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder mHolder;
private Camera mCamera;
private boolean isPreview;
private Context context;
/**
* 预览尺寸集合
*/
private final com.axs.camera.SizeMap mPreviewSizes = new com.axs.camera.SizeMap();
/**
* 图片尺寸集合
*/
private final com.axs.camera.SizeMap mPictureSizes = new com.axs.camera.SizeMap();
/**
* 屏幕旋转显示角度
*/
private int mDisplayOrientation;
/**
* 设备屏宽比
*/
private com.axs.camera.AspectRatio mAspectRatio;
/**
* 注释:构造函数
*
* @param context
* @param mCamera
*/
public CameraPreview(Context context, Camera mCamera) {
super(context);
this.context = context;
this.mCamera = mCamera;
this.mHolder = getHolder();
this.mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
mDisplayOrientation = ((Activity) context).getWindowManager().getDefaultDisplay().getRotation();
mAspectRatio = com.axs.camera.AspectRatio.of(9, 16);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
//设置设备高宽比
mAspectRatio = getDeviceAspectRatio((Activity) context);
//设置预览方向
mCamera.setDisplayOrientation(getDisplayOrientation());
Camera.Parameters parameters = mCamera.getParameters();
//获取所有支持的预览尺寸
mPreviewSizes.clear();
for (Camera.Size size : parameters.getSupportedPreviewSizes()) {
int width = Math.min(size.width, size.height);
int heigth = Math.max(size.width, size.height);
mPreviewSizes.add(new com.axs.camera.Size(width, heigth));
}
//获取所有支持的图片尺寸
mPictureSizes.clear();
for (Camera.Size size : parameters.getSupportedPictureSizes()) {
int width = Math.min(size.width, size.height);
int heigth = Math.max(size.width, size.height);
mPictureSizes.add(new com.axs.camera.Size(width, heigth));
}
com.axs.camera.Size previewSize = chooseOptimalSize(mPreviewSizes.sizes(mAspectRatio));
com.axs.camera.Size pictureSize = mPictureSizes.sizes(mAspectRatio).last();
//设置相机参数
parameters.setPreviewSize(Math.max(previewSize.getWidth(), previewSize.getHeight()), Math.min(previewSize.getWidth(), previewSize.getHeight()));
parameters.setPictureSize(Math.max(pictureSize.getWidth(), pictureSize.getHeight()), Math.min(pictureSize.getWidth(), pictureSize.getHeight()));
parameters.setPictureFormat(ImageFormat.JPEG);
parameters.setRotation(getDisplayOrientation());
mCamera.setParameters(parameters);
//把这个预览效果展示在SurfaceView上面
mCamera.setPreviewDisplay(holder);
//开启预览效果
mCamera.startPreview();
isPreview = true;
} catch (Exception e) {
Log.e("CameraPreview", "相机预览错误: " + e.getMessage());
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (holder.getSurface() == null) {
return;
}
//停止预览效果
mCamera.stopPreview();
//重新设置预览效果
try {
mCamera.setPreviewDisplay(mHolder);
} catch (IOException e) {
e.printStackTrace();
}
mCamera.startPreview();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (mCamera != null) {
if (isPreview) {
//正在预览
mCamera.stopPreview();
mCamera.release();
}
}
}
/**
* 注释:获取设备屏宽比
*/
private com.axs.camera.AspectRatio getDeviceAspectRatio(Activity activity) {
int width = activity.getWindow().getDecorView().getWidth();
int height = activity.getWindow().getDecorView().getHeight();
return com.axs.camera.AspectRatio.of(Math.min(width, height), Math.max(width, height));
}
/**
* 注释:选择合适的预览尺寸
*
* @param sizes
* @return
*/
@SuppressWarnings("SuspiciousNameCombination")
private com.axs.camera.Size chooseOptimalSize(SortedSet<com.axs.camera.Size> sizes) {
int desiredWidth;
int desiredHeight;
final int surfaceWidth = getWidth();
final int surfaceHeight = getHeight();
if (isLandscape(mDisplayOrientation)) {
desiredWidth = surfaceHeight;
desiredHeight = surfaceWidth;
} else {
desiredWidth = surfaceWidth;
desiredHeight = surfaceHeight;
}
com.axs.camera.Size result = new com.axs.camera.Size(desiredWidth, desiredHeight);
if (sizes != null && !sizes.isEmpty()) {
for (com.axs.camera.Size size : sizes) {
if (desiredWidth <= size.getWidth() && desiredHeight <= size.getHeight()) {
return size;
}
result = size;
}
}
return result;
}
/**
* Test if the supplied orientation is in landscape.
*
* @param orientationDegrees Orientation in degrees (0,90,180,270)
* @return True if in landscape, false if portrait
*/
private boolean isLandscape(int orientationDegrees) {
return (orientationDegrees == Surface.ROTATION_90 ||
orientationDegrees == Surface.ROTATION_270);
}
/**
* 注释:获取摄像头应该显示的方向
*
* @return
*/
private int getDisplayOrientation() {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, info);
int orientation;
int degrees = 0;
if (mDisplayOrientation == Surface.ROTATION_0) {
degrees = 0;
} else if (mDisplayOrientation == Surface.ROTATION_90) {
degrees = 90;
} else if (mDisplayOrientation == Surface.ROTATION_180) {
degrees = 180;
} else if (mDisplayOrientation == Surface.ROTATION_270) {
degrees = 270;
}
orientation = (degrees + 45) / 90 * 90;
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation - orientation + 360) % 360;
} else {
// back-facing
result = (info.orientation + orientation) % 360;
}
return result;
}
}
package com.axs.camera;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.hardware.Camera;
import android.util.AttributeSet;
import android.util.Log;
import android.view.WindowManager;
import androidx.appcompat.widget.AppCompatImageView;
import java.util.ArrayList;
import java.util.List;
/**
* 注释:对焦框
*/
public class OverCameraView extends AppCompatImageView {
private Context context;
//焦点附近设置矩形区域作为对焦区域
private Rect touchFocusRect;
private Paint touchFocusPaint;
//是否正在对焦
private boolean isFoucuing;
public OverCameraView(Context context) {
this(context, null, 0);
}
public OverCameraView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public OverCameraView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
this.context = context;
//画笔设置
touchFocusPaint = new Paint();
touchFocusPaint.setColor(Color.GREEN);
touchFocusPaint.setStyle(Paint.Style.STROKE);
touchFocusPaint.setStrokeWidth(3);
}
public boolean isFoucuing() {
return isFoucuing;
}
public void setFoucuing(boolean foucuing) {
isFoucuing = foucuing;
}
/**
* 注释:对焦并绘制对焦矩形框
*
* @param camera
* @param autoFocusCallback
* @param x
* @param y
*/
public void setTouchFoucusRect(Camera camera, Camera.AutoFocusCallback autoFocusCallback, float x, float y) {
//以焦点为中心,宽度为200的矩形框
touchFocusRect = new Rect((int) (x - 100), (int) (y - 100), (int) (x + 100), (int) (y + 100));
//对焦光感区域
int left = touchFocusRect.left * 2000 / getWindowWidth(context) - 1000;
int top = touchFocusRect.top * 2000 / getWindowHeight(context) - 1000;
int right = touchFocusRect.right * 2000 / getWindowWidth(context) - 1000;
int bottom = touchFocusRect.bottom * 2000 / getWindowHeight(context) - 1000;
// 如果超出了(-1000,1000)到(1000, 1000)的范围,则会导致相机崩溃
left = left < -1000 ? -1000 : left;
top = top < -1000 ? -1000 : top;
right = right > 1000 ? 1000 : right;
bottom = bottom > 1000 ? 1000 : bottom;
final Rect targetFocusRect = new Rect(left, top, right, bottom);
//对焦
doTouchFocus(camera, autoFocusCallback, targetFocusRect);
//刷新界面,调用onDraw(Canvas canvas)函数绘制矩形框
postInvalidate();
}
/**
* 注释:设置camera参数,并完成对焦
*
* @param camera
* @param autoFocusCallback
* @param tfocusRect
*/
public void doTouchFocus(Camera camera, Camera.AutoFocusCallback autoFocusCallback, final Rect tfocusRect) {
if (camera == null || isFoucuing) {
return;
}
try {
final List<Camera.Area> focusList = new ArrayList<>();
Camera.Area focusArea = new Camera.Area(tfocusRect, 1000);
focusList.add(focusArea);
Camera.Parameters para = camera.getParameters();
para.setFocusAreas(focusList);
para.setMeteringAreas(focusList);
para.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
camera.cancelAutoFocus();
camera.setParameters(para);
camera.autoFocus(autoFocusCallback);
isFoucuing = true;
} catch (Exception e) {
Log.e("设置相机参数异常", e.getMessage());
}
}
/**
* 注释:对焦完成后,清除对焦矩形框
*/
public void disDrawTouchFocusRect() {
//将对焦区域设置为null,刷新界面后对焦框消失
touchFocusRect = null;
//刷新界面,调用onDraw(Canvas canvas)函数
postInvalidate();
}
@Override
protected void onDraw(Canvas canvas) {
//在画布上绘图,postInvalidate()后自动调用
drawTouchFocusRect(canvas);
super.onDraw(canvas);
}
/**
* 获取屏幕高度
*/
@SuppressWarnings("deprecation")
public static int getWindowHeight(Context cxt) {
WindowManager wm = (WindowManager) cxt
.getSystemService(Context.WINDOW_SERVICE);
return wm.getDefaultDisplay().getHeight();
}
/**
* 获取屏幕宽度
*/
@SuppressWarnings("deprecation")
public static int getWindowWidth(Context cxt) {
WindowManager wm = (WindowManager) cxt
.getSystemService(Context.WINDOW_SERVICE);
return wm.getDefaultDisplay().getWidth();
}
private void drawTouchFocusRect(Canvas canvas) {
if (null != touchFocusRect) {
//根据对焦区域targetFocusRect,绘制自己想要的对焦框样式,本文在矩形四个角取L形状
//左下角
canvas.drawRect(touchFocusRect.left - 2, touchFocusRect.bottom, touchFocusRect.left + 20, touchFocusRect.bottom + 2, touchFocusPaint);
canvas.drawRect(touchFocusRect.left - 2, touchFocusRect.bottom - 20, touchFocusRect.left, touchFocusRect.bottom, touchFocusPaint);
//左上角
canvas.drawRect(touchFocusRect.left - 2, touchFocusRect.top - 2, touchFocusRect.left + 20, touchFocusRect.top, touchFocusPaint);
canvas.drawRect(touchFocusRect.left - 2, touchFocusRect.top, touchFocusRect.left, touchFocusRect.top + 20, touchFocusPaint);
//右上角
canvas.drawRect(touchFocusRect.right - 20, touchFocusRect.top - 2, touchFocusRect.right + 2, touchFocusRect.top, touchFocusPaint);
canvas.drawRect(touchFocusRect.right, touchFocusRect.top, touchFocusRect.right + 2, touchFocusRect.top + 20, touchFocusPaint);
//右下角
canvas.drawRect(touchFocusRect.right - 20, touchFocusRect.bottom, touchFocusRect.right + 2, touchFocusRect.bottom + 2, touchFocusPaint);
canvas.drawRect(touchFocusRect.right, touchFocusRect.bottom - 20, touchFocusRect.right + 2, touchFocusRect.bottom, touchFocusPaint);
}
}
}
package com.axs.camera;
import android.content.Context;
import android.os.Build;
import com.yanzhenjie.permission.AndPermission;
/**
* 注释: Android权限申请工具类
*/
public class PermissionUtils {
/**
* 注释:权限申请回调
*/
public interface PermissionListener {
/**
* 成功
*/
void onSuccess(Context context);
/**
* 失败
*/
void onFailed(Context context);
}
/**
* 注释:应用相关组权限
*/
public static void applicationPermissions(Context context, PermissionListener listener, String[]... permissions) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
if (!AndPermission.hasPermissions(context, permissions)) {
AndPermission.with(context)
.runtime()
.permission(permissions)
.rationale((mContext, data, executor) -> {
//选择显示提示弹窗
executor.execute();
})
.onGranted((permission) -> {
listener.onSuccess(context);
})
.onDenied((permission) -> {
listener.onFailed(context);
})
.start();
} else {
listener.onSuccess(context);
}
} else {
listener.onSuccess(context);
}
}
/**
* 注释:应用相关单个权限
*/
public static void applicationPermissions(Context context, PermissionListener listener, String... permissions) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
if (!AndPermission.hasPermissions(context, permissions)) {
AndPermission.with(context)
.runtime()
.permission(permissions)
.rationale((mContext, data, executor) -> {
//选择显示提示弹窗
executor.execute();
})
.onGranted((permission) -> {
listener.onSuccess(context);
})
.onDenied((permission) -> {
listener.onFailed(context);
})
.start();
} else {
listener.onSuccess(context);
}
} else {
listener.onSuccess(context);
}
}
}
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.axs.camera;
/**
* 注释:尺寸对象
*/
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 String toString() {
return mWidth + "x" + mHeight;
}
@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(Size another) {
return mWidth * mHeight - another.mWidth * another.mHeight;
}
}
package com.axs.camera;
import androidx.collection.ArrayMap;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* 注释:
*/
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 boolean add(Size size) {
for (AspectRatio ratio : mRatios.keySet()) {
if (ratio.matches(size)) {
final SortedSet<Size> sizes = mRatios.get(ratio);
if (sizes.contains(size)) {
return false;
} else {
sizes.add(size);
return true;
}
}
}
// None of the existing ratio matches the provided size; add a new key
SortedSet<Size> sizes = new TreeSet<>();
sizes.add(size);
mRatios.put(AspectRatio.of(size.getWidth(), size.getHeight()), sizes);
return true;
}
/**
* Removes the specified aspect ratio and all sizes associated with it.
*
* @param ratio The aspect ratio to be removed.
*/
public void remove(AspectRatio ratio) {
mRatios.remove(ratio);
}
Set<AspectRatio> ratios() {
return mRatios.keySet();
}
SortedSet<Size> sizes(AspectRatio ratio) {
if (mRatios.get(ratio) != null) {
return mRatios.get(ratio);
}
//如果找不到合适屏宽比,找最接近屏幕的
AspectRatio retRatio = ratio;
float diff = 1;
for (AspectRatio size : ratios()) {
if (Math.abs(ratio.toFloat() - size.toFloat()) < diff) {
retRatio = size;
diff = Math.abs(ratio.toFloat() - size.toFloat());
}
}
return mRatios.get(retRatio);
}
void clear() {
mRatios.clear();
}
boolean isEmpty() {
return mRatios.isEmpty();
}
}