├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── android │ │ └── xz │ │ └── camerademo │ │ ├── CameraActivity.java │ │ ├── MainActivity.java │ │ ├── MediaDisplayActivity.java │ │ ├── base │ │ └── BaseCameraActivity.java │ │ ├── mediacodec_activity │ │ ├── MediaCodecBufferActivity.java │ │ └── MediaCodecSurfaceActivity.java │ │ ├── util │ │ └── ScreenTools.java │ │ └── view │ │ └── CaptureButton.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable-xhdpi │ └── ic_switch_camera.png │ ├── drawable-xxhdpi │ └── ic_switch_camera.png │ ├── drawable │ ├── btn_capture_bg.xml │ ├── btn_capture_normal.xml │ ├── btn_capture_pressed.xml │ ├── ic_launcher_background.xml │ ├── img_switch_bg.xml │ ├── img_switch_normal.xml │ ├── img_switch_pressed.xml │ └── tv_timer_bg.xml │ ├── layout │ ├── activity_camera.xml │ ├── activity_display_media.xml │ ├── activity_glessurface_camera.xml │ ├── activity_glessurface_camera2.xml │ ├── activity_glsurface_camera.xml │ ├── activity_glsurface_camera2.xml │ ├── activity_gltexture_camera.xml │ ├── activity_gltexture_camera2.xml │ ├── activity_main.xml │ ├── activity_media_codec_buffer.xml │ ├── activity_media_codec_surface.xml │ ├── activity_surface_camera.xml │ ├── activity_surface_camera2.xml │ ├── activity_texture_camera.xml │ └── activity_texture_camera2.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-mdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxxhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── values-night │ └── themes.xml │ ├── values │ ├── colors.xml │ ├── strings.xml │ └── themes.xml │ └── xml │ ├── backup_rules.xml │ └── data_extraction_rules.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img ├── camera.jpg └── index.jpg ├── lib-camera ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── libs │ ├── arm64-v8a │ │ └── libyuv.so │ └── armeabi-v7a │ │ └── libyuv.so ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── android │ └── xz │ ├── camera │ ├── Camera2Manager.java │ ├── CameraManager.java │ ├── ICameraManager.java │ ├── YUVFormat.java │ ├── callback │ │ ├── CameraCallback.java │ │ ├── PictureBufferCallback.java │ │ └── PreviewBufferCallback.java │ └── view │ │ ├── Camera2GLESSurfaceView.java │ │ ├── Camera2GLSurfaceView.java │ │ ├── Camera2GLTextureView.java │ │ ├── Camera2SurfaceView.java │ │ ├── Camera2TextureView.java │ │ ├── CameraGLESSurfaceView.java │ │ ├── CameraGLSurfaceView.java │ │ ├── CameraGLTextureView.java │ │ ├── CameraSurfaceView.java │ │ ├── CameraTextureView.java │ │ └── base │ │ ├── BaseCameraView.java │ │ ├── BaseGLESSurfaceView.java │ │ ├── BaseGLSurfaceView.java │ │ ├── BaseGLTextureView.java │ │ ├── BaseSurfaceView.java │ │ ├── BaseTextureView.java │ │ ├── RenderHandler.java │ │ └── RenderThread.java │ ├── encoder │ ├── BufferMovieEncoder.java │ ├── IAudioEncoder.java │ ├── IVideoEncoder.java │ ├── MediaAudioEncoder.java │ ├── MediaEncoder.java │ ├── MediaMuxerWrapper.java │ ├── MediaRecordListener.java │ ├── MediaSurfaceEncoder.java │ ├── MediaVideoBufferEncoder.java │ ├── MediaVideoEncoder.java │ ├── TextureEncoder.java │ ├── TextureMovieEncoder.java │ ├── TextureMovieEncoder1.java │ ├── TextureMovieEncoder2.java │ └── VideoEncoderCore.java │ ├── gles │ ├── EglCore.java │ ├── EglSurfaceBase.java │ ├── GLESUtils.java │ ├── MatrixUtils.java │ ├── WindowSurface.java │ └── filiter │ │ ├── AFilter.java │ │ ├── CameraFilter.java │ │ └── Texture2DFilter.java │ ├── permission │ ├── IPermissionsResult.java │ └── PermissionUtils.java │ └── util │ ├── FileUtils.java │ ├── ImageUtils.java │ ├── Logs.java │ └── YUVUtils.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidCamera 2 | 3 | 本项目主要涉及Android开发中Camera的相关操作、预览方式、视频录制等。项目结构简单代码耦合性低,适合学习和使用 4 | 5 |

6 | 7 | 8 |

9 | 10 | 11 | 12 | ## lib-camera 13 | |包|说明 | 14 | |--|--| 15 | | camera | camera相关操作功能包,包括Camera和Camera2。以及各种预览视图 | 16 | | encoder | MediaCdoec录制视频相关,包括对ByteBuffer和Surface的录制 | 17 | | gles | opengles操作相关 | 18 | | permission | 权限相关 | 19 | | util | 工具类 | 20 | 21 | 每个包都可独立使用,耦合度低,方便白嫖:) 22 | 23 | 24 | 25 | ## 预览视图 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
类名功能说明
CameraSurfaceView1.SurfaceView+Camera
2.包含MediaCodec+Buffer录制视频
CameraTextureViewTextureView+Camera
CameraGLSurfaceView1.GLSurfaceView+Camera
2.MediaCodec+Surface录制视频,多线程共享EGL方式
CameraGLESSurfaceView1.SurfaceView+OpenGL ES+Camera
2.MediaCodec+Surface录制视频,三种渲染方式:
  • Draw Twice
  • Draw FBO
  • Draw Blit framebuffer
CameraGLTextureView1.TextureView+OpenGL ES+Camera
2.MediaCodec+Surface录制视频,三种渲染方式:
  • Draw Twice
  • Draw FBO
  • Draw Blit framebuffer
