├── demo
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── attrs_cover_view.xml
│ │ │ │ └── styles.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── layout
│ │ │ │ ├── item.xml
│ │ │ │ ├── sample_cover_view.xml
│ │ │ │ └── activity_main.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── drawable-xxhdpi
│ │ │ │ └── ic_camera.xml
│ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ ├── drawable
│ │ │ │ └── ic_launcher_background.xml
│ │ │ └── layout-land
│ │ │ │ └── activity_main.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── laikang
│ │ │ └── jtcameraviewdemo
│ │ │ ├── CoverView.java
│ │ │ └── MainActivity.java
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── laikang
│ │ │ └── jtcameraviewdemo
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── laikang
│ │ └── jtcameraviewdemo
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── JTCameraView
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ └── values
│ │ │ │ └── strings.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── laikang
│ │ │ └── jtcameraview
│ │ │ ├── JTCameraListener.java
│ │ │ ├── Constants.java
│ │ │ ├── DisplayOrientationDetector.java
│ │ │ └── JTCameraView.java
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── laikang
│ │ │ └── jtcameraview
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── laikang
│ │ └── jtcameraview
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .idea
├── vcs.xml
├── misc.xml
├── gradle.xml
└── uiDesigner.xml
├── .gitignore
├── gradle.properties
├── gradlew.bat
├── README.md
└── gradlew
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/JTCameraView/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':demo', ':JTCameraView'
2 |
--------------------------------------------------------------------------------
/JTCameraView/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | FTD_UI
3 |
4 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | JTCameraViewDemo
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vee-zhang/JTCameraViewDemo/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vee-zhang/JTCameraViewDemo/HEAD/demo/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vee-zhang/JTCameraViewDemo/HEAD/demo/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vee-zhang/JTCameraViewDemo/HEAD/demo/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vee-zhang/JTCameraViewDemo/HEAD/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vee-zhang/JTCameraViewDemo/HEAD/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vee-zhang/JTCameraViewDemo/HEAD/demo/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vee-zhang/JTCameraViewDemo/HEAD/demo/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vee-zhang/JTCameraViewDemo/HEAD/demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vee-zhang/JTCameraViewDemo/HEAD/demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vee-zhang/JTCameraViewDemo/HEAD/demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/item.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Jun 19 10:15:44 CST 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/modules.xml
5 | /.idea/navEditor.xml
6 | /.idea/assetWizardSettings.xml
7 | .DS_Store
8 | /build
9 | /captures
10 | .externalNativeBuild
11 | /.idea/
12 | JTCameraView/libs/
13 | JTCameraView/src/main/res/drawable/
14 | demo/libs/
15 |
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/attrs_cover_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/JTCameraView/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/JTCameraView/src/test/java/com/laikang/jtcameraview/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.laikang.jtcameraview;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/demo/src/test/java/com/laikang/jtcameraviewdemo/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.laikang.jtcameraviewdemo;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/JTCameraView/src/main/java/com/laikang/jtcameraview/JTCameraListener.java:
--------------------------------------------------------------------------------
1 | package com.laikang.jtcameraview;
2 |
3 | import android.graphics.Bitmap;
4 | import android.graphics.Matrix;
5 | import android.graphics.RectF;
6 | import android.hardware.Camera;
7 |
8 | import java.io.File;
9 | import java.util.List;
10 |
11 | public interface JTCameraListener {
12 |
13 | void onCameraOpend();
14 |
15 | void onPreviewStart();
16 |
17 | void onPreviewStop();
18 |
19 | void onShutter();
20 |
21 | void onCupture(Bitmap bitmap);
22 |
23 | void onCut(File file);
24 |
25 | void onCameraClosed();
26 |
27 | void onGetFaces(JTCameraView.Face[] faces);
28 | }
29 |
--------------------------------------------------------------------------------
/JTCameraView/src/main/java/com/laikang/jtcameraview/Constants.java:
--------------------------------------------------------------------------------
1 | package com.laikang.jtcameraview;
2 |
3 |
4 | import android.support.annotation.IntDef;
5 |
6 | public interface Constants {
7 |
8 | int CAMERA_FACING_BACK = 0;
9 | int CAMERA_FACING_FRONT = 1;
10 |
11 | @IntDef({CAMERA_FACING_BACK, CAMERA_FACING_FRONT})
12 | @interface CameraFacing {
13 | }
14 |
15 | int FLASH_OFF = 0;
16 | int FLASH_ON = 1;
17 | int FLASH_TORCH = 2;
18 | int FLASH_AUTO = 3;
19 | int FLASH_RED_EYE = 4;
20 |
21 | @IntDef({FLASH_OFF, FLASH_ON, FLASH_TORCH, FLASH_AUTO, FLASH_RED_EYE})
22 | @interface FlashMode {
23 | }
24 |
25 | int LANDSCAPE_90 = 90;
26 | int LANDSCAPE_270 = 270;
27 | }
28 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-xxhdpi/ic_camera.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
11 |
14 |
15 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/sample_cover_view.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 |
15 |
16 |
--------------------------------------------------------------------------------
/demo/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/JTCameraView/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/JTCameraView/src/androidTest/java/com/laikang/jtcameraview/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.laikang.jtcameraview;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.laikang.ftd_ui.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/demo/src/androidTest/java/com/laikang/jtcameraviewdemo/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.laikang.jtcameraviewdemo;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.laikang.facetonguediagnose", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
22 |
23 |
--------------------------------------------------------------------------------
/demo/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "com.laikang.jtcameraviewdemo"
7 | minSdkVersion 21
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | implementation fileTree(include: ['*.jar'], dir: 'libs')
23 | implementation 'com.android.support:appcompat-v7:28.0.0'
24 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
25 | testImplementation 'junit:junit:4.12'
26 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
28 | implementation project(':JTCameraView')
29 | implementation 'com.github.bumptech.glide:glide:4.9.0'
30 | annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
31 | }
32 |
--------------------------------------------------------------------------------
/demo/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/JTCameraView/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.jfrog.bintray'
3 | apply plugin: 'com.github.dcendents.android-maven'
4 |
5 | android {
6 | compileSdkVersion 28
7 |
8 |
9 | defaultConfig {
10 | minSdkVersion 21
11 | targetSdkVersion 28
12 | versionCode 2
13 | versionName "1.0.1"
14 |
15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
16 |
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 |
26 | }
27 |
28 | dependencies {
29 | implementation fileTree(dir: 'libs', include: ['*.jar'])
30 |
31 | implementation 'com.android.support:appcompat-v7:28.0.0'
32 | testImplementation 'junit:junit:4.12'
33 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
34 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
35 | }
36 |
37 | def group = "com.william"
38 | def version = "2.0.0-beta"
39 | def description = "一个View帮你搞定自定义相机,支持横竖屏切换、前后摄像头切换、预览、拍照等功能。"
40 | def siteUrl = 'https://github.com/william198824/JTCameraViewDemo'
41 | def gitUrl = 'git@github.com:william198824/JTCameraViewDemo.git'
42 | def issueUrl = 'https://github.com/william198824/JTCameraViewDemo/issues'
43 |
44 | setGroup(group)
45 | setVersion(version)
46 | setDescription(description)
47 |
48 | task sourcesJar(type: Jar) {
49 | from android.sourceSets.main.java.srcDirs
50 | classifier = 'sources'
51 | }
52 | task javadoc(type: Javadoc) {
53 | options.encoding = "UTF-8"
54 | source = android.sourceSets.main.java.srcDirs
55 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
56 | failOnError false
57 | }
58 | task javadocJar(type: Jar, dependsOn: javadoc) {
59 | classifier = 'javadoc'
60 | from javadoc.destinationDir
61 | }
62 | artifacts {
63 | archives javadocJar
64 | archives sourcesJar
65 | }
66 |
67 | def properties = new Properties()
68 | properties.load(project.rootProject.file('local.properties').newDataInputStream())
69 |
70 | bintray {
71 | user = properties.getProperty("user")
72 | key = properties.getProperty("key")
73 | configurations = ['archives']
74 | pkg {
75 | repo = "MyProject"
76 | name = "JTCameraView"
77 | desc = description
78 | websiteUrl = siteUrl
79 | issueTrackerUrl = issueUrl
80 | vcsUrl = gitUrl
81 | licenses = ["Apache-2.0"]
82 | labels = ['android','camera']
83 | publish = true
84 | publicDownloadNumbers = true
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/JTCameraView/src/main/java/com/laikang/jtcameraview/DisplayOrientationDetector.java:
--------------------------------------------------------------------------------
1 | package com.laikang.jtcameraview;
2 |
3 | import android.content.Context;
4 | import android.util.SparseIntArray;
5 | import android.view.Display;
6 | import android.view.OrientationEventListener;
7 | import android.view.Surface;
8 |
9 |
10 | /**
11 | * Monitors the value returned from {@link Display#getRotation()}.
12 | */
13 | abstract class DisplayOrientationDetector {
14 |
15 | private final OrientationEventListener mOrientationEventListener;
16 |
17 | /**
18 | * Mapping from Surface.Rotation_n to degrees.
19 | */
20 | static final SparseIntArray DISPLAY_ORIENTATIONS = new SparseIntArray();
21 |
22 | static {
23 | DISPLAY_ORIENTATIONS.put(Surface.ROTATION_0, 0);
24 | DISPLAY_ORIENTATIONS.put(Surface.ROTATION_90, 90);
25 | DISPLAY_ORIENTATIONS.put(Surface.ROTATION_180, 180);
26 | DISPLAY_ORIENTATIONS.put(Surface.ROTATION_270, 270);
27 | }
28 |
29 | Display mDisplay;
30 |
31 | private int mLastKnownDisplayOrientation = 0;
32 |
33 | public DisplayOrientationDetector(Context context) {
34 | mOrientationEventListener = new OrientationEventListener(context) {
35 |
36 | /** This is either Surface.Rotation_0, _90, _180, _270, or -1 (invalid). */
37 | private int mLastKnownRotation = -1;
38 |
39 | @Override
40 | public void onOrientationChanged(int orientation) {
41 | if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN || mDisplay == null) {
42 | return;
43 | }
44 | final int rotation = mDisplay.getRotation();
45 | if (mLastKnownRotation != rotation) {
46 | mLastKnownRotation = rotation;
47 | dispatchOnDisplayOrientationChanged(DISPLAY_ORIENTATIONS.get(rotation));
48 | }
49 | }
50 | };
51 | }
52 |
53 | public void enable(Display display) {
54 | mDisplay = display;
55 | mOrientationEventListener.enable();
56 | // Immediately dispatch the first callback
57 | dispatchOnDisplayOrientationChanged(DISPLAY_ORIENTATIONS.get(display.getRotation()));
58 | }
59 |
60 | public void disable() {
61 | mOrientationEventListener.disable();
62 | mDisplay = null;
63 | }
64 |
65 | public int getLastKnownDisplayOrientation() {
66 | return mLastKnownDisplayOrientation;
67 | }
68 |
69 | void dispatchOnDisplayOrientationChanged(int displayOrientation) {
70 | mLastKnownDisplayOrientation = displayOrientation;
71 | onDisplayOrientationChanged(displayOrientation);
72 | }
73 |
74 | /**
75 | * Called when display orientation is changed.
76 | *
77 | * @param displayOrientation One of 0, 90, 180, and 270.
78 | */
79 | public abstract void onDisplayOrientationChanged(int displayOrientation);
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JTCameraView
2 |
3 | ## 一个View帮你搞定Android自定义相机
4 |
5 | ### 功能一览
6 |
7 | 1. 预览
8 | 2. 拍照
9 | 3. 自动对焦
10 | 4. 手动对焦
11 | 5. 白平衡调节
12 | 6. 情景模式调节
13 | 7. 缩放设置
14 | 8. 闪光灯模式设置
15 | 9. 滤镜设置
16 | 10. 曝光度设置
17 | 11. 多摄像头切换
18 | 12. 横竖屏支持
19 |
20 | ### 集成
21 |
22 | ```groovy
23 | implementation 'com.william:JTCameraView:2.0.0-beta'
24 | ```
25 |
26 | ### 权限
27 |
28 | ```xml
29 |
30 |
31 |
32 |
33 |
34 | ```
35 |
36 | ### 使用
37 |
38 | ```xml
39 |
42 | ```
43 |
44 | 你没看错,这就是一个单纯的View!你可以任意改变他的小小,它会根据当前尺寸自动设置合适的分辨率,且尽力做到「所见即所得」。
45 |
46 | #### 预览
47 |
48 | ```java
49 | public void startPreview(View v) {
50 | if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
51 | ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
52 | } else {
53 | mJTCameraView.startPreview();
54 | }
55 | }
56 | ```
57 |
58 | **注意:JTCameraView一切的功能都是以预览为基础,所以必须先预览!当然在预览前要添加权限。**
59 |
60 | #### 停止预览
61 |
62 | ```java
63 | mJTCameraView.stopPreview();
64 | ```
65 |
66 | #### 拍照
67 |
68 | ```java
69 | mJTCameraView.takePicture();
70 | ```
71 |
72 | #### 摄像头切换
73 |
74 | ```java
75 | mJTCameraView.setCameraFacing(mFacing);
76 | ```
77 |
78 | #### 设置缩放
79 |
80 | ```java
81 | mJTCameraView.setZoom(value);
82 | ```
83 |
84 | #### 设置曝光度
85 |
86 | ```java
87 | mJTCameraView.setExposure(value);
88 | ```
89 |
90 | #### 设置场景
91 |
92 | ```java
93 | mJTCameraView.setSceneMode(value);
94 | ```
95 |
96 | #### 设置闪光灯
97 |
98 | ```java
99 | mJTCameraView.setFlashInternal
100 | ```
101 |
102 | #### 人脸识别等更多功能说不清除,请看Demo
103 |
104 | ### 测试通过机型
105 |
106 | - 小米 5
107 | - 小米 8
108 | - 乐视 1S
109 | - 三星 S6
110 | - vivo X21
111 | - 华为 mate 20X
112 | - 华为 mate S
113 | - 华为 mate 8
114 | - 荣耀 Note 8
115 |
116 | ### FAQ
117 |
118 | 1. 为何不用kotlin?
119 |
120 | 虽然google官方在大力推进kotlin,并且kotlin的应用如雨后春笋,但目前大多数项目还是用java编写的。那么作为一个开源库,存在的意义就是被应用的更加广泛,那么就不得不更好的兼容老项目。
121 |
122 | 2. 为何不使用camera2的api,或者直接使用更新的CameraX?
123 |
124 | 其实这个项目一开始就是用camera2写的,但是写完了之后在不同的机型上发现了很多兼容性问题,而且现阶段厂商对camera2的支持性并不好。以我自己的华为mate20X为例,作为2018年旗舰手机,对camera2的支持性竟然为「低」。
125 |
126 | 当然在面对问题的时候,我们不喜欢以各种理由为借口,绕过或者遗留问题,所以我也是尽可能的查官文或者google解决方案,后来发现google自己的pixcel系列机型都存在问题,so...希望有一天厂商做好准备后,我会很高兴的采用camera2来重写。至于CameraX,只是基于camera2的封装罢了。
127 |
128 | 3. 与google开源的CameraView有什么区别?
129 |
130 | 其实我在写的时候,很多地方都参照了cameraView,大家可以发现很多代码、命名都与cameraView相同。
131 |
132 | CameraView有一点不好,就是在5.0版本以上系统会强制使用camera2,那么就带来了兼容性问题。
133 |
134 | 另外CameraView本质上是双层View嵌套,那么就存在着页面重复绘制问题。所以我采用单TextureView的方式来实现,从而保证了轻量化。
135 |
136 | CameraView单纯的只是摄像头调用,然并卵,并没有解决令我们头疼的兼容性问题。所以JTCamera在拍照之后处理了我们测试中遇到的一些问题,帮助苦逼的开发者(对,就是你)更快的集成摄像头功能。
137 |
138 | 4. 为何功能这么少?
139 |
140 | 因为目前只有我一个人来开发维护,你懂的。任何开源项目的由来都只有一个:方便自己的基础上服务他人。目前我在项目中只需要用到这些功能,所以暂时只开发了这么多,但我保证以后会慢慢更新。也希望朋友们在JTCamera的基础开发其他功能,然后找我合并!
141 |
142 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/laikang/jtcameraviewdemo/CoverView.java:
--------------------------------------------------------------------------------
1 | package com.laikang.jtcameraviewdemo;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Matrix;
8 | import android.graphics.Paint;
9 | import android.graphics.Point;
10 | import android.graphics.Rect;
11 | import android.graphics.RectF;
12 | import android.graphics.drawable.Drawable;
13 | import android.hardware.Camera;
14 | import android.text.TextPaint;
15 | import android.util.AttributeSet;
16 | import android.util.Printer;
17 | import android.view.MotionEvent;
18 | import android.view.View;
19 |
20 | import com.laikang.jtcameraview.JTCameraView;
21 |
22 | /**
23 | * TODO: document your custom view class.
24 | */
25 | public class CoverView extends View {
26 |
27 | private JTCameraView.Face[] faces;
28 |
29 | private Paint mPaint = new Paint();
30 |
31 | private Listener listener;
32 |
33 | private Rect focusArea = new Rect();
34 | private float focusWidth = 100;
35 | private float focusHeight = 100;
36 | private float fozusRadius = 100;
37 |
38 | public void setListener(Listener listener) {
39 | this.listener = listener;
40 | }
41 |
42 | public CoverView(Context context) {
43 | super(context);
44 | init(null, 0);
45 | }
46 |
47 | public CoverView(Context context, AttributeSet attrs) {
48 | super(context, attrs);
49 | init(attrs, 0);
50 | }
51 |
52 | public CoverView(Context context, AttributeSet attrs, int defStyle) {
53 | super(context, attrs, defStyle);
54 | init(attrs, defStyle);
55 | }
56 |
57 |
58 | private void init(AttributeSet attrs, int defStyle) {
59 | mPaint.setStyle(Paint.Style.STROKE);
60 | mPaint.setAntiAlias(true);
61 | mPaint.setColor(Color.RED);
62 | mPaint.setTextSize(100);
63 | }
64 |
65 | public void update(JTCameraView.Face[] faces) {
66 | this.faces = faces;
67 | invalidate();
68 | }
69 |
70 | Matrix matrix;
71 |
72 | @Override
73 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
74 | super.onLayout(changed, left, top, right, bottom);
75 | matrix = new Matrix();
76 | matrix.postScale(getWidth() / 2000f, getHeight() / 2000f);
77 | matrix.postTranslate(getWidth() / 2f, getHeight() / 2f);
78 | }
79 |
80 | public void clean() {
81 | this.faces = null;
82 | invalidate();
83 | }
84 |
85 | private float clickX;
86 | private float clickY;
87 |
88 | @Override
89 | protected void onDraw(Canvas canvas) {
90 | super.onDraw(canvas);
91 |
92 | if (clickX != 0 && clickY != 0) {
93 | mPaint.setColor(Color.RED);
94 | canvas.drawCircle(clickX, clickY, fozusRadius, mPaint);
95 | }
96 | if (faces == null) {
97 | return;
98 | }
99 | mPaint.setColor(Color.GREEN);
100 | for (JTCameraView.Face face : faces) {
101 | RectF r = new RectF();
102 | matrix.mapRect(r, face.getRectF());
103 | canvas.drawRect(r, mPaint);
104 | canvas.drawText("可信度:" + face.getScore(), r.left, r.bottom, mPaint);
105 | }
106 | if (clickX != 0 && clickY != 0) {
107 | mPaint.setColor(Color.BLUE);
108 | canvas.drawRect(clickX - focusWidth / 2, clickY - focusHeight / 2, clickX + focusWidth / 2, clickY + focusHeight / 2, mPaint);
109 | }
110 | }
111 |
112 | @Override
113 | public boolean onTouchEvent(MotionEvent event) {
114 | if (event.getAction() == MotionEvent.ACTION_UP) {
115 | clickX = event.getX();
116 | clickY = event.getY();
117 | invalidate();
118 | // focusArea.left = (int) (clickX - focusWidth / 2 < 0 ? 0 : clickX - focusWidth / 2);
119 | // focusArea.top = (int) (clickY - focusHeight / 2 < 0 ? 0 : clickY - focusHeight / 2);
120 | // focusArea.right = (int) (clickX + focusWidth / 2 > getRight() ? getRight() : clickX + focusWidth / 2);
121 | // focusArea.bottom = (int) (clickY + focusHeight / 2 > getBottom() ? getBottom() : clickY + focusHeight / 2);
122 |
123 | focusArea.left = Math.max((int) (clickX - focusWidth / 2), 0);
124 | focusArea.top = Math.max((int) (clickY - focusHeight / 2), 0);
125 | focusArea.right = Math.min((int) (clickX + focusWidth / 2), getRight());
126 | focusArea.bottom = Math.min((int) (clickY + focusHeight / 2), getBottom());
127 | if (listener != null) {
128 | listener.onFocus(focusArea);
129 | }
130 | }
131 | return true;
132 | }
133 |
134 | public interface Listener {
135 | void onFocus(Rect rect);
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
20 |
21 |
30 |
31 |
41 |
42 |
51 |
52 |
60 |
61 |
62 |
70 |
71 |
78 |
79 |
86 |
87 |
94 |
95 |
102 |
103 |
110 |
111 |
118 |
119 |
126 |
127 |
134 |
135 |
142 |
143 |
150 |
151 |
158 |
159 |
166 |
167 |
168 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout-land/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
18 |
19 |
27 |
28 |
38 |
39 |
50 |
51 |
61 |
62 |
70 |
71 |
78 |
79 |
86 |
87 |
94 |
95 |
102 |
103 |
110 |
111 |
118 |
119 |
126 |
127 |
134 |
135 |
142 |
143 |
150 |
151 |
158 |
159 |
166 |
167 |
--------------------------------------------------------------------------------
/.idea/uiDesigner.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | -
6 |
7 |
8 | -
9 |
10 |
11 | -
12 |
13 |
14 | -
15 |
16 |
17 | -
18 |
19 |
20 |
21 |
22 |
23 | -
24 |
25 |
26 |
27 |
28 |
29 | -
30 |
31 |
32 |
33 |
34 |
35 | -
36 |
37 |
38 |
39 |
40 |
41 | -
42 |
43 |
44 |
45 |
46 | -
47 |
48 |
49 |
50 |
51 | -
52 |
53 |
54 |
55 |
56 | -
57 |
58 |
59 |
60 |
61 | -
62 |
63 |
64 |
65 |
66 | -
67 |
68 |
69 |
70 |
71 | -
72 |
73 |
74 | -
75 |
76 |
77 |
78 |
79 | -
80 |
81 |
82 |
83 |
84 | -
85 |
86 |
87 |
88 |
89 | -
90 |
91 |
92 |
93 |
94 | -
95 |
96 |
97 |
98 |
99 | -
100 |
101 |
102 | -
103 |
104 |
105 | -
106 |
107 |
108 | -
109 |
110 |
111 | -
112 |
113 |
114 |
115 |
116 | -
117 |
118 |
119 | -
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/laikang/jtcameraviewdemo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.laikang.jtcameraviewdemo;
2 |
3 | import android.Manifest;
4 | import android.content.pm.PackageManager;
5 | import android.graphics.Bitmap;
6 | import android.graphics.Matrix;
7 | import android.graphics.Rect;
8 | import android.graphics.RectF;
9 | import android.hardware.Camera;
10 | import android.os.Environment;
11 | import android.os.Handler;
12 | import android.os.HandlerThread;
13 | import android.os.Message;
14 | import android.support.v4.app.ActivityCompat;
15 | import android.support.v7.app.AppCompatActivity;
16 | import android.os.Bundle;
17 | import android.util.Log;
18 | import android.view.View;
19 | import android.widget.AdapterView;
20 | import android.widget.ArrayAdapter;
21 | import android.widget.ImageView;
22 | import android.widget.SeekBar;
23 | import android.widget.Spinner;
24 | import android.widget.TextView;
25 | import android.widget.Toast;
26 |
27 | import com.bumptech.glide.Glide;
28 | import com.bumptech.glide.load.engine.DiskCacheStrategy;
29 | import com.laikang.jtcameraview.JTCameraListener;
30 | import com.laikang.jtcameraview.Constants;
31 | import com.laikang.jtcameraview.JTCameraView;
32 |
33 | import java.io.BufferedOutputStream;
34 | import java.io.File;
35 | import java.io.FileOutputStream;
36 | import java.io.IOException;
37 | import java.io.OutputStream;
38 | import java.lang.ref.WeakReference;
39 | import java.util.ArrayList;
40 | import java.util.List;
41 |
42 | import static com.laikang.jtcameraview.Constants.CAMERA_FACING_BACK;
43 | import static com.laikang.jtcameraview.Constants.CAMERA_FACING_FRONT;
44 |
45 | @SuppressWarnings("deprecation")
46 | public class MainActivity extends AppCompatActivity implements
47 | JTCameraListener,
48 | Spinner.OnItemSelectedListener,
49 | SeekBar.OnSeekBarChangeListener,
50 | Camera.FaceDetectionListener,
51 | CoverView.Listener {
52 | private static final String TAG = "MainActivity";
53 |
54 | private static final int REQUEST_CAMERA_PERMISSION = 100;
55 |
56 | private static final String[] options = new String[]{"自动", "开启", "常开", "抗红眼", "关闭"};
57 |
58 | private JTCameraView mJTCameraView;
59 | private CoverView mCoverView;
60 | private ImageView iv;
61 | private Spinner flashSpinner;
62 | private Spinner sceneSpinner;
63 | private Spinner whiteBalanceSpinner;
64 | private Spinner colorEffectSpinner;
65 | private SeekBar expotureSeekBar;
66 | private TextView seekBarTitle;
67 | private SeekBar zoomSeekBar;
68 |
69 | private ArrayAdapter flashAdapter;
70 | private ArrayAdapter sceneAdapter;
71 | private ArrayAdapter whiteBalanceAdapter;
72 | private ArrayAdapter colorEffectAdapter;
73 |
74 | private List sceneModeList = new ArrayList<>();
75 | private List whiteBalanceModeList = new ArrayList<>();
76 | private List colorModeList = new ArrayList<>();
77 |
78 | private Handler mBackgroundHandler;
79 | private HandlerThread mBackgroundThread;
80 |
81 | private MainHandler mMainHandler;
82 |
83 | private int mFacing = CAMERA_FACING_FRONT;
84 |
85 | @Override
86 | public void onItemSelected(AdapterView> parent, View view, int position, long id) {
87 | switch (parent.getId()) {
88 | case R.id.spinner_flash:
89 | setFlashMode(position);
90 | break;
91 | case R.id.spinner_scene_mode:
92 | setSceneMode(position);
93 | break;
94 | }
95 | }
96 |
97 | /**
98 | * 设置闪关灯模式看这里
99 | *
100 | * @param position
101 | */
102 | private void setFlashMode(int position) {
103 | switch (position) {
104 | case 1:
105 | mJTCameraView.setFlashInternal(Constants.FLASH_ON);
106 | break;
107 | case 2:
108 | mJTCameraView.setFlashInternal(Constants.FLASH_TORCH);
109 | break;
110 | case 3:
111 | mJTCameraView.setFlashInternal(Constants.FLASH_RED_EYE);
112 | break;
113 | case 4:
114 | mJTCameraView.setFlashInternal(Constants.FLASH_OFF);
115 | default://默认为自动模式
116 | mJTCameraView.setFlashInternal(Constants.FLASH_AUTO);
117 | }
118 | }
119 |
120 | private void setSceneMode(int position) {
121 | mJTCameraView.setSceneMode(sceneModeList.get(position));
122 | }
123 |
124 | @Override
125 | public void onNothingSelected(AdapterView> parent) {
126 |
127 | }
128 |
129 | @Override
130 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
131 | if (!seekBar.isPressed()) {
132 | return;
133 | }
134 | int value;
135 | switch (seekBar.getId()) {
136 | case R.id.seekBar_zoom:
137 | value = progress > mJTCameraView.getMaxZoom() ? mJTCameraView.getMaxZoom() : progress;
138 | mJTCameraView.setZoom(value);
139 | break;
140 | case R.id.seekBar_expoture:
141 | value = mJTCameraView.getMinExposureCompensation() + progress;
142 | mJTCameraView.setExposure(value);
143 | seekBarTitle.setText("曝光度:" + value);
144 | break;
145 | }
146 | }
147 |
148 | @Override
149 | public void onStartTrackingTouch(SeekBar seekBar) {
150 |
151 | }
152 |
153 | @Override
154 | public void onStopTrackingTouch(SeekBar seekBar) {
155 |
156 | }
157 |
158 | @Override
159 | public void onFaceDetection(Camera.Face[] faces, Camera camera) {
160 | // mCoverView.update(faces);
161 | }
162 |
163 | @Override
164 | public void onFocus(Rect rect) {
165 | mJTCameraView.focus(rect);
166 | }
167 |
168 |
169 | private static class MainHandler extends Handler {
170 | private WeakReference mainActivity;
171 |
172 | public MainHandler(MainActivity mainActivity) {
173 | this.mainActivity = new WeakReference<>(mainActivity);
174 | }
175 |
176 | @Override
177 | public void handleMessage(Message msg) {
178 | super.handleMessage(msg);
179 | String path = (String) msg.obj;
180 | File img = new File(path);
181 | Glide.with(mainActivity.get()).load(img).skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.NONE).into(mainActivity.get().iv);
182 | }
183 | }
184 |
185 | @Override
186 | public void onGetFaces(JTCameraView.Face[] faces) {
187 | mCoverView.update(faces);
188 | }
189 |
190 |
191 | @Override
192 | protected void onCreate(Bundle savedInstanceState) {
193 | super.onCreate(savedInstanceState);
194 | setContentView(R.layout.activity_main);
195 |
196 | flashSpinner = findViewById(R.id.spinner_flash);
197 | flashAdapter = new ArrayAdapter<>(this, R.layout.item, options);
198 | flashSpinner.setAdapter(flashAdapter);
199 | flashSpinner.setOnItemSelectedListener(this);
200 |
201 | sceneSpinner = findViewById(R.id.spinner_scene_mode);
202 | sceneSpinner.setOnItemSelectedListener(this);
203 | sceneAdapter = new ArrayAdapter(this, R.layout.item, sceneModeList);
204 | sceneSpinner.setAdapter(sceneAdapter);
205 |
206 | whiteBalanceSpinner = findViewById(R.id.spinner_white_balance_mode);
207 | whiteBalanceSpinner.setOnItemSelectedListener(this);
208 | whiteBalanceAdapter = new ArrayAdapter(this, R.layout.item, whiteBalanceModeList);
209 | whiteBalanceSpinner.setAdapter(whiteBalanceAdapter);
210 |
211 | colorEffectSpinner = findViewById(R.id.spinner_color_effect);
212 | colorEffectSpinner.setOnItemSelectedListener(this);
213 | colorEffectAdapter = new ArrayAdapter(this, R.layout.item, colorModeList);
214 | colorEffectSpinner.setAdapter(colorEffectAdapter);
215 |
216 | expotureSeekBar = findViewById(R.id.seekBar_expoture);
217 | expotureSeekBar.setOnSeekBarChangeListener(this);
218 | seekBarTitle = findViewById(R.id.tv4);
219 |
220 | zoomSeekBar = findViewById(R.id.seekBar_zoom);
221 | zoomSeekBar.setOnSeekBarChangeListener(this);
222 |
223 | mJTCameraView = findViewById(R.id.ftdv);
224 | mJTCameraView.setListener(this);
225 | iv = findViewById(R.id.iv);
226 | mMainHandler = new MainHandler(this);
227 | mCoverView = findViewById(R.id.cover);
228 | mCoverView.setListener(this);
229 | }
230 |
231 |
232 | /**
233 | * 预览看这里
234 | *
235 | * @param v
236 | */
237 | public void startPreview(View v) {
238 | if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
239 | ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
240 | } else {
241 | mJTCameraView.startPreview();
242 | }
243 | }
244 |
245 | /**
246 | * 拍照看这里
247 | *
248 | * @param v
249 | */
250 | public void takePicture(View v) {
251 | mJTCameraView.takePicture();
252 | }
253 |
254 | /**
255 | * 停止预览看这里
256 | *
257 | * @param v
258 | */
259 | public void stopPreview(View v) {
260 | mJTCameraView.stopPreview();
261 | }
262 |
263 | /**
264 | * 切换前后摄像头
265 | *
266 | * @param v
267 | */
268 | public void changeFacing(View v) {
269 | if (mFacing == CAMERA_FACING_FRONT) {
270 | mFacing = CAMERA_FACING_BACK;
271 | } else {
272 | mFacing = CAMERA_FACING_FRONT;
273 | }
274 | mJTCameraView.setCameraFacing(mFacing);
275 | mCoverView.clean();
276 | }
277 |
278 | @Override
279 | public void onCameraOpend() {
280 | //只有摄像头开启后才能拿到硬件支持信息,当切换摄像头时这个方法会重复调用
281 | this.sceneModeList.clear();
282 | this.whiteBalanceModeList.clear();
283 | this.colorModeList.clear();
284 |
285 | this.sceneModeList.addAll(mJTCameraView.getSceneModeList());
286 | this.sceneAdapter.notifyDataSetChanged();
287 | this.whiteBalanceModeList.addAll(mJTCameraView.getWhiteBalanceModeList());
288 | this.whiteBalanceAdapter.notifyDataSetChanged();
289 | this.colorModeList.addAll(mJTCameraView.getColorEffectModeList());
290 | this.colorEffectAdapter.notifyDataSetChanged();
291 |
292 | int expotureMaxValue = mJTCameraView.getMaxExposureCompensation() - mJTCameraView.getMinExposureCompensation();
293 | this.expotureSeekBar.setMax(expotureMaxValue);
294 | this.zoomSeekBar.setMax(mJTCameraView.getMaxZoom());
295 | }
296 |
297 | @Override
298 | public void onPreviewStart() {
299 | showToast("已启动预览");
300 | }
301 |
302 | @Override
303 | public void onPreviewStop() {
304 | showToast("已停止预览");
305 | }
306 |
307 | @Override
308 | public void onShutter() {
309 | showToast("在这里播放快门声音或者拍照提示");
310 | }
311 |
312 | @Override
313 | public void onCupture(final Bitmap bitmap) {
314 | getBackgroundHandler().post(new Runnable() {
315 | @Override
316 | public void run() {
317 | File file = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "picture.jpg");
318 | try {
319 | OutputStream os = new FileOutputStream(file);
320 | BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
321 | bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);//将图片压缩的流里面
322 | os.flush();
323 | os.close();
324 | bitmap.recycle();
325 | Message msg = mMainHandler.obtainMessage();
326 | msg.obj = file.getPath();
327 | mMainHandler.sendMessage(msg);
328 | } catch (IOException e) {
329 | Log.w(TAG, "图像文件写入失败: " + file, e);
330 | }
331 |
332 | }
333 | });
334 | }
335 |
336 | @Override
337 | public void onCut(File file) {
338 |
339 | }
340 |
341 | @Override
342 | public void onCameraClosed() {
343 | showToast("摄像头已关闭");
344 | }
345 |
346 | private void showToast(String str) {
347 | Toast.makeText(this, str, Toast.LENGTH_SHORT);
348 | }
349 |
350 | private Handler getBackgroundHandler() {
351 | if (mBackgroundHandler == null) {
352 | mBackgroundThread = new HandlerThread("background");
353 | mBackgroundThread.start();
354 | mBackgroundHandler = new Handler(mBackgroundThread.getLooper()) {
355 | @Override
356 | public void handleMessage(Message msg) {
357 | super.handleMessage(msg);
358 | String path = (String) msg.obj;
359 | File img = new File(path);
360 | Glide.with(iv).load(img).into(iv);
361 | }
362 | };
363 | }
364 | return mBackgroundHandler;
365 | }
366 |
367 | @Override
368 | protected void onDestroy() {
369 | super.onDestroy();
370 | if (mBackgroundThread == null) {
371 | return;
372 | }
373 | mBackgroundThread.quitSafely();
374 | try {
375 | mBackgroundThread.join();
376 | mBackgroundThread = null;
377 | mBackgroundHandler = null;
378 | } catch (InterruptedException e) {
379 | Log.e(TAG, "close: 后台线程关闭失败:", e);
380 | }
381 | }
382 | }
383 |
--------------------------------------------------------------------------------
/JTCameraView/src/main/java/com/laikang/jtcameraview/JTCameraView.java:
--------------------------------------------------------------------------------
1 | package com.laikang.jtcameraview;
2 |
3 | import android.content.Context;
4 | import android.content.pm.PackageManager;
5 | import android.graphics.Bitmap;
6 | import android.graphics.BitmapFactory;
7 | import android.graphics.Matrix;
8 | import android.graphics.Point;
9 | import android.graphics.Rect;
10 | import android.graphics.RectF;
11 | import android.graphics.SurfaceTexture;
12 | import android.graphics.YuvImage;
13 | import android.hardware.Camera;
14 | import android.support.v4.view.ViewCompat;
15 | import android.util.AttributeSet;
16 | import android.util.Log;
17 | import android.util.Size;
18 | import android.view.TextureView;
19 |
20 | import java.io.ByteArrayOutputStream;
21 | import java.io.File;
22 | import java.io.FileNotFoundException;
23 | import java.io.FileOutputStream;
24 | import java.io.IOException;
25 | import java.util.ArrayList;
26 | import java.util.Collections;
27 | import java.util.Comparator;
28 | import java.util.List;
29 | import java.util.concurrent.atomic.AtomicBoolean;
30 |
31 | import static com.laikang.jtcameraview.Constants.CAMERA_FACING_BACK;
32 | import static com.laikang.jtcameraview.Constants.CAMERA_FACING_FRONT;
33 |
34 | @SuppressWarnings("deprecation")
35 | public class JTCameraView extends TextureView {
36 |
37 | private static final String TAG = "FtdView";
38 | private boolean autoPreview;
39 | private boolean isCameraCanBeUse;
40 | private boolean isAutoFocus = true;
41 | private int mFlash = Constants.FLASH_RED_EYE;
42 |
43 | private Camera mCamera;
44 | private Camera.Parameters mCameraParameters;
45 |
46 |
47 | private List sceneModeList;
48 | private List whiteBalanceModeList;
49 | private List colorEffectModeList;
50 |
51 | private int mFacingFrontCameraId = -1;
52 | private Camera.CameraInfo mFacingFrontCameraInfo;
53 | private int mFacingBackCameraId = -1;
54 | private Camera.CameraInfo mFacingBackCameraInfo;
55 | private int mCameraId = -1;
56 | private Camera.CameraInfo mCameraInfo;
57 |
58 | private int mDisplayOrientation;
59 | private int mPictureOrientation;
60 | private int mPreviewOrientation;
61 | private Camera.Size mPreviewSize;
62 | private Camera.Size mPictureSize;
63 | private boolean isShowingPreview;
64 |
65 | private int maxZoom;
66 |
67 | private Matrix matrix = new Matrix();
68 |
69 | private final AtomicBoolean isPictureCaptureInProgress = new AtomicBoolean(false);
70 |
71 | public boolean isShowingPreview() {
72 | return isShowingPreview;
73 | }
74 |
75 | private Size mSize;
76 |
77 | private JTCameraListener mListener;
78 |
79 | public void setListener(JTCameraListener mListener) {
80 | this.mListener = mListener;
81 | }
82 |
83 | private static int mCameraFacing = Camera.CameraInfo.CAMERA_FACING_FRONT;
84 |
85 | private DisplayOrientationDetector mDisplayOrientationDetector;
86 |
87 | public JTCameraView(Context context) {
88 | this(context, null);
89 | }
90 |
91 | public JTCameraView(Context context, AttributeSet attrs) {
92 | this(context, attrs, 0);
93 | }
94 |
95 | public JTCameraView(Context context, AttributeSet attrs, int defStyleAttr) {
96 | this(context, attrs, defStyleAttr, 0);
97 | }
98 |
99 | public JTCameraView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
100 | super(context, attrs, defStyleAttr, defStyleRes);
101 | isCameraCanBeUse = checkCameraHardware(context);
102 | if (!isCameraCanBeUse) {
103 | Log.i(TAG, "FtdView: 相机不可用,请检查设备是否故障,或是否有配备摄像头组件!");
104 | return;
105 | }
106 | setupCameraInfo();
107 | initTextureView();
108 | mDisplayOrientationDetector = new DisplayOrientationDetector(context) {
109 | @Override
110 | public void onDisplayOrientationChanged(int displayOrientation) {
111 | mDisplayOrientation = displayOrientation;
112 | setDisplayOrientation();
113 | }
114 | };
115 | }
116 |
117 | /**
118 | * View初始化设置
119 | */
120 | private void initTextureView() {
121 | this.setFocusable(true);
122 | this.setFocusableInTouchMode(true);
123 | this.setKeepScreenOn(true);//保持屏幕长亮
124 | this.setSurfaceTextureListener(new SurfaceTextureListener() {
125 | /**
126 | * surface在创建的时候调用
127 | */
128 | @Override
129 | public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
130 | setupSizeCache(width, height);
131 | startPreview();
132 | }
133 |
134 | /**
135 | * surface的bufferSize变化时调用。
136 | **/
137 | @Override
138 | public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
139 | setupSizeCache(width, height);
140 | startPreview();
141 | }
142 |
143 | /**
144 | * surface被销毁的时候调用,如退出游戏画面,一般在该方法中停止绘图线程。
145 | */
146 | @Override
147 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
148 | cleanTransform();
149 | releaseCamera();
150 | return true;
151 | }
152 |
153 | @Override
154 | public void onSurfaceTextureUpdated(SurfaceTexture surface) {
155 |
156 | }
157 | });
158 | }
159 |
160 | /**
161 | * 获取当前摄像头是前置还是后置
162 | *
163 | * @return 前置:CAMERA_FACING_FRONT,后置:CAMERA_FACING_BACK
164 | */
165 | @Constants.CameraFacing
166 | public int getCameraFacing() {
167 | return mCameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT ? CAMERA_FACING_FRONT : CAMERA_FACING_BACK;
168 | }
169 |
170 | /**
171 | * 切换后置(前置)摄像头
172 | *
173 | * @param facing
174 | */
175 | public void setCameraFacing(@Constants.CameraFacing int facing) {
176 | if (facing == CAMERA_FACING_FRONT) {
177 | if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT || mFacingFrontCameraId < 0) {
178 | return;
179 | }
180 | mCameraId = mFacingFrontCameraId;
181 | } else {
182 | if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK || mFacingBackCameraId < 0) {
183 | return;
184 | }
185 | mCameraId = mFacingBackCameraId;
186 | }
187 | mCameraFacing = facing;
188 | setupCameraInfo();
189 | cleanTransform();
190 | startPreview();
191 | }
192 |
193 | private void setupSizeCache(int width, int height) {
194 | mSize = new Size(width, height);
195 | }
196 |
197 | /**
198 | * 设置方向
199 | */
200 | private void setDisplayOrientation() {
201 | mPictureOrientation = calcCameraRotation(mDisplayOrientation);
202 | // mCameraParameters.setRotation(mPictureOrientation);
203 | mPreviewOrientation = calcDisplayOrientation(mDisplayOrientation);
204 | if (isCameraOpened()) {
205 | mCamera.setParameters(mCameraParameters);
206 | mCamera.setDisplayOrientation(mPreviewOrientation);
207 | }
208 | }
209 |
210 | /**
211 | * 计算输出的jpeg图像的方向(需旋转角度)
212 | *
213 | * @param screenOrientationDegrees
214 | * @return
215 | */
216 | private int calcCameraRotation(int screenOrientationDegrees) {
217 | if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
218 | return (mCameraInfo.orientation + screenOrientationDegrees) % 360;
219 | } else { // back-facing
220 | final int landscapeFlip = isLandscape(screenOrientationDegrees) ? 180 : 0;
221 | return (mCameraInfo.orientation + screenOrientationDegrees + landscapeFlip) % 360;
222 | }
223 | }
224 |
225 | /**
226 | * 计算预览图像的方向(需旋转角度)
227 | *
228 | * @param screenOrientationDegrees
229 | * @return
230 | */
231 | private int calcDisplayOrientation(int screenOrientationDegrees) {
232 | if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
233 | return (360 - (mCameraInfo.orientation + screenOrientationDegrees) % 360) % 360;
234 | } else { // back-facing
235 | return (mCameraInfo.orientation - screenOrientationDegrees + 360) % 360;
236 | }
237 | }
238 |
239 | /**
240 | * 如果是横向
241 | *
242 | * @param orientationDegrees
243 | * @return
244 | */
245 | private boolean isLandscape(int orientationDegrees) {
246 | return (orientationDegrees == Constants.LANDSCAPE_90 || orientationDegrees == Constants.LANDSCAPE_270);
247 | }
248 |
249 | /**
250 | * 检查设备是否有相机
251 | */
252 | private boolean checkCameraHardware(Context context) {
253 | return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
254 | }
255 |
256 | /**
257 | * 获取Camera系统的信息
258 | */
259 | private void setupCameraInfo() {
260 | int sensorCount = Camera.getNumberOfCameras();
261 | for (int i = 0; i < sensorCount; i++) {
262 | final Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
263 | Camera.getCameraInfo(i, cameraInfo);
264 | if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
265 | mFacingFrontCameraId = i;
266 | mFacingFrontCameraInfo = cameraInfo;
267 | } else if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
268 | mFacingBackCameraId = i;
269 | mFacingBackCameraInfo = cameraInfo;
270 | }
271 | }
272 | if (mFacingFrontCameraId == -1) {//只有后置摄像头,如山寨机,或其他奇葩设备
273 | mCameraId = mFacingBackCameraId;
274 | mCameraInfo = mFacingBackCameraInfo;
275 | } else if (mFacingBackCameraId == -1) {//只有前置摄像头,如来康镜,触控一体机,或其他奇葩设备
276 | mCameraId = mFacingFrontCameraId;
277 | mCameraInfo = mFacingFrontCameraInfo;
278 | } else if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
279 | mCameraId = mFacingFrontCameraId;
280 | mCameraInfo = mFacingFrontCameraInfo;
281 | } else {
282 | mCameraId = mFacingBackCameraId;
283 | mCameraInfo = mFacingBackCameraInfo;
284 | }
285 | }
286 |
287 | /**
288 | * 开启对应的相机
289 | *
290 | * @param cameraId
291 | */
292 | private void openCamera(int cameraId) {
293 | if (mCamera != null) {
294 | releaseCamera();
295 | }
296 | this.mCamera = Camera.open(cameraId);
297 | this.mCameraParameters = mCamera.getParameters();
298 | this.sceneModeList = mCameraParameters.getSupportedSceneModes();
299 | this.whiteBalanceModeList = mCameraParameters.getSupportedWhiteBalance();
300 | this.colorEffectModeList = mCameraParameters.getSupportedColorEffects();
301 | this.maxZoom = mCameraParameters.getMaxZoom();
302 | if (mListener != null) {
303 | mListener.onCameraOpend();
304 | }
305 | }
306 |
307 | /**
308 | * 设置情景模式
309 | *
310 | * @param mode
311 | */
312 | public void setSceneMode(String mode) {
313 | mCameraParameters.setSceneMode(mode);
314 | if (isCameraOpened()) {
315 | mCamera.setParameters(mCameraParameters);
316 | }
317 | }
318 |
319 | /**
320 | * 设置白平衡模式
321 | *
322 | * @param mode
323 | */
324 | public void setWhiteBalanceMode(String mode) {
325 | mCameraParameters.setWhiteBalance(mode);
326 | if (isCameraOpened()) {
327 | mCamera.setParameters(mCameraParameters);
328 | }
329 | }
330 |
331 | private boolean isCameraOpened() {
332 | return mCamera != null;
333 | }
334 |
335 | /**
336 | * 清除之前的图像处理,避免重复处理
337 | */
338 | private void cleanTransform() {
339 | matrix.reset();
340 | setTransform(matrix);
341 | }
342 |
343 | /**
344 | * 预览图像(surface)处理
345 | */
346 | private void configureTransform() {
347 | if (mSize == null || mPreviewSize == null) {
348 | return;
349 | }
350 | matrix = getTransform(matrix);
351 |
352 | float sWidth = (float) mSize.getWidth();
353 | float sHeight = (float) mSize.getHeight();
354 |
355 | float pWidth, pHeight;
356 | if (isLandscape(mDisplayOrientation)) {
357 | pWidth = (float) mPreviewSize.width;
358 | pHeight = (float) mPreviewSize.height;
359 | } else {
360 | pWidth = (float) mPreviewSize.height;
361 | pHeight = (float) mPreviewSize.width;
362 | }
363 |
364 | float previewRatio = pHeight / pWidth;
365 |
366 | float wScale = 1f;
367 | float hScale = 1f;
368 |
369 | // float widthDiffer = sWidth - pWidth;
370 | // float heightDiffer = sHeight - pHeight;
371 |
372 | // if (widthDiffer > 0 && heightDiffer < 0) {
373 | // hScale = sWidth * previewRatio / sHeight;
374 | // } else if (widthDiffer < 0 && heightDiffer > 0) {
375 | // wScale = sHeight / previewRatio / sWidth;
376 | // } else {
377 |
378 | if (sHeight / sWidth > pHeight / pWidth) {
379 | wScale = sHeight / previewRatio / sWidth;
380 | } else {
381 | hScale = sWidth * previewRatio / sHeight;
382 | }
383 | // }
384 |
385 | matrix.postScale(wScale, hScale, sWidth / 2, sHeight / 2);
386 | setTransform(matrix);
387 | }
388 |
389 |
390 | /**
391 | * 调整相机参数
392 | */
393 | private void adjustCameraParameters() {
394 | this.mPreviewSize = chooseOptimalSize(mCameraParameters.getSupportedPreviewSizes(), mSize.getWidth(), mSize.getHeight());
395 | this.mPictureSize = chooseOptimalSize(mCameraParameters.getSupportedPictureSizes(), mSize.getWidth(), mSize.getHeight());
396 | mCameraParameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
397 | mCameraParameters.setPictureSize(mPictureSize.width, mPictureSize.height);
398 | mCameraParameters.setRotation(calcCameraRotation(mDisplayOrientation));
399 | setAutoFocus(isAutoFocus);
400 | setFlashInternal(mFlash);
401 | mCamera.setParameters(mCameraParameters);
402 | mCamera.setDisplayOrientation(calcDisplayOrientation(mDisplayOrientation));
403 | }
404 |
405 | /**
406 | * 开启/关闭自动对焦
407 | *
408 | * @param autoFocus true:开 false:关
409 | */
410 | private void setAutoFocus(boolean autoFocus) {
411 | isAutoFocus = autoFocus;
412 | if (isCameraOpened()) {
413 | final List modes = mCameraParameters.getSupportedFocusModes();
414 | if (autoFocus && modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
415 | mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
416 | } else if (modes.contains(Camera.Parameters.FOCUS_MODE_FIXED)) {
417 | mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED);
418 | } else if (modes.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) {
419 | mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY);
420 | } else {
421 | mCameraParameters.setFocusMode(modes.get(0));
422 | }
423 | }
424 | }
425 |
426 | /**
427 | * 设置闪光灯模式
428 | */
429 | public void setFlashInternal(@Constants.FlashMode int flash) {
430 | if (isCameraOpened()) {
431 | List modes = mCameraParameters.getSupportedFlashModes();
432 | String mode = getFlashMode(flash);
433 | if (modes != null && modes.contains(mode)) {
434 | mCameraParameters.setFlashMode(mode);
435 | mFlash = flash;
436 | }
437 | String currentMode = getFlashMode(mFlash);
438 | if (modes == null || !modes.contains(currentMode)) {
439 | mCameraParameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
440 | mFlash = Constants.FLASH_OFF;
441 | }
442 | mCamera.setParameters(mCameraParameters);
443 | } else {
444 | mFlash = flash;
445 | }
446 | }
447 |
448 | /**
449 | * 获取最小曝光度
450 | *
451 | * @return
452 | */
453 | public int getMinExposureCompensation() {
454 | return mCameraParameters.getMinExposureCompensation();
455 | }
456 |
457 | /**
458 | * 获取最大曝光度
459 | *
460 | * @return
461 | */
462 | public int getMaxExposureCompensation() {
463 | return mCameraParameters.getMaxExposureCompensation();
464 | }
465 |
466 | /**
467 | * 设置焦距
468 | *
469 | * @param value
470 | */
471 | public void setZoom(int value) {
472 | mCameraParameters.setZoom(value);
473 | if (isCameraOpened()) {
474 | mCamera.setParameters(mCameraParameters);
475 | }
476 | }
477 |
478 | /**
479 | * 手动调整曝光度
480 | *
481 | * @param value
482 | */
483 | public void setExposure(int value) {
484 | mCameraParameters.setExposureCompensation(value);
485 | if (isCameraOpened()) {
486 | mCamera.setParameters(mCameraParameters);
487 | }
488 | }
489 |
490 | private String getFlashMode(@Constants.FlashMode int mode) {
491 | switch (mode) {
492 | case Constants.FLASH_ON:
493 | return Camera.Parameters.FLASH_MODE_ON;
494 | case Constants.FLASH_TORCH:
495 | return Camera.Parameters.FLASH_MODE_TORCH;
496 | case Constants.FLASH_AUTO:
497 | return Camera.Parameters.FLASH_MODE_AUTO;
498 | case Constants.FLASH_RED_EYE:
499 | return Camera.Parameters.FLASH_MODE_RED_EYE;
500 | default:
501 | return Camera.Parameters.FLASH_MODE_OFF;
502 | }
503 | }
504 |
505 | /**
506 | * 选择合适的成像尺寸
507 | *
508 | * @param sizeList
509 | * @param width
510 | * @return
511 | */
512 | @SuppressWarnings("SuspiciousNameCombination")
513 | private Camera.Size chooseOptimalSize(List sizeList, int width, int height) {
514 | if (sizeList == null || width <= 0) {
515 | return null;
516 | }
517 | ArrayList list = new ArrayList<>(10);
518 | //去除长款相等的尺寸,这种尺寸容易引起图片倒伏
519 | for (Camera.Size size : sizeList) {
520 | if (size.height != size.width) {
521 | list.add(size);
522 | }
523 | }
524 | int displayWidth = width;
525 | int displayHeight = height;
526 | if (mPreviewOrientation - mDisplayOrientation > 0) {
527 | displayWidth = height;
528 | displayHeight = width;
529 | }
530 | CameraSizeComparator comparator = new CameraSizeComparator(displayWidth, displayHeight);
531 |
532 | // Log.d(TAG, "chooseOptimalSizes: \n");
533 | StringBuffer sb = new StringBuffer();
534 | for (Camera.Size size : sizeList) {
535 | sb.append(size.height).append("*").append(size.width).append("\n");
536 | }
537 | // Log.d(TAG, "chooseOptimalSize: \n" + sb.toString());
538 | return Collections.max(list, comparator);
539 | }
540 |
541 | /**
542 | * 计算长宽比
543 | *
544 | * @param size
545 | * @return
546 | */
547 | private float getResolutionRatio(Camera.Size size) {
548 | return size.height / size.width;
549 | }
550 |
551 | /**
552 | * 释放摄像头
553 | */
554 | public void releaseCamera() {
555 | if (mCamera != null) {
556 | mCamera.setPreviewCallback(null);//todo
557 | mCamera.lock();//todo 这俩是啥意思要查一下
558 | mCamera.release();
559 | mCamera = null;
560 | if (mListener != null) {
561 | mListener.onCameraClosed();
562 | }
563 | }
564 | }
565 |
566 | /**
567 | * 启动预览
568 | */
569 | byte[] imageData;
570 |
571 | public void startPreview() {
572 | openCamera(mCameraId);
573 | adjustCameraParameters();
574 | configureTransform();
575 | try {
576 | mCamera.setPreviewCallback(new Camera.PreviewCallback() {
577 | @Override
578 | public void onPreviewFrame(byte[] data, Camera camera) {
579 | imageData = data;
580 | }
581 | });
582 | mCamera.setPreviewTexture(getSurfaceTexture());
583 | mCamera.startPreview();
584 | startFaceDetector();
585 | isShowingPreview = true;
586 | if (mListener != null) {
587 | mListener.onPreviewStart();
588 | }
589 | } catch (Exception e) {
590 | Log.e(TAG, "startPreview: ", e);
591 | }
592 | }
593 |
594 |
595 | private Matrix faceMatrix = new Matrix();
596 |
597 | /**
598 | * 启动面部检测
599 | */
600 | private void startFaceDetector() {
601 | if (mCameraParameters.getMaxNumDetectedFaces() > 0) {
602 | faceMatrix.setScale(mCameraId == CAMERA_FACING_FRONT ? -1f : 1f, 1f);
603 | faceMatrix.postRotate(mPreviewOrientation);
604 | mCamera.setFaceDetectionListener(new Camera.FaceDetectionListener() {
605 | @Override
606 | public void onFaceDetection(Camera.Face[] faces, Camera camera) {
607 | Face[] translatedfaces = new Face[faces.length];
608 | RectF rectF = new RectF();
609 | for (int i = 0; i < faces.length; i++) {
610 | faceMatrix.mapRect(rectF, new RectF(faces[i].rect));
611 | translatedfaces[i] = new Face(faces[i], rectF);
612 | }
613 | mListener.onGetFaces(translatedfaces);
614 | }
615 | });
616 | mCamera.startFaceDetection();
617 | } else {
618 | Log.e(TAG, "当前摄像头不支持面部检测!");
619 | }
620 | }
621 |
622 | /**
623 | * 手动对焦
624 | *
625 | * @param rect 屏幕对焦区域
626 | */
627 | public void focus(Rect rect) {
628 | rect.left = (int) (rect.left / (float) getWidth() * 2000) - 1000;
629 | rect.top = (int) (rect.top / (float) getHeight() * 2000) - 1000;
630 | rect.right = (int) (rect.right / (float) getWidth() * 2000) - 1000;
631 | rect.bottom = (int) (rect.bottom / (float) getHeight() * 2000) - 1000;
632 | Camera.Area cameraArea = new Camera.Area(rect, 1000);
633 | List meteringAreas = new ArrayList();
634 | List focusAreas = new ArrayList();
635 | if (mCameraParameters.getMaxNumMeteringAreas() > 0) {
636 | meteringAreas.add(cameraArea);
637 | focusAreas.add(cameraArea);
638 | }
639 | mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); // 设置对焦模式
640 | mCameraParameters.setFocusAreas(focusAreas); // 设置对焦区域
641 | mCameraParameters.setMeteringAreas(meteringAreas); // 设置测光区域
642 |
643 | mCamera.cancelAutoFocus(); // 每次对焦前,需要先取消对焦
644 | mCamera.setParameters(mCameraParameters); // 设置相机参数
645 | mCamera.autoFocus(new Camera.AutoFocusCallback() {
646 | @Override
647 | public void onAutoFocus(boolean success, Camera camera) {
648 | int i = 0;
649 | }
650 | });
651 | }
652 |
653 | public static class Face {
654 | private RectF rectF;
655 | private int id;
656 | private Point leftEye;
657 | private Point rightEye;
658 | private Point mouth;
659 | private int score;
660 |
661 | public Face(Camera.Face face, RectF rectF) {
662 | id = face.id;
663 | leftEye = face.leftEye;//这三个直接赋值原则上是肯定不对的,这里只是先这样写
664 | rightEye = face.rightEye;
665 | mouth = face.mouth;
666 | score = face.score;
667 | this.rectF = rectF;
668 | }
669 |
670 | public RectF getRectF() {
671 | return rectF;
672 | }
673 |
674 | public int getId() {
675 | return id;
676 | }
677 |
678 | public Point getLeftEye() {
679 | return leftEye;
680 | }
681 |
682 | public Point getRightEye() {
683 | return rightEye;
684 | }
685 |
686 | public Point getMouth() {
687 | return mouth;
688 | }
689 |
690 | public int getScore() {
691 | return score;
692 | }
693 | }
694 |
695 | /**
696 | * 停止预览,画面会定格
697 | */
698 | public void stopPreview() {
699 | if (mCamera != null) {
700 | mCamera.stopPreview();
701 | }
702 | isShowingPreview = false;
703 | if (mListener != null) {
704 | mListener.onPreviewStop();
705 | }
706 | releaseCamera();
707 | }
708 |
709 | /**
710 | * 拍照
711 | */
712 | public void takePicture() {
713 | if (!isCameraOpened()) {
714 | throw new IllegalStateException(
715 | "Camera is not ready. Call start() before takePicture().");
716 | }
717 | if (this.isAutoFocus) {
718 | mCamera.cancelAutoFocus();
719 | mCamera.autoFocus(new Camera.AutoFocusCallback() {
720 | @Override
721 | public void onAutoFocus(boolean success, Camera camera) {
722 | takePictureInternal();
723 | }
724 | });
725 | } else {
726 | takePictureInternal();
727 | }
728 | }
729 |
730 | private void takePictureInternal() {
731 | if (isPictureCaptureInProgress.getAndSet(true)) {
732 | return;
733 | }
734 | Log.e(TAG, "takePictureInternal: 当值为" + isPictureCaptureInProgress + "时调用");
735 | mCamera.takePicture(new Camera.ShutterCallback() {
736 |
737 | @Override
738 | public void onShutter() {
739 | if (mListener != null) {
740 | mListener.onShutter();
741 | }
742 | }
743 | }, null, null, new Camera.PictureCallback() {
744 | @Override
745 | public void onPictureTaken(final byte[] data, Camera camera) {
746 | isPictureCaptureInProgress.set(false);
747 |
748 | Bitmap rawBitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
749 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
750 | rawBitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
751 |
752 | // Bitmap rotedBitmap;
753 | //
754 | // if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
755 | // rotedBitmap = BitmapUtils.mirror(rawBitmap);
756 | // } else {
757 | // rotedBitmap = BitmapUtils.rotate(rawBitmap, 180f);
758 | // }
759 | // rawBitmap.recycle();
760 | rawBitmap = mCameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT ? BitmapUtils.mirror(rawBitmap) : rawBitmap;
761 | try {
762 | baos.flush();
763 | baos.close();
764 | } catch (IOException e) {
765 | e.printStackTrace();
766 | }
767 | if (mListener != null) {
768 | mListener.onCupture(rawBitmap);
769 | }
770 | camera.cancelAutoFocus();
771 | camera.startPreview();
772 |
773 | }
774 | });
775 | }
776 |
777 | /**
778 | * 截取预览帧图像
779 | *
780 | * @param file
781 | */
782 | public void cut(File file) {
783 | YuvImage image = new YuvImage(this.imageData, mCameraParameters.getPreviewFormat(), mPreviewSize.width, mPreviewSize.height, null);
784 | FileOutputStream fos = null;
785 | Rect rect = new Rect(0, 0, mPreviewSize.width, mPreviewSize.height);
786 | try {
787 | fos = new FileOutputStream(file);
788 | } catch (FileNotFoundException e) {
789 | e.printStackTrace();
790 | }
791 | image.compressToJpeg(rect, 100, fos);
792 | mListener.onCut(file);
793 | }
794 |
795 | private static class BitmapUtils {
796 | public static Bitmap mirror(Bitmap bitmap) {
797 | int width = bitmap.getWidth();
798 | int height = bitmap.getHeight();
799 | Matrix matrix = new Matrix();
800 | if (width >= height) {
801 | matrix.setRotate(-90f);
802 | }
803 | matrix.postScale(-1f, 1f);
804 |
805 |
806 | return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
807 | }
808 |
809 | public static Bitmap rotate(Bitmap bitmap, Float degree) {
810 | Matrix matrix = new Matrix();
811 | matrix.postRotate(degree);
812 | return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
813 | }
814 |
815 | public static Bitmap scale(Bitmap bitmap, int w) {
816 | int width = bitmap.getWidth();
817 | int height = bitmap.getHeight();
818 | int h = w * height / width;
819 |
820 | return Bitmap.createScaledBitmap(bitmap, w, h, true);
821 | }
822 | }
823 |
824 | @Override
825 | protected void onAttachedToWindow() {
826 | super.onAttachedToWindow();
827 | if (!isInEditMode()) {
828 | mDisplayOrientationDetector.enable(ViewCompat.getDisplay(this));
829 | }
830 | }
831 |
832 | @Override
833 | protected void onDetachedFromWindow() {
834 | if (!isInEditMode()) {
835 | mDisplayOrientationDetector.disable();
836 | }
837 | super.onDetachedFromWindow();
838 | }
839 |
840 | /**
841 | * 相机成像尺寸比较器
842 | */
843 | private class CameraSizeComparator implements Comparator {
844 | private float mWdith;
845 | private float mHeight;
846 | private float mRatio;
847 | private float mArea;
848 |
849 | public CameraSizeComparator(int width, int height) {
850 | mWdith = (float) width;
851 | mHeight = (float) height;
852 | mRatio = mWdith / mHeight;
853 | mArea = mWdith * mHeight;
854 | }
855 |
856 | @Override
857 | public int compare(Camera.Size o1, Camera.Size o2) {
858 |
859 | float heightDiffer1 = Math.abs(mHeight - o1.height);
860 | float heightDiffer2 = Math.abs(mHeight - o2.height);
861 | float widthDiffer1 = Math.abs(mWdith - o1.width);
862 | float widthDiffer2 = Math.abs(mWdith - o2.width);
863 | if (heightDiffer1 < heightDiffer2 && widthDiffer1 < widthDiffer2) {
864 | return 1;
865 | } else {
866 | return -1;
867 | }
868 | }
869 | }
870 |
871 | public List getSceneModeList() {
872 | return sceneModeList;
873 | }
874 |
875 | public List getWhiteBalanceModeList() {
876 | return whiteBalanceModeList;
877 | }
878 |
879 | public List getColorEffectModeList() {
880 | return colorEffectModeList;
881 | }
882 |
883 | public int getMaxZoom() {
884 | return maxZoom;
885 | }
886 |
887 | }
888 |
--------------------------------------------------------------------------------