From 83314b6f165772b8fe8fc6af0a66a134889e4aea Mon Sep 17 00:00:00 2001 From: DywaneQ Date: Fri, 10 Aug 2018 12:46:28 +0800 Subject: [PATCH] First Commit --- .gitignore | 70 +-- .idea/gradle.xml | 18 + .idea/misc.xml | 52 ++ .idea/modules.xml | 9 + .idea/runConfigurations.xml | 12 + app/.gitignore | 1 + app/build.gradle | 33 ++ app/proguard-rules.pro | 21 + .../camerademo/ExampleInstrumentedTest.java | 26 + app/src/main/AndroidManifest.xml | 47 ++ app/src/main/java/com/dwq/camerademo/App.java | 29 ++ .../com/dwq/camerademo/Camera2Activity.java | 105 ++++ .../com/dwq/camerademo/CameraActivity.java | 185 +++++++ .../java/com/dwq/camerademo/MainActivity.java | 72 +++ .../camera/BaseCamera2TextureView.java | 254 ++++++++++ .../camerademo/camera/Camera2TextureView.java | 452 ++++++++++++++++++ .../camerademo/camera/CameraSurfaceView.java | 78 +++ .../camera/CaptureRequestFactory.java | 116 +++++ .../camerademo/camera/CompareSizesByArea.java | 22 + .../camerademo/event/ImageAvailableEvent.java | 40 ++ .../com/dwq/camerademo/utils/CameraUtils.java | 387 +++++++++++++++ .../com/dwq/camerademo/utils/CommonUtils.java | 77 +++ .../camerademo/utils/GlideImageLoader.java | 129 +++++ .../com/dwq/camerademo/utils/ImageSaver.java | 94 ++++ .../com/dwq/camerademo/utils/ImageUtils.java | 39 ++ .../utils/SharedPreferencesUtil.java | 228 +++++++++ .../drawable-v24/ic_launcher_foreground.xml | 34 ++ .../res/drawable/ic_launcher_background.xml | 170 +++++++ app/src/main/res/drawable/selector.xml | 5 + app/src/main/res/layout/activity_camera2.xml | 40 ++ .../res/layout/activity_camera_surface.xml | 32 ++ app/src/main/res/layout/activity_main.xml | 36 ++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3056 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5024 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2096 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2858 bytes .../main/res/mipmap-xhdpi/camera_button.png | Bin 0 -> 21651 bytes .../mipmap-xhdpi/camera_button_pressed.png | Bin 0 -> 6162 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4569 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7098 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6464 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10676 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9250 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15523 bytes app/src/main/res/values/colors.xml | 6 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/styles.xml | 17 + .../com/dwq/camerademo/ExampleUnitTest.java | 17 + build.gradle | 28 ++ gradle.properties | 17 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 160 +++++++ gradlew.bat | 90 ++++ settings.gradle | 1 + 57 files changed, 3205 insertions(+), 63 deletions(-) create mode 100644 .idea/gradle.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/runConfigurations.xml create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/com/dwq/camerademo/ExampleInstrumentedTest.java create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/com/dwq/camerademo/App.java create mode 100644 app/src/main/java/com/dwq/camerademo/Camera2Activity.java create mode 100644 app/src/main/java/com/dwq/camerademo/CameraActivity.java create mode 100644 app/src/main/java/com/dwq/camerademo/MainActivity.java create mode 100644 app/src/main/java/com/dwq/camerademo/camera/BaseCamera2TextureView.java create mode 100644 app/src/main/java/com/dwq/camerademo/camera/Camera2TextureView.java create mode 100644 app/src/main/java/com/dwq/camerademo/camera/CameraSurfaceView.java create mode 100644 app/src/main/java/com/dwq/camerademo/camera/CaptureRequestFactory.java create mode 100644 app/src/main/java/com/dwq/camerademo/camera/CompareSizesByArea.java create mode 100644 app/src/main/java/com/dwq/camerademo/event/ImageAvailableEvent.java create mode 100644 app/src/main/java/com/dwq/camerademo/utils/CameraUtils.java create mode 100644 app/src/main/java/com/dwq/camerademo/utils/CommonUtils.java create mode 100644 app/src/main/java/com/dwq/camerademo/utils/GlideImageLoader.java create mode 100644 app/src/main/java/com/dwq/camerademo/utils/ImageSaver.java create mode 100644 app/src/main/java/com/dwq/camerademo/utils/ImageUtils.java create mode 100644 app/src/main/java/com/dwq/camerademo/utils/SharedPreferencesUtil.java create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/selector.xml create mode 100644 app/src/main/res/layout/activity_camera2.xml create mode 100644 app/src/main/res/layout/activity_camera_surface.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/camera_button.png create mode 100644 app/src/main/res/mipmap-xhdpi/camera_button_pressed.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/test/java/com/dwq/camerademo/ExampleUnitTest.java create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore index 39b6783..39fb081 100644 --- a/.gitignore +++ b/.gitignore @@ -1,65 +1,9 @@ -# Built application files -*.apk -*.ap_ - -# Files for the ART/Dalvik VM -*.dex - -# Java class files -*.class - -# Generated files -bin/ -gen/ -out/ - -# Gradle files -.gradle/ -build/ - -# Local configuration file (sdk path, etc) -local.properties - -# Proguard folder generated by Eclipse -proguard/ - -# Log Files -*.log - -# Android Studio Navigation editor temp files -.navigation/ - -# Android Studio captures folder -captures/ - -# IntelliJ *.iml -.idea/workspace.xml -.idea/tasks.xml -.idea/gradle.xml -.idea/assetWizardSettings.xml -.idea/dictionaries -.idea/libraries -.idea/caches - -# Keystore files -# Uncomment the following line if you do not want to check your keystore files in. -#*.jks - -# External native build folder generated in Android Studio 2.2 and later +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures .externalNativeBuild - -# Google Services (e.g. APIs or Firebase) -google-services.json - -# Freeline -freeline.py -freeline/ -freeline_project_description.json - -# fastlane -fastlane/report.xml -fastlane/Preview.html -fastlane/screenshots -fastlane/test_output -fastlane/readme.md diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..7ac24c7 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..92b5adf --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + C:\Users\g\AppData\Roaming\Subversion + + + + + + 1.8 + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..0b27994 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..f0b855c --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,33 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 26 + defaultConfig { + applicationId "com.dwq.camerademo" + minSdkVersion 16 + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:26.1.0' + + // glide 图片加载库 + implementation('com.github.bumptech.glide:glide:4.6.1') { + exclude group: "com.android.support" + } + implementation 'com.jakewharton:butterknife:+' + annotationProcessor 'com.jakewharton:butterknife-compiler:+' + + compile 'org.greenrobot:eventbus:3.1.1' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/dwq/camerademo/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/dwq/camerademo/ExampleInstrumentedTest.java new file mode 100644 index 0000000..c4d25a3 --- /dev/null +++ b/app/src/androidTest/java/com/dwq/camerademo/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.dwq.camerademo; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.dwq.camerademo", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e0dab61 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/dwq/camerademo/App.java b/app/src/main/java/com/dwq/camerademo/App.java new file mode 100644 index 0000000..b55e5bc --- /dev/null +++ b/app/src/main/java/com/dwq/camerademo/App.java @@ -0,0 +1,29 @@ +package com.dwq.camerademo; + +import android.app.Application; +import android.os.Handler; + + +public class App extends Application { + + /** + * 启动照相Intent的RequestCode.自定义相机. + */ + public static final int TAKE_PHOTO_CUSTOM = 100; + /** + * 启动照相Intent的RequestCode.系统相机. + */ + public static final int TAKE_PHOTO_SYSTEM = 200; + /** + * 主线程Handler. + */ + public static Handler mHandler; + public static App sApp; + + @Override + public void onCreate() { + super.onCreate(); + sApp = this; + mHandler = new Handler(); + } +} diff --git a/app/src/main/java/com/dwq/camerademo/Camera2Activity.java b/app/src/main/java/com/dwq/camerademo/Camera2Activity.java new file mode 100644 index 0000000..a6adfa9 --- /dev/null +++ b/app/src/main/java/com/dwq/camerademo/Camera2Activity.java @@ -0,0 +1,105 @@ +package com.dwq.camerademo; + +import android.graphics.BitmapFactory; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.RequiresApi; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.Toast; + +import com.dwq.camerademo.camera.Camera2TextureView; +import com.dwq.camerademo.event.ImageAvailableEvent; +import com.dwq.camerademo.utils.CommonUtils; +import com.dwq.camerademo.utils.ImageSaver; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.io.File; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Unbinder; + +/** + * Created by DWQ on 2018/8/9. + * E-Mail:lomapa@163.com + */ +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) +public class Camera2Activity extends AppCompatActivity { + + private Unbinder unbinder; + + //拍照 + @BindView(R.id.cameraTextureView) + Camera2TextureView cameraTextureView; + + File mFile; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_NO_TITLE); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + setContentView(R.layout.activity_camera2); + unbinder = ButterKnife.bind(this); + EventBus.getDefault().register(this); + initView(); + } + + private void initView() { + + } + + + @Override + public void onResume() { + super.onResume(); + cameraTextureView.openCamera(); + } + + @Override + public void onPause() { + if (cameraTextureView != null) { + cameraTextureView.closeCamera(); + } + super.onPause(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (unbinder != null) { + unbinder.unbind(); + } + EventBus.getDefault().unregister(this); + } + + @OnClick(R.id.iv_camera_button) + public void onClick(View view) { + switch (view.getId()) { + case R.id.iv_camera_button:// 快门拍照 + Toast.makeText(this, "拍照!!!!", Toast.LENGTH_SHORT).show(); + cameraTextureView.takePicture(); + break; + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) //拍照完成后,拿到ImageReader,然后做保存图片的操作 + public void onImageReaderAvailable(ImageAvailableEvent.ImageReaderAvailable imageReaderAvailable) { + mFile = CommonUtils.createImageFile(System.currentTimeMillis() + ".jpg"); + new Thread(new ImageSaver(imageReaderAvailable.getImageReader(), this, mFile)).start(); + } + + @Subscribe(threadMode = ThreadMode.MAIN) //存储图像完成后,拿到ImagePath图片路径 + public void onImagePathAvailable(ImageAvailableEvent.ImagePathAvailable imagePathAvailable) { + setResult(200, getIntent().putExtra("imagePath", imagePathAvailable.getImagePath())); + finish(); + } +} diff --git a/app/src/main/java/com/dwq/camerademo/CameraActivity.java b/app/src/main/java/com/dwq/camerademo/CameraActivity.java new file mode 100644 index 0000000..8575fe1 --- /dev/null +++ b/app/src/main/java/com/dwq/camerademo/CameraActivity.java @@ -0,0 +1,185 @@ +package com.dwq.camerademo; + +import android.Manifest; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; +import android.hardware.Camera; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.Toast; + +import com.dwq.camerademo.camera.CameraSurfaceView; +import com.dwq.camerademo.utils.CameraUtils; +import com.dwq.camerademo.utils.CommonUtils; +import com.dwq.camerademo.utils.ImageUtils; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Unbinder; + +/** + * Created by DWQ on 2018/8/9. + * E-Mail:lomapa@163.com + */ + +public class CameraActivity extends AppCompatActivity implements View.OnClickListener { + + private static final int REQUEST_CAMERA = 0x01; + + private CameraSurfaceView mCameraSurfaceView; + + private int mOrientation; + + // CameraSurfaceView 容器包装类 + private FrameLayout mAspectLayout; + private boolean mCameraRequested; + private Unbinder unbinder; + private File mFile; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_NO_TITLE); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + setContentView(R.layout.activity_camera_surface); + unbinder = ButterKnife.bind(this); + // Android 6.0相机动态权限检查 + if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) + == PackageManager.PERMISSION_GRANTED) { + initView(); + } else { + ActivityCompat.requestPermissions(this, + new String[]{ + Manifest.permission.CAMERA, + Manifest.permission.WRITE_EXTERNAL_STORAGE + }, REQUEST_CAMERA); + } + } + + /** + * 初始化View + */ + private void initView() { + mAspectLayout = findViewById(R.id.layout_aspect); + mCameraSurfaceView = new CameraSurfaceView(this); + mAspectLayout.addView(mCameraSurfaceView); + mOrientation = CameraUtils.calculateCameraPreviewOrientation(CameraActivity.this); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + switch (requestCode) { + // 相机权限 + case REQUEST_CAMERA: + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + mCameraRequested = true; + initView(); + } + break; + } + } + + @Override + protected void onResume() { + super.onResume(); + if (mCameraRequested) { + CameraUtils.startPreview(); + } + } + + @Override + protected void onPause() { + super.onPause(); + CameraUtils.stopPreview(); + } + + + @OnClick(R.id.iv_camera_button) + public void onClick(View view) { + switch (view.getId()) { + case R.id.iv_camera_button:// 快门拍照 + Toast.makeText(this, "拍照!!!!", Toast.LENGTH_SHORT).show(); + takePicture(); + break; + } + } + + /** + * 拍照 + */ + private void takePicture() { + CameraUtils.takePicture(new Camera.ShutterCallback() { + @Override + public void onShutter() { + + } + }, null, new Camera.PictureCallback() { + @Override + public void onPictureTaken(byte[] data, Camera camera) { + CameraUtils.startPreview(); + Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); + mFile = CommonUtils.createImageFile(System.currentTimeMillis() + ".jpg"); + + if (bitmap != null) { + bitmap = ImageUtils.getRotatedBitmap(bitmap, mOrientation); + Matrix matrix = new Matrix(); + matrix.reset(); + matrix.setRotate(180); + Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, + bitmap.getWidth(), bitmap.getHeight(), matrix, true); + try { + FileOutputStream fout = new FileOutputStream(mFile); + BufferedOutputStream bos = new BufferedOutputStream(fout); + newBitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos); + bos.flush(); + bos.close(); + fout.close(); + } catch (IOException e) { + e.printStackTrace(); + } + setResult(200, getIntent().putExtra("imagePath", mFile.getAbsolutePath())); + finish(); + } + + } + }); + } + + + /** + * 切换相机 + */ + private void switchCamera() { + if (mCameraSurfaceView != null) { + CameraUtils.switchCamera(1 - CameraUtils.getCameraID(), mCameraSurfaceView.getHolder()); + // 切换相机后需要重新计算旋转角度 + mOrientation = CameraUtils.calculateCameraPreviewOrientation(CameraActivity.this); + } + } + + + @Override + protected void onDestroy() { + super.onDestroy(); + if (unbinder != null) { + unbinder.unbind(); + } + } +} diff --git a/app/src/main/java/com/dwq/camerademo/MainActivity.java b/app/src/main/java/com/dwq/camerademo/MainActivity.java new file mode 100644 index 0000000..eddd89a --- /dev/null +++ b/app/src/main/java/com/dwq/camerademo/MainActivity.java @@ -0,0 +1,72 @@ +package com.dwq.camerademo; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.View; +import android.widget.ImageView; + +import com.dwq.camerademo.utils.GlideImageLoader; + +import java.io.File; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Unbinder; + +public class MainActivity extends AppCompatActivity { + + + private Unbinder unbinder; + + @BindView(R.id.iv_display) + ImageView ivDisplay; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + unbinder = ButterKnife.bind(this); + } + + @OnClick({R.id.btn_camera1, R.id.btn_camera2}) + public void onClick(View view) { + Intent intent = null; + switch (view.getId()) { + case R.id.btn_camera1: + intent = new Intent(this, CameraActivity.class); + break; + case R.id.btn_camera2: + intent = new Intent(this, Camera2Activity.class); + break; + } + startActivityForResult(intent, App.TAKE_PHOTO_CUSTOM); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (unbinder != null) { + unbinder.unbind(); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode != RESULT_OK && resultCode != 200) + return; + + if (requestCode == App.TAKE_PHOTO_CUSTOM) { + File imageFile = new File(data.getStringExtra("imagePath")); + Log.d("imageFile", "imageFile = " + imageFile.getAbsolutePath()); +// FrescoUtils.load(imageFile.getAbsolutePath()).into(ivDisplay); + GlideImageLoader.loadImage(ivDisplay, imageFile); + } + + + } + +} diff --git a/app/src/main/java/com/dwq/camerademo/camera/BaseCamera2TextureView.java b/app/src/main/java/com/dwq/camerademo/camera/BaseCamera2TextureView.java new file mode 100644 index 0000000..2207057 --- /dev/null +++ b/app/src/main/java/com/dwq/camerademo/camera/BaseCamera2TextureView.java @@ -0,0 +1,254 @@ +package com.dwq.camerademo.camera; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.SurfaceTexture; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CameraManager; +import android.hardware.camera2.CaptureRequest; +import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; +import android.support.annotation.NonNull; +import android.support.annotation.RequiresApi; +import android.util.AttributeSet; +import android.util.Size; +import android.view.Surface; +import android.view.TextureView; +import android.view.WindowManager; + +import java.util.concurrent.Semaphore; + +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) +public abstract class BaseCamera2TextureView extends TextureView { + private int mRatioWidth = 0; + private int mRatioHeight = 0; + + public Context context; + public WindowManager windowManager; + public HandlerThread mBackgroundThread; + public Handler mBackgroundHandler; + public Handler mMainHandler; + + public String mCameraId; + public int cameraNum = 0; + public Size mPreviewSize; + public Semaphore mCameraOpenCloseLock = new Semaphore(1); + public int mSensorOrientation; + + protected CameraManager manager; + protected CameraDevice mCameraDevice; + protected CameraCaptureSession mCaptureSession; + protected Surface surface; + protected CameraCharacteristics mCameraCharacteristics; + + protected int textureViewWidth, textureViewHeight; + + + //TextureView的状态监听,TextureView好了之后,打开相机 + protected final SurfaceTextureListener mSurfaceTextureListener = new SurfaceTextureListener() { + @Override + public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) { + textureViewWidth = width; + textureViewHeight = height; + openCameraReal(width, height, 0); + } + + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) { + configureTransform(width, height); + } + + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) { + return true; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture texture) { + } + + }; + + + //****************************************************************************************** + // 初始化方法 + //******************************************************************************************** + + public BaseCamera2TextureView(Context context) { + this(context, null); + init(context); + } + + public BaseCamera2TextureView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + init(context); + } + + public BaseCamera2TextureView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context); + } + + private void init(Context c) { + this.context = c; + windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); + + } + + protected void setAspectRatio(int width, int height) { + if (width < 0 || height < 0) { + throw new IllegalArgumentException("Size cannot be negative."); + } + mRatioWidth = width; + mRatioHeight = height; + requestLayout(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + if (0 == mRatioWidth || 0 == mRatioHeight) { + setMeasuredDimension(width, height); + } else { + if (width < height * mRatioWidth / mRatioHeight) { + setMeasuredDimension(width, width * mRatioHeight / mRatioWidth); + } else { + setMeasuredDimension(height * mRatioWidth / mRatioHeight, height); + } + } + } + + + //****************************************************************************************** + // public 方法,供外部调用 + //******************************************************************************************** + + public void openCamera() { + startBackgroundThread(); + if (isAvailable()) { + openCameraReal(textureViewWidth, textureViewHeight, cameraNum); + } else { + setSurfaceTextureListener(mSurfaceTextureListener); + } + } + + public void closeCamera() { + closeCameraReal(); + stopBackgroundThread(); + } + + public void switchCamera() { + if (cameraNum == 0) { + cameraNum = 1; + } else if (cameraNum == 1) { + cameraNum = 0; + } + closeCameraReal(); + openCameraReal(textureViewWidth, textureViewHeight, cameraNum); + } + + + //****************************************************************************************** + // private 方法,内部调用 + //******************************************************************************************** + //后台线程 + private void startBackgroundThread() { + mBackgroundThread = new HandlerThread("CameraBackground"); + mBackgroundThread.start(); + mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); + mMainHandler = new Handler(context.getMainLooper()); + } + + private void stopBackgroundThread() { + if (mBackgroundHandler != null) { + mBackgroundThread.quitSafely(); + try { + mBackgroundThread.join(); + mBackgroundThread = null; + mBackgroundHandler = null; + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + + //打开相机,预览 + @SuppressLint("MissingPermission") + private void openCameraReal(int width, int height, final int cameraNum) { + configureCamera(width, height, cameraNum); + configureTransform(width, height); + + try { + manager.openCamera(mCameraId, deviceStateCallback, mBackgroundHandler); + } catch (CameraAccessException e) { + e.printStackTrace(); + } + } + + //监听,相机打开好后,进入预览 + protected final CameraDevice.StateCallback deviceStateCallback = new CameraDevice.StateCallback() { + @Override + public void onOpened(@NonNull CameraDevice cameraDevice) { + mCameraOpenCloseLock.release(); + mCameraDevice = cameraDevice; + createCameraPreviewSession(); + } + + @Override + public void onDisconnected(@NonNull CameraDevice cameraDevice) { + mCameraOpenCloseLock.release(); + cameraDevice.close(); + mCameraDevice = null; + } + + @Override + public void onError(@NonNull CameraDevice cameraDevice, int error) { + mCameraOpenCloseLock.release(); + cameraDevice.close(); + mCameraDevice = null; + } + + }; + + protected void initSurface() { + SurfaceTexture texture = getSurfaceTexture(); + assert texture != null; + texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); + surface = new Surface(texture); + } + + protected void closePreviewSession() { + if (mCaptureSession != null) { + mCaptureSession.close(); + mCaptureSession = null; + } + } + + protected void updatePreview(CaptureRequest captureRequest, CameraCaptureSession.CaptureCallback captureSessionCaptureCallback) { + try { + if (mCaptureSession != null && mBackgroundHandler != null) { + mCaptureSession.setRepeatingRequest(captureRequest, captureSessionCaptureCallback, mBackgroundHandler); + } + } catch (CameraAccessException e) { + e.printStackTrace(); + } + } + + + //abstract方法 + public abstract void configureCamera(int width, int height, int cameraNum); + + public abstract void configureTransform(int width, int height); + + public abstract void createCameraPreviewSession(); + + public abstract void closeCameraReal(); +} diff --git a/app/src/main/java/com/dwq/camerademo/camera/Camera2TextureView.java b/app/src/main/java/com/dwq/camerademo/camera/Camera2TextureView.java new file mode 100644 index 0000000..4de3268 --- /dev/null +++ b/app/src/main/java/com/dwq/camerademo/camera/Camera2TextureView.java @@ -0,0 +1,452 @@ + +package com.dwq.camerademo.camera; + +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.ImageFormat; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.SurfaceTexture; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraMetadata; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.TotalCaptureResult; +import android.hardware.camera2.params.MeteringRectangle; +import android.hardware.camera2.params.StreamConfigurationMap; +import android.media.ImageReader; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.RequiresApi; +import android.util.AttributeSet; +import android.util.Size; +import android.util.SparseIntArray; +import android.view.Surface; + +import com.dwq.camerademo.event.ImageAvailableEvent; + +import org.greenrobot.eventbus.EventBus; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) +public class Camera2TextureView extends BaseCamera2TextureView { + private int mState = STATE_PREVIEW; + private static final int STATE_PREVIEW = 0; + private static final int STATE_WAITING_LOCK = 1; + private static final int STATE_WAITING_PRECAPTURE = 2; + private static final int STATE_WAITING_NON_PRECAPTURE = 3; + private static final int STATE_PICTURE_TAKEN = 4; + + private CaptureRequest.Builder mPreviewRequestBuilder; + private CaptureRequest.Builder mCaptureStillBuilder; + private int mAfState = CameraMetadata.CONTROL_AF_STATE_INACTIVE; + + public static final SparseIntArray ORIENTATIONS = new SparseIntArray(); + + static { + ORIENTATIONS.append(Surface.ROTATION_0, 90); + ORIENTATIONS.append(Surface.ROTATION_90, 0); + ORIENTATIONS.append(Surface.ROTATION_180, 270); + ORIENTATIONS.append(Surface.ROTATION_270, 180); + } + + //****************************************************************************************** + // 初始化方法 + //******************************************************************************************** + + public Camera2TextureView(Context context) { + super(context); + } + + public Camera2TextureView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public Camera2TextureView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + + //****************************************************************************************** + // public 方法,供外部调用 + //******************************************************************************************** + + public void takePicture() { + lockFocus(); + } + + + //设置预览区域 + public void focusRegion(float x, float y) throws CameraAccessException { + try { + mCameraCharacteristics = manager.getCameraCharacteristics(mCameraId); + } catch (CameraAccessException e) { + e.printStackTrace(); + } + Rect rect = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); + int areaSize = 200; + int right = rect.right; + int bottom = rect.bottom; + int viewWidth = getWidth(); + int viewHeight = getHeight(); + int ll, rr; + Rect newRect; + int centerX = (int) x; + int centerY = (int) y; + ll = ((centerX * right) - areaSize) / viewWidth; + rr = ((centerY * bottom) - areaSize) / viewHeight; + int focusLeft = clamp(ll, 0, right); + int focusBottom = clamp(rr, 0, bottom); + newRect = new Rect(focusLeft, focusBottom, focusLeft + areaSize, focusBottom + areaSize); + MeteringRectangle meteringRectangle = new MeteringRectangle(newRect, 500); + MeteringRectangle[] meteringRectangleArr = {meteringRectangle}; + CaptureRequestFactory.setPreviewBuilderFocusRegion(mPreviewRequestBuilder, meteringRectangleArr); + updatePreview(mPreviewRequestBuilder.build(), captureSessionCaptureCallback); + CaptureRequestFactory.setPreviewBuilderFocusTrigger(mPreviewRequestBuilder); + mCaptureSession.capture(mPreviewRequestBuilder.build(), captureSessionCaptureCallback, mBackgroundHandler); + } + + + //拉长、缩小焦距 + public void changeFocusDistance(int distance) throws CameraAccessException { + mCameraCharacteristics = manager.getCameraCharacteristics(mCameraId); + Rect rect = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); + int radio = mCameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM).intValue() / 2; + int realRadio = mCameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM).intValue(); + int centerX = rect.centerX(); + int centerY = rect.centerY(); + int minMidth = (rect.right - ((distance * centerX) / 100 / radio) - 1) - ((distance * centerX / radio) / 100 + 8); + int minHeight = (rect.bottom - ((distance * centerY) / 100 / radio) - 1) - ((distance * centerY / radio) / 100 + 16); + if (minMidth < rect.right / realRadio || minHeight < rect.bottom / realRadio) { + return; + } + Rect newRect = new Rect((distance * centerX / radio) / 100 + 40, (distance * centerY / radio) / 100 + 40, rect.right - ((distance * centerX) / 100 / radio) - 1, rect.bottom - ((distance * centerY) / 100 / radio) - 1); + mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, newRect); + updatePreview(mPreviewRequestBuilder.build(), captureSessionCaptureCallback); + } + + + // 设置防手抖功能 + public void setSteadyPhoto(boolean isSteady) throws CameraAccessException { + CaptureRequestFactory.setPreviewBuilderSteady(mPreviewRequestBuilder, isSteady); + updatePreview(mPreviewRequestBuilder.build(), captureSessionCaptureCallback); + } + + //****************************************************************************************** + // 预览方法,内部调用 + //******************************************************************************************** + @Override + public void configureCamera(int width, int height, int cameraNum) { + try { + for (String cameraId : manager.getCameraIdList()) { + CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); + Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); + if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {// 前置 + mCameraId = cameraId; + } else { + continue; + } + + //设置图像输出 + StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + Size largest = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new CompareSizesByArea()); + initImageReader(largest); + mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height, largest); + int orientation = getResources().getConfiguration().orientation; + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight()); + } else { + setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth()); + } + } +// mCameraId = manager.getCameraIdList()[cameraNum]; + + } catch (CameraAccessException e) { + e.printStackTrace(); + } catch (NullPointerException e) { + e.printStackTrace(); + } + } + + private void initImageReader(Size largest) { + mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), ImageFormat.JPEG, /*maxImages*/5); + mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler); + } + + private Size chooseOptimalSize(Size[] choices, int width, int height, Size largest) { + List bigEnough = new ArrayList(); + for (Size size : choices) { + if (size.getHeight() == size.getWidth() * height / width) { + bigEnough.add(size); + } + } + + if (bigEnough.size() > 0) { + return Collections.max(bigEnough, new CompareSizesByArea()); + } else { + return largest; + } + } + + @Override + public void configureTransform(int width, int height) { + int rotation = windowManager.getDefaultDisplay().getRotation(); + final Matrix matrix = new Matrix(); + RectF viewRect = new RectF(0, 0, width, height); + RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth()); + float centerX = viewRect.centerX(); + float centerY = viewRect.centerY(); + if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { + bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); + matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); + float scale = Math.max( + (float) height / mPreviewSize.getHeight(), + (float) width / mPreviewSize.getWidth()); + matrix.postScale(scale, scale, centerX, centerY); + matrix.postRotate(90 * (rotation - 2), centerX, centerY); + } else if (Surface.ROTATION_180 == rotation) { + matrix.postRotate(180, centerX, centerY); + } + if (mMainHandler != null) { + mMainHandler.post(new Runnable() { + @Override + public void run() { + setTransform(matrix); + } + }); + } + } + + @Override + public void createCameraPreviewSession() { + try { + initSurface(); + mState = STATE_PREVIEW; + mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), captureSessionStateCallback, mBackgroundHandler); + } catch (CameraAccessException e) { + e.printStackTrace(); + } + } + + + //CaptureSession的状态监听 + protected CameraCaptureSession.StateCallback captureSessionStateCallback = new CameraCaptureSession.StateCallback() { + @Override + public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { + if (null == mCameraDevice) { + return; + } + mCaptureSession = cameraCaptureSession; + try { +// boolean antiShake = SharedPreferencesUtil.getInstance().getBoolean(Constants.IMAGE_ANTI_SHAKE, false); + mPreviewRequestBuilder = CaptureRequestFactory.createPreviewBuilder(mCameraDevice, surface); + CaptureRequestFactory.setPreviewBuilderPreview(mPreviewRequestBuilder); +// CaptureRequestFactory.setPreviewBuilderSteady(mPreviewRequestBuilder, antiShake); + updatePreview(mPreviewRequestBuilder.build(), captureSessionCaptureCallback); + } catch (CameraAccessException e) { + e.printStackTrace(); + } + } + + @Override + public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { + } + }; + + + //预览和拍照数据获取监听,获取到原始数据后做进一步处理 + CameraCaptureSession.CaptureCallback captureSessionCaptureCallback = new CameraCaptureSession.CaptureCallback() { + @Override + public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) { + checkState(request, partialResult); + } + + @Override + public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { + checkState(request, result); + } + + private void checkState(CaptureRequest request, CaptureResult result) { + Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); + Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); + switch (mState) { + case STATE_PREVIEW: //对预览数据处理,根据afState即聚焦状态,如果为空、跟上次一样不做处理 + break; + + case STATE_WAITING_LOCK: + if (cameraNum == 0) { + mState = STATE_PICTURE_TAKEN; + doStillCapture(); + break; + } +// if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState || CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState +// || CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED == afState || CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED == afState) { +// if (afState == null) { +// doStillCapture(); +// } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState +// || CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) { +// aeState = result.get(CaptureResult.CONTROL_AE_STATE); +// if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { +// mState = STATE_PICTURE_TAKEN; +// doStillCapture(); +// } else { +// tryCaptureAgain(); +// } +// } +// } + +// /** +// * 判断可以立即拍摄的autoFocusState增加到4种. +// */ +// if (afState == null) { +// doStillCapture(); +// } else if (CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED == afState || +// CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState || +// CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) { +// // CONTROL_AE_STATE can be null on some devices +//// Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); +// /** +// * 判断可以立即拍摄的autoExposureState增加到4种. +// */ +// if (aeState == null || +// aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED || +// aeState == CaptureResult.CONTROL_AE_STATE_LOCKED || +// aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED) { +// mState = STATE_PICTURE_TAKEN; +// doStillCapture(); +// } else { +// tryCaptureAgain(); +// } +// } + break; + + case STATE_WAITING_PRECAPTURE: + if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || + aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) { + mState = STATE_WAITING_NON_PRECAPTURE; + } + break; + + case STATE_WAITING_NON_PRECAPTURE: + aeState = result.get(CaptureResult.CONTROL_AE_STATE); + if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { + mState = STATE_PICTURE_TAKEN; + doStillCapture(); + } + break; + } + } + + }; + + private void doStillCapture() { + try { + if (mCaptureStillBuilder == null) { + mCaptureStillBuilder = CaptureRequestFactory.createCaptureStillBuilder(mCameraDevice, mImageReader.getSurface()); + } +// int quality = SharePreferenceUtils.getInstance(context, Constants.SETTINGS).getInt(Constants.IMAGE_QUALITY, 90); +// CaptureRequestFactory.setCaptureBuilderStill(mCaptureStillBuilder, windowManager, quality); + // 前置拍照需要将图像旋转270 +// mCaptureStillBuilder.set(CaptureRequest.JPEG_ORIENTATION, 270); + mCaptureSession.stopRepeating(); + mCaptureSession.capture(mCaptureStillBuilder.build(), captureStillCallback, null); + } catch (CameraAccessException e) { + e.printStackTrace(); + } + } + + private CameraCaptureSession.CaptureCallback captureStillCallback = new CameraCaptureSession.CaptureCallback() { + @Override + public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) { + unlockFocus(); + } + }; + + private void tryCaptureAgain() { + try { + if (mCaptureStillBuilder != null) { + CaptureRequestFactory.setCaptureBuilderPrecapture(mCaptureStillBuilder); + mCaptureSession.capture(mCaptureStillBuilder.build(), captureSessionCaptureCallback, mBackgroundHandler); + } + } catch (CameraAccessException e) { + e.printStackTrace(); + } + } + + protected ImageReader mImageReader; + protected final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() { + @Override + public void onImageAvailable(ImageReader reader) { + ImageAvailableEvent.ImageReaderAvailable imageReaderAvailable = new ImageAvailableEvent.ImageReaderAvailable(); + imageReaderAvailable.setImageReader(reader); + EventBus.getDefault().post(imageReaderAvailable); + } + }; + + + //拍照,要先发锁定焦点的preview请求,待captureSessionCaptureCallback回调,进入STATE_WAITING_CAPTURE从而可以调用真的拍照请求。 + private void lockFocus() { + try { + CaptureRequestFactory.setPreviewBuilderLockfocus(mPreviewRequestBuilder); + mState = STATE_WAITING_LOCK; + mCaptureSession.capture(mPreviewRequestBuilder.build(), captureSessionCaptureCallback, mBackgroundHandler); + } catch (CameraAccessException e) { + e.printStackTrace(); + } + } + + //解除焦点锁定,进入预览状态 + public void unlockFocus() { + try { + if (mCaptureSession != null && mPreviewRequestBuilder != null && captureSessionCaptureCallback != null && mBackgroundHandler != null) { + CaptureRequestFactory.setPreviewBuilderUnlockfocus(mPreviewRequestBuilder); + mCaptureSession.capture(mPreviewRequestBuilder.build(), captureSessionCaptureCallback, mBackgroundHandler); + + mState = STATE_PREVIEW; + mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), captureSessionCaptureCallback, mBackgroundHandler); + } + } catch (CameraAccessException e) { + e.printStackTrace(); + } + } + + + @Override + public void closeCameraReal() { + try { + mCameraOpenCloseLock.acquire(); + if (null != mCaptureSession) { + mCaptureSession.close(); + mCaptureSession = null; + } + if (null != mCameraDevice) { + mCameraDevice.close(); + mCameraDevice = null; + } + } catch (InterruptedException e) { + throw new RuntimeException("Interrupted while trying to lock camera closing.", e); + } finally { + mCameraOpenCloseLock.release(); + } + } + + + private int clamp(int x, int min, int max) { + if (x < min) { + return min; + } else if (x > max) { + return max; + } else { + return x; + } + } + + +} diff --git a/app/src/main/java/com/dwq/camerademo/camera/CameraSurfaceView.java b/app/src/main/java/com/dwq/camerademo/camera/CameraSurfaceView.java new file mode 100644 index 0000000..129da41 --- /dev/null +++ b/app/src/main/java/com/dwq/camerademo/camera/CameraSurfaceView.java @@ -0,0 +1,78 @@ +package com.dwq.camerademo.camera; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.WindowManager; + +import com.dwq.camerademo.utils.CameraUtils; + +/** + * Created by DWQ on 2018/8/9. + * E-Mail:lomapa@163.com + */ + +public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback { + + private static final String TAG = CameraSurfaceView.class.getSimpleName(); + + private SurfaceHolder mSurfaceHolder; + + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = getScreenWidth(); + int height = (getScreenWidth() * 4) / 3; + setMeasuredDimension(width, height); + } + + /** + * 得到屏幕宽度 + * + * @return + */ + private int getScreenWidth() { + WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); + DisplayMetrics displayMetrics = new DisplayMetrics(); + windowManager.getDefaultDisplay().getMetrics(displayMetrics); + return displayMetrics.widthPixels; + } + + public CameraSurfaceView(Context context) { + super(context); + init(); + } + + public CameraSurfaceView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public CameraSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + mSurfaceHolder = getHolder(); + mSurfaceHolder.addCallback(this); + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + CameraUtils.openFrontalCamera(CameraUtils.DESIRED_PREVIEW_FPS); + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + CameraUtils.startPreviewDisplay(holder); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + CameraUtils.releaseCamera(); + } +} + diff --git a/app/src/main/java/com/dwq/camerademo/camera/CaptureRequestFactory.java b/app/src/main/java/com/dwq/camerademo/camera/CaptureRequestFactory.java new file mode 100644 index 0000000..18dcdc8 --- /dev/null +++ b/app/src/main/java/com/dwq/camerademo/camera/CaptureRequestFactory.java @@ -0,0 +1,116 @@ +package com.dwq.camerademo.camera; + +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CameraMetadata; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.params.MeteringRectangle; +import android.os.Build; +import android.support.annotation.RequiresApi; +import android.util.Log; +import android.view.Surface; + +import java.util.List; + +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) +public class CaptureRequestFactory +{ + //创建预览请求,后面的setXXX方法是根据不同情况设置预览参数 + public static CaptureRequest.Builder createPreviewBuilder(CameraDevice cameraDevice, Surface surface) throws CameraAccessException + { + CaptureRequest.Builder previewBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); + previewBuilder.addTarget(surface); + return previewBuilder; + } + + //设置预览-自动聚焦模式 + public static void setPreviewBuilderPreview(CaptureRequest.Builder previewBuilder) throws CameraAccessException + { + Log.d("AutoFocus","自动聚焦!"); + previewBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); + } + + //设置预览-锁定焦点 + public static void setPreviewBuilderLockfocus(CaptureRequest.Builder previewBuilder) throws CameraAccessException + { + previewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START); + } + + //设置预览-解除锁定焦点 + public static void setPreviewBuilderUnlockfocus(CaptureRequest.Builder previewBuilder) throws CameraAccessException + { + previewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); + previewBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); + } + + //设置预览-聚焦区域 + public static void setPreviewBuilderFocusRegion(CaptureRequest.Builder previewBuilder, MeteringRectangle[] meteringRectangleArr) throws CameraAccessException + { + previewBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, meteringRectangleArr); + previewBuilder.set(CaptureRequest.CONTROL_AE_REGIONS, meteringRectangleArr); + previewBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO); + } + + //设置预览-开始手动聚焦 + public static void setPreviewBuilderFocusTrigger(CaptureRequest.Builder previewBuilder) throws CameraAccessException + { + previewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START); + previewBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_START); + } + + + + // 设置预览-防手抖 + public static CaptureRequest setPreviewBuilderSteady(CaptureRequest.Builder previewBuilder, boolean antiShake) throws CameraAccessException + { + if(antiShake){ + previewBuilder.set(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE, CameraMetadata.LENS_OPTICAL_STABILIZATION_MODE_ON); + }else { + previewBuilder.set(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE, CameraMetadata.LENS_OPTICAL_STABILIZATION_MODE_OFF); + } + return previewBuilder.build(); + } + + + //创建拍照请求 + public static CaptureRequest.Builder createCaptureStillBuilder(CameraDevice cameraDevice, Surface surface) throws CameraAccessException + { + CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); + captureBuilder.addTarget(surface); + return captureBuilder; + } + + + //设置拍照模式-连续拍摄 + public static void setCaptureBuilderPrecapture(CaptureRequest.Builder captureBuilder) throws CameraAccessException + { + captureBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); + } + + + //设置拍照模式-延时拍着 + public static void setCaptureBuilderDelay(CaptureRequest.Builder captureBuilder, long nanoseconds) + { + captureBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, nanoseconds); + } + + + + + //创建录像请求 + public static CaptureRequest.Builder createRecordBuilder(CameraDevice cameraDevice, List surfaces) throws CameraAccessException + { + CaptureRequest.Builder recordBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); + recordBuilder.addTarget(surfaces.get(0)); + recordBuilder.addTarget(surfaces.get(1)); + return recordBuilder; + } + + //设置预览模式-录像预览 + public static void setPreviewBuilderRecordPreview(CaptureRequest.Builder previewBuilder) throws CameraAccessException + { + previewBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO); +// previewBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); + } + +} diff --git a/app/src/main/java/com/dwq/camerademo/camera/CompareSizesByArea.java b/app/src/main/java/com/dwq/camerademo/camera/CompareSizesByArea.java new file mode 100644 index 0000000..a6bd2b9 --- /dev/null +++ b/app/src/main/java/com/dwq/camerademo/camera/CompareSizesByArea.java @@ -0,0 +1,22 @@ +package com.dwq.camerademo.camera; + +import android.os.Build; +import android.support.annotation.RequiresApi; +import android.util.Size; + +import java.util.Comparator; + +/** + * Created by Razer on 2017/4/9. + */ +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) +public class CompareSizesByArea implements Comparator +{ + @Override + public int compare(Size lhs, Size rhs) + { + // We cast here to ensure the multiplications won't overflow + return Long.signum((long) lhs.getWidth() * lhs.getHeight() - + (long) rhs.getWidth() * rhs.getHeight()); + } +} diff --git a/app/src/main/java/com/dwq/camerademo/event/ImageAvailableEvent.java b/app/src/main/java/com/dwq/camerademo/event/ImageAvailableEvent.java new file mode 100644 index 0000000..19a7cd7 --- /dev/null +++ b/app/src/main/java/com/dwq/camerademo/event/ImageAvailableEvent.java @@ -0,0 +1,40 @@ +package com.dwq.camerademo.event; + + +import android.media.ImageReader; + +public class ImageAvailableEvent +{ + public static class ImageReaderAvailable + { + private ImageReader imageReader; + + public ImageReader getImageReader() + { + return imageReader; + } + + public void setImageReader(ImageReader imageReader) + { + this.imageReader = imageReader; + } + } + + + public static class ImagePathAvailable + { + private String imagePath; + + public String getImagePath() + { + return imagePath; + } + + public void setImagePath(String imagePath) + { + this.imagePath = imagePath; + } + } + + +} diff --git a/app/src/main/java/com/dwq/camerademo/utils/CameraUtils.java b/app/src/main/java/com/dwq/camerademo/utils/CameraUtils.java new file mode 100644 index 0000000..ca10899 --- /dev/null +++ b/app/src/main/java/com/dwq/camerademo/utils/CameraUtils.java @@ -0,0 +1,387 @@ +package com.dwq.camerademo.utils; + +import android.app.Activity; +import android.hardware.Camera; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; + +import java.io.IOException; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Created by DWQ on 2018/8/9. + * E-Mail:lomapa@163.com + */ + +public class CameraUtils { + + // 相机默认宽高,相机的宽度和高度跟屏幕坐标不一样,手机屏幕的宽度和高度是反过来的。 + public static final int DEFAULT_WIDTH = 1440; + public static final int DEFAULT_HEIGHT = 1080; + public static final int DESIRED_PREVIEW_FPS = 30; + + private static int mCameraID = Camera.CameraInfo.CAMERA_FACING_FRONT; + private static Camera mCamera; + private static int mCameraPreviewFps; + private static int mOrientation = 0; + + /** + * 打开相机,默认打开前置相机 + * + * @param expectFps + */ + public static void openFrontalCamera(int expectFps) { + if (mCamera != null) { + throw new RuntimeException("camera already initialized!"); + } + Camera.CameraInfo info = new Camera.CameraInfo(); + int numCameras = Camera.getNumberOfCameras(); + for (int i = 0; i < numCameras; i++) { + Camera.getCameraInfo(i, info); + if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { + mCamera = Camera.open(i); + mCameraID = info.facing; + break; + } + } + // 如果没有前置摄像头,则打开默认的后置摄像头 + if (mCamera == null) { + mCamera = Camera.open(); + mCameraID = Camera.CameraInfo.CAMERA_FACING_BACK; + } + // 没有摄像头时,抛出异常 + if (mCamera == null) { + throw new RuntimeException("Unable to open camera"); + } + + Camera.Parameters parameters = mCamera.getParameters(); + mCameraPreviewFps = CameraUtils.chooseFixedPreviewFps(parameters, expectFps * 1000); + parameters.setRecordingHint(true); + mCamera.setParameters(parameters); + setPreviewSize(mCamera, CameraUtils.DEFAULT_WIDTH, CameraUtils.DEFAULT_HEIGHT); + setPictureSize(mCamera, CameraUtils.DEFAULT_WIDTH, CameraUtils.DEFAULT_HEIGHT); + mCamera.setDisplayOrientation(mOrientation); + } + + /** + * 根据ID打开相机 + * + * @param cameraID + * @param expectFps + */ + public static void openCamera(int cameraID, int expectFps) { + if (mCamera != null) { + throw new RuntimeException("camera already initialized!"); + } + mCamera = Camera.open(cameraID); + if (mCamera == null) { + throw new RuntimeException("Unable to open camera"); + } + mCameraID = cameraID; + Camera.Parameters parameters = mCamera.getParameters(); + mCameraPreviewFps = CameraUtils.chooseFixedPreviewFps(parameters, expectFps * 1000); + parameters.setRecordingHint(true); + mCamera.setParameters(parameters); + setPreviewSize(mCamera, CameraUtils.DEFAULT_WIDTH, CameraUtils.DEFAULT_HEIGHT); + setPictureSize(mCamera, CameraUtils.DEFAULT_WIDTH, CameraUtils.DEFAULT_HEIGHT); + mCamera.setDisplayOrientation(mOrientation); + } + + /** + * 开始预览 + * + * @param holder + */ + public static void startPreviewDisplay(SurfaceHolder holder) { + if (mCamera == null) { + throw new IllegalStateException("Camera must be set when start preview"); + } + try { + mCamera.setPreviewDisplay(holder); + mCamera.startPreview(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 切换相机 + * + * @param cameraID + */ + public static void switchCamera(int cameraID, SurfaceHolder holder) { + if (mCameraID == cameraID) { + return; + } + mCameraID = cameraID; + // 释放原来的相机 + releaseCamera(); + // 打开相机 + openCamera(cameraID, CameraUtils.DESIRED_PREVIEW_FPS); + // 打开预览 + startPreviewDisplay(holder); + } + + /** + * 释放相机 + */ + public static void releaseCamera() { + if (mCamera != null) { + mCamera.stopPreview(); + mCamera.release(); + mCamera = null; + } + } + + /** + * 开始预览 + */ + public static void startPreview() { + if (mCamera != null) { + mCamera.startPreview(); + } + } + + /** + * 停止预览 + */ + public static void stopPreview() { + if (mCamera != null) { + mCamera.stopPreview(); + } + } + + /** + * 拍照 + */ + public static void takePicture(Camera.ShutterCallback shutterCallback, + Camera.PictureCallback rawCallback, + Camera.PictureCallback pictureCallback) { + if (mCamera != null) { + mCamera.takePicture(shutterCallback, rawCallback, pictureCallback); + } + } + + /** + * 设置预览大小 + * + * @param camera + * @param expectWidth + * @param expectHeight + */ + public static void setPreviewSize(Camera camera, int expectWidth, int expectHeight) { + Camera.Parameters parameters = camera.getParameters(); + for (Camera.Size size : parameters.getSupportedPreviewSizes()) { + Log.d("SupportSize", "size = " + size.width + "*" + size.height); + } + Camera.Size size = calculatePerfectSize(parameters.getSupportedPreviewSizes(), + expectWidth, expectHeight); + parameters.setPreviewSize(size.width, size.height); + camera.setParameters(parameters); + } + + /** + * 获取预览大小 + * + * @return + */ + public static Camera.Size getPreviewSize() { + if (mCamera != null) { + return mCamera.getParameters().getPreviewSize(); + } + return null; + } + + /** + * 设置拍摄的照片大小 + * + * @param camera + * @param expectWidth + * @param expectHeight + */ + public static void setPictureSize(Camera camera, int expectWidth, int expectHeight) { + Camera.Parameters parameters = camera.getParameters(); + Camera.Size size = calculatePerfectSize(parameters.getSupportedPictureSizes(), + expectWidth, expectHeight); + parameters.setPictureSize(size.width, size.height); + camera.setParameters(parameters); + } + + /** + * 获取照片大小 + * + * @return + */ + public static Camera.Size getPictureSize() { + if (mCamera != null) { + return mCamera.getParameters().getPictureSize(); + } + return null; + } + + /** + * 计算最完美的Size + * + * @param sizes + * @param expectWidth + * @param expectHeight + * @return + */ + public static Camera.Size calculatePerfectSize(List sizes, int expectWidth, + int expectHeight) { + sortList(sizes); // 根据宽度进行排序 + Camera.Size result = sizes.get(0); + boolean widthOrHeight = false; // 判断存在宽或高相等的Size + // 辗转计算宽高最接近的值 + for (Camera.Size size : sizes) { + // 如果宽高相等,则直接返回 + if (size.width == expectWidth && size.height == expectHeight) { + result = size; + break; + } + // 仅仅是宽度相等,计算高度最接近的size + if (size.width == expectWidth) { + widthOrHeight = true; + if (Math.abs(result.height - expectHeight) + > Math.abs(size.height - expectHeight)) { + result = size; + } + } + // 高度相等,则计算宽度最接近的Size + else if (size.height == expectHeight) { + widthOrHeight = true; + if (Math.abs(result.width - expectWidth) + > Math.abs(size.width - expectWidth)) { + result = size; + } + } + // 如果之前的查找不存在宽或高相等的情况,则计算宽度和高度都最接近的期望值的Size + else if (!widthOrHeight) { + if (Math.abs(result.width - expectWidth) + > Math.abs(size.width - expectWidth) + && Math.abs(result.height - expectHeight) + > Math.abs(size.height - expectHeight)) { + result = size; + } + } + } + return result; + } + + /** + * 排序 + * + * @param list + */ + private static void sortList(List list) { + Collections.sort(list, new Comparator() { + @Override + public int compare(Camera.Size pre, Camera.Size after) { + if (pre.width > after.width) { + return 1; + } else if (pre.width < after.width) { + return -1; + } + return 0; + } + }); + } + + /** + * 选择合适的FPS + * + * @param parameters + * @param expectedThoudandFps 期望的FPS + * @return + */ + public static int chooseFixedPreviewFps(Camera.Parameters parameters, int expectedThoudandFps) { + List supportedFps = parameters.getSupportedPreviewFpsRange(); + for (int[] entry : supportedFps) { + if (entry[0] == entry[1] && entry[0] == expectedThoudandFps) { + parameters.setPreviewFpsRange(entry[0], entry[1]); + return entry[0]; + } + } + int[] temp = new int[2]; + int guess; + parameters.getPreviewFpsRange(temp); + if (temp[0] == temp[1]) { + guess = temp[0]; + } else { + guess = temp[1] / 2; + } + return guess; + } + + /** + * 设置预览角度,setDisplayOrientation本身只能改变预览的角度 + * previewFrameCallback以及拍摄出来的照片是不会发生改变的,拍摄出来的照片角度依旧不正常的 + * 拍摄的照片需要自行处理 + * 这里Nexus5X的相机简直没法吐槽,后置摄像头倒置了,切换摄像头之后就出现问题了。 + * + * @param activity + */ + public static int calculateCameraPreviewOrientation(Activity activity) { + Camera.CameraInfo info = new Camera.CameraInfo(); + Camera.getCameraInfo(mCameraID, info); + int rotation = activity.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; + } else { + result = (info.orientation - degrees + 360) % 360; + } + mOrientation = result; + return result; + } + + + /** + * 获取当前的Camera ID + * + * @return + */ + public static int getCameraID() { + return mCameraID; + } + + /** + * 获取当前预览的角度 + * + * @return + */ + public static int getPreviewOrientation() { + return mOrientation; + } + + /** + * 获取FPS(千秒值) + * + * @return + */ + public static int getCameraPreviewThousandFps() { + return mCameraPreviewFps; + } +} + diff --git a/app/src/main/java/com/dwq/camerademo/utils/CommonUtils.java b/app/src/main/java/com/dwq/camerademo/utils/CommonUtils.java new file mode 100644 index 0000000..be48b69 --- /dev/null +++ b/app/src/main/java/com/dwq/camerademo/utils/CommonUtils.java @@ -0,0 +1,77 @@ +package com.dwq.camerademo.utils; + +import android.content.Context; +import android.content.res.Resources; +import android.os.Build; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.WindowManager; + + +import com.dwq.camerademo.App; + +import java.io.File; + +public class CommonUtils { + + /** + * dp转px + * + * @param dpValue dp + * @return px + */ + public static int dp2px(float dpValue) { + final float scale = App.sApp.getResources().getDisplayMetrics().density; + return (int) (dpValue * scale + 0.5f); + } + + public static int px2dp(float pxValue) { + final float scale = App.sApp.getResources().getDisplayMetrics().density; + return (int) (pxValue / scale + 0.5f); + } + + /** + * 判断设备是否具有虚拟导航栏 + * @return 设备是否具有虚拟导航栏 + */ + public static boolean hasNavigationBar() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) return true; + WindowManager wm = (WindowManager) App.sApp.getSystemService(Context.WINDOW_SERVICE); + Display d = wm.getDefaultDisplay(); + DisplayMetrics realDisplayMetrics = new DisplayMetrics(); + d.getRealMetrics(realDisplayMetrics); + int realHeight = realDisplayMetrics.heightPixels; + int realWidth = realDisplayMetrics.widthPixels; + DisplayMetrics displayMetrics = new DisplayMetrics(); + d.getMetrics(displayMetrics); + int displayHeight = displayMetrics.heightPixels; + int displayWidth = displayMetrics.widthPixels; + return (realWidth - displayWidth) > 0 || (realHeight - displayHeight) > 0; + } + + /** + * 得到以px为单位的虚拟导航栏高度,若设备没有虚拟导航栏,返回0. + * @return 虚拟导航栏高度(px),若设备没有虚拟导航栏,返回0. + */ + public static int getNavigationBarHeightInPx() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) return dp2px(48); + int navBarHeightInPx = 0; + Resources rs = App.sApp.getResources(); + int id = rs.getIdentifier("navigation_bar_height", "dimen", "android"); + if (id > 0 && hasNavigationBar()) navBarHeightInPx = rs.getDimensionPixelSize(id); + return navBarHeightInPx; + } + + /** + * 创建File对象,对应于data/data/${packageName}/cache/fileName. + * + * @param fileName 文件名 + * @return File + */ + @SuppressWarnings("ResultOfMethodCallIgnored") + public static File createImageFile(String fileName) { + File dir = new File(App.sApp.getExternalCacheDir(), "images"); + if (!dir.exists()) dir.mkdirs(); + return new File(dir, fileName); + } +} diff --git a/app/src/main/java/com/dwq/camerademo/utils/GlideImageLoader.java b/app/src/main/java/com/dwq/camerademo/utils/GlideImageLoader.java new file mode 100644 index 0000000..aba9da4 --- /dev/null +++ b/app/src/main/java/com/dwq/camerademo/utils/GlideImageLoader.java @@ -0,0 +1,129 @@ +package com.dwq.camerademo.utils; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.support.annotation.DrawableRes; +import android.support.annotation.Nullable; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.RequestOptions; +import com.bumptech.glide.request.target.Target; +import com.bumptech.glide.signature.MediaStoreSignature; +import com.bumptech.glide.signature.ObjectKey; + +import java.io.File; + +/** + * Created by dwq on 2018/3/16/016. + * e-mail:lomapa@163.com + * Glide 工具类 + */ + +public class GlideImageLoader { + + + public GlideImageLoader(int defaultImage) { + this.defaultImage = defaultImage; + } + + private int defaultImage; + + /** + * Url加载图片 + * + * @param view + * @param url + * @param defaultImage + */ + public static void loadImage(ImageView view, String url, int defaultImage) { + loadImage(view, url, defaultImage, -1, false, false); + } + + public static void loadHeaderImage(ImageView imageView, String url, int defaultImage) { + loadImage(imageView, url, defaultImage, -1, true, false); + } + + public static void loadImage(ImageView view, String url) { + loadImage(view, url, -1); + } + + public static void loadImage(ImageView view, File imgFile) { + loadImage(view, imgFile, -1, -1, false, true); + } + + + public static void loadImageFitCenter(ImageView view, String url) { + loadImage(view, url, -1, -1, false, true); + } + + /** + * 加载Bitmap + * + * @param imageView + * @param bitmap + * @param defaultImage + */ + public static void loadBmpImage(ImageView imageView, Bitmap bitmap, int defaultImage) { + loadImage(imageView, bitmap, defaultImage, -1, false, false); + } + + @SuppressLint("CheckResult") + private static void loadImage(final ImageView view, Object img + , @DrawableRes int defaultImage + , @DrawableRes int errorImage + , boolean isUpdateCache + , boolean isFitCenter + ) { + // 不能崩 + if (view == null) { +// LogUtils.e("GlideUtils -> display -> imageView is null"); + return; + } + Context context = view.getContext(); + // View你还活着吗? + if (context instanceof Activity) { + if (((Activity) context).isFinishing()) { + return; + } + } + + RequestOptions options = new RequestOptions().centerCrop(); + if (defaultImage != -1) { + options.placeholder(defaultImage); + } + if (errorImage != -1) { + options.error(errorImage); + } + // 为头像图片缓存添加signature来判断是否更新缓存 + if (isUpdateCache) { + options.signature(new ObjectKey(SharedPreferencesUtil.getInstance() + .getString("head_signature"))); + } + if (isFitCenter) { + options.fitCenter(); + } + Glide.with(context) + .load(img) + .apply(options) + .listener(new RequestListener() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + return false; + } + + @Override + public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + return false; + } + }).into(view); + } + +} diff --git a/app/src/main/java/com/dwq/camerademo/utils/ImageSaver.java b/app/src/main/java/com/dwq/camerademo/utils/ImageSaver.java new file mode 100644 index 0000000..3340322 --- /dev/null +++ b/app/src/main/java/com/dwq/camerademo/utils/ImageSaver.java @@ -0,0 +1,94 @@ +package com.dwq.camerademo.utils; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; +import android.media.Image; +import android.media.ImageReader; +import android.os.Build; +import android.support.annotation.RequiresApi; + +import com.dwq.camerademo.event.ImageAvailableEvent; + +import org.greenrobot.eventbus.EventBus; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +@RequiresApi(api = Build.VERSION_CODES.KITKAT) +public class ImageSaver implements Runnable { + private ImageReader mImageReader; + private Context context; + private File file; + + + public ImageSaver(ImageReader mImageReader, Context context, File file) { + this.mImageReader = mImageReader; + this.context = context; + this.file = file; + } + + + @Override + public void run() { + Image image = mImageReader.acquireLatestImage(); + + ByteBuffer buffer = image.getPlanes()[0].getBuffer(); + byte[] bytes = new byte[buffer.remaining()]; + buffer.get(bytes); + Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); + + if (bitmap != null) { + Matrix matrix = new Matrix(); + matrix.reset(); + matrix.setRotate(270); + Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, + bitmap.getWidth(), bitmap.getHeight(), matrix, true); + try { + FileOutputStream fout = new FileOutputStream(file); + BufferedOutputStream bos = new BufferedOutputStream(fout); + newBitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos); + bos.flush(); + bos.close(); + fout.close(); + } catch (IOException e) { + e.printStackTrace(); + } + ImageAvailableEvent.ImagePathAvailable imagePathAvailable = new ImageAvailableEvent.ImagePathAvailable(); + imagePathAvailable.setImagePath(file.getAbsolutePath()); + EventBus.getDefault().post(imagePathAvailable); + + } + } + + + /** + * 保存 + * + * @param bytes + * @param file + * @throws IOException + */ + private void save(byte[] bytes, File file) throws IOException { + OutputStream os = null; + try { + os = new FileOutputStream(file); + os.write(bytes); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (os != null) { + os.close(); + } + } + } + +} diff --git a/app/src/main/java/com/dwq/camerademo/utils/ImageUtils.java b/app/src/main/java/com/dwq/camerademo/utils/ImageUtils.java new file mode 100644 index 0000000..4f7f29e --- /dev/null +++ b/app/src/main/java/com/dwq/camerademo/utils/ImageUtils.java @@ -0,0 +1,39 @@ +package com.dwq.camerademo.utils; + +import android.graphics.Bitmap; +import android.graphics.Matrix; + +/** + * Created by DWQ on 2018/8/9. + * E-Mail:lomapa@163.com + */ + +public class ImageUtils { + + /** + * 旋转图片 + * @param bitmap + * @param rotation + * @Return + */ + public static Bitmap getRotatedBitmap(Bitmap bitmap, int rotation) { + Matrix matrix = new Matrix(); + matrix.postRotate(rotation); + return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), + bitmap.getHeight(), matrix, false); + } + + /** + * 镜像翻转图片 + * @param bitmap + * @Return + */ + public static Bitmap getFlipBitmap(Bitmap bitmap) { + Matrix matrix = new Matrix(); + matrix.setScale(-1, 1); + matrix.postTranslate(bitmap.getWidth(), 0); + return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), + bitmap.getHeight(), matrix, false); + } +} + diff --git a/app/src/main/java/com/dwq/camerademo/utils/SharedPreferencesUtil.java b/app/src/main/java/com/dwq/camerademo/utils/SharedPreferencesUtil.java new file mode 100644 index 0000000..accc984 --- /dev/null +++ b/app/src/main/java/com/dwq/camerademo/utils/SharedPreferencesUtil.java @@ -0,0 +1,228 @@ +/** + * Copyright 2016 JustWayward Team + *