Camera2SurfaceViewSurfaceView+Camera2
Camera2TextureViewTextureView+Camera2
Camera2GLSurfaceView1.GLSurfaceView+Camera2
2.MediaCodec+Surface录制视频,多线程共享EGL方式
Camera2GLESSurfaceView1.SurfaceView+OpenGL ES+Camera2
2.MediaCodec+Surface录制视频,三种渲染方式:
  • Draw Twice
  • Draw FBO
  • Draw Blit framebuffer
Camera2GLTextureView1.TextureView+OpenGL ES+Camera2
2.MediaCodec+Surface录制视频,三种渲染方式:
  • Draw Twice
  • Draw FBO
  • Draw Blit framebuffer
75 | 76 | 77 | 78 | ## FAQ 79 | 80 | ### 1.如何切换预览尺寸 81 | 82 | `ICameraManager`提供了`setPreviewSize(Size size)`接口可以在openCamera之前设置想要的预览尺寸 83 | 84 | ### 2.如何获取预览帧数据 85 | 86 | `ICameraManager`提供了`addPreviewBufferCallback(PreviewBufferCallback previewBufferCallback)`接口可以在回调中获取Camera预览数据,格式为**NV21** 87 | 88 | > 注意:获取的byte[]是可复用的,需要您自行arrayCopy一份使用 89 | 90 | ### 3.如何拍照 91 | 92 | `ICameraManager`提供了`takePicture(PictureBufferCallback pictureCallback)`接口可以在回调中获取拍照数据,格式为**JPG** 93 | 94 | 95 | 96 | ## Blog 97 | 98 | Camera系列 99 | 100 | [Android Camera系列(一):SurfaceView+Camera](https://blog.csdn.net/xiaozhiwz/article/details/141472537) 101 | 102 | [Android Camera系列(二):TextureView+Camera](https://blog.csdn.net/xiaozhiwz/article/details/141855031) 103 | 104 | [Android Camera系列(三):GLSurfaceView+Camera](https://blog.csdn.net/xiaozhiwz/article/details/141860162) 105 | 106 | [Android Camera系列(四):TextureView+OpenGL ES+Camera](https://blog.csdn.net/xiaozhiwz/article/details/142781497) 107 | 108 | [Android Camera系列(五):Camera2](https://blog.csdn.net/xiaozhiwz/article/details/142555345) 109 | 110 | [Android Camera系列(六):MediaCodec视频编码上-编码YUV](https://blog.csdn.net/xiaozhiwz/article/details/143114530) 111 | 112 | [Android Camera系列(七):MediaCodec视频编码中-OpenGL ES多线程渲染](https://blog.csdn.net/xiaozhiwz/article/details/143144103) 113 | 114 | [Android Camera系列(八):MediaCodec视频编码下-OpenGL ES离屏渲染](https://blog.csdn.net/xiaozhiwz/article/details/144508534) 115 | 116 | 117 | 118 | OpenGL ES系列 119 | 120 | [Android OpenGLES开发:EGL环境搭建](https://blog.csdn.net/xiaozhiwz/article/details/141868444) 121 | 122 | [Android OpenGLES2.0开发(一):艰难的开始](https://blog.csdn.net/xiaozhiwz/article/details/142354149) 123 | 124 | [Android OpenGLES2.0开发(二):环境搭建](https://blog.csdn.net/xiaozhiwz/article/details/142366766) 125 | 126 | [Android OpenGLES2.0开发(三):绘制一个三角形](https://blog.csdn.net/xiaozhiwz/article/details/142453506) 127 | 128 | [Android OpenGLES2.0开发(四):矩阵变换和相机投影](https://blog.csdn.net/xiaozhiwz/article/details/142488394) 129 | 130 | [Android OpenGLES2.0开发(五):绘制正方形和圆形](https://blog.csdn.net/xiaozhiwz/article/details/142530158) 131 | 132 | [Android OpenGLES2.0开发(六):着色器语言GLSL](https://blog.csdn.net/xiaozhiwz/article/details/142790866) 133 | 134 | [Android OpenGLES2.0开发(七):纹理贴图之显示图片](https://blog.csdn.net/xiaozhiwz/article/details/142871148) 135 | 136 | [Android OpenGLES2.0开发(八):Camera预览](https://blog.csdn.net/xiaozhiwz/article/details/143239446) 137 | 138 | [Android OpenGLES2.0开发(九):图片滤镜](https://blog.csdn.net/xiaozhiwz/article/details/143847341) 139 | 140 | [Android OpenGLES2.0开发(十):FBO离屏渲染](https://blog.csdn.net/xiaozhiwz/article/details/144554451) 141 | 142 | [Android OpenGLES2.0开发(十一):渲染YUV](https://blog.csdn.net/xiaozhiwz/article/details/142589796) 143 | 144 | 145 | 146 | 147 | 148 | 参考: 149 | 150 | 1. [https://github.com/afei-cn/CameraDemo](https://github.com/afei-cn/CameraDemo) 151 | 2. [https://github.com/saki4510t/UVCCamera](https://github.com/saki4510t/UVCCamera) 152 | 3. [https://github.com/google/grafika](https://github.com/google/grafika) -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /release 3 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | namespace 'com.android.xz.camerademo' 7 | compileSdk 34 8 | 9 | defaultConfig { 10 | applicationId "com.android.xz.camerademo" 11 | minSdk 21 12 | targetSdk 33 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | } 30 | 31 | dependencies { 32 | implementation 'androidx.appcompat:appcompat:1.6.1' 33 | implementation 'com.google.android.material:material:1.5.0' 34 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 35 | implementation project(':lib-camera') 36 | } -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 35 | 38 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/xz/camerademo/CameraActivity.java: -------------------------------------------------------------------------------- 1 | package com.android.xz.camerademo; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | 8 | import com.android.xz.camerademo.base.BaseCameraActivity; 9 | 10 | public class CameraActivity extends BaseCameraActivity { 11 | 12 | public static final String EXTRA_LAYOUT_ID = "com.android.xz.camera.EXTRA_LAYOUT_ID"; 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | } 18 | 19 | @Override 20 | public int getLayoutId() { 21 | Intent intent = getIntent(); 22 | return intent.getIntExtra(EXTRA_LAYOUT_ID, R.layout.activity_surface_camera); 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android/xz/camerademo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.android.xz.camerademo; 2 | 3 | import android.Manifest; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.view.View; 8 | import android.widget.Toast; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.annotation.Nullable; 12 | import androidx.appcompat.app.AppCompatActivity; 13 | 14 | import com.android.xz.camerademo.mediacodec_activity.MediaCodecBufferActivity; 15 | import com.android.xz.camerademo.mediacodec_activity.MediaCodecSurfaceActivity; 16 | import com.android.xz.permission.IPermissionsResult; 17 | import com.android.xz.permission.PermissionUtils; 18 | import com.android.xz.util.ImageUtils; 19 | 20 | public class MainActivity extends AppCompatActivity implements View.OnClickListener { 21 | 22 | private Context mContext; 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.activity_main); 28 | mContext = this; 29 | ImageUtils.init(getApplicationContext()); 30 | 31 | findViewById(R.id.surfaceCameraBtn).setOnClickListener(this); 32 | findViewById(R.id.textureCameraBtn).setOnClickListener(this); 33 | findViewById(R.id.glTextureCameraBtn).setOnClickListener(this); 34 | findViewById(R.id.glSurfaceCameraBtn).setOnClickListener(this); 35 | findViewById(R.id.surfaceCamera2Btn).setOnClickListener(this); 36 | findViewById(R.id.textureCamera2Btn).setOnClickListener(this); 37 | findViewById(R.id.glSurfaceCamera2Btn).setOnClickListener(this); 38 | findViewById(R.id.glTextureCamera2Btn).setOnClickListener(this); 39 | findViewById(R.id.glSurfaceHolderCameraBtn).setOnClickListener(this); 40 | findViewById(R.id.glSurfaceHolderCamera2Btn).setOnClickListener(this); 41 | findViewById(R.id.mediaCodecBufferBtn).setOnClickListener(this); 42 | findViewById(R.id.mediaCodecSurfaceBtn).setOnClickListener(this); 43 | } 44 | 45 | @Override 46 | public void onClick(View v) { 47 | PermissionUtils.getInstance().requestPermission(this, new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO}, new IPermissionsResult() { 48 | @Override 49 | public void passPermissions() { 50 | switch (v.getId()) { 51 | case R.id.surfaceCameraBtn: 52 | startCameraActivity(R.layout.activity_surface_camera); 53 | break; 54 | case R.id.textureCameraBtn: 55 | startCameraActivity(R.layout.activity_texture_camera); 56 | break; 57 | case R.id.glTextureCameraBtn: 58 | startCameraActivity(R.layout.activity_gltexture_camera); 59 | break; 60 | case R.id.glSurfaceCameraBtn: 61 | startCameraActivity(R.layout.activity_glsurface_camera); 62 | break; 63 | case R.id.glSurfaceHolderCameraBtn: 64 | startCameraActivity(R.layout.activity_glessurface_camera); 65 | break; 66 | case R.id.surfaceCamera2Btn: 67 | startCameraActivity(R.layout.activity_surface_camera2); 68 | break; 69 | case R.id.textureCamera2Btn: 70 | startCameraActivity(R.layout.activity_texture_camera2); 71 | break; 72 | case R.id.glSurfaceCamera2Btn: 73 | startCameraActivity(R.layout.activity_glsurface_camera2); 74 | break; 75 | case R.id.glTextureCamera2Btn: 76 | startCameraActivity(R.layout.activity_gltexture_camera2); 77 | break; 78 | case R.id.glSurfaceHolderCamera2Btn: 79 | startCameraActivity(R.layout.activity_glessurface_camera2); 80 | break; 81 | case R.id.mediaCodecBufferBtn: 82 | startActivity(new Intent(mContext, MediaCodecBufferActivity.class)); 83 | break; 84 | case R.id.mediaCodecSurfaceBtn: 85 | startActivity(new Intent(mContext, MediaCodecSurfaceActivity.class)); 86 | break; 87 | } 88 | } 89 | 90 | @Override 91 | public void forbidPermissions() { 92 | Toast.makeText(mContext, "用户拒绝Camera授权", Toast.LENGTH_SHORT).show(); 93 | } 94 | }); 95 | } 96 | 97 | private void startCameraActivity(int layoutId) { 98 | Intent intent = new Intent(this, CameraActivity.class); 99 | intent.putExtra(CameraActivity.EXTRA_LAYOUT_ID, layoutId); 100 | startActivity(intent); 101 | } 102 | 103 | @Override 104 | protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 105 | super.onActivityResult(requestCode, resultCode, data); 106 | PermissionUtils.getInstance().onActivityResult(requestCode, resultCode, data); 107 | } 108 | 109 | @Override 110 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 111 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 112 | PermissionUtils.getInstance().onRequestPermissionsResult(this, requestCode, permissions, grantResults); 113 | } 114 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android/xz/camerademo/MediaDisplayActivity.java: -------------------------------------------------------------------------------- 1 | package com.android.xz.camerademo; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | 5 | import android.graphics.Bitmap; 6 | import android.graphics.BitmapFactory; 7 | import android.media.MediaPlayer; 8 | import android.net.Uri; 9 | import android.os.Bundle; 10 | import android.util.Size; 11 | import android.view.View; 12 | import android.view.ViewTreeObserver; 13 | import android.view.Window; 14 | import android.view.WindowManager; 15 | import android.widget.ImageView; 16 | import android.widget.MediaController; 17 | import android.widget.VideoView; 18 | 19 | import com.android.xz.camerademo.util.ScreenTools; 20 | import com.android.xz.util.ImageUtils; 21 | import com.android.xz.util.Logs; 22 | 23 | import java.io.File; 24 | import java.io.FileNotFoundException; 25 | import java.io.InputStream; 26 | 27 | public class MediaDisplayActivity extends AppCompatActivity { 28 | 29 | public static final String EXTRA_MEDIA_PATH = "com.android.xz.media_path"; 30 | 31 | private static final String TAG = MediaDisplayActivity.class.getSimpleName(); 32 | 33 | private String mMediaPath; 34 | private VideoView mVideoView; 35 | private ImageView mImageView; 36 | 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | ScreenTools.setTransparentStatusBar(this); 41 | setContentView(R.layout.activity_display_media); 42 | 43 | mMediaPath = getIntent().getStringExtra(EXTRA_MEDIA_PATH); 44 | mVideoView = findViewById(R.id.videoView); 45 | mImageView = findViewById(R.id.imageView); 46 | 47 | if (mMediaPath.endsWith("mp4")) { 48 | displayVideo(); 49 | } else { 50 | displayImage(); 51 | } 52 | } 53 | 54 | private void displayVideo() { 55 | mVideoView.setVisibility(View.VISIBLE); 56 | mImageView.setVisibility(View.GONE); 57 | // 设置path会报java.io.FileNotFoundException: No content provider 警告 58 | // mVideoView.setVideoPath(mVideoPath); 59 | mVideoView.setVideoURI(Uri.fromFile(new File(mMediaPath))); 60 | 61 | // 创建媒体控制器(MediaController) 62 | MediaController mediaController = new MediaController(this); 63 | mediaController.setAnchorView(mVideoView); 64 | 65 | // 关联媒体控制器 66 | mVideoView.setMediaController(mediaController); 67 | 68 | // 开始播放视频 69 | mVideoView.setOnCompletionListener(mp -> Logs.i(TAG, "onCompletion...")); 70 | mVideoView.setOnPreparedListener(mp -> { 71 | Logs.i(TAG, "onPrepared..."); 72 | mp.start(); 73 | }); 74 | } 75 | 76 | private void displayImage() { 77 | mVideoView.setVisibility(View.GONE); 78 | mImageView.setVisibility(View.VISIBLE); 79 | mImageView.getViewTreeObserver().addOnGlobalLayoutListener(() -> new Thread(() -> { 80 | Bitmap bitmap = ImageUtils.getCorrectOrientationBitmap(mMediaPath, new Size(mImageView.getMeasuredWidth(), mImageView.getMeasuredHeight())); 81 | mImageView.post(() -> mImageView.setImageBitmap(bitmap)); 82 | }).start()); 83 | } 84 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android/xz/camerademo/base/BaseCameraActivity.java: -------------------------------------------------------------------------------- 1 | package com.android.xz.camerademo.base; 2 | 3 | import android.content.Intent; 4 | import android.graphics.Bitmap; 5 | import android.os.AsyncTask; 6 | import android.os.Bundle; 7 | import android.util.Size; 8 | import android.view.View; 9 | import android.widget.ImageView; 10 | 11 | import androidx.appcompat.app.AppCompatActivity; 12 | 13 | import com.android.xz.camera.ICameraManager; 14 | import com.android.xz.camerademo.MediaDisplayActivity; 15 | import com.android.xz.camerademo.R; 16 | import com.android.xz.util.ImageUtils; 17 | import com.android.xz.camera.view.base.BaseCameraView; 18 | 19 | import java.util.concurrent.Executors; 20 | 21 | /** 22 | * 展示相机页面 23 | * 24 | * @author xiaozhi 25 | */ 26 | public abstract class BaseCameraActivity extends AppCompatActivity { 27 | 28 | protected ICameraManager mCameraManager; 29 | protected ImageView mPictureIv; 30 | protected BaseCameraView mBaseCameraView; 31 | 32 | @Override 33 | protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | setContentView(getLayoutId()); 36 | mBaseCameraView = findViewById(R.id.cameraView); 37 | mCameraManager = mBaseCameraView.getCameraManager(); 38 | findViewById(R.id.captureBtn).setOnClickListener(v -> capture()); 39 | findViewById(R.id.switchCameraBtn).setOnClickListener(v -> mCameraManager.switchCamera()); 40 | mPictureIv = findViewById(R.id.pictureIv); 41 | mPictureIv.setOnClickListener(v -> { 42 | String path = (String) v.getTag(); 43 | Intent intent = new Intent(this, MediaDisplayActivity.class); 44 | intent.putExtra(MediaDisplayActivity.EXTRA_MEDIA_PATH, path); 45 | startActivity(intent); 46 | }); 47 | } 48 | 49 | /** 50 | * 设置不同的layout id 51 | * 52 | * @return 53 | */ 54 | public abstract int getLayoutId(); 55 | 56 | @Override 57 | public void setContentView(View view) { 58 | super.setContentView(view); 59 | } 60 | 61 | @Override 62 | protected void onResume() { 63 | super.onResume(); 64 | mBaseCameraView.onResume(); 65 | } 66 | 67 | @Override 68 | protected void onPause() { 69 | super.onPause(); 70 | mBaseCameraView.onPause(); 71 | } 72 | 73 | @Override 74 | protected void onDestroy() { 75 | super.onDestroy(); 76 | mBaseCameraView.onDestroy(); 77 | } 78 | 79 | protected void capture() { 80 | mCameraManager.takePicture(data -> { 81 | new ImageSaveTask().executeOnExecutor(Executors.newSingleThreadExecutor(), data); // 保存图片 82 | }); 83 | } 84 | 85 | private class ImageSaveTask extends AsyncTask { 86 | 87 | private String path; 88 | 89 | @Override 90 | protected Bitmap doInBackground(byte[]... bytes) { 91 | path = ImageUtils.saveImage(bytes[0]); 92 | return ImageUtils.getCorrectOrientationBitmap(path, new Size(mPictureIv.getMeasuredWidth(), mPictureIv.getMeasuredHeight())); 93 | } 94 | 95 | @Override 96 | protected void onPostExecute(Bitmap bitmap) { 97 | mPictureIv.setImageBitmap(bitmap); 98 | mPictureIv.setTag(path); 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android/xz/camerademo/mediacodec_activity/MediaCodecBufferActivity.java: -------------------------------------------------------------------------------- 1 | package com.android.xz.camerademo.mediacodec_activity; 2 | 3 | import android.content.Intent; 4 | import android.graphics.Bitmap; 5 | import android.media.MediaMetadataRetriever; 6 | import android.os.AsyncTask; 7 | import android.os.Bundle; 8 | import android.util.Size; 9 | import android.view.View; 10 | import android.widget.ImageView; 11 | import android.widget.TextView; 12 | 13 | import androidx.appcompat.app.AppCompatActivity; 14 | 15 | import com.android.xz.camera.ICameraManager; 16 | import com.android.xz.camera.view.CameraSurfaceView; 17 | import com.android.xz.camerademo.MediaDisplayActivity; 18 | import com.android.xz.camerademo.R; 19 | import com.android.xz.camerademo.view.CaptureButton; 20 | import com.android.xz.encoder.MediaRecordListener; 21 | import com.android.xz.util.ImageUtils; 22 | 23 | import java.util.Timer; 24 | import java.util.TimerTask; 25 | import java.util.concurrent.Executors; 26 | 27 | public class MediaCodecBufferActivity extends AppCompatActivity { 28 | 29 | private static final String TAG = MediaCodecBufferActivity.class.getSimpleName(); 30 | private CameraSurfaceView mCameraSurfaceView; 31 | private ICameraManager mCameraManager; 32 | private ImageView mPictureIv; 33 | private CaptureButton mCaptureBtn; 34 | private TextView mTimeTv; 35 | private Timer mTimer = new Timer(); 36 | private TimerTask mTimerTask; 37 | 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | setContentView(R.layout.activity_media_codec_buffer); 42 | 43 | mCameraSurfaceView = findViewById(R.id.cameraView); 44 | mCameraSurfaceView.setRecordListener(mRecordListener); 45 | mCameraManager = mCameraSurfaceView.getCameraManager(); 46 | mCaptureBtn = findViewById(R.id.captureBtn); 47 | mCaptureBtn.setClickListener(mClickListener); 48 | mTimeTv = findViewById(R.id.timeTv); 49 | findViewById(R.id.switchCameraBtn).setOnClickListener(v -> mCameraManager.switchCamera()); 50 | mPictureIv = findViewById(R.id.pictureIv); 51 | mPictureIv.setOnClickListener(v -> { 52 | String path = (String) v.getTag(); 53 | Intent intent = new Intent(this, MediaDisplayActivity.class); 54 | intent.putExtra(MediaDisplayActivity.EXTRA_MEDIA_PATH, path); 55 | startActivity(intent); 56 | }); 57 | } 58 | 59 | @Override 60 | protected void onResume() { 61 | super.onResume(); 62 | mCameraSurfaceView.onResume(); 63 | } 64 | 65 | @Override 66 | protected void onPause() { 67 | super.onPause(); 68 | mCameraSurfaceView.onPause(); 69 | } 70 | 71 | private void capture() { 72 | mCameraManager.takePicture(data -> { 73 | new ImageSaveTask().executeOnExecutor(Executors.newSingleThreadExecutor(), data); // 保存图片 74 | }); 75 | } 76 | 77 | private void startRecord() { 78 | mCameraSurfaceView.startRecord(); 79 | } 80 | 81 | private void stopRecord() { 82 | mCameraSurfaceView.stopRecord(); 83 | } 84 | 85 | private final CaptureButton.ClickListener mClickListener = new CaptureButton.ClickListener() { 86 | @Override 87 | public void onCapture() { 88 | capture(); 89 | } 90 | 91 | @Override 92 | public void onStartRecord() { 93 | startRecord(); 94 | } 95 | 96 | @Override 97 | public void onStopRecord() { 98 | stopRecord(); 99 | } 100 | }; 101 | 102 | private final MediaRecordListener mRecordListener = new MediaRecordListener() { 103 | @Override 104 | public void onStart() { 105 | mTimeTv.setVisibility(View.VISIBLE); 106 | mTimer.scheduleAtFixedRate(mTimerTask = new RecordTimerTask(), 0, 1000); 107 | } 108 | 109 | @Override 110 | public void onStopped(String videoPath) { 111 | new VideoTask().executeOnExecutor(Executors.newSingleThreadExecutor(), videoPath); 112 | mPictureIv.setTag(videoPath); 113 | mCaptureBtn.stopRecord(); 114 | mTimeTv.setVisibility(View.GONE); 115 | if (mTimerTask != null) { 116 | mTimerTask.cancel(); 117 | mTimerTask = null; 118 | } 119 | } 120 | }; 121 | 122 | private class VideoTask extends AsyncTask { 123 | 124 | @Override 125 | protected Bitmap doInBackground(String... strings) { 126 | String videoPath = strings[0]; 127 | return getVideoThumb(videoPath); 128 | } 129 | 130 | @Override 131 | protected void onPostExecute(Bitmap bitmap) { 132 | super.onPostExecute(bitmap); 133 | mPictureIv.setImageBitmap(bitmap); 134 | } 135 | } 136 | 137 | private class ImageSaveTask extends AsyncTask { 138 | 139 | private String path; 140 | 141 | @Override 142 | protected Bitmap doInBackground(byte[]... bytes) { 143 | path = ImageUtils.saveImage(bytes[0]); 144 | return ImageUtils.getCorrectOrientationBitmap(path, new Size(mPictureIv.getMeasuredWidth(), mPictureIv.getMeasuredHeight())); 145 | } 146 | 147 | @Override 148 | protected void onPostExecute(Bitmap bitmap) { 149 | mPictureIv.setImageBitmap(bitmap); 150 | mPictureIv.setTag(path); 151 | } 152 | } 153 | 154 | private class RecordTimerTask extends TimerTask { 155 | 156 | private int mRecordSeconds; 157 | 158 | @Override 159 | public void run() { 160 | int hours = mRecordSeconds / 3600; 161 | mRecordSeconds %= 3600; 162 | int minutes = mRecordSeconds / 60; 163 | mRecordSeconds %= 60; 164 | int remainingSeconds = mRecordSeconds; 165 | 166 | String formattedTime = String.format("%02d:%02d:%02d", hours, minutes, remainingSeconds); 167 | mTimeTv.post(() -> mTimeTv.setText(formattedTime)); 168 | 169 | mRecordSeconds++; 170 | } 171 | } 172 | 173 | /** 174 | * 获取视频文件第一帧图 175 | * 176 | * @param path 视频文件的路径 177 | * @return Bitmap 返回获取的Bitmap 178 | */ 179 | public static Bitmap getVideoThumb(String path) { 180 | MediaMetadataRetriever media = new MediaMetadataRetriever(); 181 | media.setDataSource(path); 182 | return media.getFrameAtTime(); 183 | } 184 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android/xz/camerademo/mediacodec_activity/MediaCodecSurfaceActivity.java: -------------------------------------------------------------------------------- 1 | package com.android.xz.camerademo.mediacodec_activity; 2 | 3 | import android.content.Intent; 4 | import android.graphics.Bitmap; 5 | import android.media.MediaMetadataRetriever; 6 | import android.os.AsyncTask; 7 | import android.os.Bundle; 8 | import android.util.Size; 9 | import android.view.View; 10 | import android.widget.ImageView; 11 | import android.widget.TextView; 12 | 13 | import androidx.appcompat.app.AppCompatActivity; 14 | 15 | import com.android.xz.camera.CameraManager; 16 | import com.android.xz.camera.view.CameraGLSurfaceView; 17 | import com.android.xz.camerademo.MediaDisplayActivity; 18 | import com.android.xz.camerademo.R; 19 | import com.android.xz.camerademo.view.CaptureButton; 20 | import com.android.xz.encoder.MediaRecordListener; 21 | import com.android.xz.util.ImageUtils; 22 | 23 | import java.util.Timer; 24 | import java.util.TimerTask; 25 | import java.util.concurrent.Executors; 26 | 27 | public class MediaCodecSurfaceActivity extends AppCompatActivity { 28 | 29 | private static final String TAG = MediaCodecSurfaceActivity.class.getSimpleName(); 30 | private CameraGLSurfaceView mCameraGLSurfaceView; 31 | private CameraManager mCameraManager; 32 | private ImageView mPictureIv; 33 | private CaptureButton mCaptureBtn; 34 | private TextView mTimeTv; 35 | private Timer mTimer = new Timer(); 36 | private TimerTask mTimerTask; 37 | 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | setContentView(R.layout.activity_media_codec_surface); 42 | 43 | mCameraGLSurfaceView = findViewById(R.id.cameraView); 44 | mCameraGLSurfaceView.setRecordListener(mRecordListener); 45 | mCameraManager = (CameraManager) mCameraGLSurfaceView.getCameraManager(); 46 | mCaptureBtn = findViewById(R.id.captureBtn); 47 | mTimeTv = findViewById(R.id.timeTv); 48 | mCaptureBtn.setClickListener(mClickListener); 49 | findViewById(R.id.switchCameraBtn).setOnClickListener(v -> mCameraManager.switchCamera()); 50 | mPictureIv = findViewById(R.id.pictureIv); 51 | mPictureIv.setOnClickListener(v -> { 52 | String path = (String) v.getTag(); 53 | Intent intent = new Intent(this, MediaDisplayActivity.class); 54 | intent.putExtra(MediaDisplayActivity.EXTRA_MEDIA_PATH, path); 55 | startActivity(intent); 56 | }); 57 | } 58 | 59 | @Override 60 | protected void onResume() { 61 | super.onResume(); 62 | mCameraGLSurfaceView.onResume(); 63 | } 64 | 65 | @Override 66 | protected void onPause() { 67 | super.onPause(); 68 | mCameraGLSurfaceView.onPause(); 69 | } 70 | 71 | @Override 72 | protected void onDestroy() { 73 | super.onDestroy(); 74 | mCameraGLSurfaceView.onDestroy(); 75 | } 76 | 77 | private void capture() { 78 | mCameraManager.takePicture(data -> { 79 | new ImageSaveTask().executeOnExecutor(Executors.newSingleThreadExecutor(), data); // 保存图片 80 | }); 81 | } 82 | 83 | private void startRecord() { 84 | mCameraGLSurfaceView.startRecord(); 85 | } 86 | 87 | private void stopRecord() { 88 | mCameraGLSurfaceView.stopRecord(); 89 | } 90 | 91 | private final CaptureButton.ClickListener mClickListener = new CaptureButton.ClickListener() { 92 | @Override 93 | public void onCapture() { 94 | capture(); 95 | } 96 | 97 | @Override 98 | public void onStartRecord() { 99 | startRecord(); 100 | } 101 | 102 | @Override 103 | public void onStopRecord() { 104 | stopRecord(); 105 | } 106 | }; 107 | 108 | private final MediaRecordListener mRecordListener = new MediaRecordListener() { 109 | @Override 110 | public void onStart() { 111 | mTimeTv.setVisibility(View.VISIBLE); 112 | mTimer.scheduleAtFixedRate(mTimerTask = new RecordTimerTask(), 0, 1000); 113 | } 114 | 115 | @Override 116 | public void onStopped(String videoPath) { 117 | new VideoTask().executeOnExecutor(Executors.newSingleThreadExecutor(), videoPath); 118 | mPictureIv.setTag(videoPath); 119 | mCaptureBtn.stopRecord(); 120 | mTimeTv.setVisibility(View.GONE); 121 | if (mTimerTask != null) { 122 | mTimerTask.cancel(); 123 | mTimerTask = null; 124 | } 125 | } 126 | }; 127 | 128 | private class VideoTask extends AsyncTask { 129 | 130 | @Override 131 | protected Bitmap doInBackground(String... strings) { 132 | String videoPath = strings[0]; 133 | return getVideoThumb(videoPath); 134 | } 135 | 136 | @Override 137 | protected void onPostExecute(Bitmap bitmap) { 138 | super.onPostExecute(bitmap); 139 | mPictureIv.setImageBitmap(bitmap); 140 | } 141 | } 142 | 143 | private class ImageSaveTask extends AsyncTask { 144 | 145 | private String path; 146 | 147 | @Override 148 | protected Bitmap doInBackground(byte[]... bytes) { 149 | path = ImageUtils.saveImage(bytes[0]); 150 | return ImageUtils.getCorrectOrientationBitmap(path, new Size(mPictureIv.getMeasuredWidth(), mPictureIv.getMeasuredHeight())); 151 | } 152 | 153 | @Override 154 | protected void onPostExecute(Bitmap bitmap) { 155 | mPictureIv.setImageBitmap(bitmap); 156 | mPictureIv.setTag(path); 157 | } 158 | } 159 | 160 | private class RecordTimerTask extends TimerTask { 161 | 162 | private int mRecordSeconds; 163 | 164 | @Override 165 | public void run() { 166 | int hours = mRecordSeconds / 3600; 167 | mRecordSeconds %= 3600; 168 | int minutes = mRecordSeconds / 60; 169 | mRecordSeconds %= 60; 170 | int remainingSeconds = mRecordSeconds; 171 | 172 | String formattedTime = String.format("%02d:%02d:%02d", hours, minutes, remainingSeconds); 173 | mTimeTv.post(() -> mTimeTv.setText(formattedTime)); 174 | 175 | mRecordSeconds++; 176 | } 177 | } 178 | 179 | /** 180 | * 获取视频文件第一帧图 181 | * 182 | * @param path 视频文件的路径 183 | * @return Bitmap 返回获取的Bitmap 184 | */ 185 | public static Bitmap getVideoThumb(String path) { 186 | MediaMetadataRetriever media = new MediaMetadataRetriever(); 187 | media.setDataSource(path); 188 | return media.getFrameAtTime(); 189 | } 190 | } -------------------------------------------------------------------------------- /app/src/main/java/com/android/xz/camerademo/util/ScreenTools.java: -------------------------------------------------------------------------------- 1 | package com.android.xz.camerademo.util; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.graphics.Color; 6 | import android.os.Build; 7 | import android.util.DisplayMetrics; 8 | import android.view.View; 9 | import android.view.Window; 10 | import android.view.WindowManager; 11 | 12 | public class ScreenTools { 13 | 14 | /** 15 | * 设置透明状态栏 16 | *

