├── .gitignore
├── .idea
├── gradle.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── Demo
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── cktim
│ │ └── camera2record
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── cktim
│ │ │ └── camera2record
│ │ │ ├── Camera2RecordFinishActivity.java
│ │ │ └── MainActivity.java
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ └── activity_record_detail.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.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
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── cktim
│ └── camera2record
│ └── ExampleUnitTest.java
├── README.md
├── build.gradle
├── camera2library
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── cktim
│ │ └── camera2library
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── cktim
│ │ │ └── camera2library
│ │ │ ├── Camera2Config.java
│ │ │ └── camera
│ │ │ ├── Camera2RecordActivity.java
│ │ │ ├── Camera2Util.java
│ │ │ └── ProgressView.java
│ └── res
│ │ ├── drawable
│ │ ├── ic_capture_btn.png
│ │ ├── ic_capture_delete.webp
│ │ ├── ic_capture_light_off.webp
│ │ ├── ic_capture_light_on.webp
│ │ ├── ic_capture_switch.webp
│ │ └── selector_capture_light.xml
│ │ ├── layout
│ │ └── activity_camera2_record.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── cktim
│ └── camera2library
│ └── ExampleUnitTest.java
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/misc.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 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Demo/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/Demo/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 26
5 | defaultConfig {
6 | applicationId "com.cktim.camera2record"
7 | minSdkVersion 21
8 | targetSdkVersion 26
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | implementation fileTree(include: ['*.jar'], dir: 'libs')
23 | implementation 'com.android.support:appcompat-v7:26.1.0'
24 | implementation 'com.android.support.constraint:constraint-layout:1.0.2'
25 | testImplementation 'junit:junit:4.12'
26 | androidTestImplementation 'com.android.support.test:runner:1.0.1'
27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
28 | implementation project(':camera2library')
29 | }
30 |
--------------------------------------------------------------------------------
/Demo/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/Demo/src/androidTest/java/com/cktim/camera2record/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.cktim.camera2record;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.cktim.camera2record", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Demo/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Demo/src/main/java/com/cktim/camera2record/Camera2RecordFinishActivity.java:
--------------------------------------------------------------------------------
1 | package com.cktim.camera2record;
2 |
3 | import android.graphics.BitmapFactory;
4 | import android.os.Bundle;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.text.TextUtils;
7 | import android.view.View;
8 | import android.view.Window;
9 | import android.view.WindowManager;
10 | import android.widget.ImageView;
11 | import android.widget.TextView;
12 |
13 | import com.cktim.camera2library.Camera2Config;
14 |
15 | public class Camera2RecordFinishActivity extends AppCompatActivity {
16 | private String picPath;//图片地址
17 | private String videoPath;//视频地址
18 |
19 | private ImageView iv;
20 | private TextView tv;
21 |
22 | @Override
23 | protected void onCreate(Bundle savedInstanceState) {
24 | super.onCreate(savedInstanceState);
25 | //全屏模式
26 | requestWindowFeature(Window.FEATURE_NO_TITLE);
27 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
28 | setContentView(R.layout.activity_record_detail);
29 |
30 | iv = findViewById(R.id.iv);
31 | tv = findViewById(R.id.tv);
32 |
33 | if (getIntent() != null) {
34 | //获取传递过来的图片地址
35 | picPath = getIntent().getStringExtra(Camera2Config.INTENT_PATH_SAVE_PIC);
36 | if (TextUtils.isEmpty(picPath)) {
37 | iv.setVisibility(View.GONE);
38 | } else {
39 | iv.setImageBitmap(BitmapFactory.decodeFile(picPath));
40 | }
41 |
42 | //获取传递过来的视频地址
43 | videoPath = getIntent().getStringExtra(Camera2Config.INTENT_PATH_SAVE_VIDEO);
44 | if (TextUtils.isEmpty(videoPath)) {
45 | tv.setVisibility(View.GONE);
46 | } else {
47 | tv.setText("我是一段视频,这是我的存放地址:" + videoPath + ",你可以在这里加个播放器或者做其他处理");
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Demo/src/main/java/com/cktim/camera2record/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.cktim.camera2record;
2 |
3 | import android.os.Bundle;
4 | import android.os.Environment;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.view.View;
7 | import android.widget.Button;
8 |
9 | import com.cktim.camera2library.Camera2Config;
10 | import com.cktim.camera2library.camera.Camera2RecordActivity;
11 |
12 | public class MainActivity extends AppCompatActivity {
13 | private Button btnOpenCamera2;
14 |
15 | @Override
16 | protected void onCreate(Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 | setContentView(R.layout.activity_main);
19 | btnOpenCamera2=findViewById(R.id.btn_openCamera2);
20 |
21 | //配置Camera2相关参数,
22 | Camera2Config.RECORD_MAX_TIME = 10;
23 | Camera2Config.RECORD_MIN_TIME=2;
24 | Camera2Config.RECORD_PROGRESS_VIEW_COLOR=R.color.colorAccent;
25 | Camera2Config.PREVIEW_MAX_HEIGHT=1300;
26 | Camera2Config.PATH_SAVE_VIDEO= Environment.getExternalStorageDirectory().getAbsolutePath() + "/CameraV2222/";
27 | Camera2Config.PATH_SAVE_PIC= Environment.getExternalStorageDirectory().getAbsolutePath() + "/CameraV2222/CameraV22222222/";
28 | Camera2Config.ENABLE_CAPTURE=true;
29 | Camera2Config.ENABLE_CAPTURE=true;
30 |
31 | Camera2Config.ACTIVITY_AFTER_CAPTURE = Camera2RecordFinishActivity.class;
32 |
33 | btnOpenCamera2.setOnClickListener(new View.OnClickListener() {
34 | @Override
35 | public void onClick(View view) {
36 | Camera2RecordActivity.start(MainActivity.this);
37 | }
38 | });
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/Demo/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
24 |
25 |
--------------------------------------------------------------------------------
/Demo/src/main/res/layout/activity_record_detail.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
21 |
22 |
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CKTim/Camera2Record/3b54c0a48423c0f8e30a04372b25e4edd189af50/Demo/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CKTim/Camera2Record/3b54c0a48423c0f8e30a04372b25e4edd189af50/Demo/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CKTim/Camera2Record/3b54c0a48423c0f8e30a04372b25e4edd189af50/Demo/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CKTim/Camera2Record/3b54c0a48423c0f8e30a04372b25e4edd189af50/Demo/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CKTim/Camera2Record/3b54c0a48423c0f8e30a04372b25e4edd189af50/Demo/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CKTim/Camera2Record/3b54c0a48423c0f8e30a04372b25e4edd189af50/Demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CKTim/Camera2Record/3b54c0a48423c0f8e30a04372b25e4edd189af50/Demo/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CKTim/Camera2Record/3b54c0a48423c0f8e30a04372b25e4edd189af50/Demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CKTim/Camera2Record/3b54c0a48423c0f8e30a04372b25e4edd189af50/Demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CKTim/Camera2Record/3b54c0a48423c0f8e30a04372b25e4edd189af50/Demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Demo/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/Demo/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Camera2RecordDemo
3 |
4 |
--------------------------------------------------------------------------------
/Demo/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Demo/src/test/java/com/cktim/camera2record/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.cktim.camera2record;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Camera2Record
2 | Camera2 api实现点击拍照,长按录制。
3 |
4 | 更多介绍参考简书地址:http://www.jianshu.com/p/f8c694a4fb57
5 |
6 | # 一、先看一下效果图:
7 | 
8 | 
9 |
10 | # 二、3步集成到自己的项目中:
11 | **1.在AndroidManifest.xml申明所需要的权限:**
12 | (注:请确保进入Camera2的时候已经拥有这三项权限了,Android6.0需要动态去申请权限)
13 | ```
14 |
15 |
16 |
17 | ```
18 | **2.在project的build.gradle和app的build.gradle下分别申明如下代码:**
19 |
20 | project的build.gradle:
21 | ```
22 | allprojects {
23 | repositories {
24 | maven { url 'https://jitpack.io' }//这句代码
25 | }
26 | }
27 | ```
28 | app的build.gradle:
29 | ```
30 | dependencies {
31 | compile 'com.github.CKTim:Camera2Record:v1.0.0'
32 | }
33 | ```
34 | **3.打开Camera2:**
35 |
36 | 因为每个人对拍完照或者录完像后的处理都不一样,所以这里我采用拍完跳转activity的方式,将拍照录像后的地址传递给了下一个activity,当然这个activty界面逻辑什么的都是由你自己去编写的,你可以对获取到的图片视频地址进行你需要的编辑,例如再次压缩或者重拍等操作:
37 | getIntent().getStringExtra(Camera2Config.INTENT_PATH_SAVE_PIC);//获取图片地址
38 | getIntent().getStringExtra(Camera2Config.INTENT_PATH_SAVE_VIDEO);//获取视频地址
39 |
40 | 如下用法:
41 | ```
42 | //配置Camera2相关参数,
43 | Camera2Config.RECORD_MAX_TIME = 10;//最长录制时间
44 | Camera2Config.RECORD_MIN_TIME=2;//最短录制时间
45 | Camera2Config.RECORD_PROGRESS_VIEW_COLOR=R.color.colorAccent;//录制进度条颜色
46 | Camera2Config.PREVIEW_MAX_HEIGHT=1000;////最大高度预览尺寸,默认大于1000的第一个
47 | Camera2Config.PATH_SAVE_VIDEO=;//视频保存地址,注意需要以/结束,例如Camera2/
48 | Camera2Config.PATH_SAVE_PIC=;图片保存地址,注意需要以/结束,例如Camera2/
49 | Camera2Config.ENABLE_CAPTURE=true;//是否开启拍照功能
50 | Camera2Config.ENABLE_RECORD=true;//是否开启录像功能
51 | //拍完照需要跳转的activity,这个activity自己编写,可以获取到保存的视频或者图片地址
52 | Camera2Config.ACTIVITY_AFTER_CAPTURE = Camera2RecordFinishActivity.class;
53 | btnOpenCamera2.setOnClickListener(new View.OnClickListener() {
54 | @Override
55 | public void onClick(View view) {
56 | //进入Camera2界面
57 | Camera2RecordActivity.start(MainActivity.this);
58 | }
59 | });
60 | ```
61 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 |
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.0.1'
11 |
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | jcenter()
22 | }
23 | }
24 |
25 | task clean(type: Delete) {
26 | delete rootProject.buildDir
27 | }
28 |
--------------------------------------------------------------------------------
/camera2library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/camera2library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 26
5 |
6 |
7 |
8 | defaultConfig {
9 | minSdkVersion 21
10 | targetSdkVersion 26
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 |
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 |
25 | }
26 |
27 | dependencies {
28 | implementation fileTree(dir: 'libs', include: ['*.jar'])
29 |
30 | implementation 'com.android.support:appcompat-v7:26.1.0'
31 | implementation 'com.android.support.constraint:constraint-layout:1.0.2'
32 | implementation 'com.android.support:design:26.1.0'
33 | testImplementation 'junit:junit:4.12'
34 | androidTestImplementation 'com.android.support.test:runner:1.0.1'
35 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
36 | }
37 |
--------------------------------------------------------------------------------
/camera2library/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 |
--------------------------------------------------------------------------------
/camera2library/src/androidTest/java/com/cktim/camera2library/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.cktim.camera2library;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.cktim.camera2library.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/camera2library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/camera2library/src/main/java/com/cktim/camera2library/Camera2Config.java:
--------------------------------------------------------------------------------
1 | package com.cktim.camera2library;
2 |
3 | import com.cktim.camera2library.camera.Camera2Util;
4 |
5 | /**
6 | * Created by cxk on 2017/12/8.
7 | * 自定义的配置文件
8 | */
9 |
10 | public class Camera2Config {
11 | public static int RECORD_MAX_TIME = 10;//录制的总时长秒数,单位秒,默认10秒
12 | public static int RECORD_MIN_TIME = 2;//最小录制时长,单位秒,默认2秒
13 | public static int RECORD_PROGRESS_VIEW_COLOR = R.color.colorPrimary;//进度条颜色,默认蓝色
14 |
15 | public static int PREVIEW_MAX_HEIGHT = 1000;//最大高度预览尺寸,默认大于1000的第一个
16 |
17 | public static String PATH_SAVE_VIDEO= Camera2Util.getCamera2Path();//小视频存放地址,不设置的话默认在根目录的Camera2文件夹
18 | public static String PATH_SAVE_PIC= Camera2Util.getCamera2Path();//图片保存地址,不设置的话默认在根目录的Camera2文件夹
19 |
20 | public static Class ACTIVITY_AFTER_CAPTURE;//拍照完成后需要跳转的Activity,一般这个activity做处理照片或者视频用
21 | public static String INTENT_PATH_SAVE_VIDEO = "INTENT_PATH_SAVE_VIDEO"; //Intent跳转可用
22 | public static String INTENT_PATH_SAVE_PIC = "INTENT_PATH_SAVE_PIC";//Intent跳转可用
23 |
24 | public static boolean ENABLE_RECORD=true;//是否需要录像功能
25 | public static boolean ENABLE_CAPTURE=true;//是否需要拍照功能
26 | }
27 |
--------------------------------------------------------------------------------
/camera2library/src/main/java/com/cktim/camera2library/camera/Camera2RecordActivity.java:
--------------------------------------------------------------------------------
1 | package com.cktim.camera2library.camera;
2 |
3 | import android.Manifest;
4 | import android.animation.AnimatorSet;
5 | import android.animation.ObjectAnimator;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.content.pm.PackageManager;
9 | import android.graphics.ImageFormat;
10 | import android.graphics.Matrix;
11 | import android.graphics.Rect;
12 | import android.graphics.RectF;
13 | import android.graphics.SurfaceTexture;
14 | import android.hardware.camera2.CameraAccessException;
15 | import android.hardware.camera2.CameraCaptureSession;
16 | import android.hardware.camera2.CameraCharacteristics;
17 | import android.hardware.camera2.CameraDevice;
18 | import android.hardware.camera2.CameraManager;
19 | import android.hardware.camera2.CameraMetadata;
20 | import android.hardware.camera2.CaptureRequest;
21 | import android.hardware.camera2.TotalCaptureResult;
22 | import android.hardware.camera2.params.StreamConfigurationMap;
23 | import android.media.CamcorderProfile;
24 | import android.media.Image;
25 | import android.media.ImageReader;
26 | import android.media.MediaRecorder;
27 | import android.os.Bundle;
28 | import android.os.Handler;
29 | import android.os.HandlerThread;
30 | import android.os.Message;
31 | import android.support.annotation.NonNull;
32 | import android.support.v4.app.ActivityCompat;
33 | import android.support.v7.app.AppCompatActivity;
34 | import android.text.TextUtils;
35 | import android.util.Log;
36 | import android.util.Size;
37 | import android.util.SparseIntArray;
38 | import android.view.MotionEvent;
39 | import android.view.Surface;
40 | import android.view.TextureView;
41 | import android.view.View;
42 | import android.view.Window;
43 | import android.view.WindowManager;
44 | import android.view.animation.LinearInterpolator;
45 | import android.widget.ImageView;
46 | import android.widget.TextView;
47 | import android.widget.Toast;
48 |
49 | import com.cktim.camera2library.Camera2Config;
50 | import com.cktim.camera2library.R;
51 |
52 | import java.io.FileOutputStream;
53 | import java.io.IOException;
54 | import java.nio.ByteBuffer;
55 | import java.text.SimpleDateFormat;
56 | import java.util.ArrayList;
57 | import java.util.Arrays;
58 | import java.util.Collections;
59 | import java.util.Comparator;
60 | import java.util.Date;
61 | import java.util.List;
62 |
63 | /**
64 | * 利用Camera2实现点击拍照,长按录像
65 | *
66 | * 联系方式: 471497226@qq.com
67 | */
68 | public class Camera2RecordActivity extends AppCompatActivity implements TextureView.SurfaceTextureListener {
69 | //views
70 | private TextureView mTextureView;
71 | private TextView tvBalanceTime;//录制剩余时间
72 | private ImageView ivTakePhoto;//拍照&录像按钮
73 | private ImageView ivSwitchCamera;//切换前后摄像头
74 | private ImageView ivLightOn;//开关闪光灯
75 | private ImageView ivClose;//关闭该Activity
76 | private ProgressView mProgressView;//录像进度条
77 |
78 | //拍照方向
79 | private static final SparseIntArray ORIENTATION = new SparseIntArray();
80 |
81 | static {
82 | ORIENTATION.append(Surface.ROTATION_0, 90);
83 | ORIENTATION.append(Surface.ROTATION_90, 0);
84 | ORIENTATION.append(Surface.ROTATION_180, 270);
85 | ORIENTATION.append(Surface.ROTATION_270, 180);
86 | }
87 |
88 | //constant
89 | private static final String TAG = "Camera2RecordActivity";
90 | private static final int PERMISSIONS_REQUEST = 1;//拍照完成回调
91 | private static final int CAPTURE_OK = 0;//拍照完成回调
92 | private String mCameraId;//后置摄像头ID
93 | private String mCameraIdFront;//前置摄像头ID
94 | private Size mPreviewSize;//预览的Size
95 | private Size mCaptureSize;//拍照Size
96 | private int width;//TextureView的宽
97 | private int height;//TextureView的高
98 | private boolean isCameraFront = false;//当前是否是前置摄像头
99 | private boolean isLightOn = false;//当前闪光灯是否开启
100 |
101 | //Camera2
102 | private CameraDevice mCameraDevice;
103 | private CaptureRequest.Builder mPreviewBuilder;
104 | private CaptureRequest mCaptureRequest;
105 | private CameraCaptureSession mPreviewSession;
106 | private CameraCharacteristics characteristics;
107 | private ImageReader mImageReader;
108 | private int mSensorOrientation;
109 | private String picSavePath;//图片保存路径
110 | private String videoSavePath;//视频保存路径
111 |
112 | //handler
113 | private HandlerThread mCameraThread;
114 | private Handler mCameraHandler;
115 | private LongPressRunnable longPressRunnable;//长按后处理的逻辑Runnable
116 |
117 | //录像
118 | private static final int MAX_RECORD_TIME = Camera2Config.RECORD_MAX_TIME;//最大录制时长,默认10S
119 | private static final int MIN_RECORD_TIME = Camera2Config.RECORD_MIN_TIME;//最小录制时长,默认2S
120 | private boolean isRecording = false;//是否正在录制视频
121 | private boolean isStop = false;//是否停止过了MediaRecorder
122 | private int currentTime;
123 | private MediaRecorder mMediaRecorder;
124 |
125 | public static void start(Context context) {
126 | Intent intent = new Intent(context, Camera2RecordActivity.class);
127 | context.startActivity(intent);
128 | }
129 |
130 | @Override
131 | protected void onCreate(Bundle savedInstanceState) {
132 | super.onCreate(savedInstanceState);
133 | //全屏模式
134 | requestWindowFeature(Window.FEATURE_NO_TITLE);
135 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
136 | //透明导航栏
137 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
138 | setContentView(R.layout.activity_camera2_record);
139 |
140 | initViews();
141 | initTextureView();
142 | }
143 |
144 | /**
145 | * **************************************初始化相关**********************************************
146 | */
147 | //初始化TextureView
148 | private void initTextureView() {
149 | mCameraThread = new HandlerThread("CameraThread");
150 | mCameraThread.start();
151 | mCameraHandler = new Handler(mCameraThread.getLooper());
152 | mTextureView.setSurfaceTextureListener(this);
153 | }
154 |
155 | //初始化视图控件
156 | private void initViews() {
157 | mTextureView = findViewById(R.id.textureView);
158 | tvBalanceTime = findViewById(R.id.tv_balanceTime);
159 | ivTakePhoto = findViewById(R.id.iv_takePhoto);
160 | ivSwitchCamera = findViewById(R.id.iv_switchCamera);
161 | ivLightOn = findViewById(R.id.iv_lightOn);
162 | ivClose = findViewById(R.id.iv_close);
163 | mProgressView = findViewById(R.id.progressView);
164 |
165 | ivSwitchCamera.setOnClickListener(clickListener);
166 | ivLightOn.setOnClickListener(clickListener);
167 | ivClose.setOnClickListener(clickListener);
168 |
169 | //触摸事件
170 | onTouchListner();
171 | }
172 |
173 | View.OnClickListener clickListener = new View.OnClickListener() {
174 | @Override
175 | public void onClick(View view) {
176 | int i = view.getId();
177 | if (i == R.id.iv_switchCamera) {
178 | //切换摄像头
179 | switchCamera();
180 | } else if (i == R.id.iv_lightOn) {
181 | //开启关闭闪光灯
182 | openLight();
183 | } else if (i == R.id.iv_close) {
184 | //关闭Activity
185 | finish();
186 | }
187 | }
188 | };
189 |
190 | //拍照按钮触摸事件
191 | private void onTouchListner() {
192 | longPressRunnable = new LongPressRunnable();
193 | ivTakePhoto.setOnTouchListener(new View.OnTouchListener() {
194 | @Override
195 | public boolean onTouch(View view, MotionEvent event) {
196 | switch (event.getAction()) {
197 | case MotionEvent.ACTION_DOWN:
198 | isRecording = false;
199 | mCameraHandler.postDelayed(longPressRunnable, 500);//同时延长500启动长按后处理的逻辑Runnable
200 | break;
201 | case MotionEvent.ACTION_MOVE:
202 | break;
203 | case MotionEvent.ACTION_UP:
204 | case MotionEvent.ACTION_CANCEL:
205 | //根据当前按钮的状态进行相应的处理
206 | handlerUnpressByState();
207 | break;
208 | }
209 | return true;
210 | }
211 | });
212 |
213 | mTextureView.setOnTouchListener(new View.OnTouchListener() {
214 | @Override
215 | public boolean onTouch(View view, MotionEvent event) {
216 | //两指缩放
217 | changeZoom(event);
218 | return true;
219 | }
220 | });
221 | }
222 |
223 | //长按线程
224 | private class LongPressRunnable implements Runnable {
225 | @Override
226 | public void run() {
227 | //判断是否需要录像功能
228 | if (Camera2Config.ENABLE_RECORD) {
229 | prepareMediaRecorder();
230 | startButtonAnima();
231 | isRecording = true; //如果按下后经过500毫秒则会修改当前状态为长按状态,标记为正在录制中
232 | startMediaRecorder();//开始录制
233 | }
234 | }
235 | }
236 |
237 | //当手指松开按钮时候处理的逻辑
238 | private void handlerUnpressByState() {
239 | mCameraHandler.removeCallbacks(longPressRunnable);//移除长按逻辑的Runnable
240 | //根据当前状态处理
241 | if (isRecording) {
242 | stopButtonAnima();
243 | isRecording = false;
244 | mCameraHandler.postDelayed(new Runnable() {
245 | @Override
246 | public void run() {
247 | //停止录制,先判断是不是停止过了
248 | if (!isStop) {
249 | stopMediaRecorder();
250 | }
251 | }
252 | }, 200);
253 | } else {
254 | isRecording = false;
255 | //判断是否需要拍照功能
256 | if (Camera2Config.ENABLE_CAPTURE) {
257 | capture();
258 | }
259 | }
260 | }
261 |
262 | //开始按下按钮动画
263 | public void startButtonAnima() {
264 | AnimatorSet animatorSet = new AnimatorSet();//组合动画
265 | ObjectAnimator scaleX = ObjectAnimator.ofFloat(ivTakePhoto, "scaleX", 1f, 1.3f);
266 | ObjectAnimator scaleY = ObjectAnimator.ofFloat(ivTakePhoto, "scaleY", 1f, 1.3f);
267 |
268 | animatorSet.setDuration(100);
269 | animatorSet.setInterpolator(new LinearInterpolator());
270 | animatorSet.play(scaleX).with(scaleY);//两个动画同时开始
271 | animatorSet.start();
272 | }
273 |
274 | //停止按下按钮动画
275 | public void stopButtonAnima() {
276 | AnimatorSet animatorSet = new AnimatorSet();//组合动画
277 | ObjectAnimator scaleX = ObjectAnimator.ofFloat(ivTakePhoto, "scaleX", 1.3f, 1f);
278 | ObjectAnimator scaleY = ObjectAnimator.ofFloat(ivTakePhoto, "scaleY", 1.3f, 1f);
279 |
280 | animatorSet.setDuration(100);
281 | animatorSet.setInterpolator(new LinearInterpolator());
282 | animatorSet.play(scaleX).with(scaleY);//两个动画同时开始
283 | animatorSet.start();
284 | }
285 |
286 | /**
287 | * ******************************SurfaceTextureListener*****************************************
288 | */
289 | @Override
290 | public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
291 | //当SurefaceTexture可用的时候,设置相机参数并打开相机
292 | this.width = width;
293 | this.height = height;
294 |
295 | setupCamera(width, height);//配置相机参数
296 | openCamera(mCameraId);//打开相机
297 | }
298 |
299 | @Override
300 | public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {
301 | configureTransform(width, height);
302 | }
303 |
304 | @Override
305 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
306 | return false;
307 | }
308 |
309 | @Override
310 | public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
311 | }
312 |
313 | /**
314 | * ******************************SetupCamera(配置Camera)*****************************************
315 | */
316 | private void setupCamera(int width, int height) {
317 | //获取摄像头的管理者CameraManager
318 | CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
319 | try {
320 | //0表示后置摄像头,1表示前置摄像头
321 | mCameraId = manager.getCameraIdList()[0];
322 | mCameraIdFront = manager.getCameraIdList()[1];
323 |
324 | //前置摄像头和后置摄像头的参数属性不同,所以这里要做下判断
325 | if (isCameraFront) {
326 | characteristics = manager.getCameraCharacteristics(mCameraIdFront);
327 | } else {
328 | characteristics = manager.getCameraCharacteristics(mCameraId);
329 | }
330 |
331 | //获取StreamConfigurationMap,它是管理摄像头支持的所有输出格式和尺寸
332 | StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
333 | mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
334 | //选择预览尺寸
335 | mPreviewSize = Camera2Util.getMinPreSize(map.getOutputSizes(SurfaceTexture.class), width, height, Camera2Config.PREVIEW_MAX_HEIGHT);
336 |
337 | Log.e(TAG, mPreviewSize.getWidth() + "----" + mPreviewSize.getHeight());
338 | Log.e(TAG, height + "----" + width);
339 |
340 | //获取相机支持的最大拍照尺寸
341 | mCaptureSize = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new Comparator() {
342 | @Override
343 | public int compare(Size lhs, Size rhs) {
344 | return Long.signum(lhs.getWidth() * lhs.getHeight() - rhs.getHeight() * rhs.getWidth());
345 | }
346 | });
347 |
348 | configureTransform(width, height);
349 |
350 | //此ImageReader用于拍照所需
351 | setupImageReader();
352 |
353 | //MediaRecorder用于录像所需
354 | mMediaRecorder = new MediaRecorder();
355 | } catch (Exception e) {
356 | e.printStackTrace();
357 | }
358 | }
359 |
360 | //配置ImageReader
361 | private void setupImageReader() {
362 | //2代表ImageReader中最多可以获取两帧图像流
363 | mImageReader = ImageReader.newInstance(mCaptureSize.getWidth(), mCaptureSize.getHeight(),
364 | ImageFormat.JPEG, 2);
365 | mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
366 | @Override
367 | public void onImageAvailable(ImageReader reader) {
368 | Image mImage = reader.acquireNextImage();
369 | ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
370 | byte[] data = new byte[buffer.remaining()];
371 | buffer.get(data);
372 | String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
373 | Camera2Util.createSavePath(Camera2Config.PATH_SAVE_PIC);//判断有没有这个文件夹,没有的话需要创建
374 | picSavePath = Camera2Config.PATH_SAVE_PIC + "IMG_" + timeStamp + ".jpg";
375 | FileOutputStream fos = null;
376 | try {
377 | fos = new FileOutputStream(picSavePath);
378 | fos.write(data, 0, data.length);
379 |
380 | Message msg = new Message();
381 | msg.what = CAPTURE_OK;
382 | msg.obj = picSavePath;
383 | mCameraHandler.sendMessage(msg);
384 | } catch (IOException e) {
385 | e.printStackTrace();
386 | } finally {
387 | if (fos != null) {
388 | try {
389 | fos.close();
390 | } catch (IOException e) {
391 | e.printStackTrace();
392 | }
393 | }
394 | }
395 | mImage.close();
396 | }
397 | }, mCameraHandler);
398 |
399 | mCameraHandler = new Handler() {
400 | @Override
401 | public void handleMessage(Message msg) {
402 | super.handleMessage(msg);
403 | switch (msg.what) {
404 | case CAPTURE_OK:
405 | //这里拍照保存完成,可以进行相关的操作,例如再次压缩等(由于封装,这里我先跳转掉完成页面)
406 | if (Camera2Config.ACTIVITY_AFTER_CAPTURE != null) {
407 | Intent intent = new Intent(Camera2RecordActivity.this, Camera2Config.ACTIVITY_AFTER_CAPTURE);
408 | intent.putExtra(Camera2Config.INTENT_PATH_SAVE_PIC, picSavePath);
409 | startActivity(intent);
410 | }
411 | break;
412 | }
413 | }
414 | };
415 | }
416 |
417 | /**
418 | * ******************************openCamera(打开Camera)*****************************************
419 | */
420 | private void openCamera(String CameraId) {
421 | //获取摄像头的管理者CameraManager
422 | CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
423 | //检查权限
424 | try {
425 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
426 | return;
427 | }
428 | //打开相机,第一个参数指示打开哪个摄像头,第二个参数stateCallback为相机的状态回调接口,第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行
429 | manager.openCamera(CameraId, mStateCallback, null);
430 | } catch (CameraAccessException e) {
431 | e.printStackTrace();
432 | }
433 | }
434 |
435 |
436 | private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
437 | @Override
438 | public void onOpened(CameraDevice camera) {
439 | mCameraDevice = camera;
440 | startPreview();
441 |
442 | if (null != mTextureView) {
443 | configureTransform(mTextureView.getWidth(), mTextureView.getHeight());
444 | }
445 | }
446 |
447 | @Override
448 | public void onDisconnected(CameraDevice cameraDevice) {
449 | cameraDevice.close();
450 | mCameraDevice = null;
451 | }
452 |
453 | @Override
454 | public void onError(CameraDevice cameraDevice, int error) {
455 | cameraDevice.close();
456 | mCameraDevice = null;
457 | }
458 | };
459 |
460 | /**
461 | * ******************************Camera2成功打开,开始预览(startPreview)*************************
462 | */
463 | public void startPreview() {
464 | if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
465 | return;
466 | }
467 |
468 | SurfaceTexture mSurfaceTexture = mTextureView.getSurfaceTexture();//获取TextureView的SurfaceTexture,作为预览输出载体
469 |
470 | if (mSurfaceTexture == null) {
471 | return;
472 | }
473 |
474 | try {
475 | closePreviewSession();
476 | mSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());//设置TextureView的缓冲区大小
477 | mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);//创建CaptureRequestBuilder,TEMPLATE_PREVIEW比表示预览请求
478 | Surface mSurface = new Surface(mSurfaceTexture);//获取Surface显示预览数据
479 | mPreviewBuilder.addTarget(mSurface);//设置Surface作为预览数据的显示界面
480 |
481 | //默认预览不开启闪光灯
482 | mPreviewBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
483 |
484 | //创建相机捕获会话,第一个参数是捕获数据的输出Surface列表,第二个参数是CameraCaptureSession的状态回调接口,当它创建好后会回调onConfigured方法,第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行
485 | mCameraDevice.createCaptureSession(Arrays.asList(mSurface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {
486 | @Override
487 | public void onConfigured(CameraCaptureSession session) {
488 | try {
489 | //创建捕获请求
490 | mCaptureRequest = mPreviewBuilder.build();
491 | mPreviewSession = session;
492 | //不停的发送获取图像请求,完成连续预览
493 | mPreviewSession.setRepeatingRequest(mCaptureRequest, null, mCameraHandler);
494 | } catch (Exception e) {
495 | e.printStackTrace();
496 | }
497 | }
498 |
499 | @Override
500 | public void onConfigureFailed(CameraCaptureSession session) {
501 |
502 | }
503 | }, null);
504 | } catch (Exception e) {
505 | e.printStackTrace();
506 | Log.e("dasdadasd", "捕获的异常" + e.toString());
507 | }
508 | }
509 |
510 | /**
511 | * ********************************************拍照*********************************************
512 | */
513 | private void capture() {
514 | if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
515 | return;
516 | }
517 | try {
518 | CaptureRequest.Builder mCaptureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
519 | //获取屏幕方向
520 | int rotation = getWindowManager().getDefaultDisplay().getRotation();
521 | mCaptureBuilder.addTarget(mImageReader.getSurface());
522 | //isCameraFront是自定义的一个boolean值,用来判断是不是前置摄像头,是的话需要旋转180°,不然拍出来的照片会歪了
523 | if (isCameraFront) {
524 | mCaptureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(Surface.ROTATION_180));
525 | } else {
526 | mCaptureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));
527 | }
528 |
529 | //锁定焦点
530 | mCaptureBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
531 |
532 | //判断预览的时候是不是已经开启了闪光灯
533 | if (isLightOn) {
534 | mCaptureBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
535 | } else {
536 | mCaptureBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
537 | }
538 |
539 | //判断预览的时候是否两指缩放过,是的话需要保持当前的缩放比例
540 | mCaptureBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoom);
541 |
542 | CameraCaptureSession.CaptureCallback CaptureCallback = new CameraCaptureSession.CaptureCallback() {
543 | @Override
544 | public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
545 | //拍完照unLockFocus
546 | unLockFocus();
547 | }
548 | };
549 | mPreviewSession.stopRepeating();
550 | //咔擦拍照
551 | mPreviewSession.capture(mCaptureBuilder.build(), CaptureCallback, null);
552 | } catch (CameraAccessException e) {
553 | e.printStackTrace();
554 | }
555 | }
556 |
557 | private void unLockFocus() {
558 | try {
559 | // 构建失能AF的请求
560 | mPreviewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
561 | //闪光灯重置为未开启状态
562 | mPreviewBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
563 | //继续开启预览
564 | mPreviewSession.setRepeatingRequest(mCaptureRequest, null, mCameraHandler);
565 | } catch (Exception e) {
566 | e.printStackTrace();
567 | }
568 | }
569 |
570 | /**
571 | * ********************************************录像*********************************************
572 | */
573 | private void setUpMediaRecorder() {
574 | try {
575 | Log.e("daasddasd", "setUpMediaRecorder");
576 | mMediaRecorder.reset();
577 | mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
578 | mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
579 | // 这里有点投机取巧的方式,不过证明方法也是不错的
580 | // 录制出来10S的视频,大概1.2M,清晰度不错,而且避免了因为手动设置参数导致无法录制的情况
581 | // 手机一般都有这个格式CamcorderProfile.QUALITY_480P,因为单单录制480P的视频还是很大的,所以我们在手动根据预览尺寸配置一下videoBitRate,值越高越大
582 | // QUALITY_QVGA清晰度一般,不过视频很小,一般10S才几百K
583 | // 判断有没有这个手机有没有这个参数
584 | if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P)) {
585 | CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
586 | profile.videoBitRate = mPreviewSize.getWidth() * mPreviewSize.getHeight();
587 | mMediaRecorder.setProfile(profile);
588 | mMediaRecorder.setPreviewDisplay(new Surface(mTextureView.getSurfaceTexture()));
589 | } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)) {
590 | CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_720P);
591 | profile.videoBitRate = mPreviewSize.getWidth() * mPreviewSize.getHeight();
592 |
593 | mMediaRecorder.setProfile(profile);
594 | mMediaRecorder.setPreviewDisplay(new Surface(mTextureView.getSurfaceTexture()));
595 | } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_QVGA)) {
596 | mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_QVGA));
597 | mMediaRecorder.setPreviewDisplay(new Surface(mTextureView.getSurfaceTexture()));
598 | } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_CIF)) {
599 | mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_CIF));
600 | mMediaRecorder.setPreviewDisplay(new Surface(mTextureView.getSurfaceTexture()));
601 | } else {
602 | mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
603 | mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
604 | mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
605 | mMediaRecorder.setVideoEncodingBitRate(10000000);
606 | mMediaRecorder.setVideoFrameRate(30);
607 | mMediaRecorder.setVideoEncodingBitRate(2500000);
608 | mMediaRecorder.setVideoFrameRate(20);
609 | mMediaRecorder.setVideoSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
610 | }
611 |
612 | //判断有没有配置过视频地址了
613 | Camera2Util.createSavePath(Camera2Config.PATH_SAVE_VIDEO);//判断有没有这个文件夹,没有的话需要创建
614 | String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
615 | videoSavePath = Camera2Config.PATH_SAVE_VIDEO + "VIDEO_" + timeStamp + ".mp4";
616 | mMediaRecorder.setOutputFile(videoSavePath);
617 |
618 | //判断是不是前置摄像头,是的话需要旋转对应的角度
619 | if (isCameraFront) {
620 | mMediaRecorder.setOrientationHint(270);
621 | } else {
622 | mMediaRecorder.setOrientationHint(90);
623 | }
624 | mMediaRecorder.prepare();
625 | } catch (Exception e) {
626 | e.printStackTrace();
627 | }
628 | }
629 |
630 | //预览录像
631 | private void prepareMediaRecorder() {
632 | if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
633 | return;
634 | }
635 |
636 | try {
637 | closePreviewSession();
638 | Log.e("aasdasdasd", "prepareMediaRecorder");
639 | setUpMediaRecorder();
640 |
641 | SurfaceTexture texture = mTextureView.getSurfaceTexture();
642 | assert texture != null;
643 | texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
644 | mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
645 | List surfaces = new ArrayList<>();
646 |
647 | // Set up Surface for the camera preview
648 | Surface previewSurface = new Surface(texture);
649 | surfaces.add(previewSurface);
650 | mPreviewBuilder.addTarget(previewSurface);
651 |
652 | // Set up Surface for the MediaRecorder
653 | Surface recorderSurface = mMediaRecorder.getSurface();
654 | surfaces.add(recorderSurface);
655 | mPreviewBuilder.addTarget(recorderSurface);
656 |
657 | //判断预览之前有没有开闪光灯
658 | if (isLightOn) {
659 | mPreviewBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
660 | } else {
661 | mPreviewBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
662 | }
663 |
664 | //保持当前的缩放比例
665 | mPreviewBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoom);
666 |
667 | mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
668 | @Override
669 | public void onConfigured(CameraCaptureSession session) {
670 | try {
671 | //创建捕获请求
672 | mCaptureRequest = mPreviewBuilder.build();
673 | mPreviewSession = session;
674 | //设置反复捕获数据的请求,这样预览界面就会一直有数据显示
675 | mPreviewSession.setRepeatingRequest(mCaptureRequest, null, mCameraHandler);
676 | } catch (Exception e) {
677 | e.printStackTrace();
678 | Log.e("dasdasdasdas", "捕获的异常2" + e.toString());
679 | }
680 | }
681 |
682 | @Override
683 | public void onConfigureFailed(CameraCaptureSession session) {
684 | Toast.makeText(Camera2RecordActivity.this, "onConfigureFailed", Toast.LENGTH_SHORT).show();
685 | }
686 | }, null);
687 | } catch (Exception e) {
688 | e.printStackTrace();
689 | }
690 | }
691 |
692 | //开始录像
693 | private void startMediaRecorder() {
694 | Log.e("daasddasd", "startMediaRecorder");
695 | // Start recording
696 | try {
697 | mMediaRecorder.start();
698 | //开始计时,判断是否已经超过录制时间了
699 | mCameraHandler.postDelayed(recordRunnable, 0);
700 | isStop = false;
701 | } catch (Exception e) {
702 | e.printStackTrace();
703 | }
704 | }
705 |
706 | Runnable recordRunnable = new Runnable() {
707 | @Override
708 | public void run() {
709 | currentTime++;
710 | //开始显示进度条
711 | mProgressView.setVisibility(View.VISIBLE);
712 | mProgressView.setIsStart(true);
713 | //显示时间
714 | tvBalanceTime.setVisibility(View.VISIBLE);
715 | tvBalanceTime.setText(MAX_RECORD_TIME - currentTime + "s");
716 |
717 | //如果超过最大录制时长则自动结束
718 | if (currentTime > MAX_RECORD_TIME) {
719 | stopMediaRecorder();
720 | } else {
721 | mCameraHandler.postDelayed(this, 1000);
722 | }
723 | }
724 | };
725 |
726 | //停止录像
727 | private void stopMediaRecorder() {
728 | if (TextUtils.isEmpty(videoSavePath)) {
729 | return;
730 | }
731 | try {
732 | //结束ProgressView
733 | mProgressView.setIsStart(false);
734 | mCameraHandler.removeCallbacks(recordRunnable);
735 | mMediaRecorder.stop();
736 | mMediaRecorder.reset();
737 | isStop = true;
738 |
739 | //判断录制时常是不是小于指定秒数
740 | if (currentTime <= MIN_RECORD_TIME) {
741 | Toast.makeText(this, "录制时间过短", Toast.LENGTH_LONG).show();
742 | } else {
743 | //正常录制结束,跳转到完成页,这里你也可以自己处理
744 | if (Camera2Config.ACTIVITY_AFTER_CAPTURE != null) {
745 | Intent intent = new Intent(Camera2RecordActivity.this, Camera2Config.ACTIVITY_AFTER_CAPTURE);
746 | intent.putExtra(Camera2Config.INTENT_PATH_SAVE_VIDEO, videoSavePath);
747 | startActivity(intent);
748 | }
749 | }
750 |
751 | //录制完成后重置录制界面
752 | showResetCameraLayout();
753 |
754 | } catch (Exception e) {
755 | //这里抛出的异常是由于MediaRecorder开关时间过于短暂导致,直接按照录制时间短处理
756 | Toast.makeText(this, "录制时间过短", Toast.LENGTH_LONG).show();
757 | showResetCameraLayout();
758 | }
759 |
760 | currentTime = 0;
761 | }
762 |
763 |
764 | public void showResetCameraLayout() {
765 | resetCamera();
766 | mProgressView.setVisibility(View.INVISIBLE);
767 | tvBalanceTime.setVisibility(View.GONE);
768 | //重置ProgressView
769 | mProgressView.reset();
770 | }
771 |
772 | /**
773 | * **********************************************切换摄像头**************************************
774 | */
775 | public void switchCamera() {
776 | if (mCameraDevice != null) {
777 | mCameraDevice.close();
778 | mCameraDevice = null;
779 | }
780 |
781 | if (isCameraFront) {
782 | isCameraFront = false;
783 | setupCamera(width, height);
784 | openCamera(mCameraId);
785 | } else {
786 | isCameraFront = true;
787 | setupCamera(width, height);
788 | openCamera(mCameraIdFront);
789 | }
790 | }
791 |
792 | /**
793 | * ***************************************打开和关闭闪光灯****************************************
794 | */
795 | public void openLight() {
796 | if (isLightOn) {
797 | ivLightOn.setSelected(false);
798 | isLightOn = false;
799 | mPreviewBuilder.set(CaptureRequest.FLASH_MODE,
800 | CaptureRequest.FLASH_MODE_OFF);
801 | } else {
802 | ivLightOn.setSelected(true);
803 | isLightOn = true;
804 | mPreviewBuilder.set(CaptureRequest.FLASH_MODE,
805 | CaptureRequest.FLASH_MODE_TORCH);
806 | }
807 |
808 | try {
809 | if (mPreviewSession != null)
810 | mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, mCameraHandler);
811 | } catch (Exception e) {
812 | e.printStackTrace();
813 | }
814 | }
815 |
816 | /**
817 | * *********************************放大或者缩小**********************************
818 | */
819 | //手指按下的点为(x1, y1)手指离开屏幕的点为(x2, y2)
820 | float finger_spacing;
821 | int zoom_level = 0;
822 | Rect zoom;
823 |
824 | public void changeZoom(MotionEvent event) {
825 | try {
826 | //活动区域宽度和作物区域宽度之比和活动区域高度和作物区域高度之比的最大比率
827 | float maxZoom = (characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM)) * 10;
828 | Rect m = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
829 |
830 | int action = event.getAction();
831 | float current_finger_spacing;
832 | //判断当前屏幕的手指数
833 | if (event.getPointerCount() > 1) {
834 | //计算两个触摸点的距离
835 | current_finger_spacing = getFingerSpacing(event);
836 |
837 | if (finger_spacing != 0) {
838 | if (current_finger_spacing > finger_spacing && maxZoom > zoom_level) {
839 | zoom_level++;
840 |
841 | } else if (current_finger_spacing < finger_spacing && zoom_level > 1) {
842 | zoom_level--;
843 | }
844 |
845 | int minW = (int) (m.width() / maxZoom);
846 | int minH = (int) (m.height() / maxZoom);
847 | int difW = m.width() - minW;
848 | int difH = m.height() - minH;
849 | int cropW = difW / 100 * (int) zoom_level;
850 | int cropH = difH / 100 * (int) zoom_level;
851 | cropW -= cropW & 3;
852 | cropH -= cropH & 3;
853 | zoom = new Rect(cropW, cropH, m.width() - cropW, m.height() - cropH);
854 | mPreviewBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoom);
855 | }
856 | finger_spacing = current_finger_spacing;
857 | } else {
858 | if (action == MotionEvent.ACTION_UP) {
859 | //single touch logic,可做点击聚焦操作
860 | }
861 | }
862 |
863 | try {
864 | mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), new CameraCaptureSession.CaptureCallback() {
865 | @Override
866 | public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
867 | super.onCaptureCompleted(session, request, result);
868 | }
869 | },
870 | null);
871 | } catch (Exception e) {
872 | e.printStackTrace();
873 | }
874 | } catch (Exception e) {
875 | throw new RuntimeException("can not access camera.", e);
876 | }
877 | }
878 |
879 | //计算两个触摸点的距离
880 | private float getFingerSpacing(MotionEvent event) {
881 | float x = event.getX(0) - event.getX(1);
882 | float y = event.getY(0) - event.getY(1);
883 | return (float) Math.sqrt(x * x + y * y);
884 | }
885 |
886 | /**
887 | * **************************************清除操作************************************************
888 | */
889 | public void onFinishCapture() {
890 | try {
891 | if (mPreviewSession != null) {
892 | mPreviewSession.close();
893 | mPreviewSession = null;
894 | }
895 |
896 | if (mCameraDevice != null) {
897 | mCameraDevice.close();
898 | mCameraDevice = null;
899 | }
900 |
901 | if (mImageReader != null) {
902 | mImageReader.close();
903 | mImageReader = null;
904 | }
905 |
906 | if (mMediaRecorder != null) {
907 | mMediaRecorder.release();
908 | mMediaRecorder = null;
909 | }
910 |
911 | if (mCameraHandler != null) {
912 | mCameraHandler.removeCallbacksAndMessages(null);
913 | }
914 | } catch (Exception e) {
915 | e.printStackTrace();
916 | Log.e("adsdadadad", e.toString() + "onFinish2()");
917 | }
918 | }
919 |
920 |
921 | //清除预览Session
922 | private void closePreviewSession() {
923 | if (mPreviewSession != null) {
924 | mPreviewSession.close();
925 | mPreviewSession = null;
926 | }
927 | }
928 |
929 | //重新配置打开相机
930 | public void resetCamera() {
931 | if (TextUtils.isEmpty(mCameraId)) {
932 | return;
933 | }
934 |
935 | if (mCameraDevice != null) {
936 | mCameraDevice.close();
937 | }
938 |
939 | setupCamera(width, height);
940 | openCamera(mCameraId);
941 | }
942 |
943 | /**
944 | * 屏幕方向发生改变时调用转换数据方法
945 | *
946 | * @param viewWidth mTextureView 的宽度
947 | * @param viewHeight mTextureView 的高度
948 | */
949 | private void configureTransform(int viewWidth, int viewHeight) {
950 | if (null == mTextureView || null == mPreviewSize) {
951 | return;
952 | }
953 | int rotation = getWindowManager().getDefaultDisplay().getRotation();
954 | Matrix matrix = new Matrix();
955 | RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
956 | RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
957 | float centerX = viewRect.centerX();
958 | float centerY = viewRect.centerY();
959 | if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
960 | bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
961 | matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
962 | float scale = Math.max(
963 | (float) viewHeight / mPreviewSize.getHeight(),
964 | (float) viewWidth / mPreviewSize.getWidth());
965 | matrix.postScale(scale, scale, centerX, centerY);
966 | matrix.postRotate(90 * (rotation - 2), centerX, centerY);
967 | }
968 | mTextureView.setTransform(matrix);
969 | }
970 |
971 | /**
972 | * ******************************Activity生命周期*****************************************
973 | */
974 | @Override
975 | protected void onResume() {
976 | super.onResume();
977 | //从FinishActivity退回来的时候默认重置为初始状态,因为有些机型从不可见到可见不会执行onSurfaceTextureAvailable,像有些一加手机
978 | //所以也可以在这里在进行setupCamera()和openCamera()这两个方法
979 | //每次开启预览缩放重置为正常状态
980 | if (zoom != null) {
981 | zoom.setEmpty();
982 | zoom_level = 0;
983 | }
984 |
985 | //每次开启预览默认闪光灯没开启
986 | isLightOn = false;
987 | ivLightOn.setSelected(false);
988 |
989 | //每次开启预览默认是后置摄像头
990 | isCameraFront = false;
991 | }
992 |
993 | @Override
994 | protected void onPause() {
995 | onFinishCapture();//释放资源
996 | super.onPause();
997 | }
998 |
999 | @Override
1000 | protected void onStop() {
1001 | super.onStop();
1002 | }
1003 |
1004 | @Override
1005 | protected void onDestroy() {
1006 | super.onDestroy();
1007 | }
1008 | }
1009 |
--------------------------------------------------------------------------------
/camera2library/src/main/java/com/cktim/camera2library/camera/Camera2Util.java:
--------------------------------------------------------------------------------
1 | package com.cktim.camera2library.camera;
2 |
3 | import android.os.Environment;
4 | import android.util.Size;
5 |
6 | import java.io.File;
7 | import java.util.ArrayList;
8 | import java.util.Collections;
9 | import java.util.Comparator;
10 | import java.util.List;
11 |
12 | /**
13 | * Created by cxk on 2017/12/8.
14 | *
15 | * 这里为了方便,将部分方法封装到这个Util里面
16 | */
17 |
18 | public class Camera2Util {
19 | //选择合适的视频size,并且不能大于1080p
20 | private Size chooseVideoSize(Size[] choices) {
21 | for (Size size : choices) {
22 | if (size.getWidth() == size.getHeight() * 4 / 3 && size.getWidth() <= 1080) {
23 | return size;
24 | }
25 | }
26 | return choices[choices.length - 1];
27 | }
28 |
29 | //选择sizeMap中大于并且最接近width和height的size
30 | private Size getOptimalSize(Size[] sizeMap, int width, int height) {
31 | List sizeList = new ArrayList<>();
32 | for (Size option : sizeMap) {
33 | if (width > height) {
34 | if (option.getWidth() > width && option.getHeight() > height) {
35 | sizeList.add(option);
36 | }
37 | } else {
38 | if (option.getWidth() > height && option.getHeight() > width) {
39 | sizeList.add(option);
40 | }
41 | }
42 | }
43 | if (sizeList.size() > 0) {
44 | return Collections.min(sizeList, new Comparator() {
45 | @Override
46 | public int compare(Size lhs, Size rhs) {
47 | return Long.signum(lhs.getWidth() * lhs.getHeight() - rhs.getWidth() * rhs.getHeight());
48 | }
49 | });
50 | }
51 | return sizeMap[0];
52 | }
53 |
54 |
55 | // 通过对比得到与宽高比最接近的尺寸(如果有相同尺寸,优先选择,activity我们已经固定了方向,所以这里无需在做判断
56 | protected static Size getCloselyPreSize(Size[] sizeMap, int surfaceWidth, int surfaceHeight) {
57 | int ReqTmpWidth;
58 | int ReqTmpHeight;
59 | ReqTmpWidth = surfaceHeight;
60 | ReqTmpHeight = surfaceWidth;
61 | //先查找preview中是否存在与surfaceview相同宽高的尺寸
62 | for (Size size : sizeMap) {
63 | if ((size.getWidth() == ReqTmpWidth) && (size.getHeight() == ReqTmpHeight)) {
64 | return size;
65 | }
66 | }
67 |
68 | // 得到与传入的宽高比最接近的size
69 | float reqRatio = ((float) ReqTmpWidth) / ReqTmpHeight;
70 | float curRatio, deltaRatio;
71 | float deltaRatioMin = Float.MAX_VALUE;
72 | Size retSize = null;
73 | for (Size size : sizeMap) {
74 | curRatio = ((float) size.getWidth()) / size.getHeight();
75 | deltaRatio = Math.abs(reqRatio - curRatio);
76 | if (deltaRatio < deltaRatioMin) {
77 | deltaRatioMin = deltaRatio;
78 | retSize = size;
79 | }
80 | }
81 | return retSize;
82 | }
83 |
84 |
85 | /**
86 | * 核心方法,这里是通过从sizeMap中获取和Textureview宽高比例相同的map,然后在获取接近自己想获取到的尺寸
87 | * 之所以这么做是因为我们要确保预览尺寸不要太大,这样才不会太卡
88 | *
89 | * @param sizeMap
90 | * @param surfaceWidth
91 | * @param surfaceHeight
92 | * @param maxHeight
93 | * @return
94 | */
95 | public static Size getMinPreSize(Size[] sizeMap, int surfaceWidth, int surfaceHeight, int maxHeight) {
96 | // 得到与传入的宽高比最接近的size
97 | float reqRatio = ((float) surfaceWidth) / surfaceHeight;
98 | float curRatio;
99 | List sizeList = new ArrayList<>();
100 | Size retSize = null;
101 | for (Size size : sizeMap) {
102 | curRatio = ((float) size.getHeight()) / size.getWidth();
103 | if (reqRatio == curRatio) {
104 | sizeList.add(size);
105 | }
106 | }
107 |
108 | if (sizeList != null && sizeList.size() != 0) {
109 | for (int i = sizeList.size() - 1; i >= 0; i--) {
110 | //取Size宽度大于1000的第一个数,这里我们获取大于maxHeight的第一个数,理论上我们是想获取size.getWidth宽度为1080或者1280那些,因为这样的预览尺寸已经足够了
111 | if (sizeList.get(i).getWidth() >= maxHeight) {
112 | retSize = sizeList.get(i);
113 | break;
114 | }
115 | }
116 |
117 | //可能没有宽度大于maxHeight的size,则取相同比例中最小的那个size
118 | if (retSize == null) {
119 | retSize = sizeList.get(sizeList.size() - 1);
120 | }
121 |
122 | } else {
123 | retSize = getCloselyPreSize(sizeMap, surfaceWidth, surfaceHeight);
124 | }
125 | return retSize;
126 | }
127 |
128 |
129 | /**
130 | * 使用Camera2录制和所拍的照片都会在这里
131 | */
132 | public static String getCamera2Path() {
133 | String picturePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/CameraV2/";
134 | File file = new File(picturePath);
135 | if (!file.exists()) {
136 | file.mkdirs();
137 | }
138 | return picturePath;
139 | }
140 |
141 | /**
142 | * 判断传入的地址是否已经有这个文件夹,没有的话需要创建
143 | */
144 | public static void createSavePath(String path){
145 | File file = new File(path);
146 | if (!file.exists()) {
147 | file.mkdirs();
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/camera2library/src/main/java/com/cktim/camera2library/camera/ProgressView.java:
--------------------------------------------------------------------------------
1 | package com.cktim.camera2library.camera;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Paint;
6 | import android.support.annotation.Nullable;
7 | import android.util.AttributeSet;
8 | import android.util.DisplayMetrics;
9 | import android.view.View;
10 | import android.view.WindowManager;
11 |
12 | import com.cktim.camera2library.Camera2Config;
13 | import com.cktim.camera2library.R;
14 |
15 | /**
16 | * Created by cxk on 2017/12/8.
17 | *
18 | * 长按录制时候的倒数进度条
19 | */
20 |
21 | public class ProgressView extends View {
22 | //constant
23 | private int millisecond = 1000;//每一秒
24 | private float maxProgressSize = Camera2Config.RECORD_MAX_TIME * millisecond;//总进度是10s
25 | private float eachProgressWidth = 0;//每一格的宽度
26 |
27 | private Context mContext;
28 | private WindowManager mWindowManager;
29 | private Paint progressPaint;
30 |
31 | public ProgressView(Context context) {
32 | this(context, null);
33 | }
34 |
35 | public ProgressView(Context context, AttributeSet attrs) {
36 | this(context, attrs, 0);
37 | }
38 |
39 | public ProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
40 | super(context, attrs, defStyleAttr);
41 | this.mContext = context;
42 | init();
43 | }
44 |
45 | private void init() {
46 | //设置每一刻度的宽度
47 | DisplayMetrics dm = new DisplayMetrics();
48 | mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
49 | mWindowManager.getDefaultDisplay().getMetrics(dm);
50 | eachProgressWidth = dm.widthPixels / (maxProgressSize * 1.0f);
51 | //进度条的背景颜色
52 | setBackgroundColor(getResources().getColor(R.color.transparent));
53 | //进度条的前景颜色,画笔
54 | progressPaint = new Paint();
55 | progressPaint.setStyle(Paint.Style.FILL);
56 | progressPaint.setColor(getResources().getColor(Camera2Config.RECORD_PROGRESS_VIEW_COLOR));
57 | }
58 |
59 | private long initTime = -1;//上一次刷新完成后的时间
60 | private boolean isStart = false;
61 | private float countWidth = 0;//进度条进度的进程,每次调用invalidate()都刷新一次
62 |
63 | @Override
64 | protected void onDraw(Canvas canvas) {
65 | super.onDraw(canvas);
66 |
67 | if (!isStart) {
68 | canvas.drawRect(0, 0, countWidth, getMeasuredHeight(), progressPaint);
69 | return;
70 | }
71 | if (initTime == -1) {
72 | initTime = System.currentTimeMillis();
73 | canvas.drawRect(0, 0, countWidth, getMeasuredHeight(), progressPaint);
74 | invalidate();
75 | return;
76 | }
77 | //这次刷新的时间,用于与上一次刷新完成的时间作差得出进度条需要增加的进度
78 | long thisTime = System.currentTimeMillis();
79 | countWidth += eachProgressWidth * (thisTime - initTime) * 1.0f;
80 | if (countWidth > getMeasuredWidth()) {
81 | countWidth = getMeasuredWidth();
82 | }
83 | canvas.drawRect(0, 0, countWidth, getMeasuredHeight(), progressPaint);
84 |
85 | //如果都了最大长度,就不再调用invalidate();了
86 | if (countWidth < getMeasuredWidth() && isStart) {
87 | initTime = System.currentTimeMillis();
88 | invalidate();
89 | } else {
90 | countWidth = 0;
91 | initTime = -1;
92 | isStart = false;
93 | }
94 |
95 | }
96 |
97 | //开始或暂停进度条进度刷新
98 | public void setIsStart(boolean isStart) {
99 | if (isStart == this.isStart)
100 | return;
101 | this.isStart = isStart;
102 | if (isStart) {
103 | initTime = -1;
104 | invalidate();
105 | }
106 | }
107 |
108 | //重置进度条
109 | public void reset() {
110 | countWidth = 0;
111 | initTime = -1;
112 | isStart = false;
113 | invalidate();
114 | }
115 |
116 | //设置每一个像素的宽度
117 | public void setEachProgressWidth(int width) {
118 | eachProgressWidth = width / (maxProgressSize * 1.0f);
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/camera2library/src/main/res/drawable/ic_capture_btn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CKTim/Camera2Record/3b54c0a48423c0f8e30a04372b25e4edd189af50/camera2library/src/main/res/drawable/ic_capture_btn.png
--------------------------------------------------------------------------------
/camera2library/src/main/res/drawable/ic_capture_delete.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CKTim/Camera2Record/3b54c0a48423c0f8e30a04372b25e4edd189af50/camera2library/src/main/res/drawable/ic_capture_delete.webp
--------------------------------------------------------------------------------
/camera2library/src/main/res/drawable/ic_capture_light_off.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CKTim/Camera2Record/3b54c0a48423c0f8e30a04372b25e4edd189af50/camera2library/src/main/res/drawable/ic_capture_light_off.webp
--------------------------------------------------------------------------------
/camera2library/src/main/res/drawable/ic_capture_light_on.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CKTim/Camera2Record/3b54c0a48423c0f8e30a04372b25e4edd189af50/camera2library/src/main/res/drawable/ic_capture_light_on.webp
--------------------------------------------------------------------------------
/camera2library/src/main/res/drawable/ic_capture_switch.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CKTim/Camera2Record/3b54c0a48423c0f8e30a04372b25e4edd189af50/camera2library/src/main/res/drawable/ic_capture_switch.webp
--------------------------------------------------------------------------------
/camera2library/src/main/res/drawable/selector_capture_light.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/camera2library/src/main/res/layout/activity_camera2_record.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
17 |
18 |
26 |
27 |
33 |
34 |
35 |
44 |
45 |
52 |
53 |
54 |
61 |
62 |
69 |
70 |
78 |
79 |
86 |
87 |
--------------------------------------------------------------------------------
/camera2library/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 | #000000
7 | #FFFFFF
8 | #00000000
9 |
10 |
--------------------------------------------------------------------------------
/camera2library/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 |
4 |
--------------------------------------------------------------------------------
/camera2library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Camera2CaptureLibrary
3 | MainActivity
4 |
5 |
--------------------------------------------------------------------------------
/camera2library/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/camera2library/src/test/java/com/cktim/camera2library/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.cktim.camera2library;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CKTim/Camera2Record/3b54c0a48423c0f8e30a04372b25e4edd189af50/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Dec 09 16:59:37 CST 2017
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-4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
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 Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':Demo', ':camera2library'
2 |
--------------------------------------------------------------------------------