├── .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 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | --------------------------------------------------------------------------------