17 | *

18 | *

19 | * 可在Activity的onCreat()中调用 20 | *

21 | *

22 | *

23 | * 注意:需在顶部控件布局中加入以下属性让内容出现在状态栏之下: 24 | *

25 | * android:clipToPadding="true" // true 会贴近上层布局 ; false 与上层布局有一定间隙 26 | *

27 | * android:fitsSystemWindows="true" //true 会保留actionBar,title,虚拟键的空间 ; false 不保留 28 | * 29 | * @paramactivity activity 30 | */ 31 | public static void setTransparentStatusBar(Activity activity) { 32 | // 5.0及以上 33 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 34 | View decorView = activity.getWindow().getDecorView(); 35 | int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 36 | 37 | | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; 38 | decorView.setSystemUiVisibility(option); 39 | activity.getWindow().setStatusBarColor(Color.TRANSPARENT); 40 | // 4.4到5.0 41 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 42 | WindowManager.LayoutParams localLayoutParams = activity.getWindow().getAttributes(); 43 | localLayoutParams.flags = (WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS | localLayoutParams.flags); 44 | } 45 | } 46 | 47 | /** 48 | * 修改状态栏颜色,支持4.4以上版本 49 | * 50 | * @param activity 51 | * @param colorId 52 | */ 53 | public static void setStatusBarColor(Activity activity, int colorId) { 54 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 55 | Window window = activity.getWindow(); 56 | window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 57 | window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 58 | window.setStatusBarColor(activity.getResources().getColor(colorId)); 59 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 60 | //使用SystemBarTint库使4.4版本状态栏变色,需要先将状态栏设置为透明 61 | // transparencyBar(activity); 62 | // SystemBarTintManager tintManager = new SystemBarTintManager(activity); 63 | // tintManager.setStatusBarTintEnabled(true); 64 | // tintManager.setStatusBarTintResource(colorId); 65 | } 66 | } 67 | 68 | /** 69 | * Google原生修改状态栏文字颜色 70 | * 71 | * @param activity 72 | * @param dark 73 | */ 74 | public static void setAndroidNativeLightStatusBar(Activity activity, boolean dark) { 75 | View decor = activity.getWindow().getDecorView(); 76 | if (dark) { 77 | decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); 78 | } else { 79 | decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 80 | } 81 | } 82 | 83 | /** 84 | * 获得屏幕高度 85 | * 86 | * @param context 87 | * @return 88 | */ 89 | public static int getScreenWidth(Context context) { 90 | WindowManager wm = (WindowManager) context 91 | .getSystemService(Context.WINDOW_SERVICE); 92 | DisplayMetrics outMetrics = new DisplayMetrics(); 93 | wm.getDefaultDisplay().getMetrics(outMetrics); 94 | return outMetrics.widthPixels; 95 | } 96 | 97 | /** 98 | * 获得屏幕宽度 99 | * 100 | * @param context 101 | * @return 102 | */ 103 | public static int getScreenHeight(Context context) { 104 | WindowManager wm = (WindowManager) context 105 | .getSystemService(Context.WINDOW_SERVICE); 106 | DisplayMetrics outMetrics = new DisplayMetrics(); 107 | wm.getDefaultDisplay().getMetrics(outMetrics); 108 | return outMetrics.heightPixels; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/xz/camerademo/view/CaptureButton.java: -------------------------------------------------------------------------------- 1 | package com.android.xz.camerademo.view; 2 | 3 | import android.animation.AnimatorSet; 4 | import android.animation.ObjectAnimator; 5 | import android.animation.ValueAnimator; 6 | import android.content.Context; 7 | import android.graphics.Canvas; 8 | import android.graphics.Color; 9 | import android.graphics.Paint; 10 | import android.graphics.RectF; 11 | import android.util.AttributeSet; 12 | import android.view.View; 13 | import android.view.animation.DecelerateInterpolator; 14 | import android.view.animation.LinearInterpolator; 15 | 16 | import androidx.annotation.NonNull; 17 | import androidx.annotation.Nullable; 18 | 19 | import com.android.xz.util.Logs; 20 | 21 | public class CaptureButton extends View { 22 | 23 | private static final String TAG = CaptureButton.class.getSimpleName(); 24 | private Context mContext; 25 | private int mWidth; 26 | private int mHeight; 27 | private int mBgColor = Color.parseColor("#CCCCCC"); 28 | private int mRecordColor = Color.parseColor("#FF0000"); 29 | private float mHalfLength; 30 | private float mCircleRadius; 31 | private Paint mBgPaint; 32 | private boolean mRecording = false; 33 | private ObjectAnimator mCaptureAnimator; 34 | private ObjectAnimator mRecordAnimator; 35 | private AnimatorSet mRecordAnimatorSet; 36 | 37 | public CaptureButton(Context context) { 38 | super(context); 39 | init(context); 40 | } 41 | 42 | public CaptureButton(Context context, @Nullable AttributeSet attrs) { 43 | super(context, attrs); 44 | init(context); 45 | } 46 | 47 | public CaptureButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 48 | super(context, attrs, defStyleAttr); 49 | init(context); 50 | } 51 | 52 | public CaptureButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 53 | super(context, attrs, defStyleAttr, defStyleRes); 54 | init(context); 55 | } 56 | 57 | private void init(Context context) { 58 | mContext = context; 59 | mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 60 | mBgPaint.setColor(mBgColor); 61 | mBgPaint.setStrokeWidth(6); 62 | 63 | setOnClickListener(onClickListener); 64 | setLongClickable(true); 65 | setOnLongClickListener(onLongClickListener); 66 | } 67 | 68 | public void setHalfLength(float halfLength) { 69 | mHalfLength = halfLength; 70 | invalidate(); 71 | } 72 | 73 | public void setCircleRadius(float circleRadius) { 74 | mCircleRadius = circleRadius; 75 | invalidate(); 76 | } 77 | 78 | @Override 79 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 80 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 81 | int width = MeasureSpec.getSize(widthMeasureSpec); 82 | int height = MeasureSpec.getSize(heightMeasureSpec); 83 | 84 | if (width > height) { 85 | setMeasuredDimension(height, height); 86 | mWidth = height; 87 | mHeight = height; 88 | } else { 89 | setMeasuredDimension(width, width); 90 | mWidth = width; 91 | mHeight = width; 92 | } 93 | Logs.i(TAG, "width:" + width); 94 | setCircleRadius(mWidth * 9 / 10 / 2); 95 | } 96 | 97 | public void stopRecord() { 98 | Logs.i(TAG, "stopRecord..."); 99 | if (mRecording) { 100 | mRecording = false; 101 | setCircleRadius(mWidth * 9 / 10 / 2); 102 | invalidate(); 103 | } 104 | } 105 | 106 | @Override 107 | protected void onDraw(@NonNull Canvas canvas) { 108 | super.onDraw(canvas); 109 | mBgPaint.setColor(mBgColor); 110 | int centerX = mWidth / 2; 111 | int centerY = mHeight / 2; 112 | int radius = mWidth / 2; 113 | canvas.drawCircle(centerX, centerY, radius, mBgPaint); 114 | 115 | mBgPaint.setColor(mRecordColor); 116 | canvas.drawRoundRect(new RectF(centerX - mHalfLength, centerY - mHalfLength, centerX + mHalfLength, centerY + mHalfLength), 6, 6, mBgPaint); 117 | mBgPaint.setColor(Color.WHITE); 118 | canvas.drawCircle(centerX, centerY, mCircleRadius, mBgPaint); 119 | } 120 | 121 | private View.OnClickListener onClickListener = new View.OnClickListener() { 122 | @Override 123 | public void onClick(View v) { 124 | if (mRecording) { 125 | stopRecord(); 126 | if (mClickListener != null) { 127 | mClickListener.onStopRecord(); 128 | } 129 | } else { 130 | if (mClickListener != null) { 131 | mClickListener.onCapture(); 132 | } 133 | } 134 | } 135 | }; 136 | 137 | private View.OnLongClickListener onLongClickListener = new OnLongClickListener() { 138 | @Override 139 | public boolean onLongClick(View v) { 140 | if (!mRecording) { 141 | mRecording = true; 142 | if (mCaptureAnimator == null) { 143 | ObjectAnimator animator = ObjectAnimator.ofFloat(CaptureButton.this, "circleRadius", mWidth * 9 / 10 / 2, 0); 144 | animator.setInterpolator(new DecelerateInterpolator()); 145 | animator.setDuration(100); 146 | ((ValueAnimator) animator).addUpdateListener(animation -> setCircleRadius((float) animation.getAnimatedValue())); 147 | mCaptureAnimator = animator; 148 | } 149 | if (mRecordAnimator == null) { 150 | ObjectAnimator animator = ObjectAnimator.ofFloat(CaptureButton.this, "halfLength", 0, mWidth / 6); 151 | animator.setInterpolator(new DecelerateInterpolator()); 152 | animator.setDuration(100); 153 | ((ValueAnimator) animator).addUpdateListener(animation -> setHalfLength((float) animation.getAnimatedValue())); 154 | mRecordAnimator = animator; 155 | } 156 | if (mRecordAnimatorSet == null) { 157 | mRecordAnimatorSet = new AnimatorSet(); 158 | } 159 | mRecordAnimatorSet.playSequentially(mCaptureAnimator, mRecordAnimator); 160 | mRecordAnimatorSet.start(); 161 | if (mClickListener != null) { 162 | mClickListener.onStartRecord(); 163 | } 164 | } 165 | return true; 166 | } 167 | }; 168 | 169 | public void setClickListener(ClickListener clickListener) { 170 | mClickListener = clickListener; 171 | } 172 | 173 | private ClickListener mClickListener; 174 | 175 | public interface ClickListener { 176 | void onCapture(); 177 | 178 | void onStartRecord(); 179 | 180 | void onStopRecord(); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_switch_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaozhi003/AndroidCamera/d6ca77f138fdd8c30e0dcc658e543e5c179e9500/app/src/main/res/drawable-xhdpi/ic_switch_camera.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_switch_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaozhi003/AndroidCamera/d6ca77f138fdd8c30e0dcc658e543e5c179e9500/app/src/main/res/drawable-xxhdpi/ic_switch_camera.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_capture_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_capture_normal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_capture_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /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/img_switch_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_switch_normal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_switch_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tv_timer_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_camera.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_display_media.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_glessurface_camera.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 17 | 18 |