+ * 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.dwq.camerademo.utils; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Build; +import android.util.Base64; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.StreamCorruptedException; +import java.util.Map; +import java.util.Set; + +/** + * Created by lfh on 2016/8/13. + */ +public class SharedPreferencesUtil { + + private static SharedPreferencesUtil prefsUtil; + public Context context; + public SharedPreferences prefs; + public SharedPreferences.Editor editor; + + public synchronized static SharedPreferencesUtil getInstance() { + return prefsUtil; + } + + public static void init(Context context, String prefsname, int mode) { + prefsUtil = new SharedPreferencesUtil(); + prefsUtil.context = context; + prefsUtil.prefs = prefsUtil.context.getSharedPreferences(prefsname, mode); + prefsUtil.editor = prefsUtil.prefs.edit(); + } + + private SharedPreferencesUtil() { + } + + + public boolean getBoolean(String key, boolean defaultVal) { + return this.prefs.getBoolean(key, defaultVal); + } + + public boolean getBoolean(String key) { + return this.prefs.getBoolean(key, false); + } + + + public String getString(String key, String defaultVal) { + return this.prefs.getString(key, defaultVal); + } + + public String getString(String key) { + return this.prefs.getString(key, null); + } + + public int getInt(String key, int defaultVal) { + return this.prefs.getInt(key, defaultVal); + } + + public int getInt(String key) { + return this.prefs.getInt(key, 0); + } + + + public float getFloat(String key, float defaultVal) { + return this.prefs.getFloat(key, defaultVal); + } + + public float getFloat(String key) { + return this.prefs.getFloat(key, 0f); + } + + public long getLong(String key, long defaultVal) { + return this.prefs.getLong(key, defaultVal); + } + + public long getLong(String key) { + return this.prefs.getLong(key, 0l); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public Set getStringSet(String key, Set defaultVal) { + return this.prefs.getStringSet(key, defaultVal); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public Set getStringSet(String key) { + return this.prefs.getStringSet(key, null); + } + + public Map getAll() { + return this.prefs.getAll(); + } + + public boolean exists(String key) { + return prefs.contains(key); + } + + + public SharedPreferencesUtil putString(String key, String value) { + editor.putString(key, value); + editor.commit(); + return this; + } + + public SharedPreferencesUtil putInt(String key, int value) { + editor.putInt(key, value); + editor.commit(); + return this; + } + + public SharedPreferencesUtil putFloat(String key, float value) { + editor.putFloat(key, value); + editor.commit(); + return this; + } + + public SharedPreferencesUtil putLong(String key, long value) { + editor.putLong(key, value); + editor.commit(); + return this; + } + + public SharedPreferencesUtil putBoolean(String key, boolean value) { + editor.putBoolean(key, value); + editor.commit(); + return this; + } + + public void commit() { + editor.commit(); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public SharedPreferencesUtil putStringSet(String key, Set value) { + editor.putStringSet(key, value); + editor.commit(); + return this; + } + + public void putObject(String key, Object object) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream out = null; + try { + out = new ObjectOutputStream(baos); + out.writeObject(object); + String objectVal = new String(Base64.encode(baos.toByteArray(), Base64.DEFAULT)); + editor.putString(key, objectVal); + editor.commit(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (baos != null) { + baos.close(); + } + if (out != null) { + out.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public T getObject(String key, Class clazz) { + if (prefs.contains(key)) { + String objectVal = prefs.getString(key, null); + byte[] buffer = Base64.decode(objectVal, Base64.DEFAULT); + ByteArrayInputStream bais = new ByteArrayInputStream(buffer); + ObjectInputStream ois = null; + try { + ois = new ObjectInputStream(bais); + T t = (T) ois.readObject(); + return t; + } catch (StreamCorruptedException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } finally { + try { + if (bais != null) { + bais.close(); + } + if (ois != null) { + ois.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return null; + } + + public SharedPreferencesUtil remove(String key) { + editor.remove(key); + editor.commit(); + return this; + } + + public SharedPreferencesUtil removeAll() { + editor.clear(); + editor.commit(); + return this; + } +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..c7bd21d --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..d5fccc5 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/selector.xml b/app/src/main/res/drawable/selector.xml new file mode 100644 index 0000000..38a6b39 --- /dev/null +++ b/app/src/main/res/drawable/selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_camera2.xml b/app/src/main/res/layout/activity_camera2.xml new file mode 100644 index 0000000..a791816 --- /dev/null +++ b/app/src/main/res/layout/activity_camera2.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_camera_surface.xml b/app/src/main/res/layout/activity_camera_surface.xml new file mode 100644 index 0000000..9fe199f --- /dev/null +++ b/app/src/main/res/layout/activity_camera_surface.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..1c91dd7 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,36 @@ + + + + + +