├── .gitignore
├── .idea
├── codeStyles
│ └── Project.xml
├── gradle.xml
├── misc.xml
└── runConfigurations.xml
├── README.md
├── app
├── .gitignore
├── CMakeLists.txt
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── example
│ │ └── administrator
│ │ └── camera
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── cpp
│ │ └── native-lib.cpp
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── administrator
│ │ │ └── camera
│ │ │ ├── AutoFitTextureView.java
│ │ │ ├── AutoLocateHorizontalView.java
│ │ │ ├── CameraActivity.java
│ │ │ ├── CameraHelper.java
│ │ │ ├── CoordinateTransformer.java
│ │ │ ├── ICamera.java
│ │ │ ├── IVideoControl.java
│ │ │ ├── MenuAdapter.java
│ │ │ └── VideoPlayer.java
│ └── res
│ │ ├── drawable-hdpi
│ │ ├── flash_auto.png
│ │ ├── flash_close.png
│ │ ├── flash_open.png
│ │ ├── ic_add.png
│ │ ├── ic_close.png
│ │ ├── ic_delete.png
│ │ ├── ic_fouces.png
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_round.png
│ │ ├── ic_minus.png
│ │ ├── ic_pause.png
│ │ ├── ic_play.png
│ │ ├── ic_record.png
│ │ ├── ic_recording.png
│ │ ├── ic_save.png
│ │ ├── ic_switch_camera.png
│ │ └── ic_video_close.png
│ │ ├── drawable-mdpi
│ │ ├── flash_auto.png
│ │ ├── flash_close.png
│ │ ├── flash_open.png
│ │ ├── ic_add.png
│ │ ├── ic_close.png
│ │ ├── ic_delete.png
│ │ ├── ic_fouces.png
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_round.png
│ │ ├── ic_minus.png
│ │ ├── ic_pause.png
│ │ ├── ic_play.png
│ │ ├── ic_record.png
│ │ ├── ic_recording.png
│ │ ├── ic_save.png
│ │ ├── ic_switch_camera.png
│ │ └── ic_video_close.png
│ │ ├── drawable-xhdpi
│ │ ├── flash_auto.png
│ │ ├── flash_close.png
│ │ ├── flash_open.png
│ │ ├── ic_add.png
│ │ ├── ic_close.png
│ │ ├── ic_delete.png
│ │ ├── ic_fouces.png
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_round.png
│ │ ├── ic_minus.png
│ │ ├── ic_pause.png
│ │ ├── ic_play.png
│ │ ├── ic_record.png
│ │ ├── ic_recording.png
│ │ ├── ic_save.png
│ │ ├── ic_switch_camera.png
│ │ └── ic_video_close.png
│ │ ├── drawable-xxhdpi
│ │ ├── flash_auto.png
│ │ ├── flash_close.png
│ │ ├── flash_open.png
│ │ ├── ic_add.png
│ │ ├── ic_close.png
│ │ ├── ic_delete.png
│ │ ├── ic_fouces.png
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_round.png
│ │ ├── ic_minus.png
│ │ ├── ic_pause.png
│ │ ├── ic_play.png
│ │ ├── ic_record.png
│ │ ├── ic_recording.png
│ │ ├── ic_save.png
│ │ ├── ic_switch_camera.png
│ │ └── ic_video_close.png
│ │ ├── drawable-xxxhdpi
│ │ ├── flash_auto.png
│ │ ├── flash_close.png
│ │ ├── flash_open.png
│ │ ├── ic_add.png
│ │ ├── ic_close.png
│ │ ├── ic_delete.png
│ │ ├── ic_fouces.png
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_round.png
│ │ ├── ic_minus.png
│ │ ├── ic_pause.png
│ │ ├── ic_play.png
│ │ ├── ic_record.png
│ │ ├── ic_recording.png
│ │ ├── ic_save.png
│ │ ├── ic_switch_camera.png
│ │ └── ic_video_close.png
│ │ ├── drawable
│ │ ├── ic_launcher_background.xml
│ │ ├── message_seekbar_blue.xml
│ │ ├── message_seekbar_thumb_blue.xml
│ │ ├── video_record_seekbar_thumb_transparent.xml
│ │ └── video_record_seekbar_transparent.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ └── item_age.xml
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ │ └── xml
│ │ └── file_paths.xml
│ └── test
│ └── java
│ └── com
│ └── example
│ └── administrator
│ └── camera
│ └── ExampleUnitTest.java
├── build.gradle
├── 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
5 | .DS_Store
6 | /build
7 | /captures
8 | /app/.externalNativeBuild
9 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.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 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
--------------------------------------------------------------------------------
/.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 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Camera
2 | 录制小视频,并且实现小视频循环播放,也简单集合视频播放功能,采用CAMERA2 api实现
3 | # 新增功能(2019/02/01)
4 | 1.添加方向传感器纠正拍照成像得角度信息。
5 | 2.拍照成像添加回调监听。
6 | 3.修复拍照成像时退出没有关闭摄像头得错误。
7 | # 新增功能 (2019/03/05)
8 | 1.添加闪光灯支持。
9 | 2.添加手动对焦支持
10 | 3.添加zoom滑动条
11 | 4.优化细节
12 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # For more information about using CMake with Android Studio, read the
2 | # documentation: https://d.android.com/studio/projects/add-native-code.html
3 |
4 | # Sets the minimum version of CMake required to build the native library.
5 |
6 | cmake_minimum_required(VERSION 3.4.1)
7 |
8 | # Creates and names a library, sets it as either STATIC
9 | # or SHARED, and provides the relative paths to its source code.
10 | # You can define multiple libraries, and CMake builds them for you.
11 | # Gradle automatically packages shared libraries with your APK.
12 |
13 | add_library( # Sets the name of the library.
14 | native-lib
15 |
16 | # Sets the library as a shared library.
17 | SHARED
18 |
19 | # Provides a relative path to your source file(s).
20 | src/main/cpp/native-lib.cpp)
21 |
22 | # Searches for a specified prebuilt library and stores the path as a
23 | # variable. Because CMake includes system libraries in the search path by
24 | # default, you only need to specify the name of the public NDK library
25 | # you want to add. CMake verifies that the library exists before
26 | # completing its build.
27 |
28 | find_library( # Sets the name of the path variable.
29 | log-lib
30 |
31 | # Specifies the name of the NDK library that
32 | # you want CMake to locate.
33 | log)
34 |
35 | # Specifies libraries CMake should link to your target library. You
36 | # can link multiple libraries, such as libraries you define in this
37 | # build script, prebuilt third-party libraries, or system libraries.
38 |
39 | target_link_libraries( # Specifies the target library.
40 | native-lib
41 |
42 | # Links the target library to the log library
43 | # included in the NDK.
44 | ${log-lib})
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "com.example.administrator.camera"
7 | minSdkVersion 21
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | /* externalNativeBuild {
13 | cmake {
14 | cppFlags ""
15 | }
16 | }*/
17 | }
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 | /*externalNativeBuild {
25 | cmake {
26 | path "CMakeLists.txt"
27 | }
28 | }*/
29 | }
30 |
31 | dependencies {
32 | implementation fileTree(dir: 'libs', include: ['*.jar'])
33 | implementation 'com.android.support:appcompat-v7:28.0.0'
34 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
35 | implementation 'com.android.support:recyclerview-v7:28.0.0'
36 | testImplementation 'junit:junit:4.12'
37 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
38 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
39 |
40 | api "io.reactivex.rxjava2:rxjava:2.1.0"
41 | api "io.reactivex.rxjava2:rxandroid:2.1.0"
42 |
43 | annotationProcessor "com.jakewharton:butterknife-compiler:8.4.0"
44 | implementation "com.jakewharton:butterknife:8.4.0"
45 | implementation "com.tbruyelle.rxpermissions2:rxpermissions:0.9.4"
46 | }
47 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/example/administrator/camera/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.example.administrator.camera;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.example.administrator.camera", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
32 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/cpp/native-lib.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | extern "C" JNIEXPORT jstring JNICALL
5 | Java_com_example_administrator_camera_MainActivity_stringFromJNI(
6 | JNIEnv *env,
7 | jobject /* this */) {
8 | std::string hello = "Hello from C++";
9 | return env->NewStringUTF(hello.c_str());
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/administrator/camera/AutoFitTextureView.java:
--------------------------------------------------------------------------------
1 |
2 | package com.example.administrator.camera;
3 |
4 | import android.content.Context;
5 | import android.graphics.Matrix;
6 | import android.util.AttributeSet;
7 | import android.util.Log;
8 | import android.view.TextureView;
9 |
10 | /**
11 | * A {@link TextureView} that can be adjusted to a specified aspect ratio.
12 | */
13 | public class AutoFitTextureView extends TextureView{
14 |
15 | private int mRatioWidth = 0;
16 | private int mRatioHeight = 0;
17 |
18 | public AutoFitTextureView(Context context) {
19 | this(context, null);
20 | }
21 |
22 | public AutoFitTextureView(Context context, AttributeSet attrs) {
23 | this(context, attrs, 0);
24 | }
25 |
26 | public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
27 | super(context, attrs, defStyle);
28 | }
29 |
30 | /**
31 | * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
32 | * calculated from the parameters. Note that the actual sizes of parameters don't matter, that
33 | * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
34 | *
35 | * @param width Relative horizontal size
36 | * @param height Relative vertical size
37 | */
38 | public void setAspectRatio(int width, int height) {
39 | if (width < 0 || height < 0) {
40 | throw new IllegalArgumentException("Size cannot be negative.");
41 | }
42 | mRatioWidth = width;
43 | mRatioHeight = height;
44 |
45 | float mRatio = (float) mRatioWidth / (float) mRatioHeight; //算出相机的缩放比例
46 |
47 | float w = mRatio * getHeight();
48 | float scale;
49 | if (w > getWidth())
50 | scale = w / (float) getWidth();
51 | else
52 | scale = (float) getWidth() / w;
53 |
54 | Matrix matrix = new Matrix();
55 | matrix.postScale(scale, 1, getWidth() / 2, getHeight() / 2);
56 | setTransform(matrix);
57 | }
58 |
59 | /**
60 | * 视频宽度适配
61 | * @param width
62 | * @param height
63 | */
64 | public void setVideoAspectRatio(int width, int height)
65 | {
66 | if (width < 0 || height < 0) {
67 | throw new IllegalArgumentException("Size cannot be negative.");
68 | }
69 | mRatioWidth = width;
70 | mRatioHeight = height;
71 |
72 | float mRatio = (float) mRatioWidth / (float) mRatioHeight; //算出相机的缩放比例
73 | if(mRatio < 1.0)
74 | {
75 | setAspectRatio(width, height);
76 | }else {
77 | float h = getWidth() / mRatio;
78 | float scale;
79 | if (h > getHeight())
80 | scale = (float) getHeight() / h;
81 | else
82 | scale = h / (float) getHeight();
83 |
84 | Matrix matrix = new Matrix();
85 | matrix.postScale(1, scale, getWidth() / 2, getHeight() / 2);
86 | setTransform(matrix);
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/administrator/camera/AutoLocateHorizontalView.java:
--------------------------------------------------------------------------------
1 | package com.example.administrator.camera;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.Nullable;
5 | import android.support.v7.widget.LinearLayoutManager;
6 | import android.support.v7.widget.RecyclerView;
7 | import android.util.AttributeSet;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.view.ViewTreeObserver;
11 | import android.widget.Scroller;
12 |
13 | /**
14 | * Created by jianglei on 2/1/17.
15 | */
16 |
17 | public class AutoLocateHorizontalView extends RecyclerView {
18 | /**
19 | * 一个屏幕中显示多少个item,必须为奇数
20 | */
21 | private int itemCount = 7;
22 | /**
23 | * 初始时选中的位置
24 | */
25 | private int initPos = 0;
26 |
27 | private int deltaX;
28 | private WrapperAdapter wrapAdapter;
29 | private Adapter adapter;
30 | private LinearLayoutManager linearLayoutManager;
31 | private boolean isInit;
32 | private OnSelectedPositionChangedListener listener;
33 | private boolean isFirstPosChanged = true; //刚初始化时是否触发位置改变的监听
34 | private int oldSelectedPos = initPos; //记录上次选中的位置
35 | /**
36 | * 当前被选中的位置
37 | */
38 | private int selectPos = initPos;
39 |
40 | private Scroller mScroller;
41 |
42 | /**
43 | * 当要调用moveToPosition()方法时要先记录已经移动了多少位置
44 | */
45 | private int oldMoveX;
46 |
47 | private boolean isMoveFinished = true;
48 |
49 | private float x;
50 | private float y;
51 |
52 | public AutoLocateHorizontalView(Context context) {
53 | super(context);
54 | }
55 |
56 | public AutoLocateHorizontalView(Context context, @Nullable AttributeSet attrs) {
57 | super(context, attrs);
58 | init();
59 | }
60 |
61 | public AutoLocateHorizontalView(Context context, @Nullable AttributeSet attrs, int defStyle) {
62 | super(context, attrs, defStyle);
63 | }
64 |
65 | private void init() {
66 | mScroller = new Scroller(getContext());
67 | getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
68 | @Override
69 | public void onGlobalLayout() {
70 | if (isInit) {
71 | if (initPos >= adapter.getItemCount()) {
72 | initPos = adapter.getItemCount() - 1;
73 | }
74 | if (isFirstPosChanged && listener != null) {
75 | listener.selectedPositionChanged(initPos);
76 | }
77 | linearLayoutManager.scrollToPositionWithOffset(0, -initPos * (wrapAdapter.getItemWidth()));
78 | isInit = false;
79 | }
80 | }
81 | });
82 | }
83 |
84 | /**
85 | * 设置初始化时选中的位置,该方法必须在{@link AutoLocateHorizontalView#setAdapter(android.support.v7.widget.RecyclerView.Adapter) }之前调用
86 | *
87 | * @param initPos 初始位置,如果位置超过了item的数量则默认选中最后一项item
88 | */
89 | public void setInitPos(int initPos) {
90 | if (adapter != null) {
91 | throw new RuntimeException("This method should be called before setAdapter()!");
92 | }
93 | this.initPos = initPos;
94 | selectPos = initPos;
95 | oldSelectedPos = initPos;
96 | }
97 |
98 | /**
99 | * 设置每次显示多少个item,该方法必须在{@link AutoLocateHorizontalView#setAdapter(android.support.v7.widget.RecyclerView.Adapter) }之前调用
100 | *
101 | * @param itemCount 必须为奇数,否则默认会设置成小于它的最大奇数
102 | */
103 | public void setItemCount(int itemCount) {
104 | if (adapter != null) {
105 | throw new RuntimeException("This method should be called before setAdapter()!");
106 | }
107 | if (itemCount % 2 == 0) {
108 | this.itemCount = itemCount - 1;
109 | } else {
110 | this.itemCount = itemCount;
111 | }
112 | }
113 |
114 | /**
115 | * 删除item后偏移距离可能需要重新计算,从而保证selectPos的正确
116 | *
117 | * @param adapter
118 | */
119 | private void correctDeltax(Adapter adapter) {
120 | if (adapter.getItemCount() <= selectPos) {
121 | deltaX -= wrapAdapter.getItemWidth() * (selectPos - adapter.getItemCount() + 1);
122 | }
123 | calculateSelectedPos();
124 | }
125 |
126 | /**
127 | * 删除时选中的数据发生改变,要重新回调方法
128 | *
129 | * @param startPos
130 | */
131 | private void reCallListenerWhenRemove(int startPos) {
132 | if (startPos <= selectPos && listener != null) {
133 | correctDeltax(adapter);
134 | listener.selectedPositionChanged(selectPos);
135 | } else {
136 | correctDeltax(adapter);
137 | }
138 | }
139 |
140 | /**
141 | * 添加数据时选中的数据发生改变,要重新回调方法
142 | *
143 | * @param startPos
144 | */
145 | private void reCallListenerWhenAdd(int startPos) {
146 | if (startPos <= selectPos && listener != null) {
147 | listener.selectedPositionChanged(selectPos);
148 | }
149 | }
150 |
151 | /**
152 | * 当使用整体刷新时要重新回调方法
153 | */
154 | private void reCallListenerWhenChanged() {
155 | if (listener != null) {
156 | listener.selectedPositionChanged(selectPos);
157 | }
158 | }
159 |
160 | @Override
161 | public void setAdapter(final Adapter adapter) {
162 | this.adapter = adapter;
163 | this.wrapAdapter = new WrapperAdapter(adapter, getContext(), itemCount);
164 | adapter.registerAdapterDataObserver(new AdapterDataObserver() {
165 |
166 | @Override
167 | public void onChanged() {
168 | super.onChanged();
169 | wrapAdapter.notifyDataSetChanged();
170 | reCallListenerWhenChanged();
171 | }
172 |
173 | @Override
174 | public void onItemRangeInserted(int positionStart, int itemCount) {
175 | wrapAdapter.notifyDataSetChanged();
176 | reCallListenerWhenAdd(positionStart);
177 | }
178 |
179 | @Override
180 | public void onItemRangeRemoved(int positionStart, int itemCount) {
181 | wrapAdapter.notifyDataSetChanged();
182 | reCallListenerWhenRemove(positionStart);
183 | }
184 | });
185 | deltaX = 0;
186 | if (linearLayoutManager == null) {
187 | linearLayoutManager = new LinearLayoutManager(getContext());
188 | }
189 | linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
190 | super.setLayoutManager(linearLayoutManager);
191 | super.setAdapter(this.wrapAdapter);
192 | isInit = true;
193 | }
194 |
195 | @Override
196 | public void setLayoutManager(LayoutManager layout) {
197 | if (!(layout instanceof LinearLayoutManager)) {
198 | throw new IllegalStateException("The LayoutManager here must be LinearLayoutManager!");
199 | }
200 | this.linearLayoutManager = (LinearLayoutManager) layout;
201 | }
202 |
203 | @Override
204 | public void onScrollStateChanged(int state) {
205 | super.onScrollStateChanged(state);
206 |
207 | if (state == SCROLL_STATE_IDLE) {
208 | if (wrapAdapter == null) {
209 | return;
210 | }
211 | int itemWidth = wrapAdapter.getItemWidth();
212 | int headerFooterWidth = wrapAdapter.getHeaderFooterWidth();
213 | if (itemWidth == 0 || headerFooterWidth == 0) {
214 | //此时adapter还没有准备好,忽略此次调用
215 | return;
216 | }
217 | //超出上个item的位置
218 | int overLastPosOffset = deltaX % itemWidth;
219 | if (overLastPosOffset == 0) {
220 | //刚好处于一个item选中位置,无需滑动偏移纠正
221 | } else if (Math.abs(overLastPosOffset) <= itemWidth / 2) {
222 | scrollBy(-overLastPosOffset, 0);
223 | } else if (overLastPosOffset > 0) {
224 | scrollBy((itemWidth - overLastPosOffset), 0);
225 | } else {
226 | scrollBy(-(itemWidth + overLastPosOffset), 0);
227 | }
228 | calculateSelectedPos();
229 | //此处通知刷新是为了重新绘制之前被选中的位置以及刚刚被选中的位置
230 | wrapAdapter.notifyItemChanged(oldSelectedPos + 1);
231 | wrapAdapter.notifyItemChanged(selectPos + 1);
232 | oldSelectedPos = selectPos;
233 | if (listener != null) {
234 | listener.selectedPositionChanged(selectPos);
235 | }
236 | }
237 |
238 |
239 | }
240 |
241 | public void moveToPosition(int position) {
242 | if(position < 0 || position > adapter.getItemCount() - 1){
243 | throw new IllegalArgumentException("Your position should be from 0 to "+(adapter.getItemCount()-1));
244 | }
245 | oldMoveX = 0;
246 | isMoveFinished = false;
247 | int itemWidth = wrapAdapter.getItemWidth();
248 | if (position != selectPos) {
249 | int deltx = (position - selectPos) * itemWidth;
250 | mScroller.startScroll(getScrollX(), getScrollY(), deltx, 0);
251 | postInvalidate();
252 | }
253 | }
254 |
255 | @Override
256 | public void computeScroll() {
257 | super.computeScroll();
258 | if (mScroller.computeScrollOffset()) {
259 | int x = mScroller.getCurrX() - oldMoveX;
260 | oldMoveX += x;
261 | scrollBy(x, 0);
262 | } else if (mScroller.isFinished()) {
263 | //此处通知刷新是为了重新绘制之前被选中的位置以及刚刚被选中的位置
264 | if (isMoveFinished) {
265 | return;
266 | }
267 | wrapAdapter.notifyItemChanged(oldSelectedPos + 1);
268 | wrapAdapter.notifyItemChanged(selectPos + 1);
269 | oldSelectedPos = selectPos;
270 | if (listener != null) {
271 | listener.selectedPositionChanged(selectPos);
272 | }
273 | isMoveFinished = true;
274 | }
275 | }
276 |
277 | @Override
278 | public void onScrolled(int dx, int dy) {
279 | super.onScrolled(dx, dy);
280 | deltaX += dx;
281 | calculateSelectedPos();
282 | }
283 |
284 | private void calculateSelectedPos() {
285 | int itemWidth = wrapAdapter.getItemWidth();
286 | if (deltaX > 0) {
287 | selectPos = (deltaX) / itemWidth + initPos;
288 | } else {
289 | selectPos = initPos + (deltaX) / itemWidth;
290 | }
291 | }
292 |
293 | class WrapperAdapter extends RecyclerView.Adapter {
294 | private Context context;
295 | private RecyclerView.Adapter adapter;
296 | private int itemCount;
297 | private static final int HEADER_FOOTER_TYPE = -1;
298 | private View itemView;
299 | /**
300 | * 头部或尾部的宽度
301 | */
302 | private int headerFooterWidth;
303 |
304 | /**
305 | * 每个item的宽度
306 | */
307 | private int itemWidth;
308 |
309 | public WrapperAdapter(Adapter adapter, Context context, int itemCount) {
310 | this.adapter = adapter;
311 | this.context = context;
312 | this.itemCount = itemCount;
313 | if (adapter instanceof IAutoLocateHorizontalView) {
314 | itemView = ((IAutoLocateHorizontalView) adapter).getItemView();
315 | } else {
316 | throw new RuntimeException(adapter.getClass().getSimpleName() + " should implements com.jianglei.view.AutoLocateHorizontalView.IAutoLocateHorizontalView !");
317 | }
318 | }
319 |
320 | @Override
321 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
322 | if (viewType == HEADER_FOOTER_TYPE) {
323 | View view = new View(context);
324 | headerFooterWidth = parent.getMeasuredWidth() / 2 - (parent.getMeasuredWidth() / itemCount) / 2;
325 | RecyclerView.LayoutParams params = new LayoutParams(headerFooterWidth, ViewGroup.LayoutParams.MATCH_PARENT);
326 | view.setLayoutParams(params);
327 | return new HeaderFooterViewHolder(view);
328 | }
329 | ViewHolder holder = adapter.onCreateViewHolder(parent, viewType);
330 | itemView = ((IAutoLocateHorizontalView) adapter).getItemView();
331 | int width = parent.getMeasuredWidth() / itemCount;
332 | ViewGroup.LayoutParams params = itemView.getLayoutParams();
333 | if (params != null) {
334 | params.width = width;
335 | itemWidth = width;
336 | itemView.setLayoutParams(params);
337 | }
338 | return holder;
339 | }
340 |
341 | @SuppressWarnings("unchecked")
342 | @Override
343 | public void onBindViewHolder(ViewHolder holder, int position) {
344 | if (!isHeaderOrFooter(position)) {
345 | adapter.onBindViewHolder(holder, position - 1);
346 | if (selectPos == position - 1) {
347 | ((IAutoLocateHorizontalView) adapter).onViewSelected(true, position - 1, holder, itemWidth);
348 | } else {
349 | ((IAutoLocateHorizontalView) adapter).onViewSelected(false, position - 1, holder, itemWidth);
350 | }
351 | }
352 | }
353 |
354 |
355 | @Override
356 | public int getItemCount() {
357 | return adapter.getItemCount() + 2;
358 | }
359 |
360 | @Override
361 | public int getItemViewType(int position) {
362 | if (position == 0 || position == getItemCount() - 1) {
363 | return HEADER_FOOTER_TYPE;
364 | }
365 | return adapter.getItemViewType(position - 1);
366 | }
367 |
368 |
369 | private boolean isHeaderOrFooter(int pos) {
370 | if (pos == 0 || pos == getItemCount() - 1) {
371 | return true;
372 | }
373 | return false;
374 | }
375 |
376 | public int getHeaderFooterWidth() {
377 | return headerFooterWidth;
378 | }
379 |
380 | public int getItemWidth() {
381 | return itemWidth;
382 | }
383 |
384 | class HeaderFooterViewHolder extends RecyclerView.ViewHolder {
385 |
386 | HeaderFooterViewHolder(View itemView) {
387 | super(itemView);
388 | }
389 | }
390 |
391 |
392 | }
393 |
394 |
395 | public interface IAutoLocateHorizontalView {
396 | /**
397 | * 获取item的根布局
398 | */
399 | View getItemView();
400 |
401 | /**
402 | * 当item被选中时会触发这个回调,可以修改被选中时的样式
403 | *
404 | * @param isSelected 是否被选中
405 | * @param pos 当前view的位置
406 | * @param holder
407 | * @param itemWidth 当前整个item的宽度
408 | */
409 | void onViewSelected(boolean isSelected, int pos, ViewHolder holder, int itemWidth);
410 | }
411 |
412 | /***
413 | * 选中位置改变时的监听
414 | */
415 | public interface OnSelectedPositionChangedListener {
416 | void selectedPositionChanged(int pos);
417 | }
418 |
419 | public void setOnSelectedPositionChangedListener(OnSelectedPositionChangedListener listener) {
420 | this.listener = listener;
421 | }
422 | }
423 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/administrator/camera/CameraActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.administrator.camera;
2 |
3 | import android.Manifest;
4 | import android.app.Activity;
5 | import android.content.BroadcastReceiver;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.content.IntentFilter;
9 | import android.content.pm.PackageManager;
10 | import android.graphics.Point;
11 | import android.graphics.SurfaceTexture;
12 | import android.hardware.Sensor;
13 | import android.hardware.SensorEvent;
14 | import android.hardware.SensorEventListener;
15 | import android.hardware.SensorManager;
16 | import android.media.MediaRecorder;
17 | import android.net.Uri;
18 | import android.os.Build;
19 | import android.os.Bundle;
20 | import android.os.Environment;
21 | import android.support.v4.content.ContextCompat;
22 | import android.support.v4.content.FileProvider;
23 | import android.support.v4.content.LocalBroadcastManager;
24 | import android.support.v7.app.AppCompatActivity;
25 | import android.support.v7.widget.LinearLayoutManager;
26 | import android.util.Log;
27 | import android.view.MotionEvent;
28 | import android.view.Surface;
29 | import android.view.TextureView;
30 | import android.view.View;
31 | import android.view.animation.Animation;
32 | import android.view.animation.Transformation;
33 | import android.widget.FrameLayout;
34 | import android.widget.ImageButton;
35 | import android.widget.ImageView;
36 | import android.widget.RelativeLayout;
37 | import android.widget.SeekBar;
38 | import android.widget.TextView;
39 |
40 | import com.tbruyelle.rxpermissions2.Permission;
41 | import com.tbruyelle.rxpermissions2.RxPermissions;
42 |
43 | import java.io.File;
44 | import java.util.ArrayList;
45 | import java.util.List;
46 | import java.util.concurrent.TimeUnit;
47 |
48 | import butterknife.BindView;
49 | import butterknife.ButterKnife;
50 | import butterknife.OnClick;
51 |
52 | import io.reactivex.Observable;
53 | import io.reactivex.android.schedulers.AndroidSchedulers;
54 | import io.reactivex.disposables.Disposable;
55 | import io.reactivex.functions.Consumer;
56 | import io.reactivex.functions.Function;
57 |
58 | public class CameraActivity extends AppCompatActivity implements IVideoControl.PlaySeekTimeListener,
59 | IVideoControl.PlayStateListener, ICamera.TakePhotoListener, SensorEventListener, ICamera.CameraReady {
60 |
61 | public static final String ACTION_EXIT = "action_exit";
62 |
63 | /**
64 | * 摄像头模式
65 | */
66 | public final static int CAMERA_MODE = 0;
67 | /**
68 | * 视频播放器模式
69 | */
70 | public final static int VIDEO_MODE = 1;
71 |
72 | /**
73 | * 视频最长的时长是15s
74 | */
75 | private final static int VIDEO_MAX_TIME = 15;
76 |
77 | /**
78 | * 视频播放模式
79 | */
80 | public final static int VIDEO_PLAY_MODE = 0;
81 |
82 | /**
83 | * 视频录像模式
84 | */
85 | public final static int VIDEO_RECORD_MODE = 1;
86 |
87 | /**
88 | * 拍照模式
89 | */
90 | public final static int VIDEO_TAKE_PHOTO = 2;
91 |
92 | /**
93 | * 当前面板是预览状态
94 | */
95 | public final static int TEXTURE_PREVIEW_STATE = 0;
96 |
97 | /**
98 | * 当前面板是录像状态
99 | */
100 | public final static int TEXTURE_RECORD_STATE = 1;
101 |
102 | /**
103 | * 当前面板是图片状态
104 | */
105 | public final static int TEXTURE_PHOTO_STATE = 2;
106 |
107 | /**
108 | * 当前面板是视频播放状态
109 | */
110 |
111 | public final static int TEXTURE_PLAY_STATE = 3;
112 |
113 | /**
114 | * 当前是摄像头模式还是视频播放模式
115 | */
116 | private int MODE;
117 |
118 | /**
119 | * 当前的模式,默认为拍照模式
120 | */
121 | private int NOW_MODE = VIDEO_TAKE_PHOTO;
122 |
123 | /**
124 | * 当前的显示面板状态
125 | */
126 | private int TEXTURE_STATE = TEXTURE_PREVIEW_STATE;
127 |
128 | @BindView(R.id.video_menu)
129 | AutoLocateHorizontalView mAutoLocateHorizontalView;
130 |
131 | @BindView(R.id.video_texture)
132 | AutoFitTextureView textureView;
133 |
134 | @BindView(R.id.video_close)
135 | ImageButton mCloseImageButton;
136 |
137 | @BindView(R.id.video_time)
138 | TextView mTimeTextView;
139 |
140 | @BindView(R.id.video_switch_camera)
141 | ImageButton mSwitchCameraButton;
142 |
143 | @BindView(R.id.video_play)
144 | ImageButton mPlayImageButton;
145 |
146 | @BindView(R.id.video_delete)
147 | ImageButton mDeleteImageButton;
148 |
149 | @BindView(R.id.video_record)
150 | ImageButton mRecordImageButton;
151 |
152 | @BindView(R.id.video_save)
153 | ImageButton mSaveImageButton;
154 |
155 | @BindView(R.id.video_mine_play)
156 | ImageButton mMiniPlayImageButton;
157 |
158 | @BindView(R.id.video_seek_bar)
159 | SeekBar mVideoSeekBar;
160 |
161 | @BindView(R.id.video_seek_time)
162 | TextView mVideoSeekTimeTextView;
163 |
164 | @BindView(R.id.video_record_seek_bar)
165 | SeekBar mVideoRecordSeekBar;
166 |
167 | @BindView(R.id.video_hint_text)
168 | TextView mVideoHintText;
169 |
170 | @BindView(R.id.video_switch_flash)
171 | ImageButton mFlashSwitch;
172 |
173 | @BindView(R.id.video_photo)
174 | ImageView mPhotoImageView;
175 | @BindView(R.id.video_scale_bar_layout)
176 | RelativeLayout mSeekBarLayout;
177 | @BindView(R.id.video_scale)
178 | SeekBar mScaleSeekBar;
179 | @BindView(R.id.video_fouces)
180 | ImageView mFoucesImage;
181 |
182 | private CameraHelper cameraHelper;
183 |
184 | private ICamera.CameraType mNowCameraType = ICamera.CameraType.BACK;
185 |
186 | private VideoPlayer mVideoPlayer;
187 |
188 | private MenuAdapter mMenuAdapter;
189 |
190 | /**
191 | * 视频播放时模式下的视频路径
192 | */
193 | private String mVideoPath;
194 |
195 | /**
196 | * 录像保存或者图片保存的路径
197 | */
198 | private String mMediaPath;
199 |
200 |
201 | private LocalBroadcastManager mLocalBroadcastManager;
202 | private ExitBroadcastReceiver mExitBroadcastReceiver;
203 |
204 | private RxPermissions mRxPermissions;
205 |
206 | private CameraTouch mCameraTouch;
207 |
208 | /**
209 | * 视频播放模式控件隐藏
210 | */
211 | private Runnable mHindViewRunnable = new Runnable() {
212 | @Override
213 | public void run() {
214 | hindPlayView();
215 | }
216 | };
217 |
218 | /**
219 | * 3s后隐藏的runnable
220 | */
221 | private Runnable SeekBarLayoutRunnalbe = new Runnable() {
222 | @Override
223 | public void run() {
224 | mSeekBarLayout.setVisibility(View.GONE);
225 | }
226 | };
227 |
228 | private Runnable mImageFoucesRunnable = new Runnable() {
229 | @Override
230 | public void run() {
231 | mFoucesImage.setVisibility(View.GONE);
232 | }
233 | };
234 |
235 | private boolean isNoPremissionPause = false;
236 |
237 | private FoucesAnimation mFoucesAnimation;
238 |
239 | private class FoucesAnimation extends Animation {
240 |
241 | private int width = dip2px(CameraActivity.this, 150);
242 | private int W = dip2px(CameraActivity.this, 65);
243 |
244 | private int oldMarginLeft;
245 | private int oldMarginTop;
246 |
247 | @Override
248 | protected void applyTransformation(float interpolatedTime, Transformation t) {
249 |
250 | FrameLayout.LayoutParams layoutParams =
251 | (FrameLayout.LayoutParams) mFoucesImage.getLayoutParams();
252 | int w = (int) (width * (1 - interpolatedTime));
253 | if (w < W) {
254 | w = W;
255 | }
256 | layoutParams.width = w;
257 | layoutParams.height = w;
258 | if(w == W) {
259 | mFoucesImage.setLayoutParams(layoutParams);
260 | return;
261 | }
262 | layoutParams.leftMargin = oldMarginLeft - (w/2);
263 | layoutParams.topMargin = oldMarginTop + (w/8);
264 | mFoucesImage.setLayoutParams(layoutParams);
265 | }
266 |
267 | public void setOldMargin(int oldMarginLeft, int oldMarginTop)
268 | {
269 | this.oldMarginLeft = oldMarginLeft;
270 | this.oldMarginTop = oldMarginTop;
271 | removeImageFoucesRunnable();
272 | imageFoucesDelayedHind();
273 | }
274 | }
275 |
276 | /**
277 | * 播放视频启动的模式
278 | *
279 | * @param activity
280 | * @param path
281 | */
282 | public static void startCameraActivityForPlayVideo(Activity activity, String path) {
283 | Intent intent = new Intent(activity, CameraActivity.class);
284 | intent.putExtra("mode", VIDEO_MODE);
285 | intent.putExtra("videoPath", path);
286 | activity.startActivity(intent);
287 | }
288 |
289 | /**
290 | * 摄像头录像和拍照启动的模式
291 | *
292 | * @param activity
293 | * @param requestCode
294 | */
295 | public static void startCamearActivityForCamear(Activity activity, int requestCode) {
296 | Intent intent = new Intent(activity, CameraActivity.class);
297 | intent.putExtra("mode", CAMERA_MODE);
298 | activity.startActivityForResult(intent, requestCode);
299 | }
300 |
301 |
302 | @Override
303 | protected void onCreate(Bundle savedInstanceState) {
304 | super.onCreate(savedInstanceState);
305 | setContentView(R.layout.activity_main);
306 | mRxPermissions = new RxPermissions(this);
307 | ButterKnife.bind(this);
308 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
309 | getWindow().setStatusBarColor(getResources().getColor(android.R.color.transparent));
310 | }
311 |
312 | mVideoPlayer = new VideoPlayer();
313 | //设置时间戳回调
314 | mVideoPlayer.setPlaySeekTimeListener(this);
315 |
316 | MODE = getIntent().getIntExtra("mode", CAMERA_MODE);
317 | if (MODE == CAMERA_MODE) //摄像头模式
318 | {
319 | initCameraMode();
320 | } else if (MODE == VIDEO_MODE) //视频播放模式
321 | {
322 | mVideoPath = getIntent().getStringExtra("videoPath");
323 | initVideoMode();
324 | }
325 |
326 | /**
327 | * 退出app的监听
328 | */
329 | mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
330 | IntentFilter intentFilter = new IntentFilter(ACTION_EXIT);
331 | mExitBroadcastReceiver = new ExitBroadcastReceiver();
332 | mLocalBroadcastManager.registerReceiver(mExitBroadcastReceiver, intentFilter);
333 |
334 | mFoucesAnimation = new FoucesAnimation();
335 | }
336 |
337 | /**
338 | * 显示播放界面的控件出来
339 | */
340 | private void showPlayView() {
341 | showVideoPlaySeekBar();
342 | mMiniPlayImageButton.setVisibility(View.VISIBLE);
343 | mPlayImageButton.setVisibility(View.VISIBLE);
344 | mCloseImageButton.setVisibility(View.VISIBLE);
345 | mVideoSeekTimeTextView.setVisibility(View.VISIBLE);
346 | }
347 |
348 | /**
349 | * 隐藏播放界面的控件出来
350 | */
351 | private void hindPlayView() {
352 | hindVideoPlaySeekBar();
353 | mMiniPlayImageButton.setVisibility(View.GONE);
354 | mPlayImageButton.setVisibility(View.GONE);
355 | mCloseImageButton.setVisibility(View.GONE);
356 | mVideoSeekTimeTextView.setVisibility(View.GONE);
357 | }
358 |
359 | /**
360 | * 初始化摄像头模式
361 | */
362 | private void initCameraMode() {
363 |
364 | if(ContextCompat.checkSelfPermission(this,
365 | Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED
366 | ||
367 | ContextCompat.checkSelfPermission(this,Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED
368 | ||
369 | ContextCompat.checkSelfPermission(this,Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
370 | ||
371 | ContextCompat.checkSelfPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
372 | )
373 | {
374 | isNoPremissionPause = true;
375 | }
376 | mRxPermissions.requestEach(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO,
377 | Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE)
378 | .subscribe(new Consumer() {
379 | @Override
380 | public void accept(Permission permission) {
381 | if (permission.granted && permission.name.equals(Manifest.permission.CAMERA)) {
382 | initCamera(mNowCameraType);
383 | }
384 | }
385 | });
386 |
387 | cameraHelper = new CameraHelper(this);
388 | cameraHelper.setTakePhotoListener(this);
389 | cameraHelper.setCameraReady(this);
390 | mVideoPlayer.setLoopPlay(true);
391 |
392 | List menus = new ArrayList<>();
393 | menus.add("拍照");
394 | menus.add("录像");
395 | mMenuAdapter = new MenuAdapter(this, menus, mAutoLocateHorizontalView);
396 | LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
397 | linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
398 | mAutoLocateHorizontalView.setLayoutManager(linearLayoutManager);
399 | mAutoLocateHorizontalView.setAdapter(mMenuAdapter);
400 | mAutoLocateHorizontalView.setOnSelectedPositionChangedListener(new AutoLocateHorizontalView.OnSelectedPositionChangedListener() {
401 | @Override
402 | public void selectedPositionChanged(int pos) {
403 | if (pos == 0) {
404 | NOW_MODE = VIDEO_TAKE_PHOTO; //拍照模式
405 | cameraHelper.setCameraState(ICamera.CameraMode.TAKE_PHOTO);
406 | mVideoHintText.setText("点击拍照");
407 | }
408 | if (pos == 1) {
409 | NOW_MODE = VIDEO_RECORD_MODE; //录像模式
410 | cameraHelper.setCameraState(ICamera.CameraMode.RECORD_VIDEO);
411 | mVideoHintText.setText("点击录像");
412 | }
413 | }
414 | });
415 | mCameraTouch = new CameraTouch();
416 | mAutoLocateHorizontalView.setOnTouchListener(new View.OnTouchListener() {
417 |
418 | private long mClickOn;
419 | private float mLastX;
420 | private float mLastY;
421 |
422 | @Override
423 | public boolean onTouch(View v, MotionEvent event) {
424 | switch (event.getActionMasked()) {
425 | case MotionEvent.ACTION_DOWN:
426 | if (event.getPointerCount() == 1) {
427 | mClickOn = System.currentTimeMillis();
428 | mLastX = event.getX();
429 | mLastY = event.getY();
430 | }
431 | break;
432 | case MotionEvent.ACTION_UP:
433 | if (event.getPointerCount() == 1) {
434 | if((System.currentTimeMillis() - mClickOn) < 500)
435 | {
436 | moveFouces((int) event.getX(), (int) event.getY());
437 | }
438 | }
439 | break;
440 | case MotionEvent.ACTION_POINTER_DOWN:
441 | mCameraTouch.onScaleStart(event);
442 | return true;
443 | case MotionEvent.ACTION_MOVE:
444 | if (event.getPointerCount() == 2) {
445 | mCameraTouch.onScale(event);
446 | return true;
447 | }
448 | else
449 | {
450 | float x = event.getX()-mLastX;
451 | float y = event.getY()-mLastY;
452 | if(Math.abs(x) >= 10 || Math.abs(y) >= 10) {
453 | mClickOn = 0;
454 | }
455 | }
456 | break;
457 | case MotionEvent.ACTION_POINTER_UP:
458 | mCameraTouch.onScaleEnd(event);
459 | return true;
460 | }
461 | return false;
462 | }
463 | });
464 |
465 | textureView.setOnTouchListener(new View.OnTouchListener() {
466 |
467 | private long mClickOn;
468 | private float mLastX;
469 | private float mLastY;
470 |
471 | @Override
472 | public boolean onTouch(View v, MotionEvent event) {
473 | if (TEXTURE_STATE == TEXTURE_PLAY_STATE)
474 | return true;
475 | switch (event.getActionMasked()) {
476 | case MotionEvent.ACTION_DOWN:
477 | if (event.getPointerCount() == 1) {
478 | mClickOn = System.currentTimeMillis();
479 | mLastX = event.getX();
480 | mLastY = event.getY();
481 | }
482 | break;
483 | case MotionEvent.ACTION_UP:
484 | if (event.getPointerCount() == 1) {
485 | if((System.currentTimeMillis() - mClickOn) < 500)
486 | {
487 | moveFouces((int) event.getX(), (int) event.getY());
488 | }
489 | }
490 | break;
491 | case MotionEvent.ACTION_POINTER_DOWN:
492 | mCameraTouch.onScaleStart(event);
493 | break;
494 | case MotionEvent.ACTION_MOVE:
495 | if (event.getPointerCount() == 2){
496 | mCameraTouch.onScale(event);
497 | }
498 | else
499 | {
500 | float x = event.getX()-mLastX;
501 | float y = event.getY()-mLastY;
502 | if(Math.abs(x) >= 10 || Math.abs(y) >= 10) {
503 | mClickOn = 0;
504 | }
505 | }
506 | break;
507 | case MotionEvent.ACTION_POINTER_UP:
508 | mCameraTouch.onScaleEnd(event);
509 | break;
510 | }
511 | return true;
512 | }
513 | });
514 |
515 | cutPadding();
516 | registerSensor();
517 | initScaleSeekbar();
518 | }
519 |
520 | public int dip2px(Context context,float dipValue) {
521 |
522 | return (int) (dipValue * context.getResources().getDisplayMetrics().density + 0.5f);
523 | }
524 |
525 | /**
526 | * 移动焦点图标
527 | * @param x
528 | * @param y
529 | */
530 | private void moveFouces(int x, int y) {
531 | mFoucesImage.setVisibility(View.VISIBLE);
532 | FrameLayout.LayoutParams layoutParams
533 | = (FrameLayout.LayoutParams) mFoucesImage.getLayoutParams();
534 | mFoucesImage.setLayoutParams(layoutParams);
535 | mFoucesAnimation.setDuration(500);
536 | mFoucesAnimation.setRepeatCount(0);
537 | mFoucesAnimation.setOldMargin(x, y);
538 | mFoucesImage.startAnimation(mFoucesAnimation);
539 | cameraHelper.requestFocus(x,y);
540 | }
541 |
542 | /**
543 | * 初始化视频播放模式
544 | */
545 | private void initVideoMode() {
546 | hindMenu();
547 | hindSwitchCamera();
548 | hindVideoRecordSeekBar();
549 | mCloseImageButton.setVisibility(View.GONE);
550 | mCloseImageButton.setImageResource(R.drawable.ic_video_close);
551 | mVideoPlayer.setPlayStateListener(this);
552 | mRecordImageButton.setVisibility(View.GONE);
553 | mVideoHintText.setVisibility(View.GONE);
554 | textureView.setOnClickListener(new View.OnClickListener() {
555 | @Override
556 | public void onClick(View v) { //单机屏幕显示出控件
557 | if (mMiniPlayImageButton.getVisibility() == View.VISIBLE) {
558 | hindPlayView();
559 | } else {
560 | showPlayView();
561 | textureView.postDelayed(mHindViewRunnable, 3000);
562 | }
563 | }
564 | });
565 |
566 | mVideoSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
567 |
568 | private int progress;
569 |
570 | @Override
571 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
572 | if (fromUser) {
573 | this.progress = progress;
574 | }
575 | }
576 |
577 | @Override
578 | public void onStartTrackingTouch(SeekBar seekBar) {
579 | //触摸进度条取消几秒后隐藏的事件
580 | textureView.removeCallbacks(mHindViewRunnable);
581 | }
582 |
583 | @Override
584 | public void onStopTrackingTouch(SeekBar seekBar) {
585 | mVideoPlayer.seekTo(progress);
586 | textureView.postDelayed(mHindViewRunnable, 3000);
587 | }
588 | });
589 | }
590 |
591 | /**
592 | * 重新设置录像的进度条样式
593 | */
594 | private void cutPadding() {
595 | Point point = new Point();
596 | getWindowManager().getDefaultDisplay().getSize(point);
597 | int width = point.x;
598 | int padding = mVideoRecordSeekBar.getPaddingLeft();
599 | FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) mVideoRecordSeekBar.getLayoutParams();
600 | layoutParams.width = width + padding;
601 | mVideoRecordSeekBar.setLayoutParams(layoutParams);
602 | mVideoRecordSeekBar.setPadding(0, 0, 0, 0);
603 | }
604 |
605 | /**
606 | * 初始化摄像头
607 | *
608 | * @param cameraType
609 | */
610 | private void initCamera(ICamera.CameraType cameraType) {
611 | if (cameraHelper == null)
612 | return;
613 | if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
614 | != PackageManager.PERMISSION_GRANTED) {
615 | return;
616 | }
617 | cameraHelper.setTextureView(textureView);
618 | cameraHelper.openCamera(cameraType);
619 | }
620 |
621 | @Override
622 | protected void onResume() {
623 | super.onResume();
624 | if (cameraHelper != null)
625 | cameraHelper.startBackgroundThread();
626 |
627 | if (textureView.isAvailable()) {
628 | if (MODE == CAMERA_MODE) {
629 | if (TEXTURE_STATE == TEXTURE_PREVIEW_STATE) //预览状态
630 | initCamera(mNowCameraType);
631 | else if (TEXTURE_STATE == TEXTURE_PLAY_STATE) //视频播放状态
632 | mVideoPlayer.play();
633 | mVideoPlayer.setVideoPlayWindow(new Surface(textureView.getSurfaceTexture()));
634 | }
635 | } else {
636 | textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
637 | @Override
638 | public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
639 | if (MODE == CAMERA_MODE) {
640 | if (TEXTURE_STATE == TEXTURE_PREVIEW_STATE) //预览状态
641 | initCamera(mNowCameraType);
642 | else if (TEXTURE_STATE == TEXTURE_PLAY_STATE) //视频播放状态
643 | mVideoPlayer.play();
644 | mVideoPlayer.setVideoPlayWindow(new Surface(textureView.getSurfaceTexture()));
645 | } else if (MODE == VIDEO_MODE) {
646 | mVideoPlayer.setVideoPlayWindow(new Surface(textureView.getSurfaceTexture()));
647 | Log.e("videoPath", "path:" + mVideoPath);
648 | mVideoPlayer.setDataSourceAndPlay(mVideoPath);
649 | isPlaying = true;
650 | TEXTURE_STATE = TEXTURE_PLAY_STATE; //视频播放状态
651 | }
652 | }
653 |
654 | @Override
655 | public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
656 | }
657 |
658 | @Override
659 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
660 | return true;
661 | }
662 |
663 | @Override
664 | public void onSurfaceTextureUpdated(SurfaceTexture surface) {
665 |
666 | }
667 | });
668 | }
669 | }
670 |
671 | @Override
672 | protected void onPause() {
673 | super.onPause();
674 | if(isNoPremissionPause) {
675 | isNoPremissionPause = false;
676 | return;
677 | }
678 | Log.e("camera", "mode:" + MODE);
679 | if (MODE == CAMERA_MODE) {
680 | if (TEXTURE_STATE == TEXTURE_PREVIEW_STATE) {
681 | cameraHelper.closeCamera();
682 | cameraHelper.stopBackgroundThread();
683 | } else if (TEXTURE_STATE == TEXTURE_PLAY_STATE) {
684 | mVideoPlayer.pause();
685 | }
686 | }
687 | }
688 |
689 | /**
690 | * 切换摄像头
691 | */
692 | @OnClick(R.id.video_switch_camera)
693 | public void switchCamera() {
694 | if (mNowCameraType == ICamera.CameraType.FRONT) {
695 | cameraHelper.switchCamera(ICamera.CameraType.BACK);
696 | mNowCameraType = ICamera.CameraType.BACK;
697 | } else {
698 | cameraHelper.switchCamera(ICamera.CameraType.FRONT);
699 | mNowCameraType = ICamera.CameraType.FRONT;
700 | }
701 | mCameraTouch.resetScale();
702 | }
703 |
704 | private boolean isRecording = false;
705 |
706 | /**
707 | * 视频录制
708 | */
709 | private boolean isRecordClick = false;
710 |
711 | @OnClick(R.id.video_record)
712 | public void recordVideoOrTakePhoto() {
713 | if (isRecordClick)
714 | return;
715 | isRecordClick = true;
716 | //录像模式
717 | if (NOW_MODE == VIDEO_RECORD_MODE) {
718 | if (!isRecording) {
719 |
720 | if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
721 | return;
722 | }
723 |
724 | if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
725 | return;
726 | }
727 |
728 | mMediaPath = getVideoFilePath();
729 | isRecording = cameraHelper.startVideoRecord(mMediaPath, MediaRecorder.OutputFormat.MPEG_4);
730 | if (isRecording) {
731 | mRecordImageButton.setImageResource(R.drawable.ic_recording);
732 | hindSwitchCamera();
733 | recordCountDown();
734 | hindMenu();
735 | // mVideoHintText.setVisibility(View.GONE);
736 | mVideoHintText.setText("点击停止");
737 | mCloseImageButton.setVisibility(View.GONE);
738 | mFlashSwitch.setVisibility(View.GONE);
739 | TEXTURE_STATE = TEXTURE_RECORD_STATE;
740 | }
741 | } else {
742 | stopRecordCountTime();
743 | isRecording = false;
744 | cameraHelper.stopVideoRecord();
745 |
746 | mRecordImageButton.setImageResource(R.drawable.ic_record);
747 | mRecordImageButton.setVisibility(View.GONE);
748 | mCloseImageButton.setVisibility(View.VISIBLE);
749 | mVideoHintText.setVisibility(View.GONE);
750 | showRecordEndView();
751 | hindVideoRecordSeekBar();
752 | playVideo();
753 | }
754 | }
755 |
756 | //拍照模式
757 | if (NOW_MODE == VIDEO_TAKE_PHOTO) {
758 | if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
759 | return;
760 | }
761 |
762 | mMediaPath = getPhotoFilePath();
763 | if (cameraHelper.takePhone(mMediaPath, ICamera.MediaType.JPEG)) {
764 |
765 | }
766 | }
767 |
768 | isRecordClick = false;
769 | }
770 |
771 | /**
772 | * 隐藏切换菜单
773 | */
774 | private void hindMenu() {
775 | mAutoLocateHorizontalView.setVisibility(View.GONE);
776 | }
777 |
778 | /**
779 | * 显示切换菜单
780 | */
781 | private void showMeun() {
782 | mAutoLocateHorizontalView.setVisibility(View.VISIBLE);
783 | }
784 |
785 | /**
786 | * 隐藏切换摄像头按钮
787 | */
788 | private void hindSwitchCamera() {
789 | mSwitchCameraButton.setVisibility(View.GONE);
790 | }
791 |
792 | /**
793 | * 显示切换摄像头按钮
794 | */
795 | private void showSwitchCamera() {
796 | mSwitchCameraButton.setVisibility(View.VISIBLE);
797 | }
798 |
799 | /**
800 | * 显示视频录像的进度条
801 | */
802 | private void showVideoRecordSeekBar() {
803 | mVideoRecordSeekBar.setVisibility(View.VISIBLE);
804 | }
805 |
806 | /**
807 | * 隐藏视频录像的进度条
808 | */
809 | private void hindVideoRecordSeekBar() {
810 | mVideoRecordSeekBar.setVisibility(View.GONE);
811 | mVideoRecordSeekBar.setProgress(0);
812 | }
813 |
814 | /**
815 | * 中止计时
816 | */
817 | private void stopRecordCountTime() {
818 | if (mDisposable != null && !mDisposable.isDisposed())
819 | mDisposable.dispose();
820 | mDisposable = null;
821 | mVideoSeekTimeTextView.setVisibility(View.GONE);
822 | }
823 |
824 | /**
825 | * 录像倒计时终止器
826 | */
827 | private Disposable mDisposable;
828 |
829 | /**
830 | * 录像时长倒计时
831 | */
832 | private void recordCountDown() {
833 | mTimeTextView.setVisibility(View.VISIBLE);
834 | showVideoRecordSeekBar();
835 | final int count = 15;
836 | mDisposable = Observable.interval(1, 1, TimeUnit.SECONDS)
837 | .take(count + 1)
838 | .map(new Function() {
839 | @Override
840 | public Long apply(Long aLong) {
841 | return count - aLong;
842 | }
843 | }).observeOn(AndroidSchedulers.mainThread())
844 | .subscribe(new Consumer() {
845 | @Override
846 | public void accept(Long aLong) {
847 | long time = 16 - aLong;
848 | if (time < 10)
849 | mTimeTextView.setText("0:0" + String.valueOf(time));
850 | else
851 | mTimeTextView.setText("0:" + String.valueOf(time));
852 | mVideoRecordSeekBar.setProgress((int) time);
853 | if (time == VIDEO_MAX_TIME) {
854 | mTimeTextView.postDelayed(new Runnable() {
855 | @Override
856 | public void run() {
857 | recordVideoOrTakePhoto();
858 | hindVideoRecordSeekBar();
859 | }
860 | }, 300);
861 |
862 | }
863 | }
864 | });
865 | }
866 |
867 | /**
868 | * 显示录像完成后底部两个按钮
869 | */
870 | private void showRecordEndView() {
871 | mSaveImageButton.setVisibility(View.VISIBLE);
872 | mDeleteImageButton.setVisibility(View.VISIBLE);
873 | }
874 |
875 | /**
876 | * 隐藏录像完成后底部两个按钮
877 | */
878 | private void hindRecordEndView() {
879 | mSaveImageButton.setVisibility(View.GONE);
880 | mDeleteImageButton.setVisibility(View.GONE);
881 | }
882 |
883 | /**
884 | * 关闭摄像头
885 | */
886 | private void closeCamera() {
887 | mRecordImageButton.setClickable(false);
888 | cameraHelper.closeCamera();
889 | cameraHelper.stopBackgroundThread();
890 | }
891 |
892 | /**
893 | * 播放视频
894 | */
895 | public void playVideo() {
896 | closeCamera();
897 | if (mMediaPath != null && mVideoPlayer != null) {
898 | mVideoPlayer.setDataSourceAndPlay(mMediaPath);
899 | isPlaying = true;
900 |
901 | TEXTURE_STATE = TEXTURE_PLAY_STATE; //视频播放状态
902 | }
903 | }
904 |
905 | private boolean isPlaying = false;
906 |
907 | /**
908 | * 暂停或者播放视频
909 | */
910 | @OnClick({R.id.video_mine_play, R.id.video_play})
911 | public void playOrPause() {
912 | if (!isPlaying) {
913 | mMiniPlayImageButton.setImageResource(R.drawable.ic_pause);
914 | mPlayImageButton.setImageResource(R.drawable.ic_pause);
915 | mPlayImageButton.postDelayed(new Runnable() {
916 | @Override
917 | public void run() {
918 | mPlayImageButton.setVisibility(View.GONE);
919 | }
920 | }, 1000);
921 | mVideoPlayer.play();
922 | isPlaying = true;
923 | } else {
924 | mMiniPlayImageButton.setImageResource(R.drawable.ic_play);
925 | mPlayImageButton.setImageResource(R.drawable.ic_play);
926 | isPlaying = false;
927 | mVideoPlayer.pause();
928 | }
929 | }
930 |
931 | @OnClick(R.id.video_delete)
932 | public void deleteVideoOrPicture() {
933 | if (TEXTURE_STATE == TEXTURE_PLAY_STATE) {
934 | mVideoPlayer.stop();
935 | cameraHelper.startBackgroundThread();
936 | cameraHelper.openCamera(mNowCameraType);
937 | mCameraTouch.resetScale(); //重新打开摄像头重置一下放大倍数
938 | File file = new File(mMediaPath);
939 | if (file.exists())
940 | file.delete();
941 | mVideoHintText.setText("点击录像");
942 | } else if (TEXTURE_STATE == TEXTURE_PHOTO_STATE) {
943 | File file = new File(mMediaPath);
944 | if (file.exists())
945 | file.delete();
946 | /* cameraHelper.resumePreview();*/
947 | textureView.setVisibility(View.VISIBLE);
948 | mPhotoImageView.setVisibility(View.GONE);
949 | mVideoHintText.setText("点击拍照");
950 | }
951 |
952 | TEXTURE_STATE = TEXTURE_PREVIEW_STATE;
953 |
954 | hindRecordEndView();
955 | showSwitchCamera();
956 | showMeun();
957 | mRecordImageButton.setVisibility(View.VISIBLE);
958 | mTimeTextView.setVisibility(View.GONE);
959 | mTimeTextView.setText("0:00");
960 | mVideoHintText.setVisibility(View.VISIBLE);
961 | mFlashSwitch.setVisibility(View.VISIBLE);
962 | }
963 |
964 | /**
965 | * 发送视频或者图片
966 | */
967 | @OnClick(R.id.video_save)
968 | public void saveVideoOrPhoto() {
969 | final Intent data;
970 | data = new Intent();
971 | data.putExtra("path", mMediaPath);
972 | if (NOW_MODE == VIDEO_TAKE_PHOTO)
973 | data.putExtra("mediaType", "image");
974 | else if (NOW_MODE == VIDEO_RECORD_MODE) {
975 | data.putExtra("mediaType", "video");
976 | }
977 |
978 | setResult(RESULT_OK, data);
979 | finish();
980 |
981 | saveMedia(new File(mMediaPath));
982 | }
983 |
984 | /**
985 | * 刷新相册
986 | *
987 | * @param mediaFile
988 | */
989 | private void saveMedia(File mediaFile) {
990 | Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
991 | Uri uri = Uri.fromFile(mediaFile);
992 | intent.setData(uri);
993 | sendBroadcast(intent);
994 | }
995 |
996 | public void close(View view) {
997 | finish();
998 | }
999 |
1000 | /**
1001 | * 视频录像保存的路径
1002 | *
1003 | * @return
1004 | */
1005 | private String getVideoFilePath() {
1006 | File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsoluteFile();
1007 | return (dir == null ? "" : (dir.getAbsolutePath() + "/"))
1008 | + System.currentTimeMillis() + ".mp4";
1009 | }
1010 |
1011 | /**
1012 | * 图片拍照的路径
1013 | *
1014 | * @return
1015 | */
1016 | private String getPhotoFilePath() {
1017 | File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsoluteFile();
1018 | return (dir == null ? "" : (dir.getAbsolutePath() + "/"))
1019 | + System.currentTimeMillis() + ".jpeg";
1020 | }
1021 |
1022 | @Override
1023 | protected void onDestroy() {
1024 | super.onDestroy();
1025 | if (mSensorManager != null)
1026 | mSensorManager.unregisterListener(this);
1027 | if (MODE == CAMERA_MODE) {
1028 | //如果正在录像,就停止并且删除
1029 | if (TEXTURE_STATE == TEXTURE_RECORD_STATE) {
1030 | if (mDisposable != null && !mDisposable.isDisposed())
1031 | mDisposable.dispose();
1032 | cameraHelper.stopVideoRecord();
1033 | closeCamera();
1034 | deleteVideoOrPicture();
1035 | }
1036 |
1037 | if (TEXTURE_STATE == TEXTURE_PHOTO_STATE) {
1038 | closeCamera();
1039 | deleteVideoOrPicture();
1040 | }
1041 | }
1042 | if (isPlaying)
1043 | mVideoPlayer.stop();
1044 | mVideoPlayer.destroy();
1045 | if (cameraHelper != null)
1046 | cameraHelper.destroy();
1047 | if (mExitBroadcastReceiver != null)
1048 | mLocalBroadcastManager.unregisterReceiver(mExitBroadcastReceiver);
1049 | }
1050 |
1051 | @Override
1052 | public void onSeekTime(int allTime, final int time) {
1053 | if (mVideoSeekBar.getVisibility() != View.VISIBLE)
1054 | return;
1055 | if (mVideoSeekBar.getMax() != allTime)
1056 | mVideoSeekBar.setMax(allTime);
1057 | mVideoSeekBar.setProgress(time);
1058 | mVideoSeekTimeTextView.post(new Runnable() {
1059 | @Override
1060 | public void run() {
1061 | float t = (float) time / 1000.0f;
1062 | mVideoSeekTimeTextView.setText(secToTime(Math.round(t)));
1063 | }
1064 | });
1065 | }
1066 |
1067 | /**
1068 | * 显示视频播放进度条
1069 | */
1070 | private void showVideoPlaySeekBar() {
1071 | mVideoSeekBar.setVisibility(View.VISIBLE);
1072 | }
1073 |
1074 | /**
1075 | * 隐藏视频播放进度条
1076 | */
1077 | private void hindVideoPlaySeekBar() {
1078 | mVideoSeekBar.setVisibility(View.GONE);
1079 | }
1080 |
1081 | @Override
1082 | public void onStartListener(int width, int height) {
1083 | textureView.setVideoAspectRatio(width, height);
1084 | mMiniPlayImageButton.setImageResource(R.drawable.ic_pause);
1085 | mPlayImageButton.setImageResource(R.drawable.ic_pause);
1086 | }
1087 |
1088 | @Override
1089 | public void onCompletionListener() {
1090 | isPlaying = false;
1091 | mMiniPlayImageButton.setImageResource(R.drawable.ic_play);
1092 | mPlayImageButton.setImageResource(R.drawable.ic_play);
1093 | mPlayImageButton.setVisibility(View.VISIBLE);
1094 | }
1095 |
1096 | /**
1097 | * 整数s转 xx:xx:xx
1098 | *
1099 | * @param time
1100 | * @return
1101 | */
1102 | private String secToTime(int time) {
1103 | String timeStr;
1104 | int hour;
1105 | int minute;
1106 | int second;
1107 | if (time <= 0)
1108 | return "00:00";
1109 | else {
1110 | minute = time / 60;
1111 | if (minute < 60) {
1112 | second = time % 60;
1113 | timeStr = unitFormat(minute) + ":" + unitFormat(second);
1114 | } else {
1115 | hour = minute / 60;
1116 | if (hour > 99)
1117 | return "99:59:59";
1118 | minute = minute % 60;
1119 | second = time - hour * 3600 - minute * 60;
1120 | timeStr = unitFormat(hour) + ":" + unitFormat(minute) + ":" + unitFormat(second);
1121 | }
1122 | }
1123 | return timeStr;
1124 | }
1125 |
1126 | private String unitFormat(int i) {
1127 | String retStr;
1128 | if (i >= 0 && i < 10)
1129 | retStr = "0" + Integer.toString(i);
1130 | else
1131 | retStr = "" + i;
1132 | return retStr;
1133 | }
1134 |
1135 | @Override
1136 | public void onTakePhotoFinish(final File file, final int photoRotation, final int width, final int height) {
1137 | runOnUiThread(new Runnable() {
1138 | @Override
1139 | public void run() {
1140 | hindSwitchCamera();
1141 | hindMenu();
1142 | showRecordEndView();
1143 | mFlashSwitch.setVisibility(View.GONE);
1144 | mRecordImageButton.setVisibility(View.GONE);
1145 | mVideoHintText.setVisibility(View.GONE);
1146 | TEXTURE_STATE = TEXTURE_PHOTO_STATE;
1147 | textureView.setVisibility(View.GONE);
1148 | mPhotoImageView.setImageURI(getUriFromFile(CameraActivity.this,file));
1149 | mPhotoImageView.setVisibility(View.VISIBLE);
1150 | }
1151 | });
1152 | }
1153 |
1154 | public Uri getUriFromFile(Context context, File file){
1155 | if (Build.VERSION.SDK_INT >= 24) {
1156 | return FileProvider.getUriForFile(context,getPackageName()+".fileprovider", file);
1157 | } else {
1158 | return Uri.fromFile(file);
1159 | }
1160 | }
1161 |
1162 | @OnClick(R.id.video_switch_flash)
1163 | public void flashSwitch() {
1164 | Object o = mFlashSwitch.getTag();
1165 | if (o == null || ((int) o) == 0) {
1166 | mFlashSwitch.setImageResource(R.drawable.flash_auto);
1167 | mFlashSwitch.setTag(1);
1168 | cameraHelper.flashSwitchState(ICamera.FlashState.AUTO);
1169 | } else if (((int) o) == 1) {
1170 | mFlashSwitch.setImageResource(R.drawable.flash_open);
1171 | mFlashSwitch.setTag(2);
1172 | cameraHelper.flashSwitchState(ICamera.FlashState.OPEN);
1173 | } else {
1174 | mFlashSwitch.setImageResource(R.drawable.flash_close);
1175 | mFlashSwitch.setTag(0);
1176 | cameraHelper.flashSwitchState(ICamera.FlashState.CLOSE);
1177 | }
1178 | }
1179 |
1180 | private void initScaleSeekbar() {
1181 | mScaleSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
1182 |
1183 | @Override
1184 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
1185 | if (fromUser) {
1186 | float scale = (float) progress / (float) seekBar.getMax() * cameraHelper.getMaxZoom();
1187 | cameraHelper.cameraZoom(scale);
1188 | mCameraTouch.setScale(scale);
1189 | }
1190 | }
1191 |
1192 | @Override
1193 | public void onStartTrackingTouch(SeekBar seekBar) {
1194 | Log.e("touch", "touch:start");
1195 | removeSeekBarRunnable();
1196 | }
1197 |
1198 | @Override
1199 | public void onStopTrackingTouch(SeekBar seekBar) {
1200 | Log.e("touch", "touch:stop");
1201 | seekBarDelayedHind();
1202 | }
1203 | });
1204 | }
1205 |
1206 |
1207 | /**
1208 | * 注册陀螺仪传感器
1209 | */
1210 | private SensorManager mSensorManager;
1211 | private Sensor mSensor;
1212 | private Sensor mLightSensor;
1213 |
1214 | private void registerSensor() {
1215 | mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
1216 | mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
1217 | mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
1218 | if (mSensor == null)
1219 | return;
1220 | mSensorManager.registerListener(this, mSensor, Sensor.TYPE_ORIENTATION);
1221 | mSensorManager.registerListener(this, mLightSensor, Sensor.TYPE_LIGHT);
1222 | }
1223 |
1224 | @Override
1225 | public void onSensorChanged(SensorEvent event) {
1226 | if (event.sensor.getType() == Sensor.TYPE_ORIENTATION) {
1227 | float x = event.values[0];
1228 | float z = event.values[2];
1229 | float y = event.values[1];
1230 | if (z > 55.0f) {
1231 | //向右横屏
1232 | cameraHelper.setDeviceRotation(1);
1233 | } else if (z < -55.0f) {
1234 | //向左横屏
1235 | cameraHelper.setDeviceRotation(3);
1236 | } else if (y > 60.0f) {
1237 | //是倒竖屏
1238 | cameraHelper.setDeviceRotation(2);
1239 | } else {
1240 | //正竖屏
1241 | cameraHelper.setDeviceRotation(0);
1242 | }
1243 | }
1244 | if (event.sensor.getType() == Sensor.TYPE_LIGHT) {
1245 | float light = event.values[0];
1246 | cameraHelper.setLight(light);
1247 | }
1248 | }
1249 |
1250 | @Override
1251 | public void onAccuracyChanged(Sensor sensor, int accuracy) {
1252 |
1253 | }
1254 |
1255 | @Override
1256 | public void onCameraReady() {
1257 | mRecordImageButton.setClickable(true);
1258 | }
1259 |
1260 | private boolean isCanHind;
1261 |
1262 | private void removeSeekBarRunnable() {
1263 | isCanHind = true;
1264 | mSeekBarLayout.removeCallbacks(SeekBarLayoutRunnalbe);
1265 | Log.e("hind", "nohind");
1266 | }
1267 |
1268 | private void seekBarDelayedHind() {
1269 | //3s后颖仓seekbar进度条
1270 | Log.e("hind", "hind");
1271 | if (isCanHind)
1272 | mSeekBarLayout.postDelayed(SeekBarLayoutRunnalbe, 3000);
1273 | isCanHind = false;
1274 | }
1275 |
1276 | private void removeImageFoucesRunnable()
1277 | {
1278 | mFoucesImage.removeCallbacks(mImageFoucesRunnable);
1279 | }
1280 |
1281 | private void imageFoucesDelayedHind()
1282 | {
1283 | mFoucesImage.postDelayed(mImageFoucesRunnable,1000);
1284 | }
1285 |
1286 | private class CameraTouch {
1287 | private float mOldScale = 1.0f;
1288 | private float mScale;
1289 | private float mSpan = 0;
1290 | private float mOldSpan;
1291 | private float mFirstDistance = 0;
1292 |
1293 | public void onScale(MotionEvent event) {
1294 | if (event.getPointerCount() == 2) {
1295 | if (mFirstDistance == 0)
1296 | mFirstDistance = distance(event);
1297 |
1298 | float distance = distance(event);
1299 | float scale;
1300 | if (distance > mFirstDistance) {
1301 | scale = (distance - mFirstDistance) / 80;
1302 | scale = scale + mSpan;
1303 | mOldSpan = scale;
1304 | mScale = scale;
1305 | } else if (distance < mFirstDistance) {
1306 | scale = distance / mFirstDistance;
1307 | mOldSpan = scale;
1308 | mScale = scale * mOldScale;
1309 | } else {
1310 | return;
1311 | }
1312 |
1313 | cameraHelper.cameraZoom(mScale);
1314 | mScaleSeekBar.setProgress((int) ((mScale / cameraHelper.getMaxZoom()) * mScaleSeekBar.getMax()));
1315 | if (mScale < 1.0f)
1316 | mScaleSeekBar.setProgress(0);
1317 | }
1318 | }
1319 |
1320 | public void onScaleStart(MotionEvent event) {
1321 | mFirstDistance = 0;
1322 | setScaleMax((int) cameraHelper.getMaxZoom());
1323 |
1324 | mSeekBarLayout.setVisibility(View.VISIBLE);
1325 | removeSeekBarRunnable();
1326 | Log.e("scale", "scale:start");
1327 | }
1328 |
1329 | public void onScaleEnd(MotionEvent event) {
1330 | if (mScale < 1.0f)
1331 | mOldScale = 1.0f;
1332 | else if (mScale > cameraHelper.getMaxZoom())
1333 | mOldScale = cameraHelper.getMaxZoom();
1334 | else
1335 | mOldScale = mScale;
1336 | mSpan = mOldSpan;
1337 |
1338 | if (event != null)
1339 | seekBarDelayedHind();
1340 | Log.e("scale", "scale:end");
1341 | }
1342 |
1343 | public void resetScale() {
1344 | mOldScale = 1.0f;
1345 | mSpan = 0f;
1346 | mFirstDistance = 0f;
1347 | mScaleSeekBar.setProgress(0);
1348 | }
1349 |
1350 | public void setScale(float scale) {
1351 | mScale = scale;
1352 | mOldSpan = scale;
1353 | onScaleEnd(null);
1354 | }
1355 |
1356 | /**
1357 | * 计算两个手指间的距离
1358 | *
1359 | * @param event
1360 | * @return
1361 | */
1362 | private float distance(MotionEvent event) {
1363 | float dx = event.getX(1) - event.getX(0);
1364 | float dy = event.getY(1) - event.getY(0);
1365 | /** 使用勾股定理返回两点之间的距离 */
1366 | return (float) Math.sqrt(dx * dx + dy * dy);
1367 | }
1368 |
1369 | private void setScaleMax(int max) {
1370 | mScaleSeekBar.setMax(max * 100);
1371 | }
1372 | }
1373 |
1374 | private class ExitBroadcastReceiver extends BroadcastReceiver {
1375 | @Override
1376 | public void onReceive(Context context, Intent intent) {
1377 | finish();
1378 | }
1379 | }
1380 | }
1381 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/administrator/camera/CameraHelper.java:
--------------------------------------------------------------------------------
1 | package com.example.administrator.camera;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.Activity;
5 | import android.content.Context;
6 | import android.graphics.ImageFormat;
7 | import android.graphics.Point;
8 | import android.graphics.Rect;
9 | import android.graphics.RectF;
10 | import android.graphics.SurfaceTexture;
11 | import android.hardware.camera2.CameraAccessException;
12 | import android.hardware.camera2.CameraCaptureSession;
13 | import android.hardware.camera2.CameraCharacteristics;
14 | import android.hardware.camera2.CameraDevice;
15 | import android.hardware.camera2.CameraManager;
16 | import android.hardware.camera2.CameraMetadata;
17 | import android.hardware.camera2.CaptureFailure;
18 | import android.hardware.camera2.CaptureRequest;
19 | import android.hardware.camera2.CaptureResult;
20 | import android.hardware.camera2.TotalCaptureResult;
21 | import android.hardware.camera2.params.MeteringRectangle;
22 | import android.hardware.camera2.params.StreamConfigurationMap;
23 | import android.media.Image;
24 | import android.media.ImageReader;
25 | import android.media.MediaRecorder;
26 | import android.os.Handler;
27 | import android.os.HandlerThread;
28 | import android.util.Log;
29 | import android.util.Size;
30 | import android.util.SparseIntArray;
31 | import android.view.Surface;
32 | import android.view.TextureView;
33 |
34 | import java.io.File;
35 | import java.io.FileOutputStream;
36 | import java.io.IOException;
37 | import java.nio.ByteBuffer;
38 | import java.util.ArrayList;
39 | import java.util.Arrays;
40 | import java.util.Collections;
41 | import java.util.Comparator;
42 | import java.util.List;
43 | import java.util.concurrent.atomic.AtomicBoolean;
44 |
45 |
46 | /**
47 | * 摄像头帮助类
48 | */
49 | public class CameraHelper implements ICamera {
50 |
51 | private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
52 |
53 | static {
54 | ORIENTATIONS.append(Surface.ROTATION_0, 90);
55 | ORIENTATIONS.append(Surface.ROTATION_90, 0);
56 | ORIENTATIONS.append(Surface.ROTATION_180, 270);
57 | ORIENTATIONS.append(Surface.ROTATION_270, 180);
58 | }
59 |
60 | /**
61 | * 设备旋转方向
62 | */
63 | private int mDeviceRotation;
64 |
65 | private int mPhotoRotation;
66 |
67 | private float mLight;
68 |
69 | private AtomicBoolean mIsCameraOpen;
70 |
71 | private CameraManager mCameraManager;
72 |
73 | private TakePhotoListener mTakePhotoListener;
74 |
75 | private CameraReady mCameraReady;
76 |
77 | /**
78 | * 摄像头的id集合
79 | */
80 | private String[] mCameraIds;
81 |
82 | /**
83 | * 摄像头支持的最大size
84 | */
85 | private Size mLargest;
86 |
87 | /**
88 | * 可缩放区域
89 | */
90 | private Size mZoomSize;
91 |
92 | private Size mVideoSize;
93 |
94 | private Context mContext;
95 |
96 | /**
97 | * 需要打开的摄像头id
98 | */
99 | private String mCameraId;
100 |
101 | private MediaRecorder mMediaRecorder;
102 |
103 | private CaptureRequest.Builder mPreviewBuilder;
104 |
105 | private CameraDevice mCameraDevice;
106 |
107 | private CameraCaptureSession mPreviewSession;
108 |
109 | private TextureView mTextureView;
110 | /**
111 | * 后台线程
112 | */
113 | private HandlerThread mBackgroundThread;
114 |
115 | /**
116 | * 后台handle
117 | */
118 | private Handler mBackgroundHandler;
119 |
120 | private AtomicBoolean mIsRecordVideo = new AtomicBoolean();
121 |
122 | private CameraType mNowCameraType;
123 |
124 | /**
125 | * 拍照的图片读取类
126 | */
127 | private ImageReader mImageReader;
128 |
129 | /**
130 | * 是否支持闪光灯
131 | */
132 | private boolean mFlashSupported;
133 |
134 | /**
135 | * 图片的路径
136 | */
137 | private String mPhotoPath;
138 |
139 | /**
140 | * Orientation of the camera sensor
141 | */
142 | private int mSensorOrientation;
143 |
144 | /**
145 | * 最大的放大倍数
146 | */
147 | private float mMaxZoom = 0;
148 |
149 | /**
150 | * 放大的矩阵,拍照使用
151 | */
152 | private Rect mZoomRect;
153 |
154 | /**
155 | * 摄像头支持的分辨率流集合
156 | */
157 | private StreamConfigurationMap mMap;
158 |
159 | private FlashState mNowFlashState = FlashState.CLOSE;
160 |
161 | private boolean mIsCapture = false;
162 |
163 | private CameraCharacteristics mCharacteristics;
164 |
165 | private boolean mNoAFRun = false;
166 |
167 | private boolean mIsAFRequest = false;
168 |
169 | private CameraMode CAMERA_STATE = CameraMode.TAKE_PHOTO;
170 |
171 | private Surface mPreViewSurface;
172 |
173 | private Surface mRecordSurface;
174 |
175 | private CoordinateTransformer mCoordinateTransformer;
176 |
177 | private Rect mPreviewRect;
178 | private Rect mFocusRect;
179 |
180 | /**
181 | * 根据摄像头管理器获取一个帮助类
182 | *
183 | * @param context
184 | * @return
185 | */
186 | public CameraHelper(Context context) {
187 | this.mContext = context;
188 | mIsCameraOpen = new AtomicBoolean(false);
189 | CameraManager cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
190 | mCameraManager = cameraManager;
191 | try {
192 | mCameraIds = mCameraManager.getCameraIdList();
193 | } catch (CameraAccessException e) {
194 | e.printStackTrace();
195 | }
196 | mFocusRect = new Rect();
197 | }
198 |
199 |
200 | @Override
201 | public void cameraZoom(float scale) {
202 | if (scale < 1.0f)
203 | scale = 1.0f;
204 | if (scale <= mMaxZoom) {
205 |
206 | int cropW = (int) ((mZoomSize.getWidth() / (mMaxZoom * 2.6)) * scale);
207 | int cropH = (int) ((mZoomSize.getHeight() / (mMaxZoom * 2.6)) * scale);
208 |
209 | Rect zoom = new Rect(cropW, cropH,
210 | mZoomSize.getWidth() - cropW,
211 | mZoomSize.getHeight() - cropH);
212 | mPreviewBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoom);
213 | mZoomRect = zoom;
214 | updatePreview(); //重复更新预览请求
215 | }
216 | }
217 |
218 | /**
219 | * 获取最大zoom
220 | *
221 | * @return
222 | */
223 | public float getMaxZoom() {
224 | return mMaxZoom;
225 | }
226 |
227 | /**
228 | * 初始化拍照的图片读取类
229 | */
230 | private void initImageReader() {
231 | //取最大的分辨率
232 | Size largest = Collections.max(Arrays.asList(mMap.getOutputSizes(ImageFormat.JPEG)),
233 | new CompareSizesByArea());
234 | mZoomSize = largest;
235 | //实例化拍照用的图片读取类
236 | if (mImageReader != null)
237 | mImageReader.close();
238 | mImageReader = ImageReader.newInstance(largest.getWidth(),
239 | largest.getHeight(), ImageFormat.JPEG, 2);
240 | }
241 |
242 | /**
243 | * 初始化一个适合的预览尺寸
244 | */
245 | private void initSize() {
246 | Size largest = Collections.max(
247 | Arrays.asList(mMap.getOutputSizes(ImageFormat.JPEG)),
248 | new CompareSizesByArea());
249 |
250 | Point displaySize = new Point();
251 | ((Activity) mContext).getWindowManager().getDefaultDisplay().getSize(displaySize);
252 |
253 | mLargest = chooseOptimalSize(mMap.getOutputSizes(SurfaceTexture.class),
254 | this.mTextureView.getWidth(),
255 | this.mTextureView.getHeight(),
256 | displaySize.x,
257 | displaySize.y,
258 | largest
259 | );
260 | }
261 |
262 | @SuppressLint("MissingPermission")
263 | @Override
264 | public boolean openCamera(CameraType cameraType) {
265 |
266 | if (mIsCameraOpen.get())
267 | return true;
268 | mIsCameraOpen.set(true);
269 | mZoomRect = null;
270 | this.mNowCameraType = cameraType;
271 | int cameraTypeId;
272 | switch (cameraType) {
273 | default:
274 | case BACK:
275 | cameraTypeId = CameraCharacteristics.LENS_FACING_BACK;
276 | break;
277 | case FRONT:
278 | cameraTypeId = CameraCharacteristics.LENS_FACING_FRONT;
279 | break;
280 | case USB:
281 | cameraTypeId = CameraCharacteristics.LENS_FACING_EXTERNAL;
282 | break;
283 | }
284 |
285 | try {
286 | for (String cameraId : mCameraIds) {
287 | mCharacteristics = mCameraManager.getCameraCharacteristics(cameraId);
288 | Integer facing = mCharacteristics.get(CameraCharacteristics.LENS_FACING);
289 | if (facing != null && facing != cameraTypeId) {
290 | continue;
291 | }
292 |
293 | Float maxZoom = mCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
294 | if (maxZoom != null) {
295 | mMaxZoom = maxZoom.floatValue();
296 | }
297 |
298 | //获取摄像头支持的流配置信息
299 | mMap = mCharacteristics
300 | .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
301 | if (mMap == null)
302 | return false;
303 | //初始化拍照的图片读取类
304 | initImageReader();
305 | //初始化尺寸
306 | initSize();
307 |
308 | //获取摄像头角度
309 | mSensorOrientation = mCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
310 |
311 | mVideoSize = chooseVideoSize(mMap.getOutputSizes(MediaRecorder.class));
312 | if (mTextureView != null) {
313 | ((AutoFitTextureView) mTextureView).setAspectRatio(mLargest.getHeight(), mLargest.getWidth());
314 | }
315 |
316 | //检查是否这个摄像头是否支持闪光灯,拍照模式的时候使用
317 | Boolean available = mCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
318 | mFlashSupported = available == null ? false : available;
319 |
320 | this.mCameraId = cameraId;
321 | mPreviewRect = new Rect(0, 0, mTextureView.getWidth(), mTextureView.getHeight());
322 | mCoordinateTransformer = new CoordinateTransformer(mCharacteristics, new RectF(mPreviewRect));
323 | }
324 | } catch (Exception e) {
325 | e.printStackTrace();
326 | }
327 | return openCamera(mCameraId);
328 | }
329 |
330 | @SuppressLint("MissingPermission")
331 | private boolean openCamera(String cameraId) {
332 | try {
333 | mCameraManager.openCamera(cameraId, mStateCallback, null);
334 | } catch (CameraAccessException e) {
335 | e.printStackTrace();
336 | return false;
337 | }
338 | return true;
339 | }
340 |
341 | @Override
342 | public void closeCamera() {
343 | Log.e("camera", "关闭摄像头");
344 | mIsCameraOpen.set(false);
345 |
346 | closePreviewSession();
347 | if (mCameraDevice != null) {
348 | mCameraDevice.close();
349 | mCameraDevice = null;
350 | }
351 | if (mImageReader != null) {
352 | mImageReader.close();
353 | mImageReader = null;
354 | }
355 | }
356 |
357 | @Override
358 | public boolean switchCamera(CameraType cameraType) {
359 | closeCamera();
360 | return openCamera(cameraType);
361 | }
362 |
363 | @Override
364 | public boolean startPreview() {
365 | if (mBackgroundHandler == null)
366 | return false;
367 | try {
368 |
369 | //初始化预览的尺寸
370 | initSize();
371 |
372 | SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
373 | surfaceTexture.setDefaultBufferSize(mLargest.getWidth(), mLargest.getHeight());
374 | mPreViewSurface = new Surface(surfaceTexture);
375 |
376 | mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); //创建一个预览请求
377 | mPreviewBuilder.addTarget(mPreViewSurface); //添加预览输出目标画面
378 |
379 | if (mZoomRect != null)
380 | mPreviewBuilder.set(CaptureRequest.SCALER_CROP_REGION, mZoomRect); //放大的矩阵
381 | mCameraDevice.createCaptureSession(Arrays.asList(mPreViewSurface,
382 | mImageReader.getSurface()), //当前线程创建一个预览请求
383 | new CameraCaptureSession.StateCallback() {
384 | @Override
385 | public void onConfigured(CameraCaptureSession session) {
386 | mPreviewSession = session;
387 | setup3AControlsLocked(mPreviewBuilder);
388 | updatePreview(); //重复更新预览请求
389 | if (mCameraReady != null)
390 | mCameraReady.onCameraReady();
391 | }
392 |
393 | @Override
394 | public void onConfigureFailed(CameraCaptureSession session) {
395 |
396 | }
397 | }, mBackgroundHandler);
398 | } catch (CameraAccessException e) {
399 | e.printStackTrace();
400 | return false;
401 | }
402 | return true;
403 | }
404 |
405 | /**
406 | * 更新预览界面
407 | */
408 | private void updatePreview() {
409 | if (mCameraDevice == null)
410 | return;
411 | try {
412 | // mPreviewBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
413 | mPreviewSession.setRepeatingRequest(
414 | mPreviewBuilder.build(),
415 | null,
416 | mBackgroundHandler
417 | );
418 | } catch (Exception e) {
419 | e.printStackTrace();
420 | }
421 | }
422 |
423 |
424 | @Override
425 | public void resumePreview() {
426 | try {
427 | if (!mNoAFRun) {
428 | mPreviewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
429 | CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
430 | mPreviewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
431 | CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
432 | }
433 | if (!isLegacyLocked()) {
434 | // Tell the camera to lock focus.
435 | mPreviewBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
436 | CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL);
437 | mPreviewBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
438 | CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE);
439 | }
440 | mIsAFRequest = false;
441 | mCameraState = 0;
442 | mPreviewSession.capture(mPreviewBuilder.build(), null,
443 | mBackgroundHandler);
444 | updatePreview();
445 | } catch (CameraAccessException e) {
446 | e.printStackTrace();
447 | }
448 | }
449 |
450 | @Override
451 | public boolean startVideoRecord(String path, int mediaType) {
452 | if (mIsRecordVideo.get())
453 | new Throwable("video record is recording");
454 | if (path == null)
455 | new Throwable("path can not null");
456 | if (mediaType != MediaRecorder.OutputFormat.MPEG_4)
457 | new Throwable("this mediaType can not support");
458 | if (!setVideoRecordParam(path))
459 | return false;
460 | startRecordVideo();
461 | return true;
462 | }
463 |
464 | /**
465 | * 设置录像的参数
466 | *
467 | * @param path
468 | * @return
469 | */
470 | private boolean setVideoRecordParam(String path) {
471 | mMediaRecorder = new MediaRecorder();
472 |
473 | mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
474 | mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
475 | mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
476 | mMediaRecorder.setOutputFile(path);
477 |
478 | int bitRate = mVideoSize.getWidth() * mVideoSize.getHeight();
479 | bitRate = mVideoSize.getWidth() < 1080 ? bitRate * 2 : bitRate;
480 |
481 | mMediaRecorder.setVideoEncodingBitRate(bitRate);
482 | mMediaRecorder.setVideoFrameRate(15);
483 | mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight());
484 | mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
485 |
486 | mMediaRecorder.setAudioEncodingBitRate(8000);
487 | mMediaRecorder.setAudioChannels(1);
488 | mMediaRecorder.setAudioSamplingRate(8000);
489 | mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
490 |
491 | if (mNowCameraType == CameraType.BACK) //后置摄像头图像要旋转90度
492 | mMediaRecorder.setOrientationHint(90);
493 | else
494 | mMediaRecorder.setOrientationHint(270); //前置摄像头图像要旋转270度
495 | try {
496 | mMediaRecorder.prepare();
497 | } catch (IOException e) {
498 | e.printStackTrace();
499 | return false;
500 | }
501 | return true;
502 | }
503 |
504 | @Override
505 | public void stopVideoRecord() {
506 | if (mIsRecordVideo.get())
507 | mIsRecordVideo.set(false);
508 | else
509 | return;
510 | mMediaRecorder.stop();
511 | mMediaRecorder.reset();
512 | mMediaRecorder.release();
513 |
514 | // startPreview();
515 | }
516 |
517 | @Override
518 | public boolean takePhone(String path, MediaType mediaType) {
519 | this.mPhotoPath = path;
520 | setTakePhotoFlashMode(mPreviewBuilder);
521 | updatePreview();
522 | // lockFocus();
523 |
524 | if (!mNoAFRun) {
525 |
526 | if (mIsAFRequest) {
527 | mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
528 | mPreviewBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, AFRegions);
529 | mPreviewBuilder.set(CaptureRequest.CONTROL_AE_REGIONS, AERegions);
530 | }
531 | mPreviewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
532 | CameraMetadata.CONTROL_AF_TRIGGER_START);
533 | }
534 | if (!isLegacyLocked()) {
535 | // Tell the camera to lock focus.
536 | mPreviewBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
537 | CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_START);
538 | }
539 | mCameraState = WAITING_LOCK;
540 | if(!mFlashSupported) {
541 | capturePhoto();
542 | }else
543 | {
544 | switch (mNowFlashState)
545 | {
546 | case CLOSE:
547 | capturePhoto();
548 | break;
549 | case OPEN:
550 | case AUTO:
551 | mBackgroundHandler.postDelayed(new Runnable() {
552 | @Override
553 | public void run() {
554 | try {
555 | mPreviewSession.capture(mPreviewBuilder.build(), mCaptureCallback,
556 | mBackgroundHandler);
557 | } catch (CameraAccessException e) {
558 | e.printStackTrace();
559 | }
560 | }
561 | }, 800);
562 | break;
563 | }
564 | }
565 | return true;
566 | }
567 |
568 | private boolean isLegacyLocked() {
569 | return mCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ==
570 | CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY;
571 | }
572 |
573 | private void setup3AControlsLocked(CaptureRequest.Builder builder) {
574 | // Enable auto-magical 3A run by camera device
575 | builder.set(CaptureRequest.CONTROL_MODE,
576 | CaptureRequest.CONTROL_MODE_AUTO);
577 |
578 | Float minFocusDist =
579 | mCharacteristics.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE);
580 |
581 | // If MINIMUM_FOCUS_DISTANCE is 0, lens is fixed-focus and we need to skip the AF run.
582 | mNoAFRun = (minFocusDist == null || minFocusDist == 0);
583 |
584 | if (!mNoAFRun) {
585 | // If there is a "continuous picture" mode available, use it, otherwise default to AUTO.
586 | if (contains(mCharacteristics.get(
587 | CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES),
588 | CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)) {
589 | builder.set(CaptureRequest.CONTROL_AF_MODE,
590 | CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
591 | } else {
592 | builder.set(CaptureRequest.CONTROL_AF_MODE,
593 | CaptureRequest.CONTROL_AF_MODE_AUTO);
594 | }
595 | }
596 |
597 | // If there is an auto-magical flash control mode available, use it, otherwise default to
598 | // the "on" mode, which is guaranteed to always be available.
599 | if (mNowFlashState != FlashState.CLOSE) {
600 | if (contains(mCharacteristics.get(
601 | CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES),
602 | CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)) {
603 | builder.set(CaptureRequest.CONTROL_AE_MODE,
604 | CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
605 | } else {
606 | builder.set(CaptureRequest.CONTROL_AE_MODE,
607 | CaptureRequest.CONTROL_AE_MODE_ON);
608 | }
609 | }
610 |
611 | // If there is an auto-magical white balance control mode available, use it.
612 | if (contains(mCharacteristics.get(
613 | CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES),
614 | CaptureRequest.CONTROL_AWB_MODE_AUTO)) {
615 | // Allow AWB to run auto-magically if this device supports this
616 | builder.set(CaptureRequest.CONTROL_AWB_MODE,
617 | CaptureRequest.CONTROL_AWB_MODE_AUTO);
618 | }
619 | }
620 |
621 | /**
622 | * 真正拍照
623 | */
624 | private void capturePhoto() {
625 | mIsCapture = true;
626 | final CaptureRequest.Builder captureBuilder;
627 | try {
628 | //设置拍照后的回调监听
629 | mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
630 | captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
631 | captureBuilder.addTarget(mImageReader.getSurface());
632 | //设置自动对焦
633 | /*captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
634 | CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);*/
635 | // Use the same AE and AF modes as the preview.
636 | /* if(mNowFlashState != FlashState.CLOSE) {
637 | if(mFlashSupported)
638 | setup3AControlsLocked(captureBuilder);
639 | }*/
640 |
641 | mPhotoRotation = getOrientation(mDeviceRotation);
642 | captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, mPhotoRotation);
643 |
644 | if (mZoomRect != null)
645 | captureBuilder.set(CaptureRequest.SCALER_CROP_REGION, mZoomRect); //放大的矩阵
646 | setTakePhotoFlashMode(captureBuilder);
647 | captureBuilder.setTag(1);
648 | mPreviewSession.stopRepeating();
649 | mPreviewSession.abortCaptures();
650 | mBackgroundHandler.postDelayed(new Runnable() {
651 | @Override
652 | public void run() {
653 | try {
654 | mPreviewSession.capture(captureBuilder.build(), new CameraCaptureSession.CaptureCallback() {
655 | @Override
656 | public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
657 | }
658 |
659 | @Override
660 | public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) {
661 |
662 | }
663 |
664 |
665 | }, mBackgroundHandler);
666 | } catch (CameraAccessException e) {
667 | e.printStackTrace();
668 | }
669 | }
670 | }, 200);
671 | } catch (CameraAccessException e) {
672 | e.printStackTrace();
673 | }
674 | }
675 |
676 | @Override
677 | public Size getPreViewSize() {
678 | return mLargest;
679 | }
680 |
681 | @Override
682 | public void setSurface(Surface surface) {
683 | //this.mSurface = surface;
684 | }
685 |
686 | /**
687 | * 如果设置了textureView则不用设置Surface
688 | *
689 | * @param textureView
690 | */
691 | @Override
692 | public void setTextureView(TextureView textureView) {
693 | this.mTextureView = textureView;
694 | }
695 |
696 | @Override
697 | public void setTakePhotoListener(TakePhotoListener mTakePhotoListener) {
698 | this.mTakePhotoListener = mTakePhotoListener;
699 | }
700 |
701 | @Override
702 | public void setCameraReady(CameraReady cameraReady) {
703 | this.mCameraReady = cameraReady;
704 | }
705 |
706 | @Override
707 | public void flashSwitchState(FlashState mFlashState) {
708 | mNowFlashState = mFlashState;
709 | if (CAMERA_STATE == CameraMode.TAKE_PHOTO) {
710 | setTakePhotoFlashMode(mPreviewBuilder);
711 | updatePreview();
712 | }
713 | }
714 |
715 | @Override
716 | public void setCameraState(CameraMode cameraMode) {
717 | CAMERA_STATE = cameraMode;
718 | if (CAMERA_STATE == CameraMode.TAKE_PHOTO) {
719 | setTakePhotoFlashMode(mPreviewBuilder);
720 | updatePreview();
721 | }
722 | }
723 |
724 | private void toFocusRect(RectF rectF) {
725 | mFocusRect.left = Math.round(rectF.left);
726 | mFocusRect.top = Math.round(rectF.top);
727 | mFocusRect.right = Math.round(rectF.right);
728 | mFocusRect.bottom = Math.round(rectF.bottom);
729 | }
730 |
731 | private MeteringRectangle calcTapAreaForCamera2(int areaSize, int weight, float x, float y) {
732 | int left = clamp((int) x - areaSize / 2,
733 | mPreviewRect.left, mPreviewRect.right - areaSize);
734 | int top = clamp((int) y - areaSize / 2,
735 | mPreviewRect.top, mPreviewRect.bottom - areaSize);
736 | RectF rectF = new RectF(left, top, left + areaSize, top + areaSize);
737 | toFocusRect(mCoordinateTransformer.toCameraSpace(rectF));
738 | return new MeteringRectangle(mFocusRect, weight);
739 | }
740 |
741 | private MeteringRectangle[] AFRegions;
742 | private MeteringRectangle[] AERegions;
743 |
744 | @Override
745 | public void requestFocus(float x, float y) {
746 | mIsAFRequest = true;
747 | MeteringRectangle rect = calcTapAreaForCamera2(
748 | mTextureView.getWidth() / 5,
749 | 1000, x, y);
750 |
751 | AFRegions = new MeteringRectangle[]{rect};
752 | AERegions = new MeteringRectangle[]{rect};
753 |
754 | Log.e("AFRegions", "AFRegions:" + AFRegions[0].toString());
755 |
756 | try {
757 | final CaptureRequest.Builder mFocusBuilder =
758 | mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
759 |
760 | mFocusBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
761 | mFocusBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, AFRegions);
762 | mFocusBuilder.set(CaptureRequest.CONTROL_AE_REGIONS, AERegions);
763 | if(mZoomRect != null)
764 | mFocusBuilder.set(CaptureRequest.SCALER_CROP_REGION,mZoomRect);
765 |
766 | mFocusBuilder.addTarget(mPreViewSurface);
767 |
768 | if(CAMERA_STATE == CameraMode.RECORD_VIDEO) {
769 | if(mRecordSurface != null) {
770 | mFocusBuilder.addTarget(mRecordSurface);
771 | setRecordVideoFlashMode(mFocusBuilder);
772 | }
773 | }
774 |
775 | mPreviewSession.setRepeatingRequest(mFocusBuilder.build(),
776 | null, mBackgroundHandler);
777 |
778 | // mFocusBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_START);
779 | mFocusBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
780 |
781 | mPreviewSession.capture(mFocusBuilder.build(),
782 | null, mBackgroundHandler);
783 |
784 | } catch (CameraAccessException e) {
785 | e.printStackTrace();
786 | }
787 | }
788 |
789 | /**
790 | * 开启后台线程
791 | */
792 | public void startBackgroundThread() {
793 | mBackgroundThread = new HandlerThread(CameraHelper.class.getSimpleName());
794 | mBackgroundThread.start();
795 | mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
796 | }
797 |
798 | /**
799 | * 停止后台进程
800 | */
801 | public void stopBackgroundThread() {
802 | if (mBackgroundThread != null)
803 | mBackgroundThread.quitSafely();
804 | try {
805 | if (mBackgroundThread != null)
806 | mBackgroundThread.join();
807 | mBackgroundThread = null;
808 | mBackgroundHandler = null;
809 | } catch (InterruptedException e) {
810 | e.printStackTrace();
811 | }
812 | }
813 |
814 | private void setTakePhotoFlashMode(CaptureRequest.Builder builder) {
815 | if (!mFlashSupported)
816 | return;
817 | switch (mNowFlashState) {
818 | case CLOSE:
819 | builder.set(CaptureRequest.CONTROL_AE_MODE,
820 | CaptureRequest.CONTROL_AE_MODE_ON);
821 | builder.set(CaptureRequest.FLASH_MODE,
822 | CaptureRequest.FLASH_MODE_OFF);
823 | break;
824 | case OPEN:
825 | builder.set(CaptureRequest.CONTROL_AE_MODE,
826 | CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
827 | break;
828 | case AUTO:
829 | builder.set(CaptureRequest.CONTROL_AE_MODE,
830 | CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
831 | Log.e("mode", "自动闪光灯");
832 | break;
833 | }
834 | }
835 |
836 | private void setRecordVideoFlashMode(CaptureRequest.Builder builder) {
837 | if (!mFlashSupported)
838 | return;
839 | switch (mNowFlashState) {
840 | case CLOSE:
841 | builder.set(CaptureRequest.FLASH_MODE,
842 | CaptureRequest.FLASH_MODE_OFF);
843 | break;
844 | case OPEN:
845 | builder.set(CaptureRequest.FLASH_MODE,
846 | CaptureRequest.FLASH_MODE_TORCH);
847 | break;
848 | case AUTO:
849 | if (mLight < 10.0f) {
850 | builder.set(CaptureRequest.FLASH_MODE,
851 | CaptureRequest.FLASH_MODE_TORCH);
852 | }
853 | break;
854 | }
855 | }
856 |
857 | /**
858 | * 设置光线强度
859 | */
860 | public void setLight(float light) {
861 | this.mLight = light;
862 | }
863 |
864 | /**
865 | * 开始录像
866 | */
867 | private void startRecordVideo() {
868 | try {
869 | closePreviewSession();
870 |
871 | //录像的时候 取最大的分辨率
872 | mLargest = Collections.max(Arrays.asList(mMap.getOutputSizes(SurfaceTexture.class)),
873 | new CompareSizesByArea());
874 |
875 | if (mCameraDevice == null) return;
876 |
877 | mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
878 |
879 | setRecordVideoFlashMode(mPreviewBuilder);
880 |
881 | SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
882 | surfaceTexture.setDefaultBufferSize(mLargest.getWidth(), mLargest.getHeight());
883 | /* if(mSurface != null)
884 | mSurface.release();*/
885 | mPreViewSurface = new Surface(surfaceTexture);
886 |
887 | mPreviewBuilder.addTarget(mPreViewSurface);
888 | mRecordSurface = mMediaRecorder.getSurface();
889 | mPreviewBuilder.addTarget(mRecordSurface);
890 | List surfaceList = new ArrayList<>();
891 | surfaceList.add(mPreViewSurface);
892 | surfaceList.add(mRecordSurface);
893 | if (mZoomRect != null)
894 | mPreviewBuilder.set(CaptureRequest.SCALER_CROP_REGION, mZoomRect); //放大的矩阵
895 | mCameraDevice.createCaptureSession(surfaceList, new CameraCaptureSession.StateCallback() {
896 | @Override
897 | public void onConfigured(CameraCaptureSession session) {
898 | mPreviewSession = session;
899 | updatePreview();
900 | mIsRecordVideo.set(true);
901 | mMediaRecorder.start();
902 | }
903 |
904 | @Override
905 | public void onConfigureFailed(CameraCaptureSession session) {
906 |
907 | }
908 | }, mBackgroundHandler);
909 | } catch (CameraAccessException e) {
910 | e.printStackTrace();
911 | }
912 | }
913 |
914 | private void closePreviewSession() {
915 | if (mPreviewSession != null) {
916 | mPreviewSession.close();
917 | mPreviewSession = null;
918 | }
919 | }
920 |
921 | /**
922 | * 释放资源
923 | */
924 | public void destroy() {
925 | //mSurface.release();
926 | }
927 |
928 | private int getOrientation(int rotation) {
929 | return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360;
930 | }
931 |
932 | /**
933 | * 设置当前相机位置
934 | *
935 | * @param rotation
936 | */
937 | public void setDeviceRotation(int rotation) {
938 | this.mDeviceRotation = rotation;
939 | }
940 |
941 | /**
942 | * 异步保存照片
943 | */
944 | private class PhotoSaver implements Runnable {
945 |
946 | /**
947 | * 图片文件
948 | */
949 | private File mFile;
950 |
951 | /**
952 | * 拍照的图片
953 | */
954 | private Image mImage;
955 |
956 | public PhotoSaver(Image image, File file) {
957 | this.mImage = image;
958 | this.mFile = file;
959 | }
960 |
961 | @Override
962 | public void run() {
963 | ByteBuffer byteBuffer = mImage.getPlanes()[0].getBuffer();
964 | byte[] buffer = new byte[byteBuffer.remaining()];
965 | byteBuffer.get(buffer);
966 | FileOutputStream fileOutputStream = null;
967 | try {
968 | fileOutputStream = new FileOutputStream(mFile);
969 | fileOutputStream.write(buffer);
970 | } catch (Exception e) {
971 | e.printStackTrace();
972 | } finally {
973 | mImage.close();
974 | byteBuffer.clear();
975 | if (fileOutputStream != null) {
976 | try {
977 | fileOutputStream.close();
978 | } catch (IOException e) {
979 | e.printStackTrace();
980 | }
981 | }
982 | if (mTakePhotoListener != null)
983 | mTakePhotoListener.onTakePhotoFinish(mFile, mPhotoRotation,0, 0);
984 | resumePreview();
985 | }
986 | }
987 | }
988 |
989 |
990 | private static final int WAITING_LOCK = 1;
991 | private int mCameraState = 0;
992 |
993 | private CameraCaptureSession.CaptureCallback mCaptureCallback
994 | = new CameraCaptureSession.CaptureCallback() {
995 |
996 | @Override
997 | public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
998 | process(result);
999 | }
1000 |
1001 | @Override
1002 | public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult) {
1003 | process(partialResult);
1004 | }
1005 |
1006 | private void process(CaptureResult result) {
1007 | switch (mCameraState) {
1008 | case WAITING_LOCK:
1009 | boolean readyToCapture = true;
1010 | if (!mNoAFRun) {
1011 | Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
1012 | if (afState == null) {
1013 | break;
1014 | }
1015 |
1016 | // If auto-focus has reached locked state, we are ready to capture
1017 | readyToCapture =
1018 | (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED ||
1019 | afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED);
1020 | }
1021 |
1022 | // If we are running on an non-legacy device, we should also wait until
1023 | // auto-exposure and auto-white-balance have converged as well before
1024 | // taking a picture.
1025 | if (!isLegacyLocked()) {
1026 | Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
1027 | Integer awbState = result.get(CaptureResult.CONTROL_AWB_STATE);
1028 | if (aeState == null || awbState == null) {
1029 | break;
1030 | }
1031 |
1032 | readyToCapture = readyToCapture &&
1033 | aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED &&
1034 | awbState == CaptureResult.CONTROL_AWB_STATE_CONVERGED;
1035 | }
1036 |
1037 | // If we haven't finished the pre-capture sequence but have hit our maximum
1038 | // wait timeout, too bad! Begin capture anyway.
1039 | if (!readyToCapture) {
1040 | readyToCapture = true;
1041 | }
1042 |
1043 | if (readyToCapture) {
1044 | // Capture once for each user tap of the "Picture" button.
1045 | capturePhoto();
1046 | // After this, the camera will go back to the normal state of preview.
1047 | mCameraState = 0;
1048 | }
1049 | }
1050 | }
1051 | };
1052 |
1053 | /**
1054 | * 拍照的有效回调
1055 | */
1056 | private ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
1057 | @Override
1058 | public void onImageAvailable(ImageReader reader) {
1059 | if (mIsCapture) {
1060 | Image image = reader.acquireNextImage();
1061 | new Thread(new PhotoSaver(image, new File(mPhotoPath))).start();
1062 | mIsCapture = false;
1063 | }
1064 | }
1065 | };
1066 |
1067 | /**
1068 | * 打开摄像头状态回调
1069 | */
1070 | private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
1071 | @Override
1072 | public void onOpened(CameraDevice camera) {
1073 | mCameraDevice = camera;
1074 | startPreview();
1075 | }
1076 |
1077 | @Override
1078 | public void onDisconnected(CameraDevice camera) {
1079 | camera.close();
1080 | mCameraDevice = null;
1081 | }
1082 |
1083 | @Override
1084 | public void onError(CameraDevice camera, int error) {
1085 | camera.close();
1086 | mCameraDevice = null;
1087 | }
1088 | };
1089 |
1090 | private static Size chooseVideoSize(Size[] choices) {
1091 | for (Size size : choices) {
1092 | if (size.getWidth() == size.getHeight() * 4 / 3 && size.getWidth() <= 1080) {
1093 | return size;
1094 | }
1095 | }
1096 | return choices[choices.length - 1];
1097 | }
1098 |
1099 |
1100 | /**
1101 | * 选择一个适合的预览尺寸,不然有一些机型不支持
1102 | *
1103 | * @param choices
1104 | * @param textureViewWidth
1105 | * @param textureViewHeight
1106 | * @param maxWidth
1107 | * @param maxHeight
1108 | * @param aspectRatio
1109 | * @return
1110 | */
1111 | private static Size chooseOptimalSize(Size[] choices, int textureViewWidth,
1112 | int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {
1113 |
1114 | // Collect the supported resolutions that are at least as big as the preview Surface
1115 | List bigEnough = new ArrayList<>();
1116 | // Collect the supported resolutions that are smaller than the preview Surface
1117 | List notBigEnough = new ArrayList<>();
1118 | int w = aspectRatio.getWidth();
1119 | int h = aspectRatio.getHeight();
1120 | for (Size option : choices) {
1121 | if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
1122 | option.getHeight() == option.getWidth() * h / w) {
1123 | if (option.getWidth() >= textureViewWidth &&
1124 | option.getHeight() >= textureViewHeight) {
1125 | bigEnough.add(option);
1126 | } else {
1127 | notBigEnough.add(option);
1128 | }
1129 | }
1130 | }
1131 |
1132 | // Pick the smallest of those big enough. If there is no one big enough, pick the
1133 | // largest of those not big enough.
1134 | if (bigEnough.size() > 0) {
1135 | return Collections.min(bigEnough, new CompareSizesByArea());
1136 | } else if (notBigEnough.size() > 0) {
1137 | return Collections.max(notBigEnough, new CompareSizesByArea());
1138 | } else {
1139 | return choices[0];
1140 | }
1141 | }
1142 |
1143 | /**
1144 | * Compares two {@code Size}s based on their areas.
1145 | */
1146 | static class CompareSizesByArea implements Comparator {
1147 |
1148 | @Override
1149 | public int compare(Size lhs, Size rhs) {
1150 | // We cast here to ensure the multiplications won't overflow
1151 | return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
1152 | (long) rhs.getWidth() * rhs.getHeight());
1153 | }
1154 |
1155 | }
1156 |
1157 | /**
1158 | * Return true if the given array contains the given integer.
1159 | *
1160 | * @param modes array to check.
1161 | * @param mode integer to get for.
1162 | * @return true if the array contains the given integer, otherwise false.
1163 | */
1164 | private static boolean contains(int[] modes, int mode) {
1165 | if (modes == null) {
1166 | return false;
1167 | }
1168 | for (int i : modes) {
1169 | if (i == mode) {
1170 | return true;
1171 | }
1172 | }
1173 | return false;
1174 | }
1175 |
1176 | private static int clamp(int x, int min, int max) {
1177 | if (x > max) {
1178 | return max;
1179 | }
1180 | if (x < min) {
1181 | return min;
1182 | }
1183 | return x;
1184 | }
1185 | }
1186 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/administrator/camera/CoordinateTransformer.java:
--------------------------------------------------------------------------------
1 | package com.example.administrator.camera;
2 |
3 | import android.graphics.Matrix;
4 | import android.graphics.Rect;
5 | import android.graphics.RectF;
6 | import android.hardware.camera2.CameraCharacteristics;
7 |
8 | /**
9 | * Transform coordinates to and from preview coordinate space and camera driver
10 | * coordinate space.
11 | */
12 | public class CoordinateTransformer {
13 |
14 | private final Matrix mPreviewToCameraTransform;
15 | private RectF mDriverRectF;
16 |
17 | /**
18 | * Convert rectangles to / from camera coordinate and preview coordinate space.
19 | * @param chr camera characteristics
20 | * @param previewRect the preview rectangle size and position.
21 | */
22 | public CoordinateTransformer(CameraCharacteristics chr, RectF previewRect) {
23 | if (!hasNonZeroArea(previewRect)) {
24 | throw new IllegalArgumentException("previewRect");
25 | }
26 | Rect rect = chr.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
27 | Integer sensorOrientation = chr.get(CameraCharacteristics.SENSOR_ORIENTATION);
28 | int rotation = sensorOrientation == null ? 90 : sensorOrientation;
29 | mDriverRectF = new RectF(rect);
30 | Integer face = chr.get(CameraCharacteristics.LENS_FACING);
31 | boolean mirrorX = face != null && face == CameraCharacteristics.LENS_FACING_FRONT;
32 | mPreviewToCameraTransform = previewToCameraTransform(mirrorX, rotation, previewRect);
33 | }
34 |
35 | /**
36 | * Transform a rectangle in preview view space into a new rectangle in
37 | * camera view space.
38 | * @param source the rectangle in preview view space
39 | * @return the rectangle in camera view space.
40 | */
41 | public RectF toCameraSpace(RectF source) {
42 | RectF result = new RectF();
43 | mPreviewToCameraTransform.mapRect(result, source);
44 | return result;
45 | }
46 |
47 | private Matrix previewToCameraTransform(boolean mirrorX, int sensorOrientation,
48 | RectF previewRect) {
49 | Matrix transform = new Matrix();
50 | // Need mirror for front camera.
51 | transform.setScale(mirrorX ? -1 : 1, 1);
52 | // Because preview orientation is different form sensor orientation,
53 | // rotate to same orientation, Counterclockwise.
54 | transform.postRotate(-sensorOrientation);
55 | // Map rotated matrix to preview rect
56 | transform.mapRect(previewRect);
57 | // Map preview coordinates to driver coordinates
58 | Matrix fill = new Matrix();
59 | fill.setRectToRect(previewRect, mDriverRectF, Matrix.ScaleToFit.FILL);
60 | // Concat the previous transform on top of the fill behavior.
61 | transform.setConcat(fill, transform);
62 | // finally get transform matrix
63 | return transform;
64 | }
65 |
66 | private boolean hasNonZeroArea(RectF rect) {
67 | return rect.width() != 0 && rect.height() != 0;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/administrator/camera/ICamera.java:
--------------------------------------------------------------------------------
1 | package com.example.administrator.camera;
2 |
3 | import android.util.Size;
4 | import android.view.Surface;
5 | import android.view.TextureView;
6 |
7 | import java.io.File;
8 |
9 |
10 | public interface ICamera {
11 |
12 |
13 | void cameraZoom(float zoom);
14 |
15 | /**
16 | * 打开摄像头
17 | */
18 | boolean openCamera(CameraType cameraType);
19 |
20 | /**
21 | * 关闭摄像头
22 | */
23 | void closeCamera();
24 |
25 | /**
26 | * 切换摄像头
27 | * @param cameraType 切换摄像头类型
28 | * @return boolean 是否切换成功
29 | */
30 | boolean switchCamera(CameraType cameraType);
31 |
32 | /**
33 | * 开启相机的预览模式
34 | */
35 | boolean startPreview();
36 |
37 | /**
38 | * 停止预览
39 | */
40 | void resumePreview();
41 |
42 | /**
43 | * 开始视频的录制
44 | * @param path 存储路径
45 | * @param mediaType 文件类型
46 | */
47 | boolean startVideoRecord(String path, int mediaType);
48 |
49 | /**
50 | * 停止视频录制
51 | */
52 | void stopVideoRecord();
53 |
54 | /**
55 | * 拍照
56 | * @param path 存储路径
57 | * @param mediaType 文件类型
58 | */
59 | boolean takePhone(String path, MediaType mediaType);
60 |
61 | /**
62 | * 获取预览大小
63 | * @return
64 | */
65 | Size getPreViewSize();
66 |
67 | void setSurface(Surface surface);
68 |
69 | void setTextureView(TextureView textureView);
70 |
71 | void setTakePhotoListener(TakePhotoListener mTakePhotoListener);
72 |
73 | void setCameraReady(CameraReady mCameraReady);
74 |
75 | void flashSwitchState(FlashState mFlashState);
76 |
77 | void setCameraState(CameraMode cameraMode);
78 |
79 | /**
80 | * 手动请求对焦
81 | * @param x
82 | * @param y
83 | */
84 | void requestFocus(float x, float y);
85 |
86 | /**
87 | * 摄像头模式类型
88 | */
89 | enum CameraMode
90 | {
91 | RECORD_VIDEO,
92 | TAKE_PHOTO
93 | }
94 |
95 | /**
96 | * 当前只有mp4类型
97 | */
98 | enum MediaType
99 | {
100 | MP4,
101 | JPEG,
102 | }
103 |
104 | /**
105 | * 摄像头类型
106 | */
107 | enum CameraType
108 | {
109 | FRONT,
110 | BACK,
111 | USB
112 | }
113 |
114 | /**
115 | * 灯光状态
116 | */
117 | enum FlashState
118 | {
119 | CLOSE,
120 | AUTO,
121 | OPEN
122 | }
123 |
124 | interface TakePhotoListener{
125 | void onTakePhotoFinish(File file, int photoRotation, int width, int height);
126 | }
127 |
128 | interface CameraReady
129 | {
130 | void onCameraReady();
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/administrator/camera/IVideoControl.java:
--------------------------------------------------------------------------------
1 | package com.example.administrator.camera;
2 |
3 | public interface IVideoControl {
4 |
5 | /**
6 | * 播放视频
7 | */
8 | void play();
9 |
10 | /**
11 | * 暂停视频播放
12 | */
13 | void pause();
14 |
15 | /**
16 | * 继续播放视频,前提是暂停了视频
17 | */
18 | void resume();
19 |
20 | /**
21 | * 停止播放视频
22 | */
23 | void stop();
24 |
25 | /**
26 | * 进度条快进
27 | * @param timeStamp
28 | */
29 | void seekTo(int timeStamp);
30 |
31 | void setPlaySeekTimeListener(PlaySeekTimeListener mPlaySeekTimeListener);
32 |
33 | void setPlayStateListener(PlayStateListener mPlayStateListener);
34 |
35 | interface PlaySeekTimeListener
36 | {
37 | void onSeekTime(int allTime, int time);
38 | }
39 |
40 | /**
41 | * 播放状态
42 | */
43 | interface PlayStateListener{
44 |
45 | void onStartListener(int width, int height);
46 |
47 | void onCompletionListener();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/administrator/camera/MenuAdapter.java:
--------------------------------------------------------------------------------
1 | package com.example.administrator.camera;
2 |
3 | import android.content.Context;
4 | import android.graphics.Color;
5 | import android.support.v7.widget.RecyclerView;
6 | import android.util.TypedValue;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.TextView;
11 |
12 | import java.util.List;
13 |
14 | /**
15 | * Created by jianglei on 2/4/17.
16 | */
17 |
18 | public class MenuAdapter extends RecyclerView.Adapter implements AutoLocateHorizontalView.IAutoLocateHorizontalView {
19 | private Context context;
20 | private View view;
21 | private List ages;
22 | private AutoLocateHorizontalView recyclerView;
23 |
24 | public MenuAdapter(Context context, List ages, AutoLocateHorizontalView recyclerView){
25 | this.context = context;
26 | this.ages = ages;
27 | this.recyclerView = recyclerView;
28 | }
29 |
30 | @Override
31 | public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
32 | view = LayoutInflater.from(context).inflate(R.layout.item_age,parent,false);
33 | return new ItemViewHolder(view);
34 | }
35 |
36 | @Override
37 | public void onBindViewHolder(ItemViewHolder holder, int position) {
38 | holder.tvAge.setText(ages.get(position));
39 | }
40 |
41 | @Override
42 | public int getItemCount() {
43 | return ages.size();
44 | }
45 |
46 | @Override
47 | public View getItemView() {
48 | return view;
49 | }
50 |
51 | @Override
52 | public void onViewSelected(boolean isSelected, int pos, RecyclerView.ViewHolder holder, int itemWidth) {
53 | if(isSelected) {
54 | ((ItemViewHolder) holder).tvAge.setTextSize(TypedValue.COMPLEX_UNIT_SP,16);
55 | ((ItemViewHolder) holder).tvAge.setTextColor(Color.WHITE);
56 | ((ItemViewHolder) holder).tvAge.setAlpha(1.0f);
57 | }else{
58 | ((ItemViewHolder) holder).tvAge.setTextSize(TypedValue.COMPLEX_UNIT_SP,15);
59 | ((ItemViewHolder) holder).tvAge.setTextColor(Color.rgb(0xfe,0xfe,0xfe));
60 | ((ItemViewHolder) holder).tvAge.setAlpha(0.5f);
61 | }
62 | }
63 |
64 | class ItemViewHolder extends RecyclerView.ViewHolder{
65 | TextView tvAge;
66 | ItemViewHolder(View itemView) {
67 | super(itemView);
68 | tvAge = itemView.findViewById(R.id.tv_age);
69 | tvAge.setTag(this);
70 | tvAge.setOnClickListener(new View.OnClickListener() {
71 | @Override
72 | public void onClick(View v) {
73 | ItemViewHolder itemViewHolder = (ItemViewHolder)v.getTag();
74 | int position = recyclerView.getChildAdapterPosition(itemViewHolder.itemView);
75 | position--;
76 | recyclerView.moveToPosition(position);
77 | }
78 | });
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/administrator/camera/VideoPlayer.java:
--------------------------------------------------------------------------------
1 | package com.example.administrator.camera;
2 |
3 | import android.content.Context;
4 | import android.media.MediaPlayer;
5 | import android.net.Uri;
6 | import android.os.Handler;
7 | import android.os.HandlerThread;
8 | import android.view.Surface;
9 |
10 | import java.io.IOException;
11 | import java.util.concurrent.atomic.AtomicBoolean;
12 |
13 |
14 | public class VideoPlayer implements IVideoControl {
15 |
16 | private MediaPlayer mMediaPlayer;
17 | private Surface mSurface;
18 | private String mVideoFilePath;
19 |
20 | private IVideoControl.PlaySeekTimeListener mPlaySeekTimeListener;
21 | private IVideoControl.PlayStateListener mPlayStateListener;
22 |
23 | private HandlerThread mHandlerThread;
24 | private Handler mHandle;
25 |
26 | private AtomicBoolean mIsNowSeekTime;
27 |
28 | private boolean mIsLoop = false;
29 |
30 | public VideoPlayer()
31 | {
32 | mMediaPlayer = new MediaPlayer();
33 | mHandlerThread = new HandlerThread(VideoPlayer.class.getSimpleName());
34 | mHandlerThread.start();
35 | mHandle = new Handler(mHandlerThread.getLooper());
36 | mIsNowSeekTime = new AtomicBoolean(false);
37 | }
38 |
39 | public void setLoopPlay(boolean isLoop)
40 | {
41 | // mMediaPlayer.setLooping(isLoop);
42 | this.mIsLoop = isLoop;
43 | }
44 |
45 | public void setDataSourceAndPlay(Context context, Uri uri)
46 | {
47 | try {
48 | mMediaPlayer.setDataSource(context,uri);
49 | mMediaPlayer.setSurface(this.mSurface);
50 | mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
51 | @Override
52 | public boolean onError(MediaPlayer mp, int what, int extra) {
53 | stopVideoSeekTime();
54 | return false;
55 | }
56 | });
57 | mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
58 | @Override
59 | public void onPrepared(MediaPlayer mp) {
60 | play();
61 | startVideoSeekTime();
62 | }
63 | });
64 | mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
65 | @Override
66 | public void onCompletion(MediaPlayer mp) {
67 | if(mPlayStateListener != null)
68 | mPlayStateListener.onCompletionListener();
69 | if(mIsLoop)
70 | play();
71 | }
72 | });
73 | mMediaPlayer.prepareAsync();
74 | } catch (IOException e) {
75 | e.printStackTrace();
76 | mMediaPlayer = null;
77 | }
78 | }
79 |
80 | public void setDataSourceAndPlay(String path)
81 | {
82 | mVideoFilePath = path;
83 | try {
84 | mMediaPlayer.setDataSource(mVideoFilePath);
85 | mMediaPlayer.setSurface(this.mSurface);
86 | mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
87 | @Override
88 | public boolean onError(MediaPlayer mp, int what, int extra) {
89 | stopVideoSeekTime();
90 | return false;
91 | }
92 | });
93 | mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
94 | @Override
95 | public void onPrepared(MediaPlayer mp) {
96 | play();
97 | startVideoSeekTime();
98 | }
99 | });
100 | mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
101 | @Override
102 | public void onCompletion(MediaPlayer mp) {
103 | if(mPlayStateListener != null)
104 | mPlayStateListener.onCompletionListener();
105 | if(mIsLoop)
106 | play();
107 | }
108 | });
109 | mMediaPlayer.prepareAsync();
110 | } catch (IOException e) {
111 | e.printStackTrace();
112 | mMediaPlayer = null;
113 | }
114 | }
115 |
116 | @Override
117 | public void play() {
118 | if(mMediaPlayer != null && !mMediaPlayer.isPlaying()) {
119 | mMediaPlayer.start();
120 | if(mPlayStateListener != null)
121 | mPlayStateListener.onStartListener(mMediaPlayer.getVideoWidth(), mMediaPlayer.getVideoHeight());
122 | }
123 | }
124 |
125 | @Override
126 | public void pause() {
127 | if(mMediaPlayer != null && mMediaPlayer.isPlaying())
128 | mMediaPlayer.pause();
129 | }
130 |
131 | @Override
132 | public void resume() {
133 | if(mMediaPlayer != null && !mMediaPlayer.isPlaying())
134 | mMediaPlayer.start();
135 | }
136 |
137 | @Override
138 | public void stop() {
139 | if(mMediaPlayer != null) {
140 | mMediaPlayer.stop();
141 | mMediaPlayer.reset();
142 | stopVideoSeekTime();
143 | }
144 | }
145 |
146 | @Override
147 | public void seekTo(int timeStamp) {
148 | if(mMediaPlayer != null)
149 | mMediaPlayer.seekTo(timeStamp);
150 | }
151 |
152 | @Override
153 | public void setPlaySeekTimeListener(IVideoControl.PlaySeekTimeListener playSeekTimeListener) {
154 | this.mPlaySeekTimeListener = playSeekTimeListener;
155 | }
156 |
157 | @Override
158 | public void setPlayStateListener(PlayStateListener playStateListener) {
159 | this.mPlayStateListener = playStateListener;
160 | }
161 |
162 | public void setVideoPlayWindow(Surface surface)
163 | {
164 | this.mSurface = surface;
165 | }
166 |
167 | public boolean isPlaying(){
168 | return mMediaPlayer!=null && mMediaPlayer.isPlaying();
169 | }
170 |
171 | /**
172 | * 视频播放进度
173 | */
174 | private void startVideoSeekTime()
175 | {
176 | if(mPlaySeekTimeListener == null)
177 | return;
178 | mIsNowSeekTime.set(true);
179 | mHandle.post(new Runnable() {
180 | @Override
181 | public void run() {
182 | while (mIsNowSeekTime.get())
183 | {
184 | try {
185 | Thread.sleep(20);
186 | } catch (InterruptedException e) {
187 | e.printStackTrace();
188 | }
189 | if (mMediaPlayer == null)
190 | return;
191 | synchronized (mMediaPlayer) {
192 | if (mMediaPlayer == null)
193 | return;
194 | mPlaySeekTimeListener.onSeekTime(mMediaPlayer.getDuration(), mMediaPlayer.getCurrentPosition());
195 | }
196 | }
197 | }
198 | });
199 | }
200 |
201 | private void stopVideoSeekTime()
202 | {
203 | mIsNowSeekTime.set(false);
204 | }
205 |
206 | public void destroy()
207 | {
208 | mHandlerThread.quitSafely();
209 | if(mMediaPlayer != null) {
210 | synchronized (mMediaPlayer) {
211 | mMediaPlayer.release();
212 | mMediaPlayer = null;
213 | }
214 | }
215 | mSurface.release();
216 | }
217 |
218 |
219 | }
220 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/flash_auto.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-hdpi/flash_auto.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/flash_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-hdpi/flash_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/flash_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-hdpi/flash_open.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-hdpi/ic_add.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-hdpi/ic_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-hdpi/ic_delete.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_fouces.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-hdpi/ic_fouces.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_minus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-hdpi/ic_minus.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-hdpi/ic_pause.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-hdpi/ic_play.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_record.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-hdpi/ic_record.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_recording.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-hdpi/ic_recording.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-hdpi/ic_save.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_switch_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-hdpi/ic_switch_camera.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_video_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-hdpi/ic_video_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/flash_auto.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-mdpi/flash_auto.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/flash_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-mdpi/flash_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/flash_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-mdpi/flash_open.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-mdpi/ic_add.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-mdpi/ic_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-mdpi/ic_delete.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_fouces.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-mdpi/ic_fouces.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_minus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-mdpi/ic_minus.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-mdpi/ic_pause.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-mdpi/ic_play.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_record.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-mdpi/ic_record.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_recording.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-mdpi/ic_recording.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-mdpi/ic_save.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_switch_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-mdpi/ic_switch_camera.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_video_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-mdpi/ic_video_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/flash_auto.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xhdpi/flash_auto.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/flash_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xhdpi/flash_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/flash_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xhdpi/flash_open.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xhdpi/ic_add.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xhdpi/ic_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xhdpi/ic_delete.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_fouces.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xhdpi/ic_fouces.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_minus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xhdpi/ic_minus.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xhdpi/ic_pause.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xhdpi/ic_play.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_record.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xhdpi/ic_record.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_recording.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xhdpi/ic_recording.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xhdpi/ic_save.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_switch_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xhdpi/ic_switch_camera.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_video_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xhdpi/ic_video_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/flash_auto.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxhdpi/flash_auto.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/flash_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxhdpi/flash_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/flash_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxhdpi/flash_open.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxhdpi/ic_add.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxhdpi/ic_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxhdpi/ic_delete.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_fouces.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxhdpi/ic_fouces.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_minus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxhdpi/ic_minus.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxhdpi/ic_pause.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxhdpi/ic_play.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_record.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxhdpi/ic_record.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_recording.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxhdpi/ic_recording.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxhdpi/ic_save.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_switch_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxhdpi/ic_switch_camera.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_video_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxhdpi/ic_video_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/flash_auto.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxxhdpi/flash_auto.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/flash_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxxhdpi/flash_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/flash_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxxhdpi/flash_open.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxxhdpi/ic_add.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxxhdpi/ic_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxxhdpi/ic_delete.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_fouces.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxxhdpi/ic_fouces.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_minus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxxhdpi/ic_minus.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxxhdpi/ic_pause.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxxhdpi/ic_play.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_record.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxxhdpi/ic_record.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_recording.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxxhdpi/ic_recording.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxxhdpi/ic_save.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_switch_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxxhdpi/ic_switch_camera.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_video_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/app/src/main/res/drawable-xxxhdpi/ic_video_close.png
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/message_seekbar_blue.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | -
5 |
6 |
7 |
8 |
9 |
10 | -
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | -
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/message_seekbar_thumb_blue.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/video_record_seekbar_thumb_transparent.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/video_record_seekbar_transparent.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | -
5 |
6 |
7 |
8 |
9 |
10 | -
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | -
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
15 |
16 |
21 |
22 |
35 |
36 |
37 |
49 |
50 |
61 |
62 |
72 |
73 |
83 |
84 |
94 |
95 |
107 |
108 |
116 |
117 |
126 |
127 |
139 |
140 |
152 |
153 |
167 |
168 |
179 |
180 |
190 |
191 |
199 |
200 |
210 |
211 |
218 |
219 |
232 |
233 |
241 |
242 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_age.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Camera
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/file_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/test/java/com/example/administrator/camera/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.example.administrator.camera;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/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.2.1'
11 | classpath "com.jakewharton:butterknife-gradle-plugin:8.4.0"
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 | maven { url "https://jitpack.io" }
23 | }
24 | }
25 |
26 | task clean(type: Delete) {
27 | delete rootProject.buildDir
28 | }
29 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 |
15 |
16 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chensowf/Camera/5bbab7e245075030c07ba50b098e59f22efbb1c5/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------