├── app
├── .gitignore
├── debug
│ ├── app-debug.apk
│ └── output.json
├── release
│ ├── app-release.apk
│ └── output.json
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.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
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── xml
│ │ │ │ └── file_paths.xml
│ │ │ ├── layout
│ │ │ │ ├── welcome_activity.xml
│ │ │ │ └── activity_main.xml
│ │ │ ├── values-v28
│ │ │ │ └── styles.xml
│ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ └── drawable
│ │ │ │ └── ic_launcher_background.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── hbzhou
│ │ │ │ └── open
│ │ │ │ └── camera
│ │ │ │ ├── MyApplication.kt
│ │ │ │ ├── MainActivity.kt
│ │ │ │ └── WelcomeActivity.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── hbzhou
│ │ │ └── open
│ │ │ └── camera
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── hbzhou
│ │ └── open
│ │ └── camera
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── flowcamera
├── consumer-rules.pro
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ └── attrs.xml
│ │ │ ├── drawable
│ │ │ │ ├── ic_flash_on.xml
│ │ │ │ ├── ic_flash_off.xml
│ │ │ │ ├── ic_flash_auto.xml
│ │ │ │ ├── ic_photo.xml
│ │ │ │ └── ic_camera.xml
│ │ │ └── layout
│ │ │ │ ├── camera_view.xml
│ │ │ │ ├── flow_camera_view.xml
│ │ │ │ ├── flow_camera_view2.xml
│ │ │ │ ├── flow_camera_view3.xml
│ │ │ │ └── custom_camera_view.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── hbzhou
│ │ │ │ └── open
│ │ │ │ └── flowcamera
│ │ │ │ ├── listener
│ │ │ │ ├── ClickListener.java
│ │ │ │ ├── ResultListener.java
│ │ │ │ ├── ReturnListener.java
│ │ │ │ ├── OnVideoPlayPrepareListener.kt
│ │ │ │ ├── TypeListener.java
│ │ │ │ ├── ErrorListener.java
│ │ │ │ ├── JCameraListener.java
│ │ │ │ ├── CaptureListener.java
│ │ │ │ └── FlowCameraListener.java
│ │ │ │ ├── CaptureListener.kt
│ │ │ │ ├── CompareSizesByArea.kt
│ │ │ │ ├── view
│ │ │ │ └── CameraView.java
│ │ │ │ ├── util
│ │ │ │ ├── AudioUtil.java
│ │ │ │ ├── ScreenUtils.java
│ │ │ │ ├── LogUtil.java
│ │ │ │ ├── DeviceUtil.java
│ │ │ │ ├── AngleUtil.java
│ │ │ │ ├── FileUtil.java
│ │ │ │ ├── CheckPermission.java
│ │ │ │ └── CameraParamUtil.java
│ │ │ │ ├── state
│ │ │ │ ├── State.java
│ │ │ │ ├── BorrowVideoState.java
│ │ │ │ ├── BorrowPictureState.java
│ │ │ │ ├── PreviewState.java
│ │ │ │ └── CameraMachine.java
│ │ │ │ ├── VideoRecordEventExt.kt
│ │ │ │ ├── ReturnButton.java
│ │ │ │ ├── AutoFitTextureView.kt
│ │ │ │ ├── FoucsView.java
│ │ │ │ ├── VideoQualityExt.kt
│ │ │ │ ├── TypeButton.java
│ │ │ │ ├── CaptureImageButton.kt
│ │ │ │ ├── CaptureButton.java
│ │ │ │ ├── CaptureLayout.java
│ │ │ │ ├── CustomCameraView.java
│ │ │ │ └── JCameraView.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── hbzhou
│ │ │ └── open
│ │ │ └── flowcamera
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── hbzhou
│ │ └── open
│ │ └── flowcamera
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── assets
├── mmp1.png
├── mmp2.jpg
├── mmp3.png
├── alipayicon.jpg
├── 20191221222458.jpg
└── 20191221222518.jpg
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── gradlew.bat
├── README.md
├── gradlew
└── LICENSE
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/flowcamera/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/flowcamera/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | *.iml
3 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':flowcamera'
2 | rootProject.name='CameraView'
3 |
--------------------------------------------------------------------------------
/assets/mmp1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xionger0520/flowcamera/HEAD/assets/mmp1.png
--------------------------------------------------------------------------------
/assets/mmp2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xionger0520/flowcamera/HEAD/assets/mmp2.jpg
--------------------------------------------------------------------------------
/assets/mmp3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xionger0520/flowcamera/HEAD/assets/mmp3.png
--------------------------------------------------------------------------------
/assets/alipayicon.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xionger0520/flowcamera/HEAD/assets/alipayicon.jpg
--------------------------------------------------------------------------------
/app/debug/app-debug.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xionger0520/flowcamera/HEAD/app/debug/app-debug.apk
--------------------------------------------------------------------------------
/assets/20191221222458.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xionger0520/flowcamera/HEAD/assets/20191221222458.jpg
--------------------------------------------------------------------------------
/assets/20191221222518.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xionger0520/flowcamera/HEAD/assets/20191221222518.jpg
--------------------------------------------------------------------------------
/app/release/app-release.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xionger0520/flowcamera/HEAD/app/release/app-release.apk
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | CameraView
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xionger0520/flowcamera/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/flowcamera/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | CameraLibrary
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xionger0520/flowcamera/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xionger0520/flowcamera/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xionger0520/flowcamera/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xionger0520/flowcamera/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xionger0520/flowcamera/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xionger0520/flowcamera/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xionger0520/flowcamera/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xionger0520/flowcamera/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xionger0520/flowcamera/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xionger0520/flowcamera/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/debug/output.json:
--------------------------------------------------------------------------------
1 | [{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0","enabled":true,"outputFile":"app-debug.apk","fullName":"debug","baseName":"debug"},"path":"app-debug.apk","properties":{}}]
--------------------------------------------------------------------------------
/app/release/output.json:
--------------------------------------------------------------------------------
1 | [{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
--------------------------------------------------------------------------------
/app/src/main/java/com/hbzhou/open/camera/MyApplication.kt:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.camera
2 |
3 | import android.app.Application
4 |
5 | /**
6 | * author hbzhou
7 | * date 2019/12/16 13:54
8 | */
9 | class MyApplication : Application() {
10 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/listener/ClickListener.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.listener;
2 |
3 | /**
4 | * author hbzhou
5 | * date 2019/12/13 10:49
6 | */
7 | public interface ClickListener {
8 | void onClick();
9 | }
10 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/listener/ResultListener.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.listener;
2 |
3 | /**
4 | * author hbzhou
5 | * date 2019/12/13 10:49
6 | */
7 | public interface ResultListener {
8 | void callback();
9 | }
10 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/listener/ReturnListener.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.listener;
2 |
3 | /**
4 | * author hbzhou
5 | * date 2019/12/13 10:49
6 | */
7 | public interface ReturnListener {
8 | void onReturn();
9 | }
10 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/listener/OnVideoPlayPrepareListener.kt:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.listener
2 |
3 |
4 | /**
5 | * author hbzhou
6 | * date 2019/12/24 16:51
7 | */
8 | interface OnVideoPlayPrepareListener {
9 | fun onPrepared()
10 | }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Apr 02 20:18:16 CST 2021
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-6.7.1-bin.zip
7 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/listener/TypeListener.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.listener;
2 |
3 | /**
4 | * author hbzhou
5 | * date 2019/12/13 10:49
6 | */
7 | public interface TypeListener {
8 | void cancel();
9 |
10 | void confirm();
11 | }
12 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/listener/ErrorListener.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.listener;
2 |
3 | /**
4 | * author hbzhou
5 | * date 2019/12/13 10:49
6 | */
7 | public interface ErrorListener {
8 | void onError();
9 | void AudioPermissionError();
10 | }
11 |
--------------------------------------------------------------------------------
/flowcamera/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/flowcamera/src/main/res/drawable/ic_flash_on.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/listener/JCameraListener.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.listener;
2 |
3 | import android.graphics.Bitmap;
4 |
5 | /**
6 | * author hbzhou
7 | * date 2019/12/13 10:49
8 | */
9 | public interface JCameraListener {
10 |
11 | void captureSuccess(Bitmap bitmap);
12 |
13 | void recordSuccess(String url, Bitmap firstFrame);
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/CaptureListener.kt:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera
2 |
3 |
4 | /**
5 | * author hbzhou
6 | * date 2019/12/13 11:13
7 | */
8 | interface CaptureListener {
9 |
10 | fun takePictures()
11 |
12 | fun recordShort(time: Long)
13 |
14 | fun recordStart()
15 |
16 | fun recordEnd(time: Long)
17 |
18 | fun recordZoom(zoom: Float)
19 |
20 | fun recordError()
21 | }
--------------------------------------------------------------------------------
/flowcamera/src/main/res/drawable/ic_flash_off.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/test/java/com/hbzhou/open/camera/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.camera
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/listener/CaptureListener.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.listener;
2 |
3 | /**
4 | * author hbzhou
5 | * date 2019/12/13 10:49
6 | */
7 |
8 | public interface CaptureListener {
9 | void takePictures();
10 |
11 | void recordShort(long time);
12 |
13 | void recordStart();
14 |
15 | void recordEnd(long time);
16 |
17 | void recordZoom(float zoom);
18 |
19 | void recordError();
20 | }
21 |
--------------------------------------------------------------------------------
/flowcamera/src/main/res/drawable/ic_flash_auto.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/flowcamera/src/test/java/com/hbzhou/open/flowcamera/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/flowcamera/src/main/res/drawable/ic_photo.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/CompareSizesByArea.kt:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera
2 |
3 | import android.util.Size
4 | import java.lang.Long.signum
5 |
6 | /**
7 | * author hbzhou
8 | * date 2019/12/13 10:53
9 | */
10 | class CompareSizesByArea : Comparator {
11 |
12 | // We cast here to ensure the multiplications won't overflow
13 | override fun compare(lhs: Size, rhs: Size) =
14 | signum(lhs.width.toLong() * lhs.height - rhs.width.toLong() * rhs.height)
15 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | /.idea/compiler.xml
16 | /.idea/gradle.xml
17 | /.idea/codeStyles/codeStyleConfig.xml
18 | /.idea/codeStyles/Project.xml
19 | /.idea/jarRepositories.xml
20 | /.idea/misc.xml
21 | /.idea/vcs.xml
22 | /.idea/runConfigurations.xml
23 | /build/
24 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/listener/FlowCameraListener.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.listener;
2 |
3 | import androidx.annotation.NonNull;
4 | import androidx.annotation.Nullable;
5 |
6 | import java.io.File;
7 |
8 | /**
9 | * author hbzhou
10 | * date 2019/12/16 12:06
11 | */
12 | public interface FlowCameraListener {
13 |
14 | void captureSuccess(@NonNull File file);
15 |
16 | void recordSuccess(@NonNull File file);
17 |
18 | void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause);
19 | }
20 |
--------------------------------------------------------------------------------
/flowcamera/src/main/res/drawable/ic_camera.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/view/CameraView.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.view;
2 |
3 | import android.graphics.Bitmap;
4 |
5 | /**
6 | * author hbzhou
7 | * date 2019/12/13 10:49
8 | */
9 | public interface CameraView {
10 | void resetState(int type);
11 |
12 | void confirmState(int type);
13 |
14 | void showPicture(Bitmap bitmap, boolean isVertical);
15 |
16 | void playVideo(Bitmap firstFrame, String url);
17 |
18 | void stopVideo();
19 |
20 | void setTip(String tip);
21 |
22 | void startPreviewCallback();
23 |
24 | boolean handlerFoucs(float x, float y);
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/file_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/welcome_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v28/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/hbzhou/open/camera/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.camera
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.hbzhou.open.CameraView", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/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 |
--------------------------------------------------------------------------------
/flowcamera/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 |
--------------------------------------------------------------------------------
/flowcamera/src/androidTest/java/com/hbzhou/open/flowcamera/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.hbzhou.open.flowcamera.test", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/util/AudioUtil.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.util;
2 |
3 | import android.content.Context;
4 | import android.media.AudioManager;
5 |
6 | /**
7 | * author hbzhou
8 | * date 2019/12/13 10:49
9 | */
10 | public class AudioUtil {
11 | public static void setAudioManage(Context context) {
12 | AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
13 | audioManager.setStreamMute(AudioManager.STREAM_SYSTEM, true);
14 | audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
15 | audioManager.setStreamVolume(AudioManager.STREAM_ALARM, 0, 0);
16 | audioManager.setStreamVolume(AudioManager.STREAM_DTMF, 0, 0);
17 | audioManager.setStreamVolume(AudioManager.STREAM_NOTIFICATION, 0, 0);
18 | audioManager.setStreamVolume(AudioManager.STREAM_RING, 0, 0);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/state/State.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.state;
2 |
3 | import android.view.Surface;
4 | import android.view.SurfaceHolder;
5 |
6 | import com.hbzhou.open.flowcamera.CameraInterface;
7 |
8 | /**
9 | * author hbzhou
10 | * date 2019/12/13 10:49
11 | */
12 | public interface State {
13 |
14 | void start(SurfaceHolder holder, float screenProp);
15 |
16 | void stop();
17 |
18 | void foucs(float x, float y, CameraInterface.FocusCallback callback);
19 |
20 | void swtich(SurfaceHolder holder, float screenProp);
21 |
22 | void restart();
23 |
24 | void capture();
25 |
26 | void record(Surface surface, float screenProp);
27 |
28 | void stopRecord(boolean isShort, long time);
29 |
30 | void cancle(SurfaceHolder holder, float screenProp);
31 |
32 | void confirm();
33 |
34 | void zoom(float zoom, int type);
35 |
36 | void flash(String mode);
37 | }
38 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/util/ScreenUtils.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.util;
2 |
3 | import android.content.Context;
4 | import android.util.DisplayMetrics;
5 | import android.view.WindowManager;
6 |
7 | /**
8 | * author hbzhou
9 | * date 2019/12/13 10:49
10 | */
11 | public class ScreenUtils {
12 | public static int getScreenHeight(Context context) {
13 | DisplayMetrics metric = new DisplayMetrics();
14 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
15 | wm.getDefaultDisplay().getMetrics(metric);
16 | return metric.heightPixels;
17 | }
18 |
19 | public static int getScreenWidth(Context context) {
20 | DisplayMetrics metric = new DisplayMetrics();
21 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
22 | wm.getDefaultDisplay().getMetrics(metric);
23 | return metric.widthPixels;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/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 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/util/LogUtil.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.util;
2 |
3 | import android.util.Log;
4 |
5 | import static com.hbzhou.open.flowcamera.BuildConfig.DEBUG;
6 |
7 | /**
8 | * author hbzhou
9 | * date 2019/12/13 10:49
10 | */
11 | public class LogUtil {
12 |
13 | private static final String DEFAULT_TAG = "CJT";
14 |
15 | public static void i(String tag, String msg) {
16 | // if (DEBUG)
17 | Log.i(tag, msg);
18 | }
19 |
20 | public static void v(String tag, String msg) {
21 | if (DEBUG)
22 | Log.v(tag, msg);
23 | }
24 |
25 | public static void d(String tag, String msg) {
26 | if (DEBUG)
27 | Log.d(tag, msg);
28 | }
29 |
30 | public static void e(String tag, String msg) {
31 | if (DEBUG)
32 | Log.e(tag, msg);
33 | }
34 |
35 | public static void i(String msg) {
36 | i(DEFAULT_TAG, msg);
37 | }
38 |
39 | public static void v(String msg) {
40 | v(DEFAULT_TAG, msg);
41 | }
42 |
43 | public static void d(String msg) {
44 | d(DEFAULT_TAG, msg);
45 | }
46 |
47 | public static void e(String msg) {
48 | e(DEFAULT_TAG, msg);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/util/DeviceUtil.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.util;
2 |
3 | import android.os.Build;
4 |
5 | /**
6 | * author hbzhou
7 | * date 2019/12/13 10:49
8 | */
9 | public class DeviceUtil {
10 |
11 | private static String[] huaweiRongyao = {
12 | "hwH60", //荣耀6
13 | "hwPE", //荣耀6 plus
14 | "hwH30", //3c
15 | "hwHol", //3c畅玩版
16 | "hwG750", //3x
17 | "hw7D", //x1
18 | "hwChe2", //x1
19 | };
20 |
21 | public static String getDeviceInfo() {
22 | String handSetInfo =
23 | "手机型号:" + Build.DEVICE +
24 | "\n系统版本:" + Build.VERSION.RELEASE +
25 | "\nSDK版本:" + Build.VERSION.SDK_INT;
26 | return handSetInfo;
27 | }
28 |
29 | public static String getDeviceModel() {
30 | return Build.DEVICE;
31 | }
32 |
33 | public static boolean isHuaWeiRongyao() {
34 | int length = huaweiRongyao.length;
35 | for (int i = 0; i < length; i++) {
36 | if (huaweiRongyao[i].equals(getDeviceModel())) {
37 | return true;
38 | }
39 | }
40 | return false;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/util/AngleUtil.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.util;
2 |
3 | /**
4 | * author hbzhou
5 | * date 2019/12/13 10:49
6 | */
7 | public class AngleUtil {
8 | public static int getSensorAngle(float x, float y) {
9 | if (Math.abs(x) > Math.abs(y)) {
10 | /**
11 | * 横屏倾斜角度比较大
12 | */
13 | if (x > 4) {
14 | /**
15 | * 左边倾斜
16 | */
17 | return 270;
18 | } else if (x < -4) {
19 | /**
20 | * 右边倾斜
21 | */
22 | return 90;
23 | } else {
24 | /**
25 | * 倾斜角度不够大
26 | */
27 | return 0;
28 | }
29 | } else {
30 | if (y > 7) {
31 | /**
32 | * 左边倾斜
33 | */
34 | return 0;
35 | } else if (y < -7) {
36 | /**
37 | * 右边倾斜
38 | */
39 | return 180;
40 | } else {
41 | /**
42 | * 倾斜角度不够大
43 | */
44 | return 0;
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/flowcamera/src/main/res/values/attrs.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 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/VideoRecordEventExt.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.hbzhou.open.flowcamera
17 |
18 | import androidx.camera.video.VideoRecordEvent
19 |
20 | /**
21 | * A helper extended function to get the name(string) for the VideoRecordEvent.
22 | */
23 | fun VideoRecordEvent.getNameString() : String {
24 | return when (this) {
25 | is VideoRecordEvent.Status -> "Status"
26 | is VideoRecordEvent.Start -> "Started"
27 | is VideoRecordEvent.Finalize-> "Finalized"
28 | is VideoRecordEvent.Pause -> "Paused"
29 | is VideoRecordEvent.Resume -> "Resumed"
30 | else -> throw IllegalArgumentException("Unknown VideoRecordEvent: $this")
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 | android {
5 | compileSdkVersion 32
6 | defaultConfig {
7 | applicationId "com.hbzhou.open.camera"
8 | minSdkVersion 21
9 | targetSdkVersion 31
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | compileOptions {
21 | sourceCompatibility = 1.8
22 | targetCompatibility = 1.8
23 | }
24 | }
25 |
26 | dependencies {
27 | implementation fileTree(dir: 'libs', include: ['*.jar'])
28 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
29 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5'
30 | implementation 'androidx.appcompat:appcompat:1.2.0'
31 | implementation 'androidx.core:core-ktx:1.3.2'
32 | implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
33 | testImplementation 'junit:junit:4.13'
34 | androidTestImplementation 'androidx.test.ext:junit:1.1.2'
35 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
36 | // api project(':flowcamera')
37 | implementation 'com.github.xionger0520:flowcamera:V1.2.0'
38 | // if u use AndroidX, use the following
39 | implementation 'com.github.getActivity:XXPermissions:16.0'
40 | }
41 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/ReturnButton.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.graphics.Path;
8 | import android.view.View;
9 |
10 | /**
11 | * author hbzhou
12 | * date 2019/12/13 10:49
13 | */
14 | public class ReturnButton extends View {
15 |
16 | private int size;
17 |
18 | private int center_X;
19 | private int center_Y;
20 | private float strokeWidth;
21 |
22 | private Paint paint;
23 | Path path;
24 |
25 | public ReturnButton(Context context, int size) {
26 | this(context);
27 | this.size = size;
28 | center_X = size / 2;
29 | center_Y = size / 2;
30 |
31 | strokeWidth = size / 15f;
32 |
33 | paint = new Paint();
34 | paint.setAntiAlias(true);
35 | paint.setColor(Color.WHITE);
36 | paint.setStyle(Paint.Style.STROKE);
37 | paint.setStrokeWidth(strokeWidth);
38 |
39 | path = new Path();
40 | }
41 |
42 | public ReturnButton(Context context) {
43 | super(context);
44 | }
45 |
46 | @Override
47 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
48 | setMeasuredDimension(size, size / 2);
49 | }
50 |
51 | @Override
52 | protected void onDraw(Canvas canvas) {
53 | super.onDraw(canvas);
54 | path.moveTo(strokeWidth, strokeWidth/2);
55 | path.lineTo(center_X, center_Y - strokeWidth/2);
56 | path.lineTo(size - strokeWidth, strokeWidth/2);
57 | canvas.drawPath(path, paint);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/AutoFitTextureView.kt:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.TextureView
6 |
7 |
8 | /**
9 | * author hbzhou
10 | * date 2019/12/13 10:49
11 | */
12 | class AutoFitTextureView @JvmOverloads constructor(
13 | context: Context,
14 | attrs: AttributeSet? = null,
15 | defStyle: Int = 0
16 | ) : TextureView(context, attrs, defStyle) {
17 |
18 | private var ratioWidth = 0
19 | private var ratioHeight = 0
20 |
21 | /**
22 | * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
23 | * calculated from the parameters. Note that the actual sizes of parameters don't matter, that
24 | * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
25 | *
26 | * @param width Relative horizontal size
27 | * @param height Relative vertical size
28 | */
29 | fun setAspectRatio(width: Int, height: Int) {
30 | if (width < 0 || height < 0) {
31 | throw IllegalArgumentException("Size cannot be negative.")
32 | }
33 | ratioWidth = width
34 | ratioHeight = height
35 | requestLayout()
36 | }
37 |
38 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
39 | super.onMeasure(widthMeasureSpec, heightMeasureSpec)
40 | val width = MeasureSpec.getSize(widthMeasureSpec)
41 | val height = MeasureSpec.getSize(heightMeasureSpec)
42 | if (ratioWidth == 0 || ratioHeight == 0) {
43 | setMeasuredDimension(width, height)
44 | } else {
45 | if (width < ((height * ratioWidth) / ratioHeight)) {
46 | setMeasuredDimension(width, (width * ratioHeight) / ratioWidth)
47 | } else {
48 | setMeasuredDimension((height * ratioWidth) / ratioHeight, height)
49 | }
50 | }
51 | }
52 |
53 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/flowcamera/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 | apply plugin: 'kotlin-kapt'
5 | android {
6 | compileSdkVersion 32
7 | defaultConfig {
8 | minSdkVersion 21
9 | targetSdkVersion 31
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
13 | consumerProguardFiles 'consumer-rules.pro'
14 | }
15 |
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | compileOptions {
23 | sourceCompatibility = 1.8
24 | targetCompatibility = 1.8
25 | }
26 |
27 | }
28 |
29 | dependencies {
30 | api fileTree(dir: 'libs', include: ['*.jar'])
31 | api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
32 | api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1'
33 | api 'androidx.core:core-ktx:1.8.0'
34 | api 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
35 | api "androidx.concurrent:concurrent-futures-ktx:1.1.0"
36 | // CameraX core library
37 | def camerax_version = "1.2.0-alpha04"
38 | // // CameraX view library
39 | // def camerax_view_version = "1.0.0-alpha24"
40 | api "androidx.camera:camera-core:$camerax_version"
41 | // If you want to use Camera2 extensions
42 | api "androidx.camera:camera-camera2:$camerax_version"
43 | // If you to use the Camera View class
44 | api "androidx.camera:camera-view:$camerax_version"
45 | // CameraX Lifecycle library
46 | api "androidx.camera:camera-lifecycle:$camerax_version"
47 | // CameraX video
48 | api "androidx.camera:camera-video:${camerax_version}"
49 |
50 | api 'com.github.bumptech.glide:glide:4.13.2'
51 | kapt 'com.github.bumptech.glide:compiler:4.12.0'
52 | // PhotoView
53 | api 'com.github.chrisbanes:PhotoView:2.3.0'
54 | // custom camera view
55 | api 'com.otaliastudios:cameraview:2.7.0'
56 | }
57 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/state/BorrowVideoState.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.state;
2 |
3 | import android.view.Surface;
4 | import android.view.SurfaceHolder;
5 | import com.hbzhou.open.flowcamera.CameraInterface;
6 | import com.hbzhou.open.flowcamera.JCameraView;
7 | import com.hbzhou.open.flowcamera.util.LogUtil;
8 |
9 | /**
10 | * author hbzhou
11 | * date 2019/12/13 10:49
12 | */
13 | public class BorrowVideoState implements State {
14 | private final String TAG = "BorrowVideoState";
15 | private CameraMachine machine;
16 |
17 | public BorrowVideoState(CameraMachine machine) {
18 | this.machine = machine;
19 | }
20 |
21 | @Override
22 | public void start(SurfaceHolder holder, float screenProp) {
23 | CameraInterface.getInstance().doStartPreview(holder, screenProp);
24 | machine.setState(machine.getPreviewState());
25 | }
26 |
27 | @Override
28 | public void stop() {
29 |
30 | }
31 |
32 | @Override
33 | public void foucs(float x, float y, CameraInterface.FocusCallback callback) {
34 |
35 | }
36 |
37 |
38 | @Override
39 | public void swtich(SurfaceHolder holder, float screenProp) {
40 |
41 | }
42 |
43 | @Override
44 | public void restart() {
45 |
46 | }
47 |
48 | @Override
49 | public void capture() {
50 |
51 | }
52 |
53 | @Override
54 | public void record(Surface surface, float screenProp) {
55 |
56 | }
57 |
58 | @Override
59 | public void stopRecord(boolean isShort, long time) {
60 |
61 | }
62 |
63 | @Override
64 | public void cancle(SurfaceHolder holder, float screenProp) {
65 | machine.getView().resetState(JCameraView.TYPE_VIDEO);
66 | machine.setState(machine.getPreviewState());
67 | }
68 |
69 | @Override
70 | public void confirm() {
71 | machine.getView().confirmState(JCameraView.TYPE_VIDEO);
72 | machine.setState(machine.getPreviewState());
73 | }
74 |
75 | @Override
76 | public void zoom(float zoom, int type) {
77 | LogUtil.i(TAG, "zoom");
78 | }
79 |
80 | @Override
81 | public void flash(String mode) {
82 |
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/state/BorrowPictureState.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.state;
2 |
3 | import android.view.Surface;
4 | import android.view.SurfaceHolder;
5 | import com.hbzhou.open.flowcamera.CameraInterface;
6 | import com.hbzhou.open.flowcamera.JCameraView;
7 | import com.hbzhou.open.flowcamera.util.LogUtil;
8 |
9 | /**
10 | * author hbzhou
11 | * date 2019/12/13 10:49
12 | */
13 | public class BorrowPictureState implements State {
14 | private final String TAG = "BorrowPictureState";
15 | private CameraMachine machine;
16 |
17 | public BorrowPictureState(CameraMachine machine) {
18 | this.machine = machine;
19 | }
20 |
21 | @Override
22 | public void start(SurfaceHolder holder, float screenProp) {
23 | CameraInterface.getInstance().doStartPreview(holder, screenProp);
24 | machine.setState(machine.getPreviewState());
25 | }
26 |
27 | @Override
28 | public void stop() {
29 |
30 | }
31 |
32 |
33 | @Override
34 | public void foucs(float x, float y, CameraInterface.FocusCallback callback) {
35 | }
36 |
37 | @Override
38 | public void swtich(SurfaceHolder holder, float screenProp) {
39 |
40 | }
41 |
42 | @Override
43 | public void restart() {
44 |
45 | }
46 |
47 | @Override
48 | public void capture() {
49 |
50 | }
51 |
52 | @Override
53 | public void record(Surface surface,float screenProp) {
54 |
55 | }
56 |
57 | @Override
58 | public void stopRecord(boolean isShort, long time) {
59 | }
60 |
61 | @Override
62 | public void cancle(SurfaceHolder holder, float screenProp) {
63 | CameraInterface.getInstance().doStartPreview(holder, screenProp);
64 | machine.getView().resetState(JCameraView.TYPE_PICTURE);
65 | machine.setState(machine.getPreviewState());
66 | }
67 |
68 | @Override
69 | public void confirm() {
70 | machine.getView().confirmState(JCameraView.TYPE_PICTURE);
71 | machine.setState(machine.getPreviewState());
72 | }
73 |
74 | @Override
75 | public void zoom(float zoom, int type) {
76 | LogUtil.i(TAG, "zoom");
77 | }
78 |
79 | @Override
80 | public void flash(String mode) {
81 |
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/FoucsView.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Paint;
6 | import android.util.AttributeSet;
7 | import android.view.View;
8 |
9 | import androidx.annotation.Nullable;
10 |
11 | import com.hbzhou.open.flowcamera.util.ScreenUtils;
12 |
13 | /**
14 | * author hbzhou
15 | * date 2019/12/13 10:49
16 | */
17 | public class FoucsView extends View {
18 | private int size;
19 | private int center_x;
20 | private int center_y;
21 | private int length;
22 | private Paint mPaint;
23 |
24 | public FoucsView(Context context) {
25 | this(context, null);
26 | }
27 |
28 | public FoucsView(Context context, @Nullable AttributeSet attrs) {
29 | this(context, attrs, 0);
30 | }
31 |
32 | public FoucsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
33 | super(context, attrs, defStyleAttr);
34 | this.size = ScreenUtils.getScreenWidth(context) / 3;
35 | mPaint = new Paint();
36 | mPaint.setAntiAlias(true);
37 | mPaint.setDither(true);
38 | mPaint.setColor(0xEE16AE16);
39 | mPaint.setStrokeWidth(4);
40 | mPaint.setStyle(Paint.Style.STROKE);
41 | }
42 |
43 | @Override
44 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
45 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
46 | center_x = (int) (size / 2.0);
47 | center_y = (int) (size / 2.0);
48 | length = (int) (size / 2.0) - 2;
49 | setMeasuredDimension(size, size);
50 | }
51 |
52 | @Override
53 | protected void onDraw(Canvas canvas) {
54 | super.onDraw(canvas);
55 | canvas.drawRect(center_x - length, center_y - length, center_x + length, center_y + length, mPaint);
56 | canvas.drawLine(2, getHeight() / 2, size / 10, getHeight() / 2, mPaint);
57 | canvas.drawLine(getWidth() - 2, getHeight() / 2, getWidth() - size / 10, getHeight() / 2, mPaint);
58 | canvas.drawLine(getWidth() / 2, 2, getWidth() / 2, size / 10, mPaint);
59 | canvas.drawLine(getWidth() / 2, getHeight() - 2, getWidth() / 2, getHeight() - size / 10, mPaint);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/flowcamera/src/main/res/layout/camera_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
12 |
16 |
17 |
23 |
24 |
25 |
26 |
31 |
32 |
38 |
39 |
45 |
46 |
47 |
48 |
49 |
54 |
55 |
61 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/util/FileUtil.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.util;
2 |
3 | import android.graphics.Bitmap;
4 | import android.os.Environment;
5 |
6 | import java.io.BufferedOutputStream;
7 | import java.io.File;
8 | import java.io.FileOutputStream;
9 | import java.io.IOException;
10 |
11 | /**
12 | * author hbzhou
13 | * date 2019/12/13 10:49
14 | */
15 | public class FileUtil {
16 | private static final String TAG = "CJT";
17 | private static final File parentPath = Environment.getExternalStorageDirectory();
18 | private static String storagePath = "";
19 | private static String DST_FOLDER_NAME = "JCamera";
20 |
21 | private static String initPath() {
22 | if (storagePath.equals("")) {
23 | storagePath = parentPath.getAbsolutePath() + File.separator + DST_FOLDER_NAME;
24 | File f = new File(storagePath);
25 | if (!f.exists()) {
26 | f.mkdir();
27 | }
28 | }
29 | return storagePath;
30 | }
31 |
32 | public static String saveBitmap(String dir, Bitmap b) {
33 | DST_FOLDER_NAME = dir;
34 | String path = initPath();
35 | long dataTake = System.currentTimeMillis();
36 | String jpegName = path + File.separator + "picture_" + dataTake + ".jpg";
37 | try {
38 | FileOutputStream fout = new FileOutputStream(jpegName);
39 | BufferedOutputStream bos = new BufferedOutputStream(fout);
40 | b.compress(Bitmap.CompressFormat.JPEG, 100, bos);
41 | bos.flush();
42 | bos.close();
43 | return jpegName;
44 | } catch (IOException e) {
45 | e.printStackTrace();
46 | return "";
47 | }
48 | }
49 |
50 | public static boolean deleteFile(String url) {
51 | boolean result = false;
52 | File file = new File(url);
53 | if (file.exists()) {
54 | result = file.delete();
55 | }
56 | return result;
57 | }
58 |
59 | public static boolean isExternalStorageWritable() {
60 | String state = Environment.getExternalStorageState();
61 | if (Environment.MEDIA_MOUNTED.equals(state)) {
62 | return true;
63 | }
64 | return false;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/flowcamera/src/main/res/layout/flow_camera_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
15 |
16 |
22 |
23 |
29 |
30 |
31 |
32 |
38 |
39 |
45 |
46 |
52 |
53 |
54 |
55 |
60 |
--------------------------------------------------------------------------------
/flowcamera/src/main/res/layout/flow_camera_view2.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
9 |
10 |
14 |
15 |
21 |
22 |
28 |
29 |
30 |
31 |
37 |
38 |
44 |
45 |
51 |
52 |
53 |
54 |
59 |
60 |
--------------------------------------------------------------------------------
/flowcamera/src/main/res/layout/flow_camera_view3.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
9 |
10 |
14 |
15 |
21 |
22 |
28 |
29 |
30 |
31 |
37 |
38 |
44 |
45 |
51 |
52 |
53 |
54 |
59 |
60 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hbzhou/open/camera/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.camera
2 |
3 | import android.os.Bundle
4 | import android.widget.Toast
5 | import androidx.appcompat.app.AppCompatActivity
6 | import com.hbzhou.open.flowcamera.CustomCameraView.BUTTON_STATE_BOTH
7 | import com.hbzhou.open.flowcamera.FlowCameraView
8 | import com.hbzhou.open.flowcamera.listener.ClickListener
9 | import com.hbzhou.open.flowcamera.listener.FlowCameraListener
10 | import com.hbzhou.open.flowcamera.util.LogUtil
11 | import java.io.File
12 |
13 |
14 | class MainActivity : AppCompatActivity() {
15 | override fun onCreate(savedInstanceState: Bundle?) {
16 | super.onCreate(savedInstanceState)
17 | setContentView(R.layout.activity_main)
18 |
19 | val flowCamera = findViewById(R.id.flowCamera)
20 | // 绑定生命周期 您就不用关心Camera的开启和关闭了 不绑定无法预览
21 | flowCamera.setBindToLifecycle(this)
22 | // 设置白平衡模式
23 | // flowCamera.setWhiteBalance(WhiteBalance.AUTO)
24 | // 设置只支持单独拍照拍视频还是都支持
25 | // BUTTON_STATE_ONLY_CAPTURE BUTTON_STATE_ONLY_RECORDER BUTTON_STATE_BOTH
26 | flowCamera.setCaptureMode(BUTTON_STATE_BOTH)
27 | // 开启HDR
28 | // flowCamera.setHdrEnable(Hdr.ON)
29 | // 设置最大可拍摄小视频时长 S
30 | flowCamera.setRecordVideoMaxTime(10)
31 | // 设置拍照或拍视频回调监听
32 | flowCamera.setFlowCameraListener(object : FlowCameraListener {
33 | // 录制完成视频文件返回
34 | override fun recordSuccess(file: File) {
35 | Toast.makeText(this@MainActivity, file.absolutePath, Toast.LENGTH_SHORT).show()
36 | finish()
37 | }
38 |
39 | // 操作拍照或录视频出错
40 | override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) {
41 | LogUtil.e(
42 | videoCaptureError.toString().plus("----").plus(message).plus("---").plus(
43 | cause.toString()
44 | )
45 | )
46 | }
47 |
48 | // 拍照返回
49 | override fun captureSuccess(file: File) {
50 | Toast.makeText(this@MainActivity, file.absolutePath, Toast.LENGTH_SHORT).show()
51 | finish()
52 | }
53 | })
54 | //左边按钮点击事件
55 | flowCamera.setLeftClickListener(ClickListener { finish() })
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hbzhou/open/camera/WelcomeActivity.kt:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.camera
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.view.View
6 | import android.widget.Toast
7 | import androidx.appcompat.app.AppCompatActivity
8 | import com.hjq.permissions.OnPermissionCallback
9 | import com.hjq.permissions.Permission
10 | import com.hjq.permissions.XXPermissions
11 |
12 | /**
13 | * author hbzhou
14 | * date 2019/12/16 14:04
15 | */
16 | class WelcomeActivity : AppCompatActivity() {
17 | override fun onCreate(savedInstanceState: Bundle?) {
18 | super.onCreate(savedInstanceState)
19 | setContentView(R.layout.welcome_activity)
20 | findViewById(R.id.btn_start_camerax).setOnClickListener { initPermission() }
21 | }
22 |
23 | private fun initPermission() {
24 | XXPermissions.with(this)
25 | .permission(Permission.RECORD_AUDIO)
26 | .permission(Permission.MANAGE_EXTERNAL_STORAGE)
27 | .permission(Permission.CAMERA)
28 | // 设置权限请求拦截器(局部设置)
29 | //.interceptor(new PermissionInterceptor())
30 | // 设置不触发错误检测机制(局部设置)
31 | // .unchecked()
32 | .request(object : OnPermissionCallback {
33 |
34 | override fun onGranted(permissions: MutableList, all: Boolean) {
35 | if (!all) {
36 | Toast.makeText(this@WelcomeActivity,
37 | "获取部分权限成功,但部分权限未正常授予",
38 | Toast.LENGTH_SHORT).show()
39 | return
40 | }
41 | Toast.makeText(this@WelcomeActivity, "获取录音存储和相机权限成功!", Toast.LENGTH_SHORT)
42 | .show()
43 | startActivity(Intent(this@WelcomeActivity, MainActivity::class.java))
44 | }
45 |
46 | override fun onDenied(permissions: MutableList, never: Boolean) {
47 | if (never) {
48 | Toast.makeText(this@WelcomeActivity,
49 | "被永久拒绝授权,请手动授予录音存储和相机权限!",
50 | Toast.LENGTH_SHORT).show()
51 |
52 | // 如果是被永久拒绝就跳转到应用权限系统设置页面
53 | XXPermissions.startPermissionActivity(this@WelcomeActivity, permissions)
54 | } else {
55 | Toast.makeText(this@WelcomeActivity, "获取录音存储和相机权限失败!", Toast.LENGTH_SHORT)
56 | .show()
57 | }
58 | }
59 | })
60 | }
61 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
30 |
36 |
37 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
53 |
56 |
57 |
58 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/VideoQualityExt.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.hbzhou.open.flowcamera
17 |
18 | import androidx.camera.core.AspectRatio
19 | import androidx.camera.video.Quality
20 |
21 | /**
22 | * a helper function to retrieve the aspect ratio from a QualitySelector enum.
23 | */
24 | fun Quality.getAspectRatio(quality: Quality): Int {
25 | return when {
26 | arrayOf(Quality.UHD, Quality.FHD, Quality.HD)
27 | .contains(quality) -> AspectRatio.RATIO_16_9
28 | (quality == Quality.SD) -> AspectRatio.RATIO_4_3
29 | else -> throw UnsupportedOperationException()
30 | }
31 | }
32 |
33 | /**
34 | * a helper function to retrieve the aspect ratio string from a Quality enum.
35 | */
36 | fun Quality.getAspectRatioString(quality: Quality, portraitMode:Boolean) :String {
37 | val hdQualities = arrayOf(Quality.UHD, Quality.FHD, Quality.HD)
38 | val ratio =
39 | when {
40 | hdQualities.contains(quality) -> Pair(16, 9)
41 | quality == Quality.SD -> Pair(4, 3)
42 | else -> throw UnsupportedOperationException()
43 | }
44 |
45 | return if (portraitMode) "V,${ratio.second}:${ratio.first}"
46 | else "H,${ratio.first}:${ratio.second}"
47 | }
48 |
49 | /**
50 | * Get the name (a string) from the given Video.Quality object.
51 | */
52 | fun Quality.getNameString() :String {
53 | return when (this) {
54 | Quality.UHD -> "QUALITY_UHD(2160p)"
55 | Quality.FHD -> "QUALITY_FHD(1080p)"
56 | Quality.HD -> "QUALITY_HD(720p)"
57 | Quality.SD -> "QUALITY_SD(480p)"
58 | else -> throw IllegalArgumentException("Quality $this is NOT supported")
59 | }
60 | }
61 |
62 | /**
63 | * Translate Video.Quality name(a string) to its Quality object.
64 | */
65 | fun Quality.getQualityObject(name:String) : Quality {
66 | return when (name) {
67 | Quality.UHD.getNameString() -> Quality.UHD
68 | Quality.FHD.getNameString() -> Quality.FHD
69 | Quality.HD.getNameString() -> Quality.HD
70 | Quality.SD.getNameString() -> Quality.SD
71 | else -> throw IllegalArgumentException("Quality string $name is NOT supported")
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/flowcamera/src/main/res/layout/custom_camera_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
12 |
27 |
28 |
34 |
35 |
41 |
42 |
43 |
44 |
50 |
51 |
58 |
59 |
65 |
66 |
67 |
68 |
73 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/state/PreviewState.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.state;
2 |
3 | import android.graphics.Bitmap;
4 | import android.view.Surface;
5 | import android.view.SurfaceHolder;
6 | import com.hbzhou.open.flowcamera.CameraInterface;
7 | import com.hbzhou.open.flowcamera.JCameraView;
8 | import com.hbzhou.open.flowcamera.util.LogUtil;
9 |
10 | /**
11 | * author hbzhou
12 | * date 2019/12/13 10:49
13 | */
14 | class PreviewState implements State {
15 | public static final String TAG = "PreviewState";
16 |
17 | private CameraMachine machine;
18 |
19 | PreviewState(CameraMachine machine) {
20 | this.machine = machine;
21 | }
22 |
23 | @Override
24 | public void start(SurfaceHolder holder, float screenProp) {
25 | CameraInterface.getInstance().doStartPreview(holder, screenProp);
26 | }
27 |
28 | @Override
29 | public void stop() {
30 | CameraInterface.getInstance().doStopPreview();
31 | }
32 |
33 |
34 | @Override
35 | public void foucs(float x, float y, CameraInterface.FocusCallback callback) {
36 | LogUtil.i("preview state foucs");
37 | if (machine.getView().handlerFoucs(x, y)) {
38 | CameraInterface.getInstance().handleFocus(machine.getContext(), x, y, callback);
39 | }
40 | }
41 |
42 | @Override
43 | public void swtich(SurfaceHolder holder, float screenProp) {
44 | CameraInterface.getInstance().switchCamera(holder, screenProp);
45 | }
46 |
47 | @Override
48 | public void restart() {
49 |
50 | }
51 |
52 | @Override
53 | public void capture() {
54 | CameraInterface.getInstance().takePicture(new CameraInterface.TakePictureCallback() {
55 | @Override
56 | public void captureResult(Bitmap bitmap, boolean isVertical) {
57 | machine.getView().showPicture(bitmap, isVertical);
58 | machine.setState(machine.getBorrowPictureState());
59 | LogUtil.i("capture");
60 | }
61 | });
62 | }
63 |
64 | @Override
65 | public void record(Surface surface, float screenProp) {
66 | CameraInterface.getInstance().startRecord(surface, screenProp, null);
67 | }
68 |
69 | @Override
70 | public void stopRecord(final boolean isShort, long time) {
71 | CameraInterface.getInstance().stopRecord(isShort, new CameraInterface.StopRecordCallback() {
72 | @Override
73 | public void recordResult(String url, Bitmap firstFrame) {
74 | if (isShort) {
75 | machine.getView().resetState(JCameraView.TYPE_SHORT);
76 | } else {
77 | machine.getView().playVideo(firstFrame, url);
78 | machine.setState(machine.getBorrowVideoState());
79 | }
80 | }
81 | });
82 | }
83 |
84 | @Override
85 | public void cancle(SurfaceHolder holder, float screenProp) {
86 | LogUtil.i("浏览状态下,没有 cancle 事件");
87 | }
88 |
89 | @Override
90 | public void confirm() {
91 | LogUtil.i("浏览状态下,没有 confirm 事件");
92 | }
93 |
94 | @Override
95 | public void zoom(float zoom, int type) {
96 | LogUtil.i(TAG, "zoom");
97 | CameraInterface.getInstance().setZoom(zoom, type);
98 | }
99 |
100 | @Override
101 | public void flash(String mode) {
102 | CameraInterface.getInstance().setFlashMode(mode);
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/util/CheckPermission.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.util;
2 |
3 | import android.hardware.Camera;
4 | import android.media.AudioFormat;
5 | import android.media.AudioRecord;
6 | import android.media.MediaRecorder;
7 | import android.util.Log;
8 |
9 | /**
10 | * author hbzhou
11 | * date 2019/12/13 10:49
12 | */
13 | public class CheckPermission {
14 | public static final int STATE_RECORDING = -1;
15 | public static final int STATE_NO_PERMISSION = -2;
16 | public static final int STATE_SUCCESS = 1;
17 |
18 | /**
19 | * 用于检测是否具有录音权限
20 | *
21 | * @return
22 | */
23 | public static int getRecordState() {
24 | int minBuffer = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat
25 | .ENCODING_PCM_16BIT);
26 | AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, 44100, AudioFormat
27 | .CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, (minBuffer * 100));
28 | short[] point = new short[minBuffer];
29 | int readSize = 0;
30 | try {
31 |
32 | audioRecord.startRecording();//检测是否可以进入初始化状态
33 | } catch (Exception e) {
34 | if (audioRecord != null) {
35 | audioRecord.release();
36 | audioRecord = null;
37 | }
38 | return STATE_NO_PERMISSION;
39 | }
40 | if (audioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
41 | //6.0以下机型都会返回此状态,故使用时需要判断bulid版本
42 | //检测是否在录音中
43 | if (audioRecord != null) {
44 | audioRecord.stop();
45 | audioRecord.release();
46 | audioRecord = null;
47 | Log.d("CheckAudioPermission", "录音机被占用");
48 | }
49 | return STATE_RECORDING;
50 | } else {
51 | //检测是否可以获取录音结果
52 |
53 | readSize = audioRecord.read(point, 0, point.length);
54 |
55 |
56 | if (readSize <= 0) {
57 | if (audioRecord != null) {
58 | audioRecord.stop();
59 | audioRecord.release();
60 | audioRecord = null;
61 |
62 | }
63 | Log.d("CheckAudioPermission", "录音的结果为空");
64 | return STATE_NO_PERMISSION;
65 |
66 | } else {
67 | if (audioRecord != null) {
68 | audioRecord.stop();
69 | audioRecord.release();
70 | audioRecord = null;
71 |
72 | }
73 |
74 | return STATE_SUCCESS;
75 | }
76 | }
77 | }
78 |
79 | public synchronized static boolean isCameraUseable(int cameraID) {
80 | boolean canUse = true;
81 | Camera mCamera = null;
82 | try {
83 | mCamera = Camera.open(cameraID);
84 | // setParameters 是针对魅族MX5。MX5通过Camera.open()拿到的Camera对象不为null
85 | Camera.Parameters mParameters = mCamera.getParameters();
86 | mCamera.setParameters(mParameters);
87 | } catch (Exception e) {
88 | e.printStackTrace();
89 | canUse = false;
90 | } finally {
91 | if (mCamera != null) {
92 | mCamera.release();
93 | } else {
94 | canUse = false;
95 | }
96 | mCamera = null;
97 | }
98 | return canUse;
99 | }
100 | }
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/state/CameraMachine.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.state;
2 |
3 | import android.content.Context;
4 | import android.view.Surface;
5 | import android.view.SurfaceHolder;
6 |
7 | import com.hbzhou.open.flowcamera.CameraInterface;
8 | import com.hbzhou.open.flowcamera.view.CameraView;
9 |
10 | /**
11 | * author hbzhou
12 | * date 2019/12/13 10:49
13 | */
14 | public class CameraMachine implements State {
15 |
16 |
17 | private Context context;
18 | private State state;
19 | private CameraView view;
20 | // private CameraInterface.CameraOpenOverCallback cameraOpenOverCallback;
21 |
22 | private State previewState; //浏览状态(空闲)
23 | private State borrowPictureState; //浏览图片
24 | private State borrowVideoState; //浏览视频
25 |
26 | public CameraMachine(Context context, CameraView view, CameraInterface.CameraOpenOverCallback
27 | cameraOpenOverCallback) {
28 | this.context = context;
29 | previewState = new PreviewState(this);
30 | borrowPictureState = new BorrowPictureState(this);
31 | borrowVideoState = new BorrowVideoState(this);
32 | //默认设置为空闲状态
33 | this.state = previewState;
34 | // this.cameraOpenOverCallback = cameraOpenOverCallback;
35 | this.view = view;
36 | }
37 |
38 | public CameraView getView() {
39 | return view;
40 | }
41 |
42 | public Context getContext() {
43 | return context;
44 | }
45 |
46 | public void setState(State state) {
47 | this.state = state;
48 | }
49 |
50 | //获取浏览图片状态
51 | State getBorrowPictureState() {
52 | return borrowPictureState;
53 | }
54 |
55 | //获取浏览视频状态
56 | State getBorrowVideoState() {
57 | return borrowVideoState;
58 | }
59 |
60 | //获取空闲状态
61 | State getPreviewState() {
62 | return previewState;
63 | }
64 |
65 | @Override
66 | public void start(SurfaceHolder holder, float screenProp) {
67 | state.start(holder, screenProp);
68 | }
69 |
70 | @Override
71 | public void stop() {
72 | state.stop();
73 | }
74 |
75 | @Override
76 | public void foucs(float x, float y, CameraInterface.FocusCallback callback) {
77 | state.foucs(x, y, callback);
78 | }
79 |
80 | @Override
81 | public void swtich(SurfaceHolder holder, float screenProp) {
82 | state.swtich(holder, screenProp);
83 | }
84 |
85 | @Override
86 | public void restart() {
87 | state.restart();
88 | }
89 |
90 | @Override
91 | public void capture() {
92 | state.capture();
93 | }
94 |
95 | @Override
96 | public void record(Surface surface, float screenProp) {
97 | state.record(surface, screenProp);
98 | }
99 |
100 | @Override
101 | public void stopRecord(boolean isShort, long time) {
102 | state.stopRecord(isShort, time);
103 | }
104 |
105 | @Override
106 | public void cancle(SurfaceHolder holder, float screenProp) {
107 | state.cancle(holder, screenProp);
108 | }
109 |
110 | @Override
111 | public void confirm() {
112 | state.confirm();
113 | }
114 |
115 |
116 | @Override
117 | public void zoom(float zoom, int type) {
118 | state.zoom(zoom, type);
119 | }
120 |
121 | @Override
122 | public void flash(String mode) {
123 | state.flash(mode);
124 | }
125 |
126 | public State getState() {
127 | return this.state;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/TypeButton.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.graphics.Path;
8 | import android.graphics.RectF;
9 | import android.view.View;
10 |
11 | /**
12 | * author hbzhou
13 | * date 2019/12/13 10:49
14 | */
15 | public class TypeButton extends View{
16 | public static final int TYPE_CANCEL = 0x001;
17 | public static final int TYPE_CONFIRM = 0x002;
18 | private int button_type;
19 | private int button_size;
20 |
21 | private float center_X;
22 | private float center_Y;
23 | private float button_radius;
24 |
25 | private Paint mPaint;
26 | private Path path;
27 | private float strokeWidth;
28 |
29 | private float index;
30 | private RectF rectF;
31 |
32 | public TypeButton(Context context) {
33 | super(context);
34 | }
35 |
36 | public TypeButton(Context context, int type, int size) {
37 | super(context);
38 | this.button_type = type;
39 | button_size = size;
40 | button_radius = size / 2.0f;
41 | center_X = size / 2.0f;
42 | center_Y = size / 2.0f;
43 |
44 | mPaint = new Paint();
45 | path = new Path();
46 | strokeWidth = size / 50f;
47 | index = button_size / 12f;
48 | rectF = new RectF(center_X, center_Y - index, center_X + index * 2, center_Y + index);
49 | }
50 |
51 | @Override
52 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
53 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
54 | setMeasuredDimension(button_size, button_size);
55 | }
56 |
57 | @Override
58 | protected void onDraw(Canvas canvas) {
59 | super.onDraw(canvas);
60 | //如果类型为取消,则绘制内部为返回箭头
61 | if (button_type == TYPE_CANCEL) {
62 | mPaint.setAntiAlias(true);
63 | mPaint.setColor(0xEEDCDCDC);
64 | mPaint.setStyle(Paint.Style.FILL);
65 | canvas.drawCircle(center_X, center_Y, button_radius, mPaint);
66 |
67 | mPaint.setColor(Color.BLACK);
68 | mPaint.setStyle(Paint.Style.STROKE);
69 | mPaint.setStrokeWidth(strokeWidth);
70 |
71 | path.moveTo(center_X - index / 7, center_Y + index);
72 | path.lineTo(center_X + index, center_Y + index);
73 |
74 | path.arcTo(rectF, 90, -180);
75 | path.lineTo(center_X - index, center_Y - index);
76 | canvas.drawPath(path, mPaint);
77 | mPaint.setStyle(Paint.Style.FILL);
78 | path.reset();
79 | path.moveTo(center_X - index, (float) (center_Y - index * 1.5));
80 | path.lineTo(center_X - index, (float) (center_Y - index / 2.3));
81 | path.lineTo((float) (center_X - index * 1.6), center_Y - index);
82 | path.close();
83 | canvas.drawPath(path, mPaint);
84 |
85 | }
86 | //如果类型为确认,则绘制绿色勾
87 | if (button_type == TYPE_CONFIRM) {
88 | mPaint.setAntiAlias(true);
89 | mPaint.setColor(0xFFFFFFFF);
90 | mPaint.setStyle(Paint.Style.FILL);
91 | canvas.drawCircle(center_X, center_Y, button_radius, mPaint);
92 | mPaint.setAntiAlias(true);
93 | mPaint.setStyle(Paint.Style.STROKE);
94 | mPaint.setColor(0xFF00CC00);
95 | mPaint.setStrokeWidth(strokeWidth);
96 |
97 | path.moveTo(center_X - button_size / 6f, center_Y);
98 | path.lineTo(center_X - button_size / 21.2f, center_Y + button_size / 7.7f);
99 | path.lineTo(center_X + button_size / 4.0f, center_Y - button_size / 8.5f);
100 | path.lineTo(center_X - button_size / 21.2f, center_Y + button_size / 9.4f);
101 | path.close();
102 | canvas.drawPath(path, mPaint);
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FlowCamera
2 |
3 | [](https://jitpack.io/#xionger0520/flowcamera)
4 | [](https://jitpack.io/#xionger0520/flowcamera)
5 | [](https://github.com/xionger0520/flowcamera/blob/master/LICENSE)
6 |
7 |
8 | ## 仿微信拍照和拍小视频界面 使用最新的CameraX相机库
9 | ## 适配Android Q存储权限 可以点击拍照长按拍摄小视频也可设置只拍照 只拍视频
10 | ## 可设置白平衡 HDR 视频拍摄最大时长 闪光灯 手势缩放等
11 | ## 用了本库的攻城狮们点个star啊 本是同根生 相煎何太急
12 |
13 |
14 |
15 | ## 使用方法
16 | ### project的build.gradle中添加仓库地址
17 | ```xml
18 | allprojects {
19 | repositories {
20 | ...
21 | maven { url 'https://jitpack.io' }
22 | }
23 | }
24 | ```
25 | ### module的build.gradle中添加依赖
26 | ```xml
27 |
28 | dependencies {
29 |
30 | implementation 'com.github.xionger0520:flowcamera:V1.2.0'
31 |
32 | }
33 |
34 | CameraX需要java8环境
35 |
36 | android {
37 | ...
38 | compileOptions {
39 | sourceCompatibility = 1.8
40 | targetCompatibility = 1.8
41 | }
42 | }
43 |
44 | ```
45 | ### AndroidManifest.xml中添加权限
46 | ```xml
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | ```
58 |
59 | ### 布局文件中添加
60 | ```xml
61 |
62 |
67 | ```
68 | ### 开始使用
69 | ```xml
70 |
71 | 在fragment或者activity调用 Android6.0以上系统需要自行动态申请 存储 相机和麦克风权限
72 |
73 | 按照如下步骤配置
74 |
75 | val flowCamera = findViewById(R.id.flowCamera)
76 | // 绑定生命周期 您就不用关心Camera的开启和关闭了 不绑定无法预览
77 | flowCamera.setBindToLifecycle(this)
78 | // 设置白平衡模式
79 | flowCamera.setWhiteBalance(WhiteBalance.AUTO)
80 | // 设置只支持单独拍照拍视频还是都支持
81 | // BUTTON_STATE_ONLY_CAPTURE BUTTON_STATE_ONLY_RECORDER BUTTON_STATE_BOTH
82 | flowCamera.setCaptureMode(BUTTON_STATE_BOTH)
83 | // 开启HDR
84 | flowCamera.setHdrEnable(Hdr.ON)
85 | // 设置最大可拍摄小视频时长
86 | flowCamera.setRecordVideoMaxTime(10)
87 | // 设置拍照或拍视频回调监听
88 | flowCamera.setFlowCameraListener(object : FlowCameraListener {
89 | // 录制完成视频文件返回
90 | override fun recordSuccess(file: File) {
91 | ToastUtils.showLong(file.absolutePath)
92 | finish()
93 | }
94 | // 操作拍照或录视频出错
95 | override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) {
96 |
97 | }
98 | // 拍照返回
99 | override fun captureSuccess(file: File) {
100 | ToastUtils.showLong(file.absolutePath)
101 | finish()
102 | }
103 | })
104 | //左边按钮点击事件
105 | flowCamera.setLeftClickListener {
106 | finish()
107 | }
108 | ```
109 |
110 | ## 示例截图
111 |
112 | 
113 |
114 |
115 | ## 作者
116 |
117 | 1004695331@qq.com
118 |
119 | ## 如果本开源项目解决了你的小问题 大佬就打个赏呗
120 |
121 |
122 |
123 | ### LICENSE
124 | Copyright 2019 xionger0520
125 |
126 | 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
127 |
128 | http://www.apache.org/licenses/LICENSE-2.0
129 |
130 | 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.
131 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/util/CameraParamUtil.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera.util;
2 |
3 | import android.content.Context;
4 | import android.hardware.Camera;
5 | import android.util.Log;
6 | import android.view.Surface;
7 | import android.view.WindowManager;
8 |
9 | import java.util.Collections;
10 | import java.util.Comparator;
11 | import java.util.List;
12 |
13 | /**
14 | * author hbzhou
15 | * date 2019/12/13 10:49
16 | */
17 | public class CameraParamUtil {
18 | private static final String TAG = "JCameraView";
19 | private CameraSizeComparator sizeComparator = new CameraSizeComparator();
20 | private static CameraParamUtil cameraParamUtil = null;
21 |
22 | private CameraParamUtil() {
23 |
24 | }
25 |
26 | public static CameraParamUtil getInstance() {
27 | if (cameraParamUtil == null) {
28 | cameraParamUtil = new CameraParamUtil();
29 | return cameraParamUtil;
30 | } else {
31 | return cameraParamUtil;
32 | }
33 | }
34 |
35 | public Camera.Size getPreviewSize(List list, int th, float rate) {
36 | Collections.sort(list, sizeComparator);
37 | int i = 0;
38 | for (Camera.Size s : list) {
39 | if ((s.width > th) && equalRate(s, rate)) {
40 | Log.i(TAG, "MakeSure Preview :w = " + s.width + " h = " + s.height);
41 | break;
42 | }
43 | i++;
44 | }
45 | if (i == list.size()) {
46 | return getBestSize(list, rate);
47 | } else {
48 | return list.get(i);
49 | }
50 | }
51 |
52 | public Camera.Size getPictureSize(List list, int th, float rate) {
53 | Collections.sort(list, sizeComparator);
54 | int i = 0;
55 | for (Camera.Size s : list) {
56 | if ((s.width > th) && equalRate(s, rate)) {
57 | Log.i(TAG, "MakeSure Picture :w = " + s.width + " h = " + s.height);
58 | break;
59 | }
60 | i++;
61 | }
62 | if (i == list.size()) {
63 | return getBestSize(list, rate);
64 | } else {
65 | return list.get(i);
66 | }
67 | }
68 |
69 | private Camera.Size getBestSize(List list, float rate) {
70 | float previewDisparity = 100;
71 | int index = 0;
72 | for (int i = 0; i < list.size(); i++) {
73 | Camera.Size cur = list.get(i);
74 | float prop = (float) cur.width / (float) cur.height;
75 | if (Math.abs(rate - prop) < previewDisparity) {
76 | previewDisparity = Math.abs(rate - prop);
77 | index = i;
78 | }
79 | }
80 | return list.get(index);
81 | }
82 |
83 |
84 | private boolean equalRate(Camera.Size s, float rate) {
85 | float r = (float) (s.width) / (float) (s.height);
86 | return Math.abs(r - rate) <= 0.2;
87 | }
88 |
89 | public boolean isSupportedFocusMode(List focusList, String focusMode) {
90 | for (int i = 0; i < focusList.size(); i++) {
91 | if (focusMode.equals(focusList.get(i))) {
92 | Log.i(TAG, "FocusMode supported " + focusMode);
93 | return true;
94 | }
95 | }
96 | Log.i(TAG, "FocusMode not supported " + focusMode);
97 | return false;
98 | }
99 |
100 | public boolean isSupportedPictureFormats(List supportedPictureFormats, int jpeg) {
101 | for (int i = 0; i < supportedPictureFormats.size(); i++) {
102 | if (jpeg == supportedPictureFormats.get(i)) {
103 | Log.i(TAG, "Formats supported " + jpeg);
104 | return true;
105 | }
106 | }
107 | Log.i(TAG, "Formats not supported " + jpeg);
108 | return false;
109 | }
110 |
111 | private class CameraSizeComparator implements Comparator {
112 | public int compare(Camera.Size lhs, Camera.Size rhs) {
113 | if (lhs.width == rhs.width) {
114 | return 0;
115 | } else if (lhs.width > rhs.width) {
116 | return 1;
117 | } else {
118 | return -1;
119 | }
120 | }
121 |
122 | }
123 |
124 | public int getCameraDisplayOrientation(Context context, int cameraId) {
125 | android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
126 | android.hardware.Camera.getCameraInfo(cameraId, info);
127 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
128 | int rotation = wm.getDefaultDisplay().getRotation();
129 | int degrees = 0;
130 | switch (rotation) {
131 | case Surface.ROTATION_0:
132 | degrees = 0;
133 | break;
134 | case Surface.ROTATION_90:
135 | degrees = 90;
136 | break;
137 | case Surface.ROTATION_180:
138 | degrees = 180;
139 | break;
140 | case Surface.ROTATION_270:
141 | degrees = 270;
142 | break;
143 | }
144 | int result;
145 | if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
146 | result = (info.orientation + degrees) % 360;
147 | result = (360 - result) % 360; // compensate the mirror
148 | } else {
149 | // back-facing
150 | result = (info.orientation - degrees + 360) % 360;
151 | }
152 | return result;
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/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 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/CaptureImageButton.kt:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera
2 |
3 | import android.animation.Animator
4 | import android.animation.AnimatorListenerAdapter
5 | import android.animation.AnimatorSet
6 | import android.animation.ValueAnimator
7 | import android.content.Context
8 | import android.graphics.Canvas
9 | import android.graphics.Paint
10 | import android.graphics.RectF
11 | import android.os.CountDownTimer
12 | import android.util.AttributeSet
13 | import android.view.MotionEvent
14 | import android.view.View
15 |
16 |
17 | /**
18 | * author hbzhou
19 | * date 2019/12/13 11:10
20 | */
21 | class CaptureImageButton : View {
22 |
23 | companion object {
24 | val BUTTON_STATE_ONLY_CAPTURE = 0x101 //只能拍照
25 |
26 | val BUTTON_STATE_ONLY_RECORDER = 0x102 //只能录像
27 |
28 | val BUTTON_STATE_BOTH = 0x103
29 | }
30 |
31 | private var state //当前按钮状态
32 | = 0
33 | private var button_state //按钮可执行的功能状态(拍照,录制,两者)
34 | = 0
35 |
36 | val STATE_IDLE = 0x001 //空闲状态
37 |
38 | val STATE_PRESS = 0x002 //按下状态
39 |
40 | private val STATE_LONG_PRESS = 0x003 //长按状态
41 |
42 | val STATE_RECORDERING = 0x004 //录制状态
43 |
44 | val STATE_BAN = 0x005 //禁止状态
45 |
46 |
47 | private val progress_color = -0x11e951ea //进度条颜色
48 |
49 | private val outside_color = -0x11232324 //外圆背景色
50 |
51 | private val inside_color = -0x1 //内圆背景色
52 |
53 |
54 | private var event_Y //Touch_Event_Down时候记录的Y值
55 | = 0f
56 |
57 |
58 | private var mPaint: Paint? = null
59 |
60 | private var strokeWidth //进度条宽度
61 | = 0f
62 | private var outside_add_size //长按外圆半径变大的Size
63 | = 0
64 | private var inside_reduce_size //长安内圆缩小的Size
65 | = 0
66 |
67 | //中心坐标
68 | private var center_X = 0f
69 | private var center_Y = 0f
70 |
71 | private var button_radius //按钮半径
72 | = 0f
73 | private var button_outside_radius //外圆半径
74 | = 0f
75 | private var button_inside_radius //内圆半径
76 | = 0f
77 | private var button_size //按钮大小
78 | = 0
79 |
80 | private var progress //录制视频的进度
81 | = 0f
82 | private var duration //录制视频最大时间长度
83 | = 0
84 | private var min_duration //最短录制时间限制
85 | = 0
86 | private var recorded_time //记录当前录制的时间
87 | = 0
88 |
89 | private var rectF: RectF? = null
90 |
91 | private var longPressRunnable //长按后处理的逻辑Runnable
92 | : LongPressRunnable? = null
93 | private var captureListener //按钮回调接口
94 | : CaptureListener? = null
95 | private var timer //计时器
96 | : RecordCountDownTimer? = null
97 |
98 | constructor(context: Context) : this(context, null)
99 | constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
100 | constructor(context: Context, attrs: AttributeSet?, size: Int) : super(
101 | context,
102 | attrs,
103 | size
104 | ) {
105 |
106 | button_size = size
107 | button_radius = size / 2.0f
108 | button_outside_radius = button_radius
109 | button_inside_radius = button_radius * 0.75f
110 | strokeWidth = size / 15.toFloat()
111 | outside_add_size = size / 5
112 | inside_reduce_size = size / 8
113 | mPaint = Paint()
114 | mPaint!!.isAntiAlias = true
115 | progress = 0f
116 | longPressRunnable = LongPressRunnable()
117 | state = STATE_IDLE //初始化为空闲状态
118 | button_state = BUTTON_STATE_BOTH //初始化按钮为可录制可拍照
119 | //LogUtil.i("CaptureButtom start")
120 | duration = 10 * 1000 //默认最长录制时间为10s
121 | //LogUtil.i("CaptureButtom end")
122 | min_duration = 1500 //默认最短录制时间为1.5s
123 | center_X = (button_size + outside_add_size * 2) / 2.toFloat()
124 | center_Y = (button_size + outside_add_size * 2) / 2.toFloat()
125 | rectF = RectF(
126 | center_X - (button_radius + outside_add_size - strokeWidth / 2),
127 | center_Y - (button_radius + outside_add_size - strokeWidth / 2),
128 | center_X + (button_radius + outside_add_size - strokeWidth / 2),
129 | center_Y + (button_radius + outside_add_size - strokeWidth / 2)
130 | )
131 | timer = RecordCountDownTimer(duration.toLong(), (duration / 360).toLong()) //录制定时器
132 | }
133 |
134 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
135 | super.onMeasure(widthMeasureSpec, heightMeasureSpec)
136 | setMeasuredDimension(button_size + outside_add_size * 2, button_size + outside_add_size * 2)
137 | }
138 |
139 | override fun onDraw(canvas: Canvas) {
140 | super.onDraw(canvas)
141 | mPaint?.setStyle(Paint.Style.FILL)
142 | mPaint?.setColor(outside_color) //外圆(半透明灰色)
143 | canvas.drawCircle(center_X, center_Y, button_outside_radius, mPaint!!)
144 | mPaint?.setColor(inside_color) //内圆(白色)
145 | canvas.drawCircle(center_X, center_Y, button_inside_radius, mPaint!!)
146 | //如果状态为录制状态,则绘制录制进度条
147 | if (state == STATE_RECORDERING) {
148 | mPaint?.setColor(progress_color)
149 | mPaint?.setStyle(Paint.Style.STROKE)
150 | mPaint?.setStrokeWidth(strokeWidth)
151 | canvas.drawArc(rectF!!, -90f, progress, false, mPaint!!)
152 | }
153 | }
154 |
155 |
156 | override fun onTouchEvent(event: MotionEvent): Boolean {
157 | when (event.action) {
158 | MotionEvent.ACTION_DOWN -> {
159 | //LogUtil.i("state = $state")
160 | if (event.pointerCount > 1 || state != STATE_IDLE)
161 | return false
162 | event_Y = event.y //记录Y值
163 | state = STATE_PRESS //修改当前状态为点击按下
164 | //判断按钮状态是否为可录制状态
165 | if (button_state == BUTTON_STATE_ONLY_RECORDER || button_state == BUTTON_STATE_BOTH) postDelayed(
166 | longPressRunnable,
167 | 500
168 | ) //同时延长500启动长按后处理的逻辑Runnable
169 | }
170 | MotionEvent.ACTION_MOVE -> if (captureListener != null && state == STATE_RECORDERING && (button_state == BUTTON_STATE_ONLY_RECORDER || button_state == BUTTON_STATE_BOTH)
171 | ) { //记录当前Y值与按下时候Y值的差值,调用缩放回调接口
172 | captureListener!!.recordZoom(event_Y - event.y)
173 | }
174 | MotionEvent.ACTION_UP -> //根据当前按钮的状态进行相应的处理
175 | handlerUnpressByState()
176 | }
177 | return true
178 | }
179 |
180 | //当手指松开按钮时候处理的逻辑
181 | private fun handlerUnpressByState() {
182 | removeCallbacks(longPressRunnable) //移除长按逻辑的Runnable
183 | when (state) {
184 | STATE_PRESS -> if (captureListener != null && (button_state == BUTTON_STATE_ONLY_CAPTURE || button_state ==
185 | BUTTON_STATE_BOTH)
186 | ) {
187 | startCaptureAnimation(button_inside_radius)
188 | } else {
189 | state = STATE_IDLE
190 | }
191 | STATE_RECORDERING -> {
192 | timer!!.cancel() //停止计时器
193 | recordEnd() //录制结束
194 | }
195 | }
196 | }
197 |
198 | //录制结束
199 | private fun recordEnd() {
200 | if (captureListener != null) {
201 | if (recorded_time < min_duration) captureListener?.recordShort(recorded_time.toLong()) //回调录制时间过短
202 | else captureListener?.recordEnd(recorded_time.toLong()) //回调录制结束
203 | }
204 | resetRecordAnim() //重制按钮状态
205 | }
206 |
207 | //重制状态
208 | private fun resetRecordAnim() {
209 | state = STATE_BAN
210 | progress = 0f //重制进度
211 | invalidate()
212 | //还原按钮初始状态动画
213 | startRecordAnimation(
214 | button_outside_radius,
215 | button_radius,
216 | button_inside_radius,
217 | button_radius * 0.75f
218 | )
219 | }
220 |
221 | //内圆动画
222 | private fun startCaptureAnimation(inside_start: Float) {
223 | val inside_anim =
224 | ValueAnimator.ofFloat(inside_start, inside_start * 0.75f, inside_start)
225 | inside_anim.addUpdateListener { animation ->
226 | button_inside_radius = animation.animatedValue as Float
227 | invalidate()
228 | }
229 | inside_anim.addListener(object : AnimatorListenerAdapter() {
230 | override fun onAnimationEnd(animation: Animator) {
231 | super.onAnimationEnd(animation)
232 | //回调拍照接口
233 | captureListener?.takePictures()
234 | state = STATE_BAN
235 | }
236 | })
237 | inside_anim.duration = 100
238 | inside_anim.start()
239 | }
240 |
241 | //内外圆动画
242 | private fun startRecordAnimation(
243 | outside_start: Float,
244 | outside_end: Float,
245 | inside_start: Float,
246 | inside_end: Float
247 | ) {
248 | val outside_anim = ValueAnimator.ofFloat(outside_start, outside_end)
249 | val inside_anim = ValueAnimator.ofFloat(inside_start, inside_end)
250 | //外圆动画监听
251 | outside_anim.addUpdateListener { animation ->
252 | button_outside_radius = animation.animatedValue as Float
253 | invalidate()
254 | }
255 | //内圆动画监听
256 | inside_anim.addUpdateListener { animation ->
257 | button_inside_radius = animation.animatedValue as Float
258 | invalidate()
259 | }
260 | val set = AnimatorSet()
261 | //当动画结束后启动录像Runnable并且回调录像开始接口
262 | set.addListener(object : AnimatorListenerAdapter() {
263 | override fun onAnimationEnd(animation: Animator) {
264 | super.onAnimationEnd(animation)
265 | //设置为录制状态
266 | if (state == STATE_LONG_PRESS) {
267 | if (captureListener != null) captureListener?.recordStart()
268 | state = STATE_RECORDERING
269 | timer!!.start()
270 | }
271 | }
272 | })
273 | set.playTogether(outside_anim, inside_anim)
274 | set.duration = 100
275 | set.start()
276 | }
277 |
278 |
279 | //更新进度条
280 | private fun updateProgress(millisUntilFinished: Long) {
281 | recorded_time = (duration - millisUntilFinished).toInt()
282 | progress = 360f - millisUntilFinished / duration.toFloat() * 360f
283 | invalidate()
284 | }
285 |
286 | //录制视频计时器
287 | inner class RecordCountDownTimer internal constructor(
288 | millisInFuture: Long,
289 | countDownInterval: Long
290 | ) :
291 | CountDownTimer(millisInFuture, countDownInterval) {
292 | override fun onTick(millisUntilFinished: Long) {
293 | updateProgress(millisUntilFinished)
294 | }
295 |
296 | override fun onFinish() {
297 | updateProgress(0)
298 | recordEnd()
299 | }
300 | }
301 |
302 | //长按线程
303 | inner class LongPressRunnable : Runnable {
304 | override fun run() {
305 | state = STATE_LONG_PRESS //如果按下后经过500毫秒则会修改当前状态为长按状态
306 | //没有录制权限
307 | //if (CheckPermission.getRecordState() !== CheckPermission.STATE_SUCCESS) {
308 | state = STATE_IDLE
309 | if (captureListener != null) {
310 | captureListener?.recordError()
311 | return
312 | }
313 | //}
314 | //启动按钮动画,外圆变大,内圆缩小
315 | startRecordAnimation(
316 | button_outside_radius,
317 | button_outside_radius + outside_add_size,
318 | button_inside_radius,
319 | button_inside_radius - inside_reduce_size
320 | )
321 | }
322 | }
323 |
324 | /**************************************************
325 | * 对外提供的API *
326 | */
327 | //设置最长录制时间
328 | fun setDuration(duration: Int) {
329 | this.duration = duration
330 | timer = RecordCountDownTimer(duration.toLong(), (duration / 360).toLong()) //录制定时器
331 | }
332 |
333 | //设置最短录制时间
334 | fun setMinDuration(duration: Int) {
335 | min_duration = duration
336 | }
337 |
338 | //设置回调接口
339 | fun setCaptureLisenter(captureListener: CaptureListener?) {
340 | this.captureListener = captureListener
341 | }
342 |
343 | //设置按钮功能(拍照和录像)
344 | fun setButtonFeatures(state: Int) {
345 | button_state = state
346 | }
347 |
348 | //是否空闲状态
349 | fun isIdle(): Boolean {
350 | return state == STATE_IDLE
351 | }
352 |
353 | //设置状态
354 | fun resetState() {
355 | state = STATE_IDLE
356 | }
357 | }
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/CaptureButton.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.animation.AnimatorSet;
6 | import android.animation.ValueAnimator;
7 | import android.content.Context;
8 | import android.graphics.Canvas;
9 | import android.graphics.Paint;
10 | import android.graphics.RectF;
11 | import android.os.CountDownTimer;
12 | import android.view.MotionEvent;
13 | import android.view.View;
14 |
15 | import com.hbzhou.open.flowcamera.util.LogUtil;
16 |
17 | import static com.hbzhou.open.flowcamera.JCameraView.BUTTON_STATE_BOTH;
18 | import static com.hbzhou.open.flowcamera.JCameraView.BUTTON_STATE_ONLY_CAPTURE;
19 | import static com.hbzhou.open.flowcamera.JCameraView.BUTTON_STATE_ONLY_RECORDER;
20 |
21 |
22 | /**
23 | * author hbzhou
24 | * date 2019/12/13 10:49
25 | */
26 | public class CaptureButton extends View {
27 |
28 | private int state; //当前按钮状态
29 | private int button_state; //按钮可执行的功能状态(拍照,录制,两者)
30 |
31 | public static final int STATE_IDLE = 0x001; //空闲状态
32 | public static final int STATE_PRESS = 0x002; //按下状态
33 | public static final int STATE_LONG_PRESS = 0x003; //长按状态
34 | public static final int STATE_RECORDERING = 0x004; //录制状态
35 | public static final int STATE_BAN = 0x005; //禁止状态
36 |
37 | private int progress_color = 0xEE16AE16; //进度条颜色
38 | private int outside_color = 0xEEDCDCDC; //外圆背景色
39 | private int inside_color = 0xFFFFFFFF; //内圆背景色
40 |
41 |
42 | private float event_Y; //Touch_Event_Down时候记录的Y值
43 |
44 |
45 | private Paint mPaint;
46 |
47 | private float strokeWidth; //进度条宽度
48 | private int outside_add_size; //长按外圆半径变大的Size
49 | private int inside_reduce_size; //长安内圆缩小的Size
50 |
51 | //中心坐标
52 | private float center_X;
53 | private float center_Y;
54 |
55 | private float button_radius; //按钮半径
56 | private float button_outside_radius; //外圆半径
57 | private float button_inside_radius; //内圆半径
58 | private int button_size; //按钮大小
59 |
60 | private float progress; //录制视频的进度
61 | private int duration; //录制视频最大时间长度
62 | private int min_duration; //最短录制时间限制
63 | private int recorded_time; //记录当前录制的时间
64 |
65 | private RectF rectF;
66 |
67 | private LongPressRunnable longPressRunnable; //长按后处理的逻辑Runnable
68 | private CaptureListener captureLisenter; //按钮回调接口
69 | private RecordCountDownTimer timer; //计时器
70 |
71 | public CaptureButton(Context context) {
72 | super(context);
73 | }
74 |
75 | public CaptureButton(Context context, int size) {
76 | super(context);
77 | this.button_size = size;
78 | button_radius = size / 2.0f;
79 |
80 | button_outside_radius = button_radius;
81 | button_inside_radius = button_radius * 0.75f;
82 |
83 | strokeWidth = size / 15;
84 | outside_add_size = size / 8;
85 | inside_reduce_size = size / 8;
86 |
87 | mPaint = new Paint();
88 | mPaint.setAntiAlias(true);
89 |
90 | progress = 0;
91 | longPressRunnable = new LongPressRunnable();
92 |
93 | state = STATE_IDLE; //初始化为空闲状态
94 | button_state = BUTTON_STATE_ONLY_CAPTURE; //初始化按钮为可录制可拍照
95 | LogUtil.i("CaptureButtom start");
96 | duration = 10 * 1000; //默认最长录制时间为10s
97 | LogUtil.i("CaptureButtom end");
98 | min_duration = 1500; //默认最短录制时间为1.5s
99 |
100 | center_X = (button_size + outside_add_size * 2) / 2;
101 | center_Y = (button_size + outside_add_size * 2) / 2;
102 |
103 | rectF = new RectF(
104 | center_X - (button_radius + outside_add_size - strokeWidth / 2),
105 | center_Y - (button_radius + outside_add_size - strokeWidth / 2),
106 | center_X + (button_radius + outside_add_size - strokeWidth / 2),
107 | center_Y + (button_radius + outside_add_size - strokeWidth / 2));
108 |
109 | timer = new RecordCountDownTimer(duration, duration / 360); //录制定时器
110 | }
111 |
112 | @Override
113 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
114 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
115 | setMeasuredDimension(button_size + outside_add_size * 2, button_size + outside_add_size * 2);
116 | }
117 |
118 | @Override
119 | protected void onDraw(Canvas canvas) {
120 | super.onDraw(canvas);
121 | mPaint.setStyle(Paint.Style.FILL);
122 |
123 | mPaint.setColor(outside_color); //外圆(半透明灰色)
124 | canvas.drawCircle(center_X, center_Y, button_outside_radius, mPaint);
125 |
126 | mPaint.setColor(inside_color); //内圆(白色)
127 | canvas.drawCircle(center_X, center_Y, button_inside_radius, mPaint);
128 |
129 | //如果状态为录制状态,则绘制录制进度条
130 | if (state == STATE_RECORDERING) {
131 | mPaint.setColor(progress_color);
132 | mPaint.setStyle(Paint.Style.STROKE);
133 | mPaint.setStrokeWidth(strokeWidth);
134 | canvas.drawArc(rectF, -90, progress, false, mPaint);
135 | }
136 | }
137 |
138 |
139 | @Override
140 | public boolean onTouchEvent(MotionEvent event) {
141 | switch (event.getAction()) {
142 | case MotionEvent.ACTION_DOWN:
143 | LogUtil.i("state = " + state);
144 | if (event.getPointerCount() > 1 || state != STATE_IDLE)
145 | break;
146 | event_Y = event.getY(); //记录Y值
147 | state = STATE_PRESS; //修改当前状态为点击按下
148 |
149 | //LogUtil.e("systimestamp111---"+System.currentTimeMillis());
150 |
151 | //判断按钮状态是否为可录制状态
152 | if ((button_state == BUTTON_STATE_ONLY_RECORDER || button_state == BUTTON_STATE_BOTH))
153 | postDelayed(longPressRunnable, 500); //同时延长500启动长按后处理的逻辑Runnable
154 | break;
155 | case MotionEvent.ACTION_MOVE:
156 | if (captureLisenter != null
157 | && state == STATE_RECORDERING
158 | && (button_state == BUTTON_STATE_ONLY_RECORDER || button_state == BUTTON_STATE_BOTH)) {
159 | //记录当前Y值与按下时候Y值的差值,调用缩放回调接口
160 | captureLisenter.recordZoom(event_Y - event.getY());
161 | }
162 | break;
163 | case MotionEvent.ACTION_UP:
164 | //根据当前按钮的状态进行相应的处理 ----CodeReview---抬起瞬间应该重置状态 当前状态可能为按下和正在录制
165 | //state = STATE_BAN;
166 | handlerPressByState();
167 | break;
168 | }
169 | return true;
170 | }
171 |
172 | //当手指松开按钮时候处理的逻辑
173 | private void handlerPressByState() {
174 | removeCallbacks(longPressRunnable); //移除长按逻辑的Runnable
175 | //根据当前状态处理
176 | switch (state) {
177 | //当前是点击按下
178 | case STATE_PRESS:
179 | if (captureLisenter != null && (button_state == BUTTON_STATE_ONLY_CAPTURE || button_state ==
180 | BUTTON_STATE_BOTH)) {
181 | startCaptureAnimation(button_inside_radius);
182 | } else {
183 | state = STATE_IDLE;
184 | }
185 | break;
186 | // ---CodeReview---当内外圆动画未结束时已经是长按状态 但还没有置为STATE_RECORDERING时 应该也要结束录制 此处是一个bug
187 | case STATE_LONG_PRESS:
188 | //当前是长按状态
189 | case STATE_RECORDERING:
190 | timer.cancel(); //停止计时器
191 | recordEnd(); //录制结束
192 | break;
193 | }
194 | state = STATE_IDLE;
195 | }
196 |
197 | //录制结束
198 | public void recordEnd() {
199 | if (captureLisenter != null) {
200 | if (recorded_time < min_duration)
201 | captureLisenter.recordShort(recorded_time);//回调录制时间过短
202 | else
203 | captureLisenter.recordEnd(recorded_time); //回调录制结束
204 | }
205 | resetRecordAnim(); //重制按钮状态
206 | }
207 |
208 | //重制状态
209 | private void resetRecordAnim() {
210 | state = STATE_BAN;
211 | progress = 0; //重制进度
212 | invalidate();
213 | //还原按钮初始状态动画
214 | startRecordAnimation(
215 | button_outside_radius,
216 | button_radius,
217 | button_inside_radius,
218 | button_radius * 0.75f
219 | );
220 | }
221 |
222 | //内圆动画
223 | private void startCaptureAnimation(float inside_start) {
224 | ValueAnimator inside_anim = ValueAnimator.ofFloat(inside_start, inside_start * 0.75f, inside_start);
225 | inside_anim.addUpdateListener(animation -> {
226 | button_inside_radius = (float) animation.getAnimatedValue();
227 | invalidate();
228 | });
229 | inside_anim.addListener(new AnimatorListenerAdapter() {
230 | @Override
231 | public void onAnimationEnd(Animator animation) {
232 | super.onAnimationEnd(animation);
233 | //回调拍照接口
234 | // if (captureLisenter != null) {
235 | // captureLisenter.takePictures();
236 | // }
237 | // 为何拍照完成要将状态掷为禁止????此处貌似bug!!!!!!---CodeReview
238 | //state = STATE_BAN;
239 | //state = STATE_IDLE;
240 | }
241 |
242 | @Override
243 | public void onAnimationStart(Animator animation) {
244 | super.onAnimationStart(animation);
245 | if (captureLisenter != null) {
246 | captureLisenter.takePictures();
247 | }
248 | // 防止重复点击 状态重置
249 | state = STATE_BAN;
250 | }
251 | });
252 | inside_anim.setDuration(50);
253 | inside_anim.start();
254 | }
255 |
256 | //内外圆动画
257 | private void startRecordAnimation(float outside_start, float outside_end, float inside_start, float inside_end) {
258 | ValueAnimator outside_anim = ValueAnimator.ofFloat(outside_start, outside_end);
259 | ValueAnimator inside_anim = ValueAnimator.ofFloat(inside_start, inside_end);
260 | //外圆动画监听
261 | outside_anim.addUpdateListener(animation -> {
262 | button_outside_radius = (float) animation.getAnimatedValue();
263 | invalidate();
264 | });
265 | //内圆动画监听
266 | inside_anim.addUpdateListener(animation -> {
267 | button_inside_radius = (float) animation.getAnimatedValue();
268 | invalidate();
269 | });
270 | AnimatorSet set = new AnimatorSet();
271 | //当动画结束后启动录像Runnable并且回调录像开始接口
272 | set.addListener(new AnimatorListenerAdapter() {
273 | @Override
274 | public void onAnimationEnd(Animator animation) {
275 | super.onAnimationEnd(animation);
276 | //设置为录制状态
277 | if (state == STATE_LONG_PRESS) {
278 | if (captureLisenter != null)
279 | captureLisenter.recordStart();
280 | state = STATE_RECORDERING;
281 | timer.start();
282 | } else {
283 | // 此处动画包括长按起始动画和还原动画 若不是长按状态应该还原状态为空闲????---CodeReview
284 | state = STATE_IDLE;
285 | }
286 | }
287 | });
288 | set.playTogether(outside_anim, inside_anim);
289 | set.setDuration(100);
290 | set.start();
291 | }
292 |
293 |
294 | //更新进度条
295 | private void updateProgress(long millisUntilFinished) {
296 | recorded_time = (int) (duration - millisUntilFinished);
297 | progress = 360f - millisUntilFinished / (float) duration * 360f;
298 | invalidate();
299 | }
300 |
301 | //录制视频计时器
302 | private class RecordCountDownTimer extends CountDownTimer {
303 | RecordCountDownTimer(long millisInFuture, long countDownInterval) {
304 | super(millisInFuture, countDownInterval);
305 | }
306 |
307 | @Override
308 | public void onTick(long millisUntilFinished) {
309 | updateProgress(millisUntilFinished);
310 | }
311 |
312 | @Override
313 | public void onFinish() {
314 | //updateProgress(duration);
315 | recordEnd();
316 | }
317 | }
318 |
319 | //长按线程
320 | private class LongPressRunnable implements Runnable {
321 | @Override
322 | public void run() {
323 | state = STATE_LONG_PRESS; //如果按下后经过500毫秒则会修改当前状态为长按状态
324 | //启动按钮动画,外圆变大,内圆缩小
325 | startRecordAnimation(
326 | button_outside_radius,
327 | button_outside_radius + outside_add_size,
328 | button_inside_radius,
329 | button_inside_radius - inside_reduce_size
330 | );
331 | }
332 | }
333 |
334 | /**************************************************
335 | * 对外提供的API *
336 | **************************************************/
337 |
338 | //设置最长录制时间
339 | public void setDuration(int duration) {
340 | this.duration = duration;
341 | timer = new RecordCountDownTimer(duration, duration / 360); //录制定时器
342 | }
343 |
344 | //设置最短录制时间
345 | public void setMinDuration(int duration) {
346 | this.min_duration = duration;
347 | }
348 |
349 | //设置回调接口
350 | public void setCaptureLisenter(CaptureListener captureLisenter) {
351 | this.captureLisenter = captureLisenter;
352 | }
353 |
354 | //设置按钮功能(拍照和录像)
355 | public void setButtonFeatures(int state) {
356 | this.button_state = state;
357 | }
358 |
359 | // 获取当前按钮支持状态
360 | public int getButtonState() {
361 | return button_state;
362 | }
363 |
364 | //是否空闲状态
365 | public boolean isIdle() {
366 | return state == STATE_IDLE ? true : false;
367 | }
368 |
369 | //设置状态
370 | public void resetState() {
371 | state = STATE_IDLE;
372 | }
373 | }
374 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/CaptureLayout.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.animation.AnimatorSet;
6 | import android.animation.ObjectAnimator;
7 | import android.content.Context;
8 | import android.content.res.Configuration;
9 | import android.util.AttributeSet;
10 | import android.util.DisplayMetrics;
11 | import android.view.Gravity;
12 | import android.view.View;
13 | import android.view.WindowManager;
14 | import android.widget.FrameLayout;
15 | import android.widget.ImageView;
16 | import android.widget.TextView;
17 |
18 | import com.hbzhou.open.flowcamera.listener.ClickListener;
19 | import com.hbzhou.open.flowcamera.listener.ReturnListener;
20 | import com.hbzhou.open.flowcamera.listener.TypeListener;
21 |
22 |
23 | /**
24 | * author hbzhou
25 | * date 2019/12/13 10:49
26 | */
27 |
28 | public class CaptureLayout extends FrameLayout {
29 |
30 | // 选择拍照 拍视频 或者都有
31 | private final int BUTTON_STATE_ONLY_CAPTURE = 0x101; //只能拍照
32 | private final int BUTTON_STATE_ONLY_RECORDER = 0x102; //只能录像
33 | private final int BUTTON_STATE_BOTH = 0x103;
34 |
35 | private CaptureListener captureLisenter; //拍照按钮监听
36 | private TypeListener typeLisenter; //拍照或录制后接结果按钮监听
37 | private ReturnListener returnListener; //退出按钮监听
38 | private ClickListener leftClickListener; //左边按钮监听
39 | private ClickListener rightClickListener; //右边按钮监听
40 |
41 | public void setTypeLisenter(TypeListener typeLisenter) {
42 | this.typeLisenter = typeLisenter;
43 | }
44 |
45 | public void setCaptureLisenter(CaptureListener captureLisenter) {
46 | this.captureLisenter = captureLisenter;
47 | }
48 |
49 | public void setReturnLisenter(ReturnListener returnListener) {
50 | this.returnListener = returnListener;
51 | }
52 |
53 | private CaptureButton btn_capture; //拍照按钮
54 | private TypeButton btn_confirm; //确认按钮
55 | private TypeButton btn_cancel; //取消按钮
56 | private ReturnButton btn_return; //返回按钮
57 | private ImageView iv_custom_left; //左边自定义按钮
58 | private ImageView iv_custom_right; //右边自定义按钮
59 | private TextView txt_tip; //提示文本
60 |
61 | private String textTip;
62 |
63 | private int layout_width;
64 | private int layout_height;
65 | private int button_size;
66 | private int iconLeft = 0;
67 | private int iconRight = 0;
68 |
69 | private boolean isFirst = true;
70 |
71 | public CaptureLayout(Context context) {
72 | this(context, null);
73 | }
74 |
75 | public CaptureLayout(Context context, AttributeSet attrs) {
76 | this(context, attrs, 0);
77 | }
78 |
79 | public CaptureLayout(Context context, AttributeSet attrs, int defStyleAttr) {
80 | super(context, attrs, defStyleAttr);
81 |
82 | WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
83 | DisplayMetrics outMetrics = new DisplayMetrics();
84 | manager.getDefaultDisplay().getMetrics(outMetrics);
85 |
86 | if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
87 | layout_width = outMetrics.widthPixels;
88 | } else {
89 | layout_width = outMetrics.widthPixels / 2;
90 | }
91 | button_size = (int) (layout_width / 4.5f);
92 | layout_height = button_size + (button_size / 5) * 2 + 100;
93 |
94 | initView();
95 | initEvent();
96 | }
97 |
98 | @Override
99 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
100 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
101 | setMeasuredDimension(layout_width, layout_height);
102 | }
103 |
104 | public void initEvent() {
105 | //默认Typebutton为隐藏
106 | iv_custom_right.setVisibility(GONE);
107 | btn_cancel.setVisibility(GONE);
108 | btn_confirm.setVisibility(GONE);
109 | }
110 |
111 | public void startTypeBtnAnimator() {
112 | //拍照录制结果后的动画
113 | if (this.iconLeft != 0)
114 | iv_custom_left.setVisibility(GONE);
115 | else
116 | btn_return.setVisibility(GONE);
117 | if (this.iconRight != 0)
118 | iv_custom_right.setVisibility(GONE);
119 | btn_capture.setVisibility(GONE);
120 | btn_cancel.setVisibility(VISIBLE);
121 | btn_confirm.setVisibility(VISIBLE);
122 | btn_cancel.setClickable(false);
123 | btn_confirm.setClickable(false);
124 | ObjectAnimator animator_cancel = ObjectAnimator.ofFloat(btn_cancel, "translationX", layout_width / 4, 0);
125 | ObjectAnimator animator_confirm = ObjectAnimator.ofFloat(btn_confirm, "translationX", -layout_width / 4, 0);
126 |
127 | AnimatorSet set = new AnimatorSet();
128 | set.playTogether(animator_cancel, animator_confirm);
129 | set.addListener(new AnimatorListenerAdapter() {
130 | @Override
131 | public void onAnimationEnd(Animator animation) {
132 | super.onAnimationEnd(animation);
133 | btn_cancel.setClickable(true);
134 | btn_confirm.setClickable(true);
135 | }
136 | });
137 | set.setDuration(500);
138 | set.start();
139 | }
140 |
141 |
142 | private void initView() {
143 | setWillNotDraw(false);
144 | //拍照按钮
145 | btn_capture = new CaptureButton(getContext(), button_size);
146 | LayoutParams btn_capture_param = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
147 | btn_capture_param.gravity = Gravity.CENTER;
148 | btn_capture.setLayoutParams(btn_capture_param);
149 | btn_capture.setCaptureLisenter(new CaptureListener() {
150 | @Override
151 | public void takePictures() {
152 | if (captureLisenter != null) {
153 | captureLisenter.takePictures();
154 | }
155 | startAlphaAnimation();
156 | }
157 |
158 | @Override
159 | public void recordShort(long time) {
160 | if (captureLisenter != null) {
161 | captureLisenter.recordShort(time);
162 | }
163 | }
164 |
165 | @Override
166 | public void recordStart() {
167 | if (captureLisenter != null) {
168 | captureLisenter.recordStart();
169 | }
170 | startAlphaAnimation();
171 | }
172 |
173 | @Override
174 | public void recordEnd(long time) {
175 | if (captureLisenter != null) {
176 | captureLisenter.recordEnd(time);
177 | }
178 | }
179 |
180 | @Override
181 | public void recordZoom(float zoom) {
182 | if (captureLisenter != null) {
183 | captureLisenter.recordZoom(zoom);
184 | }
185 | }
186 |
187 | @Override
188 | public void recordError() {
189 | if (captureLisenter != null) {
190 | captureLisenter.recordError();
191 | }
192 | }
193 | });
194 |
195 | //取消按钮
196 | btn_cancel = new TypeButton(getContext(), TypeButton.TYPE_CANCEL, button_size);
197 | final LayoutParams btn_cancel_param = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
198 | btn_cancel_param.gravity = Gravity.CENTER_VERTICAL;
199 | btn_cancel_param.setMargins((layout_width / 4) - button_size / 2, 0, 0, 0);
200 | btn_cancel.setLayoutParams(btn_cancel_param);
201 | btn_cancel.setOnClickListener(view -> {
202 | if (typeLisenter != null) {
203 | typeLisenter.cancel();
204 | }
205 | });
206 |
207 | //确认按钮
208 | btn_confirm = new TypeButton(getContext(), TypeButton.TYPE_CONFIRM, button_size);
209 | LayoutParams btn_confirm_param = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
210 | btn_confirm_param.gravity = Gravity.CENTER_VERTICAL | Gravity.RIGHT;
211 | btn_confirm_param.setMargins(0, 0, (layout_width / 4) - button_size / 2, 0);
212 | btn_confirm.setLayoutParams(btn_confirm_param);
213 | btn_confirm.setOnClickListener(view -> {
214 | if (typeLisenter != null) {
215 | typeLisenter.confirm();
216 | }
217 | });
218 |
219 | //返回按钮
220 | btn_return = new ReturnButton(getContext(), (int) (button_size / 2.5f));
221 | LayoutParams btn_return_param = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
222 | btn_return_param.gravity = Gravity.CENTER_VERTICAL;
223 | btn_return_param.setMargins(layout_width / 6, 0, 0, 0);
224 | btn_return.setLayoutParams(btn_return_param);
225 | btn_return.setOnClickListener(v -> {
226 | if (leftClickListener != null) {
227 | leftClickListener.onClick();
228 | }
229 | });
230 | //左边自定义按钮
231 | iv_custom_left = new ImageView(getContext());
232 | LayoutParams iv_custom_param_left = new LayoutParams((int) (button_size / 2.5f), (int) (button_size / 2.5f));
233 | iv_custom_param_left.gravity = Gravity.CENTER_VERTICAL;
234 | iv_custom_param_left.setMargins(layout_width / 6, 0, 0, 0);
235 | iv_custom_left.setLayoutParams(iv_custom_param_left);
236 | iv_custom_left.setOnClickListener(v -> {
237 | if (leftClickListener != null) {
238 | leftClickListener.onClick();
239 | }
240 | });
241 |
242 | //右边自定义按钮
243 | iv_custom_right = new ImageView(getContext());
244 | LayoutParams iv_custom_param_right = new LayoutParams((int) (button_size / 2.5f), (int) (button_size / 2.5f));
245 | iv_custom_param_right.gravity = Gravity.CENTER_VERTICAL | Gravity.RIGHT;
246 | iv_custom_param_right.setMargins(0, 0, layout_width / 6, 0);
247 | iv_custom_right.setLayoutParams(iv_custom_param_right);
248 | iv_custom_right.setOnClickListener(v -> {
249 | if (rightClickListener != null) {
250 | rightClickListener.onClick();
251 | }
252 | });
253 |
254 | txt_tip = new TextView(getContext());
255 | LayoutParams txt_param = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
256 | txt_param.gravity = Gravity.CENTER_HORIZONTAL;
257 | txt_param.setMargins(0, 0, 0, 0);
258 | switchTextTip(btn_capture.getButtonState());
259 | txt_tip.setTextColor(0xFFFFFFFF);
260 | txt_tip.setGravity(Gravity.CENTER);
261 | txt_tip.setLayoutParams(txt_param);
262 |
263 | this.addView(btn_capture);
264 | this.addView(btn_cancel);
265 | this.addView(btn_confirm);
266 | this.addView(btn_return);
267 | this.addView(iv_custom_left);
268 | this.addView(iv_custom_right);
269 | this.addView(txt_tip);
270 |
271 | }
272 |
273 | /**************************************************
274 | * 对外提供的API *
275 | **************************************************/
276 | public void resetCaptureLayout() {
277 | btn_capture.resetState();
278 | btn_cancel.setVisibility(GONE);
279 | btn_confirm.setVisibility(GONE);
280 | btn_capture.setVisibility(VISIBLE);
281 | switchTextTip(btn_capture.getButtonState());
282 | txt_tip.setVisibility(View.VISIBLE);
283 | if (this.iconLeft != 0)
284 | iv_custom_left.setVisibility(VISIBLE);
285 | else
286 | btn_return.setVisibility(VISIBLE);
287 | if (this.iconRight != 0)
288 | iv_custom_right.setVisibility(VISIBLE);
289 | }
290 |
291 | public void startAlphaAnimation() {
292 | txt_tip.setVisibility(View.INVISIBLE);
293 | }
294 |
295 | public void setTextWithAnimation(String tip) {
296 | txt_tip.setText(tip);
297 | ObjectAnimator animator_txt_tip = ObjectAnimator.ofFloat(txt_tip, "alpha", 0f, 1f, 1f, 0f);
298 | animator_txt_tip.addListener(new AnimatorListenerAdapter() {
299 | @Override
300 | public void onAnimationEnd(Animator animation) {
301 | super.onAnimationEnd(animation);
302 | switchTextTip(btn_capture.getButtonState());
303 | txt_tip.setAlpha(1f);
304 | }
305 | });
306 | animator_txt_tip.setDuration(2500);
307 | animator_txt_tip.start();
308 | }
309 |
310 | public void setDuration(int duration) {
311 | btn_capture.setDuration(duration);
312 | }
313 |
314 | public void setButtonFeatures(int state) {
315 | btn_capture.setButtonFeatures(state);
316 | switchTextTip(state);
317 | }
318 |
319 | private void switchTextTip(int state) {
320 | switch (state) {
321 | case BUTTON_STATE_BOTH:
322 | textTip = "单击拍照,长按摄像";
323 | txt_tip.setText(textTip);
324 | break;
325 | case BUTTON_STATE_ONLY_CAPTURE:
326 | textTip = "单击拍照";
327 | txt_tip.setText(textTip);
328 | break;
329 | case BUTTON_STATE_ONLY_RECORDER:
330 | textTip = "长按摄像";
331 | txt_tip.setText(textTip);
332 | break;
333 | }
334 | }
335 |
336 | public void setTip(String tip) {
337 | textTip = tip;
338 | txt_tip.setText(textTip);
339 | }
340 |
341 | public void setIconSrc(int iconLeft, int iconRight) {
342 | this.iconLeft = iconLeft;
343 | this.iconRight = iconRight;
344 | if (this.iconLeft != 0) {
345 | iv_custom_left.setImageResource(iconLeft);
346 | iv_custom_left.setVisibility(VISIBLE);
347 | btn_return.setVisibility(GONE);
348 | } else {
349 | iv_custom_left.setVisibility(GONE);
350 | btn_return.setVisibility(VISIBLE);
351 | }
352 | if (this.iconRight != 0) {
353 | iv_custom_right.setImageResource(iconRight);
354 | iv_custom_right.setVisibility(VISIBLE);
355 | } else {
356 | iv_custom_right.setVisibility(GONE);
357 | }
358 | }
359 |
360 | public void setLeftClickListener(ClickListener leftClickListener) {
361 | this.leftClickListener = leftClickListener;
362 | }
363 |
364 | public void setRightClickListener(ClickListener rightClickListener) {
365 | this.rightClickListener = rightClickListener;
366 | }
367 | }
368 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/CustomCameraView.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.SurfaceTexture;
6 | import android.media.MediaPlayer;
7 | import android.media.MediaScannerConnection;
8 | import android.util.AttributeSet;
9 | import android.view.LayoutInflater;
10 | import android.view.Surface;
11 | import android.view.TextureView;
12 | import android.view.View;
13 | import android.view.ViewGroup;
14 | import android.webkit.MimeTypeMap;
15 | import android.widget.FrameLayout;
16 | import android.widget.ImageView;
17 | import android.widget.Toast;
18 |
19 | import androidx.annotation.NonNull;
20 | import androidx.annotation.Nullable;
21 | import androidx.lifecycle.Lifecycle;
22 | import androidx.lifecycle.LifecycleEventObserver;
23 | import androidx.lifecycle.LifecycleOwner;
24 |
25 | import com.bumptech.glide.Glide;
26 | import com.hbzhou.open.flowcamera.listener.ClickListener;
27 | import com.hbzhou.open.flowcamera.listener.FlowCameraListener;
28 | import com.hbzhou.open.flowcamera.listener.OnVideoPlayPrepareListener;
29 | import com.hbzhou.open.flowcamera.listener.TypeListener;
30 | import com.hbzhou.open.flowcamera.util.LogUtil;
31 | import com.otaliastudios.cameraview.CameraException;
32 | import com.otaliastudios.cameraview.CameraListener;
33 | import com.otaliastudios.cameraview.CameraView;
34 | import com.otaliastudios.cameraview.PictureResult;
35 | import com.otaliastudios.cameraview.VideoResult;
36 | import com.otaliastudios.cameraview.controls.Flash;
37 | import com.otaliastudios.cameraview.controls.Hdr;
38 | import com.otaliastudios.cameraview.controls.Mode;
39 | import com.otaliastudios.cameraview.controls.Preview;
40 | import com.otaliastudios.cameraview.controls.WhiteBalance;
41 | import com.otaliastudios.cameraview.size.AspectRatio;
42 | import com.otaliastudios.cameraview.size.SizeSelector;
43 | import com.otaliastudios.cameraview.size.SizeSelectors;
44 |
45 | import java.io.File;
46 | import java.io.IOException;
47 | import java.util.Objects;
48 |
49 |
50 | /**
51 | * author hbzhou
52 | * date 2019/12/27 13:30
53 | * 新增一个CustomCameraView 暂时兼容性较好
54 | */
55 | public class CustomCameraView extends FrameLayout {
56 |
57 | private Context mContext;
58 | private CameraView mCameraView;
59 | private ImageView mPhoto;
60 | private ImageView mSwitchCamera;
61 | private ImageView mFlashLamp;
62 | private CaptureLayout mCaptureLayout;
63 | private MediaPlayer mMediaPlayer;
64 | private TextureView mTextureView;
65 |
66 | //闪关灯状态
67 | private static final int TYPE_FLASH_AUTO = 0x021;
68 | private static final int TYPE_FLASH_ON = 0x022;
69 | private static final int TYPE_FLASH_OFF = 0x023;
70 | private int type_flash = TYPE_FLASH_OFF;
71 |
72 | // 选择拍照 拍视频 或者都有
73 | public static final int BUTTON_STATE_ONLY_CAPTURE = 0x101; //只能拍照
74 | public static final int BUTTON_STATE_ONLY_RECORDER = 0x102; //只能录像
75 | public static final int BUTTON_STATE_BOTH = 0x103;
76 | //回调监听
77 | private FlowCameraListener flowCameraListener;
78 | private ClickListener leftClickListener;
79 |
80 | private File videoFile;
81 | private File photoFile;
82 | //切换摄像头按钮的参数
83 | private int iconSrc; //图标资源
84 | private int iconLeft; //左图标
85 | private int iconRight; //右图标
86 | private int duration; //录制时间
87 | private long recordTime = 0;
88 |
89 | public CustomCameraView(@NonNull Context context) {
90 | this(context, null);
91 | }
92 |
93 | public CustomCameraView(@NonNull Context context, @Nullable AttributeSet attrs) {
94 | this(context, attrs, 0);
95 | }
96 |
97 | public CustomCameraView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
98 | super(context, attrs, defStyleAttr);
99 | mContext = context;
100 | TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomCameraView, defStyleAttr, 0);
101 | iconSrc = a.getResourceId(R.styleable.CustomCameraView_iconSrc, R.drawable.ic_camera);
102 | iconLeft = a.getResourceId(R.styleable.CustomCameraView_iconLeft, 0);
103 | iconRight = a.getResourceId(R.styleable.CustomCameraView_iconRight, 0);
104 | duration = a.getInteger(R.styleable.CustomCameraView_duration_max, 10 * 1000); //没设置默认为10s
105 | a.recycle();
106 | initView();
107 | }
108 |
109 | public void initView() {
110 | setWillNotDraw(false);
111 | View view = LayoutInflater.from(mContext).inflate(R.layout.custom_camera_view, this);
112 | mCameraView = view.findViewById(R.id.video_preview);
113 | mTextureView = view.findViewById(R.id.mVideo);
114 | mPhoto = view.findViewById(R.id.image_photo);
115 | mSwitchCamera = view.findViewById(R.id.image_switch);
116 | mSwitchCamera.setImageResource(iconSrc);
117 | // mFlashLamp = view.findViewById(R.id.image_flash);
118 | // setFlashRes();
119 | // mFlashLamp.setOnClickListener(v -> {
120 | // type_flash++;
121 | // if (type_flash > 0x023)
122 | // type_flash = TYPE_FLASH_AUTO;
123 | // setFlashRes();
124 | // });
125 | mCaptureLayout = view.findViewById(R.id.capture_layout);
126 | mCaptureLayout.setDuration(duration);
127 | mCaptureLayout.setIconSrc(iconLeft, iconRight);
128 | //切换摄像头
129 | mSwitchCamera.setOnClickListener(v ->
130 | mCameraView.toggleFacing()
131 | );
132 | // mCameraView.setHdr(Hdr.ON);
133 | // mCameraView.setAudio(Audio.ON);
134 | // mCameraView.mapGesture(Gesture.TAP, GestureAction.AUTO_FOCUS);
135 | // mCameraView.setEngine(Engine.CAMERA2);
136 | mCameraView.setPreview(Preview.GL_SURFACE);
137 | // mCameraView.setRotation(0);
138 |
139 | //
140 | // mCameraView.setPlaySounds(true);
141 | // mCameraView.setAudioCodec(AudioCodec.DEVICE_DEFAULT);
142 | // mCameraView.setVideoCodec(VideoCodec.DEVICE_DEFAULT);
143 | // mCameraView.setUseDeviceOrientation(true);
144 | // mCameraView.setFrameProcessingFormat();
145 | // mCameraView.setFrameProcessingFormat();
146 | mCameraView.setAutoFocusResetDelay(0);
147 | // mCameraView.setAutoFocusMarker(new DefaultAutoFocusMarker());
148 | // 修复拍照拍视频切换时预览尺寸拉伸的问题
149 | mCameraView.setSnapshotMaxHeight(2160);
150 | mCameraView.setSnapshotMaxWidth(1080);
151 | SizeSelector width = SizeSelectors.maxWidth(1080);
152 | SizeSelector height = SizeSelectors.maxHeight(2160);
153 | SizeSelector dimensions = SizeSelectors.and(width, height); // Matches sizes bigger than 1000x2000.
154 | SizeSelector ratio = SizeSelectors.aspectRatio(AspectRatio.of(9, 16), 0); // Matches 1:1 sizes.
155 |
156 | SizeSelector result = SizeSelectors.or(
157 | SizeSelectors.and(ratio, dimensions), // Try to match both constraints
158 | ratio, // If none is found, at least try to match the aspect ratio
159 | SizeSelectors.biggest() // If none is found, take the biggest
160 | );
161 | mCameraView.setPreviewStreamSize(result);
162 | mCameraView.setVideoSize(result);
163 | mCameraView.setPictureSize(result);
164 | // 修复拍照拍视频切换时预览尺寸拉伸的问题----
165 |
166 | //mCameraView.setPreview(Preview.TEXTURE);
167 | // 拍照录像回调
168 | mCameraView.addCameraListener(new CameraListener() {
169 | @Override
170 | public void onCameraError(@NonNull CameraException exception) {
171 | super.onCameraError(exception);
172 | if (flowCameraListener != null) {
173 | flowCameraListener.onError(0, Objects.requireNonNull(exception.getMessage()), null);
174 | }
175 | }
176 |
177 | @Override
178 | public void onPictureTaken(@NonNull PictureResult result) {
179 | super.onPictureTaken(result);
180 | result.toFile(initTakePicPath(mContext), file -> {
181 | if (file == null || !file.exists()) {
182 | Toast.makeText(mContext, "文件不存在!", Toast.LENGTH_LONG).show();
183 | return;
184 | }
185 | photoFile = file;
186 | Glide.with(mContext)
187 | .load(file)
188 | .into(mPhoto);
189 | mPhoto.setVisibility(View.VISIBLE);
190 | mCaptureLayout.startTypeBtnAnimator();
191 |
192 | // If the folder selected is an external media directory, this is unnecessary
193 | // but otherwise other apps will not be able to access our images unless we
194 | // scan them using [MediaScannerConnection]
195 | });
196 | }
197 |
198 | @Override
199 | public void onVideoTaken(@NonNull VideoResult result) {
200 | super.onVideoTaken(result);
201 | videoFile = result.getFile();
202 | if (!videoFile.exists() || (recordTime < 1500 && videoFile.exists() && videoFile.delete())) {
203 | return;
204 | }
205 | mCaptureLayout.startTypeBtnAnimator();
206 | mTextureView.setVisibility(View.VISIBLE);
207 | // startVideoViewPlay(videoFile);
208 | if (mTextureView.isAvailable()) {
209 | startVideoPlay(videoFile, () ->
210 | mCameraView.setVisibility(View.GONE)
211 | );
212 | } else {
213 | mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
214 | @Override
215 | public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
216 | startVideoPlay(videoFile, () ->
217 | mCameraView.setVisibility(View.GONE)
218 | );
219 | }
220 |
221 | @Override
222 | public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
223 |
224 | }
225 |
226 | @Override
227 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
228 | return false;
229 | }
230 |
231 | @Override
232 | public void onSurfaceTextureUpdated(SurfaceTexture surface) {
233 |
234 | }
235 | });
236 | }
237 | }
238 | });
239 | // 初始化缩放手势
240 | // mCameraView.mapGesture(Gesture.PINCH, GestureAction.ZOOM);
241 | //拍照 录像
242 | mCaptureLayout.setCaptureLisenter(new CaptureListener() {
243 | @Override
244 | public void takePictures() {
245 | mSwitchCamera.setVisibility(INVISIBLE);
246 | // mFlashLamp.setVisibility(INVISIBLE);
247 | mCameraView.setMode(Mode.PICTURE);
248 | // mCameraView.takePicture();
249 | mCameraView.takePictureSnapshot();
250 | }
251 |
252 | @Override
253 | public void recordStart() {
254 | mSwitchCamera.setVisibility(INVISIBLE);
255 | // mFlashLamp.setVisibility(INVISIBLE);
256 | mCameraView.setMode(Mode.VIDEO);
257 |
258 | // if (mCameraView.isTakingVideo()) {
259 | // mCameraView.stopVideo();
260 | // }
261 | // mCameraView.takeVideoSnapshot(initStartRecordingPath(mContext));
262 | mCameraView.postDelayed(() -> mCameraView.takeVideoSnapshot(initStartRecordingPath(mContext)), 100);
263 | }
264 |
265 | @Override
266 | public void recordShort(final long time) {
267 | recordTime = time;
268 | mSwitchCamera.setVisibility(VISIBLE);
269 | // mFlashLamp.setVisibility(VISIBLE);
270 | mCaptureLayout.resetCaptureLayout();
271 | mCaptureLayout.setTextWithAnimation("录制时间过短");
272 | mCameraView.stopVideo();
273 | }
274 |
275 | @Override
276 | public void recordEnd(long time) {
277 | recordTime = time;
278 | mCameraView.stopVideo();
279 | }
280 |
281 | @Override
282 | public void recordZoom(float zoom) {
283 | //mCameraView.setZoom(zoom);
284 | }
285 |
286 | @Override
287 | public void recordError() {
288 | if (flowCameraListener != null) {
289 | flowCameraListener.onError(0, "未知原因!", null);
290 | }
291 | }
292 | });
293 | //确认 取消
294 | mCaptureLayout.setTypeLisenter(new TypeListener() {
295 | @Override
296 | public void cancel() {
297 | stopVideoPlay();
298 | resetState();
299 | }
300 |
301 | @Override
302 | public void confirm() {
303 | if (mCameraView.getMode() == Mode.VIDEO) {
304 | stopVideoPlay();
305 | if (flowCameraListener != null) {
306 | flowCameraListener.recordSuccess(videoFile);
307 | }
308 | scanPhotoAlbum(videoFile);
309 | } else {
310 | mPhoto.setVisibility(INVISIBLE);
311 | if (flowCameraListener != null) {
312 | flowCameraListener.captureSuccess(photoFile);
313 | }
314 | scanPhotoAlbum(photoFile);
315 | }
316 | }
317 | });
318 | mCaptureLayout.setLeftClickListener(() -> {
319 | if (leftClickListener != null) {
320 | leftClickListener.onClick();
321 | }
322 | });
323 | }
324 |
325 | /**
326 | * 当确认保存此文件时才去扫描相册更新并显示视频和图片
327 | *
328 | * @param dataFile
329 | */
330 | private void scanPhotoAlbum(File dataFile) {
331 | if (dataFile == null) {
332 | return;
333 | }
334 | String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(dataFile.getAbsolutePath().substring(dataFile.getAbsolutePath().lastIndexOf(".") + 1));
335 | MediaScannerConnection.scanFile(
336 | mContext, new String[]{dataFile.getAbsolutePath()}, new String[]{mimeType}, null);
337 | }
338 |
339 | public File initTakePicPath(Context context) {
340 | return new File(context.getExternalMediaDirs()[0], System.currentTimeMillis() + ".jpeg");
341 | }
342 |
343 | public File initStartRecordingPath(Context context) {
344 | return new File(context.getExternalMediaDirs()[0], System.currentTimeMillis() + ".mp4");
345 | }
346 |
347 | /**************************************************
348 | * 对外提供的API *
349 | **************************************************/
350 |
351 | public void setFlowCameraListener(FlowCameraListener flowCameraListener) {
352 | this.flowCameraListener = flowCameraListener;
353 | }
354 |
355 | // 绑定生命周期 否者界面可能一片黑
356 | public void setBindToLifecycle(LifecycleOwner lifecycleOwner) {
357 | mCameraView.setLifecycleOwner(lifecycleOwner);
358 | lifecycleOwner.getLifecycle().addObserver((LifecycleEventObserver) (source, event) -> {
359 | LogUtil.i("event---", event.toString());
360 | if (event == Lifecycle.Event.ON_RESUME) {
361 | mCameraView.open();
362 | } else if (event == Lifecycle.Event.ON_PAUSE) {
363 | mCameraView.close();
364 | } else if (event == Lifecycle.Event.ON_DESTROY) {
365 | mCameraView.destroy();
366 | }
367 | });
368 | }
369 |
370 | /**
371 | * 设置拍摄模式分别是
372 | * 单独拍照 单独摄像 或者都支持
373 | *
374 | * @param state
375 | */
376 | public void setCaptureMode(int state) {
377 | if (mCaptureLayout != null) {
378 | mCaptureLayout.setButtonFeatures(state);
379 | }
380 | }
381 |
382 | /**
383 | * 设置录制视频最大时长单位 s
384 | */
385 | public void setRecordVideoMaxTime(int maxDurationTime) {
386 | mCaptureLayout.setDuration(maxDurationTime * 1000);
387 | }
388 |
389 | /**
390 | * 设置是否支持HDR
391 | *
392 | * @param hdr
393 | */
394 | public void setHdrEnable(Hdr hdr) {
395 | mCameraView.setHdr(hdr);
396 | }
397 |
398 | /**
399 | * 设置白平衡
400 | *
401 | * @param whiteBalance
402 | */
403 | public void setWhiteBalance(WhiteBalance whiteBalance) {
404 | mCameraView.setWhiteBalance(whiteBalance);
405 | }
406 |
407 | /**
408 | * 关闭相机界面按钮
409 | *
410 | * @param clickListener
411 | */
412 | public void setLeftClickListener(ClickListener clickListener) {
413 | this.leftClickListener = clickListener;
414 | }
415 |
416 | private void setFlashRes() {
417 | switch (type_flash) {
418 | case TYPE_FLASH_AUTO:
419 | mFlashLamp.setImageResource(R.drawable.ic_flash_auto);
420 | mCameraView.setFlash(Flash.AUTO);
421 | break;
422 | case TYPE_FLASH_ON:
423 | mFlashLamp.setImageResource(R.drawable.ic_flash_on);
424 | mCameraView.setFlash(Flash.ON);
425 | break;
426 | case TYPE_FLASH_OFF:
427 | mFlashLamp.setImageResource(R.drawable.ic_flash_off);
428 | mCameraView.setFlash(Flash.OFF);
429 | break;
430 | }
431 | }
432 |
433 | /**
434 | * 重置状态
435 | */
436 | private void resetState() {
437 | if (mCameraView.getMode() == Mode.VIDEO) {
438 | if (mCameraView.isTakingVideo()) {
439 | mCameraView.stopVideo();
440 | }
441 | if (videoFile != null && videoFile.exists() && videoFile.delete()) {
442 | LogUtil.i("videoFile is clear");
443 | }
444 | } else {
445 | mPhoto.setVisibility(INVISIBLE);
446 | if (photoFile != null && photoFile.exists() && photoFile.delete()) {
447 | LogUtil.i("photoFile is clear");
448 | }
449 | }
450 | mSwitchCamera.setVisibility(VISIBLE);
451 | // mFlashLamp.setVisibility(VISIBLE);
452 | mCameraView.setVisibility(View.VISIBLE);
453 | mCaptureLayout.resetCaptureLayout();
454 | }
455 |
456 | // private void startVideoViewPlay(File file) {
457 | // MediaController controller = new MediaController(mContext);
458 | // controller.setAnchorView(mTextureView);
459 | // controller.setMediaPlayer(mTextureView);
460 | // mTextureView.setMediaController(controller);
461 | // mTextureView.setVideoURI(Uri.fromFile(file));
462 | // mTextureView.setOnPreparedListener(mp -> {
463 | // mp.setLooping(true);
464 | // ViewGroup.LayoutParams lp = mTextureView.getLayoutParams();
465 | // float videoWidth = mp.getVideoWidth();
466 | // float videoHeight = mp.getVideoHeight();
467 | // float viewWidth = mTextureView.getWidth();
468 | // lp.height = (int) (viewWidth * (videoHeight / videoWidth));
469 | // mTextureView.setLayoutParams(lp);
470 | // if (!mTextureView.isPlaying()) {
471 | // mTextureView.start();
472 | // }
473 | // });
474 | // }
475 |
476 | /**
477 | * 开始循环播放视频
478 | *
479 | * @param videoFile
480 | */
481 | private void startVideoPlay(File videoFile, OnVideoPlayPrepareListener
482 | onVideoPlayPrepareListener) {
483 | try {
484 | if (mMediaPlayer == null) {
485 | mMediaPlayer = new MediaPlayer();
486 | }
487 | mMediaPlayer.setDataSource(videoFile.getAbsolutePath());
488 | mMediaPlayer.setSurface(new Surface(mTextureView.getSurfaceTexture()));
489 | mMediaPlayer.setLooping(true);
490 | mMediaPlayer.setOnPreparedListener(mp -> {
491 | mp.start();
492 | float ratio = mp.getVideoWidth() * 1f / mp.getVideoHeight();
493 | int width1 = mTextureView.getWidth();
494 | ViewGroup.LayoutParams layoutParams = mTextureView.getLayoutParams();
495 | layoutParams.height = (int) (width1 / ratio);
496 | mTextureView.setLayoutParams(layoutParams);
497 |
498 | if (onVideoPlayPrepareListener != null) {
499 | onVideoPlayPrepareListener.onPrepared();
500 | }
501 | });
502 | mMediaPlayer.prepareAsync();
503 | } catch (IOException e) {
504 | e.printStackTrace();
505 | }
506 | }
507 |
508 | /**
509 | * 停止视频播放
510 | */
511 | private void stopVideoPlay() {
512 | if (mMediaPlayer != null) {
513 | mMediaPlayer.stop();
514 | mMediaPlayer.release();
515 | mMediaPlayer = null;
516 | }
517 | mTextureView.setVisibility(View.GONE);
518 | }
519 | }
520 |
--------------------------------------------------------------------------------
/flowcamera/src/main/java/com/hbzhou/open/flowcamera/JCameraView.java:
--------------------------------------------------------------------------------
1 | package com.hbzhou.open.flowcamera;
2 |
3 | import android.animation.AnimatorSet;
4 | import android.animation.ObjectAnimator;
5 | import android.content.Context;
6 | import android.content.res.TypedArray;
7 | import android.graphics.Bitmap;
8 | import android.hardware.Camera;
9 | import android.media.AudioManager;
10 | import android.media.MediaPlayer;
11 | import android.os.Build;
12 | import android.util.AttributeSet;
13 | import android.util.Log;
14 | import android.util.TypedValue;
15 | import android.view.Gravity;
16 | import android.view.LayoutInflater;
17 | import android.view.MotionEvent;
18 | import android.view.SurfaceHolder;
19 | import android.view.View;
20 | import android.widget.FrameLayout;
21 | import android.widget.ImageView;
22 | import android.widget.VideoView;
23 |
24 | import androidx.annotation.RequiresApi;
25 |
26 | import com.hbzhou.open.flowcamera.listener.ClickListener;
27 | import com.hbzhou.open.flowcamera.listener.ErrorListener;
28 | import com.hbzhou.open.flowcamera.listener.JCameraListener;
29 | import com.hbzhou.open.flowcamera.listener.TypeListener;
30 | import com.hbzhou.open.flowcamera.state.CameraMachine;
31 | import com.hbzhou.open.flowcamera.util.FileUtil;
32 | import com.hbzhou.open.flowcamera.util.LogUtil;
33 | import com.hbzhou.open.flowcamera.util.ScreenUtils;
34 | import com.hbzhou.open.flowcamera.view.CameraView;
35 |
36 | import java.io.IOException;
37 |
38 |
39 | /**
40 | * author hbzhou
41 | * date 2019/12/13 10:49
42 | */
43 | public class JCameraView extends FrameLayout implements CameraInterface.CameraOpenOverCallback, SurfaceHolder
44 | .Callback, CameraView {
45 | // private static final String TAG = "JCameraView";
46 |
47 | //Camera状态机
48 | private CameraMachine machine;
49 |
50 | //闪关灯状态
51 | private static final int TYPE_FLASH_AUTO = 0x021;
52 | private static final int TYPE_FLASH_ON = 0x022;
53 | private static final int TYPE_FLASH_OFF = 0x023;
54 | private int type_flash = TYPE_FLASH_OFF;
55 |
56 | //拍照浏览时候的类型
57 | public static final int TYPE_PICTURE = 0x001;
58 | public static final int TYPE_VIDEO = 0x002;
59 | public static final int TYPE_SHORT = 0x003;
60 | public static final int TYPE_DEFAULT = 0x004;
61 |
62 | //录制视频比特率
63 | public static final int MEDIA_QUALITY_HIGH = 20 * 100000;
64 | public static final int MEDIA_QUALITY_MIDDLE = 16 * 100000;
65 | public static final int MEDIA_QUALITY_LOW = 12 * 100000;
66 | public static final int MEDIA_QUALITY_POOR = 8 * 100000;
67 | public static final int MEDIA_QUALITY_FUNNY = 4 * 100000;
68 | public static final int MEDIA_QUALITY_DESPAIR = 2 * 100000;
69 | public static final int MEDIA_QUALITY_SORRY = 1 * 80000;
70 |
71 |
72 | public static final int BUTTON_STATE_ONLY_CAPTURE = 0x101; //只能拍照
73 | public static final int BUTTON_STATE_ONLY_RECORDER = 0x102; //只能录像
74 | public static final int BUTTON_STATE_BOTH = 0x103; //两者都可以
75 |
76 |
77 | //回调监听
78 | private JCameraListener jCameraLisenter;
79 | private ClickListener leftClickListener;
80 | private ClickListener rightClickListener;
81 |
82 | private Context mContext;
83 | private VideoView mVideoView;
84 | private ImageView mPhoto;
85 | private ImageView mSwitchCamera;
86 | private ImageView mFlashLamp;
87 | private CaptureLayout mCaptureLayout;
88 | private FoucsView mFoucsView;
89 | private MediaPlayer mMediaPlayer;
90 |
91 | private int layout_width;
92 | private float screenProp = 0f;
93 |
94 | private Bitmap captureBitmap; //捕获的图片
95 | private Bitmap firstFrame; //第一帧图片
96 | private String videoUrl; //视频URL
97 |
98 |
99 | //切换摄像头按钮的参数
100 | private int iconSize = 0; //图标大小
101 | private int iconMargin = 0; //右上边距
102 | private int iconSrc = 0; //图标资源
103 | private int iconLeft = 0; //左图标
104 | private int iconRight = 0; //右图标
105 | private int duration = 0; //录制时间
106 |
107 | //缩放梯度
108 | private int zoomGradient = 0;
109 |
110 | private boolean firstTouch = true;
111 | private float firstTouchLength = 0;
112 |
113 | public JCameraView(Context context) {
114 | this(context, null);
115 | }
116 |
117 | public JCameraView(Context context, AttributeSet attrs) {
118 | this(context, attrs, 0);
119 | }
120 |
121 | public JCameraView(Context context, AttributeSet attrs, int defStyleAttr) {
122 | super(context, attrs, defStyleAttr);
123 | mContext = context;
124 | //get AttributeSet
125 | TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.JCameraView, defStyleAttr, 0);
126 | iconSize = a.getDimensionPixelSize(R.styleable.JCameraView_iconSize, (int) TypedValue.applyDimension(
127 | TypedValue.COMPLEX_UNIT_SP, 35, getResources().getDisplayMetrics()));
128 | iconMargin = a.getDimensionPixelSize(R.styleable.JCameraView_iconMargin, (int) TypedValue.applyDimension(
129 | TypedValue.COMPLEX_UNIT_SP, 15, getResources().getDisplayMetrics()));
130 | iconSrc = a.getResourceId(R.styleable.JCameraView_iconSrc, R.drawable.ic_camera);
131 | iconLeft = a.getResourceId(R.styleable.JCameraView_iconLeft, 0);
132 | iconRight = a.getResourceId(R.styleable.JCameraView_iconRight, 0);
133 | duration = a.getInteger(R.styleable.JCameraView_duration_max, 10 * 1000); //没设置默认为10s
134 | a.recycle();
135 | initData();
136 | initView();
137 | }
138 |
139 | private void initData() {
140 | layout_width = ScreenUtils.getScreenWidth(mContext);
141 | //缩放梯度
142 | zoomGradient = (int) (layout_width / 16f);
143 | LogUtil.i("zoom = " + zoomGradient);
144 | machine = new CameraMachine(getContext(), this, this);
145 | }
146 |
147 | private void initView() {
148 | setWillNotDraw(false);
149 | View view = LayoutInflater.from(mContext).inflate(R.layout.camera_view, this);
150 | mVideoView = (VideoView) view.findViewById(R.id.video_preview);
151 | mPhoto = (ImageView) view.findViewById(R.id.image_photo);
152 | mSwitchCamera = (ImageView) view.findViewById(R.id.image_switch);
153 | mSwitchCamera.setImageResource(iconSrc);
154 | mFlashLamp = (ImageView) view.findViewById(R.id.image_flash);
155 | setFlashRes();
156 | mFlashLamp.setOnClickListener(new OnClickListener() {
157 | @Override
158 | public void onClick(View v) {
159 | type_flash++;
160 | if (type_flash > 0x023)
161 | type_flash = TYPE_FLASH_AUTO;
162 | setFlashRes();
163 | }
164 | });
165 | mCaptureLayout = (CaptureLayout) view.findViewById(R.id.capture_layout);
166 | mCaptureLayout.setDuration(duration);
167 | mCaptureLayout.setIconSrc(iconLeft, iconRight);
168 | mFoucsView = (FoucsView) view.findViewById(R.id.fouce_view);
169 | mVideoView.getHolder().addCallback(this);
170 | //切换摄像头
171 | mSwitchCamera.setOnClickListener(new OnClickListener() {
172 | @Override
173 | public void onClick(View v) {
174 | machine.swtich(mVideoView.getHolder(), screenProp);
175 | }
176 | });
177 | //拍照 录像
178 | mCaptureLayout.setCaptureLisenter(new CaptureListener() {
179 | @Override
180 | public void takePictures() {
181 | mSwitchCamera.setVisibility(INVISIBLE);
182 | mFlashLamp.setVisibility(INVISIBLE);
183 | machine.capture();
184 | }
185 |
186 | @Override
187 | public void recordStart() {
188 | mSwitchCamera.setVisibility(INVISIBLE);
189 | mFlashLamp.setVisibility(INVISIBLE);
190 | machine.record(mVideoView.getHolder().getSurface(), screenProp);
191 | }
192 |
193 | @Override
194 | public void recordShort(final long time) {
195 | mCaptureLayout.setTextWithAnimation("录制时间过短");
196 | mSwitchCamera.setVisibility(VISIBLE);
197 | mFlashLamp.setVisibility(VISIBLE);
198 | postDelayed(new Runnable() {
199 | @Override
200 | public void run() {
201 | machine.stopRecord(true, time);
202 | }
203 | }, 1500 - time);
204 | }
205 |
206 | @Override
207 | public void recordEnd(long time) {
208 | machine.stopRecord(false, time);
209 | }
210 |
211 | @Override
212 | public void recordZoom(float zoom) {
213 | LogUtil.i("recordZoom");
214 | machine.zoom(zoom, CameraInterface.TYPE_RECORDER);
215 | }
216 |
217 | @Override
218 | public void recordError() {
219 | if (errorLisenter != null) {
220 | errorLisenter.AudioPermissionError();
221 | }
222 | }
223 | });
224 | //确认 取消
225 | mCaptureLayout.setTypeLisenter(new TypeListener() {
226 | @Override
227 | public void cancel() {
228 | machine.cancle(mVideoView.getHolder(), screenProp);
229 | }
230 |
231 | @Override
232 | public void confirm() {
233 | machine.confirm();
234 | }
235 | });
236 | //退出
237 | // mCaptureLayout.setReturnLisenter(new ReturnListener() {
238 | // @Override
239 | // public void onReturn() {
240 | // if (jCameraLisenter != null) {
241 | // jCameraLisenter.quit();
242 | // }
243 | // }
244 | // });
245 | mCaptureLayout.setLeftClickListener(new ClickListener() {
246 | @Override
247 | public void onClick() {
248 | if (leftClickListener != null) {
249 | leftClickListener.onClick();
250 | }
251 | }
252 | });
253 | mCaptureLayout.setRightClickListener(new ClickListener() {
254 | @Override
255 | public void onClick() {
256 | if (rightClickListener != null) {
257 | rightClickListener.onClick();
258 | }
259 | }
260 | });
261 | }
262 |
263 | @Override
264 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
265 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
266 | float widthSize = mVideoView.getMeasuredWidth();
267 | float heightSize = mVideoView.getMeasuredHeight();
268 | if (screenProp == 0) {
269 | screenProp = heightSize / widthSize;
270 | }
271 | }
272 |
273 | @Override
274 | public void cameraHasOpened() {
275 | CameraInterface.getInstance().doStartPreview(mVideoView.getHolder(), screenProp);
276 | }
277 |
278 | //生命周期onResume
279 | public void onResume() {
280 | LogUtil.i("JCameraView onResume");
281 | resetState(TYPE_DEFAULT); //重置状态
282 | CameraInterface.getInstance().registerSensorManager(mContext);
283 | CameraInterface.getInstance().setSwitchView(mSwitchCamera, mFlashLamp);
284 | machine.start(mVideoView.getHolder(), screenProp);
285 | }
286 |
287 | //生命周期onPause
288 | public void onPause() {
289 | LogUtil.i("JCameraView onPause");
290 | stopVideo();
291 | resetState(TYPE_PICTURE);
292 | CameraInterface.getInstance().isPreview(false);
293 | CameraInterface.getInstance().unregisterSensorManager(mContext);
294 | }
295 |
296 | //SurfaceView生命周期
297 | @Override
298 | public void surfaceCreated(SurfaceHolder holder) {
299 | LogUtil.i("JCameraView SurfaceCreated");
300 | new Thread() {
301 | @Override
302 | public void run() {
303 | CameraInterface.getInstance().doOpenCamera(JCameraView.this);
304 | }
305 | }.start();
306 | }
307 |
308 | @Override
309 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
310 | }
311 |
312 | @Override
313 | public void surfaceDestroyed(SurfaceHolder holder) {
314 | LogUtil.i("JCameraView SurfaceDestroyed");
315 | CameraInterface.getInstance().doDestroyCamera();
316 | }
317 |
318 |
319 | @Override
320 | public boolean onTouchEvent(MotionEvent event) {
321 | switch (event.getAction()) {
322 | case MotionEvent.ACTION_DOWN:
323 | if (event.getPointerCount() == 1) {
324 | //显示对焦指示器
325 | setFocusViewWidthAnimation(event.getX(), event.getY());
326 | }
327 | if (event.getPointerCount() == 2) {
328 | Log.i("CJT", "ACTION_DOWN = " + 2);
329 | }
330 | break;
331 | case MotionEvent.ACTION_MOVE:
332 | if (event.getPointerCount() == 1) {
333 | firstTouch = true;
334 | }
335 | if (event.getPointerCount() == 2) {
336 | //第一个点
337 | float point_1_X = event.getX(0);
338 | float point_1_Y = event.getY(0);
339 | //第二个点
340 | float point_2_X = event.getX(1);
341 | float point_2_Y = event.getY(1);
342 |
343 | float result = (float) Math.sqrt(Math.pow(point_1_X - point_2_X, 2) + Math.pow(point_1_Y -
344 | point_2_Y, 2));
345 |
346 | if (firstTouch) {
347 | firstTouchLength = result;
348 | firstTouch = false;
349 | }
350 | if ((int) (result - firstTouchLength) / zoomGradient != 0) {
351 | firstTouch = true;
352 | machine.zoom(result - firstTouchLength, CameraInterface.TYPE_CAPTURE);
353 | }
354 | // Log.i("CJT", "result = " + (result - firstTouchLength));
355 | }
356 | break;
357 | case MotionEvent.ACTION_UP:
358 | firstTouch = true;
359 | break;
360 | }
361 | return true;
362 | }
363 |
364 | //对焦框指示器动画
365 | private void setFocusViewWidthAnimation(float x, float y) {
366 | machine.foucs(x, y, new CameraInterface.FocusCallback() {
367 | @Override
368 | public void focusSuccess() {
369 | mFoucsView.setVisibility(INVISIBLE);
370 | }
371 | });
372 | }
373 |
374 | private void updateVideoViewSize(float videoWidth, float videoHeight) {
375 | if (videoWidth > videoHeight) {
376 | LayoutParams videoViewParam;
377 | int height = (int) ((videoHeight / videoWidth) * getWidth());
378 | videoViewParam = new LayoutParams(LayoutParams.MATCH_PARENT, height);
379 | videoViewParam.gravity = Gravity.CENTER;
380 | mVideoView.setLayoutParams(videoViewParam);
381 | }
382 | }
383 |
384 | /**************************************************
385 | * 对外提供的API *
386 | **************************************************/
387 |
388 | public void setSaveVideoPath(String path) {
389 | CameraInterface.getInstance().setSaveVideoPath(path);
390 | }
391 |
392 |
393 | public void setJCameraLisenter(JCameraListener jCameraLisenter) {
394 | this.jCameraLisenter = jCameraLisenter;
395 | }
396 |
397 |
398 | private ErrorListener errorLisenter;
399 |
400 | //启动Camera错误回调
401 | public void setErrorLisenter(ErrorListener errorLisenter) {
402 | this.errorLisenter = errorLisenter;
403 | CameraInterface.getInstance().setErrorLinsenter(errorLisenter);
404 | }
405 |
406 | //设置CaptureButton功能(拍照和录像)
407 | public void setFeatures(int state) {
408 | this.mCaptureLayout.setButtonFeatures(state);
409 | }
410 |
411 | //设置录制质量
412 | public void setMediaQuality(int quality) {
413 | CameraInterface.getInstance().setMediaQuality(quality);
414 | }
415 |
416 | @Override
417 | public void resetState(int type) {
418 | switch (type) {
419 | case TYPE_VIDEO:
420 | stopVideo(); //停止播放
421 | //初始化VideoView
422 | FileUtil.deleteFile(videoUrl);
423 | mVideoView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
424 | machine.start(mVideoView.getHolder(), screenProp);
425 | break;
426 | case TYPE_PICTURE:
427 | mPhoto.setVisibility(INVISIBLE);
428 | break;
429 | case TYPE_SHORT:
430 | break;
431 | case TYPE_DEFAULT:
432 | mVideoView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
433 | break;
434 | }
435 | mSwitchCamera.setVisibility(VISIBLE);
436 | mFlashLamp.setVisibility(VISIBLE);
437 | mCaptureLayout.resetCaptureLayout();
438 | }
439 |
440 | @Override
441 | public void confirmState(int type) {
442 | switch (type) {
443 | case TYPE_VIDEO:
444 | stopVideo(); //停止播放
445 | mVideoView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
446 | machine.start(mVideoView.getHolder(), screenProp);
447 | if (jCameraLisenter != null) {
448 | jCameraLisenter.recordSuccess(videoUrl, firstFrame);
449 | }
450 | break;
451 | case TYPE_PICTURE:
452 | mPhoto.setVisibility(INVISIBLE);
453 | if (jCameraLisenter != null) {
454 | jCameraLisenter.captureSuccess(captureBitmap);
455 | }
456 | break;
457 | case TYPE_SHORT:
458 | break;
459 | case TYPE_DEFAULT:
460 | break;
461 | }
462 | mCaptureLayout.resetCaptureLayout();
463 | }
464 |
465 | @Override
466 | public void showPicture(Bitmap bitmap, boolean isVertical) {
467 | if (isVertical) {
468 | mPhoto.setScaleType(ImageView.ScaleType.FIT_XY);
469 | } else {
470 | mPhoto.setScaleType(ImageView.ScaleType.FIT_CENTER);
471 | }
472 | captureBitmap = bitmap;
473 | mPhoto.setImageBitmap(bitmap);
474 | mPhoto.setVisibility(VISIBLE);
475 | mCaptureLayout.startAlphaAnimation();
476 | mCaptureLayout.startTypeBtnAnimator();
477 | }
478 |
479 | @Override
480 | public void playVideo(Bitmap firstFrame, final String url) {
481 | videoUrl = url;
482 | JCameraView.this.firstFrame = firstFrame;
483 | new Thread(new Runnable() {
484 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
485 | @Override
486 | public void run() {
487 | try {
488 | if (mMediaPlayer == null) {
489 | mMediaPlayer = new MediaPlayer();
490 | } else {
491 | mMediaPlayer.reset();
492 | }
493 | mMediaPlayer.setDataSource(url);
494 | mMediaPlayer.setSurface(mVideoView.getHolder().getSurface());
495 | mMediaPlayer.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT);
496 | mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
497 | mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer
498 | .OnVideoSizeChangedListener() {
499 | @Override
500 | public void
501 | onVideoSizeChanged(MediaPlayer mp, int width, int height) {
502 | updateVideoViewSize(mMediaPlayer.getVideoWidth(), mMediaPlayer
503 | .getVideoHeight());
504 | }
505 | });
506 | mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
507 | @Override
508 | public void onPrepared(MediaPlayer mp) {
509 | mMediaPlayer.start();
510 | }
511 | });
512 | mMediaPlayer.setLooping(true);
513 | mMediaPlayer.prepare();
514 | } catch (IOException e) {
515 | e.printStackTrace();
516 | }
517 | }
518 | }).start();
519 | }
520 |
521 | @Override
522 | public void stopVideo() {
523 | if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
524 | mMediaPlayer.stop();
525 | mMediaPlayer.release();
526 | mMediaPlayer = null;
527 | }
528 | }
529 |
530 | @Override
531 | public void setTip(String tip) {
532 | mCaptureLayout.setTip(tip);
533 | }
534 |
535 | @Override
536 | public void startPreviewCallback() {
537 | LogUtil.i("startPreviewCallback");
538 | handlerFoucs(mFoucsView.getWidth() / 2, mFoucsView.getHeight() / 2);
539 | }
540 |
541 | @Override
542 | public boolean handlerFoucs(float x, float y) {
543 | if (y > mCaptureLayout.getTop()) {
544 | return false;
545 | }
546 | mFoucsView.setVisibility(VISIBLE);
547 | if (x < mFoucsView.getWidth() / 2) {
548 | x = mFoucsView.getWidth() / 2;
549 | }
550 | if (x > layout_width - mFoucsView.getWidth() / 2) {
551 | x = layout_width - mFoucsView.getWidth() / 2;
552 | }
553 | if (y < mFoucsView.getWidth() / 2) {
554 | y = mFoucsView.getWidth() / 2;
555 | }
556 | if (y > mCaptureLayout.getTop() - mFoucsView.getWidth() / 2) {
557 | y = mCaptureLayout.getTop() - mFoucsView.getWidth() / 2;
558 | }
559 | mFoucsView.setX(x - mFoucsView.getWidth() / 2);
560 | mFoucsView.setY(y - mFoucsView.getHeight() / 2);
561 | ObjectAnimator scaleX = ObjectAnimator.ofFloat(mFoucsView, "scaleX", 1, 0.6f);
562 | ObjectAnimator scaleY = ObjectAnimator.ofFloat(mFoucsView, "scaleY", 1, 0.6f);
563 | ObjectAnimator alpha = ObjectAnimator.ofFloat(mFoucsView, "alpha", 1f, 0.4f, 1f, 0.4f, 1f, 0.4f, 1f);
564 | AnimatorSet animSet = new AnimatorSet();
565 | animSet.play(scaleX).with(scaleY).before(alpha);
566 | animSet.setDuration(400);
567 | animSet.start();
568 | return true;
569 | }
570 |
571 | public void setLeftClickListener(ClickListener clickListener) {
572 | this.leftClickListener = clickListener;
573 | }
574 |
575 | public void setRightClickListener(ClickListener clickListener) {
576 | this.rightClickListener = clickListener;
577 | }
578 |
579 | private void setFlashRes() {
580 | switch (type_flash) {
581 | case TYPE_FLASH_AUTO:
582 | mFlashLamp.setImageResource(R.drawable.ic_flash_auto);
583 | machine.flash(Camera.Parameters.FLASH_MODE_AUTO);
584 | break;
585 | case TYPE_FLASH_ON:
586 | mFlashLamp.setImageResource(R.drawable.ic_flash_on);
587 | machine.flash(Camera.Parameters.FLASH_MODE_ON);
588 | break;
589 | case TYPE_FLASH_OFF:
590 | mFlashLamp.setImageResource(R.drawable.ic_flash_off);
591 | machine.flash(Camera.Parameters.FLASH_MODE_OFF);
592 | break;
593 | }
594 | }
595 | }
596 |
--------------------------------------------------------------------------------