一 .功能介绍(UI布局)
打开camera app
首先会进入拍照模式的预览界面:
总共就四个控件
1.整体的预览控件TextureView
2.左下角的缩略图ImageView
3.中间的拍照按钮ImageButton
4.切换摄像头的ImageButton
主要是两个Fragment 通过ViewPage 左右滑动切换拍照模式跟录像模式具体实现在后面,在我的Demo中向左滑动切换到录像模式
进入录像的预览模式:
总共就五个控件
1.整体的预览控件TextureView
2.左下角的缩略图ImageView
3.中间的拍照按钮ImageButton
4.切换摄像头的ImageButton
5.顶部的计时器Chronometer
二 .UI的代码部分
1.首先是 activity_main.xml中
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager.widget.ViewPager
android:layout_gravity="center"
android:id="@+id/change_page"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<!--标题 <androidx.viewpager.widget.PagerTitleStrip
android:layout_gravity="top"
android:id="@+id/page_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>-->
</androidx.viewpager.widget.ViewPager>
</RelativeLayout>
主界面主要是注册了一个ViewPager 注释的部分是之前准备的标题栏,是想通过标题提示是录像界面还是拍照界面,但是自己没有找到把标题栏背景变成透明的方法于是就抛弃了,我方在这里如果后续找到的话及时更新.
2.其次是 Mainactivity.java中
package com.example.camera;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.viewpager.widget.ViewPager;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private ViewPager change_page; //拍照录像切换
private List<Fragment> layoutList; //布局集合(拍照录像切换)
//private List<String> titleList; //标题集合(拍照录像切换)
private MyPagerAdapter myPagerAdapter;
//初始化视图界面(拍照/摄像)
private void initView(){
change_page = findViewById(R.id.change_page);
layoutList = new ArrayList<>();
layoutList.add(new TakePictureFragment());
layoutList.add(new RecorderVideoFragment());
// titleList = new ArrayList<>();
// titleList.add("拍照");
// titleList.add("录像");
//设置适配器
myPagerAdapter = new MyPagerAdapter(getSupportFragmentManager(), layoutList);
change_page.setAdapter(myPagerAdapter);
}
//活动的创建
@Override
protected void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_main);
initView();
//隐藏通知栏状态栏
if (Build.VERSION.SDK_INT >= 21) {
View decorView=getWindow().getDecorView();//获取当前界面的decorView
int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|View.SYSTEM_UI_FLAG_FULLSCREEN//隐藏状态栏
|View.SYSTEM_UI_FLAG_LAYOUT_STABLE//保持整个View的稳定,使其不会随着SystemUI的变化而变化;
|View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION//让导航栏悬浮在Activity上
// |View.SYSTEM_UI_FLAG_HIDE_NAVIGATION//隐藏导航栏
|View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;//沉浸模式且状态栏和导航栏出现片刻后会自动隐藏
decorView.setSystemUiVisibility(option);
getWindow().setStatusBarColor(Color.TRANSPARENT);//设置透明颜色
getWindow().setNavigationBarColor(Color.TRANSPARENT);
}
ActionBar actionBar=getSupportActionBar();
actionBar.hide();
}
protected void onDestroy() {
super.onDestroy();
TakePictureFragment.closeCamera();
}
}
主活动中:
1.做了初始化视图界面添加两个碎片TakePictureFragment和RecorderVideoFragment
2.因为ViewPage要用到PagerAdapter 将默认的界面设置成了拍照预览界面
3.onCreate中做了个沉浸式通知栏(隐藏状态栏和通知栏)
4.onDestroy中调用了TakePictureFrament中的closeCamera
5.注释掉的部分同上是标题栏
3.MyPagerAdapter.java中
package com.example.camera;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import java.util.List;
public class MyPagerAdapter extends FragmentPagerAdapter {
private List<Fragment> layoutList;
// private List<String> titleList;
public MyPagerAdapter(FragmentManager manager, List<Fragment> layoutList ){
super(manager);
this.layoutList = layoutList;
// this.titleList = titleList;
}
@Override
public int getCount() {
// 页面数
return layoutList.size();
}
@NonNull
@Override
public Fragment getItem(int position) {
return layoutList.get(position);
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
}
}
这个类继承FragmentPageAdapter跟ViewPage联合使用,注:ViewPage跟activity也可以不一定是需要Fragment主要的功能就是通过左右滑动来切换fragment
3.fragment_take_picture.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextureView
android:id="@+id/textureView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<ImageButton
android:background="@drawable/shape_white_ring"
android:id="@+id/takePicture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/shape_take_photo"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="50dp"
/>
<ImageView
android:id="@+id/image_show"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:layout_marginLeft="50dp"
android:layout_marginBottom="50dp" />
<ImageButton
android:id="@+id/change"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_baseline_flip_camera_android_24"
android:layout_alignParentBottom="true"
android:layout_marginRight="50dp"
android:layout_marginBottom="50dp"
android:layout_alignParentRight="true"
/>
</RelativeLayout>
4 TakePpictureFragment.java中
package com.example.camera;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class TakePictureFragment extends Fragment implements View.OnClickListener {
private static final String TAG = "TakePictureFragment";
private static final SparseIntArray ORIENTATION = new SparseIntArray();
static {
//手机ROTATION逆时针旋转
ORIENTATION.append(Surface.ROTATION_0, 90);
ORIENTATION.append(Surface.ROTATION_90, 0);
ORIENTATION.append(Surface.ROTATION_180, 270);
ORIENTATION.append(Surface.ROTATION_270, 180);
}
private TextureView textureView; //预览框控件
private ImageButton takePicture; //拍照按钮
private ImageButton change; //前后摄像头切换按钮
private ImageView mImageView; // 缩略图显示
private String mCameraId; // 摄像头Id
private Size mPreviewSize; //获取分辨率
private ImageReader mImageReader; //图片阅读器
private static CameraDevice mCameraDevice; //摄像头设备
private static CameraCaptureSession mCaptureSession; //获取会话
private CaptureRequest mPreviewRequest; //获取预览请求
private CaptureRequest.Builder mPreviewRequestBuilder; //获取到预览请求的Builder通过它创建预览请求
private Surface mPreviewSurface; //预览显示图
//权限申请
private String[] permissions = {Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.RECORD_AUDIO};
private List<String> permissionList = new ArrayList();
private ArrayList<String> imageList = new ArrayList<>(); //图片集合
protected boolean isCreated=false; //Fragment是否创建成功
private boolean isVisible; //Fragment是否可见
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreateView: success");
View view = inflater.inflate(R.layout.fragment_take_picture, container, false);
//注册监听控件
initView(view);
textureView.setSurfaceTextureListener(textureListener); //surfaceView回调里面配置相机打开相机
takePicture.setOnClickListener(this); //拍照监听
mImageView.setOnClickListener(this); //缩略图监听
change.setOnClickListener(this); //摄像头切换监听
getPermission(); //申请权限
//显示最后一张图
isCreated = true; //Fragment View 创建成功
return view; //显示当前View
}
// 第一步:获取权限
/**
* 获取拍照和读写权限
*/
private void getPermission() {
Log.d(TAG, "getPermission: success");
//版本判断 当手机系统大于23时,才有必要去判断权限是否获取
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//权限是否已经 授权 GRANTED-授权 DINIED-拒绝
for (String permission : permissions) {
//检查权限是否全部授予
if (ContextCompat.checkSelfPermission(getContext(), permission) != PackageManager.PERMISSION_GRANTED) {
//如果没有就添加到权限集合
permissionList.add(permission);
}
}
//是空返回ture
if (!permissionList.isEmpty()) {
requestPermissions(permissionList.toArray(new String[permissionList.size()]), 1);
} else {
//表示全都授权了
textureView.setSurfaceTextureListener(textureListener);
//显示最后一张图片 最新
setLastImagePath();
}
}
}
//权限回调
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] mPermissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
Log.d(TAG, "onRequestPermissionsResult: success");
if (requestCode == 1) {
//权限请求失败
if (grantResults.length > 0) {
//存放没授权的权限
List<String> deniedPermissions = new ArrayList<>();
for (int i = 0; i < grantResults.length; i++) {
int grantResult = grantResults[i];
String permission = permissions[grantResult];
if (grantResult != PackageManager.PERMISSION_GRANTED) {
deniedPermissions.add(permission);
}
}
if (deniedPermissions.isEmpty()) {
//说明都授权了 打开相机
openCamera();
//显示最新的一张图片或者视频的第一帧
setLastImagePath();
} else {
// 继续申请权限
getPermission();
}
}
}
}
//判断Fragment是否可见
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
//判断是否是第一次创建
if (!isCreated) {
return;
}
//如果可见
if (isVisibleToUser) {
isVisible = true;
//显示第一张照片
setLastImagePath();
//如果textureView 可用
if (textureView.isAvailable()) {
//打开相机
openCamera();
} else {
//先配置相机再打开相机
textureView.setSurfaceTextureListener(textureListener);
}
} else {
//当切换成录像时,将当前fragment 置为不可见
Log.d(TAG, TAG + " releaseCamera");
isVisible = false;
closeCamera();
}
}
@Override
public void onResume() {
super.onResume();
//如果fragment 可见
if (isVisible) {
//textureView 可用
if (textureView.isAvailable()) {
//打开相机
openCamera();
} else {
//设置相机参数
textureView.setSurfaceTextureListener(textureListener);
}
}
}
//注册视图上监听控件ID
private void initView(View view) {
Log.d(TAG, "initView: success");
//预览框控件
textureView = view.findViewById(R.id.textureView);
//拍照控件
takePicture = view.findViewById(R.id.takePicture);
//缩略图控件
mImageView = view.findViewById(R.id.image_show);
//前后摄像头切换控件
change = view.findViewById(R.id.change);
}
@Override
public void onClick(View view) {
Log.d(TAG, "onClick: success");
switch (view.getId()) {
case R.id.takePicture:
//拍照
takePhoto();
break;
case R.id.change:
//切换摄像头
changeCamera();
break;
case R.id.image_show:
//打开相册
openAlbum();
break;
}
}
//第二步 写回调函数
// 1. SurfaceView状态回调
TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
@Override
//textureView.isAvailable()为ture
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int width, int height) {
Log.d(TAG, "onSurfaceTextureAvailable: success");
setupCamera();
// configureTransform(width, height); //旋转
openCamera(); //打开相机
}
@Override
public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surfaceTexture, int width, int height) {
// configureTransform(width, height);
}
@Override
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {
return false;
}
@Override
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surfaceTexture) {
}
};
// 2. 摄像头状态回调
private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
//打开成功获取到camera设备
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
Log.d(TAG, "onOpened: success");
mCameraDevice = cameraDevice;
//开启预览
startPreview();
}
//打开失败
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
Toast.makeText(getContext(), "摄像头设备连接失败", Toast.LENGTH_SHORT).show();
}
//打开错误
@Override
public void onError(@NonNull CameraDevice cameraDevice, int i) {
Toast.makeText(getContext(), "摄像头设备连接出错", Toast.LENGTH_SHORT).show();
}
};
// 3.实现PreviewCallback 拍照时调用
private CameraCaptureSession.CaptureCallback mPreviewCaptureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
// 一旦捕获完成
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
super.onCaptureCompleted(session, request, result);
}
};
//第三步 设置(配置)相机
//设置摄像机 id 参数
private void setupCamera() {
Log.d(TAG, "setupCamera: success");
//获取摄像头的管理者CameraManager
CameraManager manager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);
try {
//遍历所有摄像头
for (String cameraId : manager.getCameraIdList()) {
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); //获取摄像机的特征
//默认打开后置 - 忽略前置 LENS(镜头)
if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT)
{
continue;
}
//获取StreamConfigurationMap,他是管理摄像头支持的所有输出格式
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
mPreviewSize = getOptimalSize(map.getOutputSizes(SurfaceTexture.class),
720, 960); //获取最佳的预览大小
//进入回调设置相机然后打开
textureView.setSurfaceTextureListener(textureListener);
mCameraId = cameraId;
break;
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
//旋转屏幕
private void configureTransform(int viewWidth, int viewHeight) {
Log.d(TAG, "configureTransform: success");
if (textureView == null || mPreviewSize == null) {
return;
}
int rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
Log.i("TAGggg", "rotation: "+rotation);
Matrix matrix = new Matrix();
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
float centerX = viewRect.centerX();
float centerY = viewRect.centerY();
if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale = Math.max((float) viewHeight / mPreviewSize.getHeight(),
(float) viewWidth / mPreviewSize.getWidth());
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
} else if (rotation == Surface.ROTATION_180) {
Log.i("TAGggg", "rotation --- : "+rotation);
matrix.postRotate(180, centerX, centerY);
}
textureView.setTransform(matrix);
}
//选择sizeMap中大于并且接近width和height的size
private Size getOptimalSize(Size[] sizeMap, int width, int height) {
Log.d(TAG, "getOptimalSize: success");
List<Size> sizeList = new ArrayList<>();
for (Size option : sizeMap) {
//当width > height
if (width > height) {
//选取官渡大于surface的宽度并且选取的高度大于surface的高度
if (option.getWidth() > width && option.getHeight() > height) {
//符合的添加到sizeList
sizeList.add(option);
}
} else {
//如果选择宽度大于surface的高度并且选择的高度大于surface的宽度
if (option.getWidth() > height && option.getHeight() > width) {
//符合的添加到sizeList
sizeList.add(option);
}
}
}
if (sizeList.size() > 0) {
return Collections.min(sizeList, new Comparator<Size>() {
@Override
public int compare(Size size, Size t1) {
return Long.signum(size.getWidth() * size.getHeight() - t1.getWidth() * t1.getHeight());
}
});
}
return sizeMap[0];
}
//创建预览请求的Builder (TEMPLATE_PREVIEW表示预览请求)
private void setPreviewRequestBuilder() {
Log.d(TAG, "setPreviewRequestBuilder: success");
try {
//通过cameraDevice获取到预览请求的Builder
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
} catch (CameraAccessException e) {
e.printStackTrace();
}
//设置预览的显示图
mPreviewRequestBuilder.addTarget(mPreviewSurface);
MeteringRectangle[] meteringRectangles = mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AE_REGIONS);
if (meteringRectangles != null && meteringRectangles.length > 0) {
Log.d(TAG,"PreviewRequestBuilder: AF_REGIONS");
}
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_MODE_AUTO);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
}
// 重复预览
private void repeatPreview() {
Log.d(TAG, "repeatPreview: success");
mPreviewRequestBuilder.setTag(TAG);
//通过预览请求的builder的.build获取到预览请求
mPreviewRequest = mPreviewRequestBuilder.build();
//设置反复捕获会话的请求,这样预览界面就会一直有数据显示
try {
//第一个参数就是预览求情,第二个参数是PreviewCallback,第三个是处理的线程
mCaptureSession.setRepeatingRequest(mPreviewRequest, mPreviewCaptureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
//第四步 打开相机
private void openCamera() {
Log.d(TAG, "openCamera: success");
//获取摄像头的管理者 CameraManager
CameraManager manager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);
//检查权限
try {
if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA) !=
PackageManager.PERMISSION_GRANTED) {
return;
} else {
//通过manager.openCamera(id,cameraStateCallback,处理的线程)
manager.openCamera(mCameraId, stateCallback, null);
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
// 关闭相机
public static void closeCamera() {
Log.d(TAG, "closeCamera: success");
if (mCaptureSession != null) {
mCaptureSession.close();
mCaptureSession = null;
}
if (mCameraDevice != null) {
mCameraDevice.close();
mCameraDevice = null;
}
}
//第五步 开启相机预览
private void startPreview() {
Log.d(TAG, "startPreview: success");
//设置图片阅读器
setupImageReader();
SurfaceTexture mSurfaceTexture = textureView.getSurfaceTexture();
//设置TextureView的缓冲区大小
mSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
//获取Surface显示预览数据
mPreviewSurface = new Surface(mSurfaceTexture);
try {
//创建预览请求的Builder
setPreviewRequestBuilder();
//通过CameraDevice创建相机捕捉会话,第一个参数是捕获数据的输出Surface列表,第二个参数是CameraCaptureSession的状态回调接口,
// 当他创建好后会回调onCconfigured方法
//第三个参数用来确定Callback在那个线程执行,null表示在当前线程执行
mCameraDevice.createCaptureSession(Arrays.asList(mPreviewSurface, mImageReader.getSurface()),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
mCaptureSession = cameraCaptureSession;
repeatPreview();
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
//第七步 设置图片阅读
private void setupImageReader() {
Log.d(TAG, "setupImageReader: success");
//前三个参数分别是需要的尺寸和格式,最后一个参数代表每次最多获取几帧数据
mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.JPEG, 1);
//监听ImageReader的事件,当有图像流数据可用时会回调onImageAvailable
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader imageReader) {
Toast.makeText(getContext(), "图片已保存", Toast.LENGTH_SHORT).show();
//获得mage
Image image = imageReader.acquireNextImage();
//开启线程一部保存图片
ImageSaver imageSaver = new ImageSaver(getContext(), image);
new Thread(imageSaver).start();
}
}, null);
}
// 第八步 拍照
private void takePhoto() {
Log.d(TAG, "takePhoto: success");
try {
//首先创建拍照的请求 CaptureRequest
final CaptureRequest.Builder mCaptureBuilder = mCameraDevice.createCaptureRequest
(CameraDevice.TEMPLATE_STILL_CAPTURE);
//获取屏幕方向
int rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
//获取到当前预览窗口的图
mCaptureBuilder.addTarget(mImageReader.getSurface());
//设置拍照方向
if (mCameraId.equals("1")) {
rotation = 2;
}
//设置图片的方向 因为默认的是横屏 我们使用手机一般是竖屏所以需要处理
mCaptureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));
//停止预览
mCaptureSession.stopRepeating();
//开始拍照,然后回调上面的接口重启预览,因为mCaptureBuilder设置ImageReader作为target,
// 所以会自动回调ImageReader的onImageAvailable()方法保存图片
CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
super.onCaptureCompleted(session, request, result);
repeatPreview();
}
};
mCaptureSession.capture(mCaptureBuilder.build(), captureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
// 第九步 创建子线程保存图片
public class ImageSaver implements Runnable {
private Image mImage;//图片
private Context mContext;
public ImageSaver(Context context, Image image) {
Log.d(TAG, "ImageSaver: success");
mImage = image;
mContext = context;
}
@Override
public void run() {
//将照片转字节
ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
String path = Environment.getExternalStorageDirectory() +
"/DCIM/camera/myPicture" + System.currentTimeMillis() + ".jpg";
File imageFile = new File(path);
FileOutputStream fos = null;
try {
fos = new FileOutputStream(imageFile);
fos.write(data, 0, data.length);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
broadcast();
Message message = new Message();
message.what = 0;
Bundle mBundle = new Bundle();
mBundle.putString("myPath",path);
message.setData(mBundle);
handler.sendMessage(message);
mImage.close(); // 必须关闭 不然拍第二章会报错
}
}
// 异步消息处理
private Handler handler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(@NonNull Message message) {
super.handleMessage(message);
switch (message.what) {
case 0:
Bundle bundle = message.getData();
//通过指定的键值对获取到刚刚发送过来的地址
String myPath = bundle.getString("myPath");
imageList.add(myPath);
//imageList.add(bundle.getString(myPath)); // 这样不行必须分开写 不然 arrList没有数据
setLastImagePath();
break;
default:
throw new IllegalStateException("Unexpected value: " + message.what);
}
}
};
// 广播通知相册更新
public void broadcast() {
Log.d(TAG, "broadcast: success");
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/";
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri uri = Uri.fromFile(new File(path));
intent.setData(uri);
mContext.sendBroadcast(intent);
}
}
// 改变前后摄像头
private void changeCamera() {
Log.d(TAG, "changeCamera: success");
if (mCameraId.equals(String.valueOf(CameraCharacteristics.LENS_FACING_BACK))) {
Toast.makeText(getContext(), "前置转后置", Toast.LENGTH_SHORT).show();
mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_FRONT);
} else {
Toast.makeText(getContext(), "后置转前置", Toast.LENGTH_SHORT).show();
mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_BACK);
}
mCameraDevice.close();
openCamera();
}
//找到最后一张的路径
private void setLastImagePath() {
Log.d(TAG, "setLastImagePath: success");
imageList = GetImageFilePath.getFilePath();
String string = imageList.get(imageList.size() - 1);
if(string.contains(".jpg")){
setImageBitmap(string);
}else {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(string);
Bitmap bitmap = retriever.getFrameAtTime(1);
mImageView.setImageBitmap(bitmap);
}
}
// 设置缩略图
private void setImageBitmap(String path){
Log.d(TAG, "setImageBitmap: success");
Bitmap bitmap = (Bitmap) BitmapFactory.decodeFile(path);
mImageView.setImageBitmap(bitmap);
}
//打开相册
private void openAlbum() {
Log.d(TAG, "openAlbum: success");
Intent intent = new Intent();
// 在ImageShowActivity中直接从相册中遍历 不需要传递过去
//intent.putStringArrayListExtra("myList", imageList);
intent.setClass(getContext(), ImageShowActivity.class);
startActivity(intent);
}
}
其中拍照流程 复制下面加粗函数在代码中查看
1.权限申请,以及权限回调,没有权限回调第一次安装程序申请权限之后还是不会进入预览界面,重启程序后才能正常预览拍照
2.因为使用的ViewPage加Fragment所以需要setUserVisibleHint函数来保证Fragment可见的时候在对其进行初始化,不然两个Fragment同时执行初始化相机打开相机等会报错.
3.初始化控件,在该类中用的是initView
4.监听控件onClick
5.初始化 Surface 并在其回调函数中执行配置相机打开相机等操作 因为我使用的是默认的TextureView所以直接写回调函数TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener()....
6.通过CameraManager配置相机参数选择相机id setupCamera()
7.选择sizeMap中大于并且接近width和height的size getOptimalSize 如果是自己写demo这可可以不要自己设置个指定的值也可以,包括旋转屏幕处理也可以不要我在其中就注释了
8.打开相机通过CameraManager getSystemService(Context.CAMERA_SERVICE)在我的demo中在openCamera()函数中实现
9.打开相机又需要用到摄像头状态回调 CameraDevice.StateCallback 在onOpened中主要是把获取到的cameraDevice拿到 以及执行开启预览
10.开启预览之前需要设置ImageReader在我的demo中是setupImageReader()中实现
11.创建预览就需要先通过cameraDevice创建createCaptureSession 创建预览申请中又用到CameraCaptureSession.StateCallback回调在这个回调函数的onConfigured获取到mCaptureSession(捕捉会话)并在其中调用重复预览
12.创建重复预览请求通过captureRequest.Builder获取到预览请求的build再通过mPreviewRequestBuilder.build();获取到预览请求mPreviewRequest再通过
mCaptureSession.setRepeatingRequest(mPreviewRequest, mPreviewCaptureCallback, null);
实现反复进行预览请求以及反复捕获请求会话 在我的demo中repeatPreview()中实现
13.按照上面预览就成功了接下来就是拍照在demo中takePhoto()实现拍照时又会调用
CameraCaptureSession.CaptureCallback 定义的mCaptureSession进行拍照处理
14.拍完照片就是保存照片创建子线程保存图片 创建子线程保存照片就是为了提高效率做到拍完就保存而且保存照片又牵扯到广播通知相册刷新 在该demo中是通过内部类 ImageSaver 开启子线程
15.我之前写了把拍完照片的路径通过Hander 传message传出来 但是后来没用上 读者可以抛弃
16.广播通知相册更新 主要是通过Itent 加uri实现 具体在demo中的 broadcast()函数中实现
17.通过缩略图显示最新的一张图片也就是最后一张图片demo中通过setLastImagePath()和setImageBitmap() 实现 主要是用到将String转Bitmap 看看就懂了 中间用到的GetImageFilePath.getFilePath 是另外写的一个类用来遍历系统相册的所有路径我没有对其进行排序如果有需要 可以根据修改时间排序 忘了补充在其中还判断了是图片还是视频如果是视频显示第一帧
18.切换摄像头 很简单 demo中changCamera()实现
19.最后就是通过缩略图进入到系统相册openAlbum() 在这个函数中通过Itent启动另一个活动
就是ImageShowActivity
5.GetImageFilePath.java中
package com.example.camera;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.util.ArrayList;
public class GetImageFilePath {
//获取相册camera 图片路径
static ArrayList<String> imageList = new ArrayList<>() ;
public static ArrayList<String> getFilePath() {
File file= new File(Environment.getExternalStorageDirectory().toString() + "/DCIM/Camera");
File[] dirEpub = file.listFiles();
if (dirEpub.length != 0){
for (int i = 0; i < dirEpub.length; i++){
String fileName = dirEpub[i].toString();
imageList.add(fileName);
Log.i("File", "File name = " + fileName);
}
}
return imageList;
}
}
就是遍历系统相册下的所有文件路径
6.ImageShowActivity.java中
package com.example.camera;
import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
public class ImageShowActivity extends AppCompatActivity {
private final static String TAG = "ImageShowActivity";
private String lastImagePath;
//图片集合
private ArrayList<String> imageList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e(TAG, "onCreate success !!!!!");
setContentView(R.layout.activity_image_show);
// 调用获取图片路径类中的静态方法
imageList = GetImageFilePath.getFilePath();
lastImagePath = imageList.get(imageList.size() - 1);
gotoGallery(lastImagePath);
}
// 转到画廊 就是图库
public void gotoGallery(String path) {
Log.e("TAG", "gotoGallery success !!!!!");
Uri uri =getMediaUriFromPath(this, path);
Log.i("asdddd", "uri: "+uri);
Intent intent = new Intent("com.android.camera.action.REVIEW", uri);
intent.setData(uri);
startActivity(intent);
finish();
}
@SuppressLint("Range")
public Uri getMediaUriFromPath(Context context, String path) {
Log.e("TAG", "getMediaUriFromPath success !!!!!");
Uri uri = null;
if(path.contains("jpg")){
Uri picUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = context.getContentResolver().query(picUri,
null,
MediaStore.Images.Media.DISPLAY_NAME + "= ?",
new String[] {path.substring(path.lastIndexOf("/") + 1)},
null);
if(cursor.moveToFirst()) {
uri = ContentUris.withAppendedId(picUri,
cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID)));
}
cursor.close();
}else if(path.contains("mp4")){
Uri mediaUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = context.getContentResolver().query(mediaUri,
null,
MediaStore.Video.Media.DISPLAY_NAME + "= ?",
new String[] {path.substring(path.lastIndexOf("/") + 1)},
null);
if(cursor.moveToFirst()) {
uri = ContentUris.withAppendedId(mediaUri,
cursor.getLong(cursor.getColumnIndex(MediaStore.Video.Media._ID)));
}
cursor.close();
}
return uri;
}
}
重点就是gotoGallery()中通过Itent
Intent intent = new Intent("com.android.camera.action.REVIEW", uri);
就可以实现调用相册的功能
7.ImageShowActivity.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/show_pictures"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
就一个ImageView
8.RecoderVideoFragment.xml中
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextureView
android:id="@+id/textureView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<ImageButton
android:background="@drawable/shape_white_ring"
android:id="@+id/recording"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/shape_recorder"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="50dp"
/>
<ImageView
android:id="@+id/image_show"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:layout_marginBottom="50dp"
android:layout_marginLeft="50dp"
/>
<ImageButton
android:id="@+id/change"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_baseline_flip_camera_android_24"
android:layout_alignParentBottom="true"
android:layout_marginRight="50dp"
android:layout_marginBottom="50dp"
android:layout_alignParentRight="true"
/>
<Chronometer
android:id="@+id/timer"
android:textColor="#f00"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:format="%s"
android:gravity="center"
android:textSize="40sp" />
</RelativeLayout>
其实完全可以不用这样重写一次 完全可以包含上一个takepicture的xml包进行写就行其实是一模一样的就是加了个计时器控件而已
9.RecoderVideoFragment.java中
package com.example.camera;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.MediaMetadataRetriever;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Chronometer;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class RecorderVideoFragment extends Fragment implements View.OnClickListener {
private String TAG = "RecorderVideoFragment";
private ImageButton videoButton; //用来重新设置录像按钮
private TextureView mTextureView; //预览框控件
private CaptureRequest.Builder mPreviewCaptureRequest; //获取请求创建者
private CameraDevice mCameraDevice; //camera设备
private MediaRecorder mMediaRecorder; //音视频录制
//摄像头ID 默认置为后置BACK FRONT值为0 == BACK
private String mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_FRONT);
private Chronometer timer; //计时器
private ArrayList<String> imageList = new ArrayList<>(); // 路径集合
private static CameraCaptureSession mCameraCaptureSession; //获取会话
private Handler mChildHandler; //子线程
private CameraManager mCameraManager; //摄像头管理者
private boolean isVisible = false; //fragment是否可见
private boolean isRecording = false; //是否在录制视频
private HandlerThread mHandlerThread; //线程处理者
private ImageView mImageView; //缩略图按钮
private ImageButton change; //前后置切换按钮
private static final SparseIntArray ORIENTATION = new SparseIntArray();
static {
ORIENTATION.append(Surface.ROTATION_0, 90);
ORIENTATION.append(Surface.ROTATION_90, 0);
ORIENTATION.append(Surface.ROTATION_180, 270);
ORIENTATION.append(Surface.ROTATION_270, 180);
}
//Fragment 中 onCreateView返回的就是fragment要显示的view.
@Nullable
@Override
/**
* 第一个参数LayoutInflater inflater第二个参数ViewGroup container第三个参数 Bundle savedInstanceState
* LayoutInflater inflater:作用类似于findViewById()用来寻找xml布局下的具体的控件Button、TextView等,
* LayoutInflater inflater()用来找res/layout/下的xml布局文件
* ViewGroup container:表示容器,View放在里面
* Bundle savedInstanceState:保存当前的状态,在活动的生命周期中,只要离开了可见阶段,活动很可能就会被进程终止,
* 这种机制能保存当时的状态
*/
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
Log.d(TAG, "onCreateView: success");
View view = inflater.inflate(R.layout.fragment_recorder, container, false);
initView(view);
//监听视频按钮
videoButton.setOnClickListener(this);
//监听缩略图按钮
mImageView.setOnClickListener(this);
//监听前后置切换按钮
change.setOnClickListener(this);
return view;
}
//判断Fragment是否可见
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
//如果Fragment可见 把isVisible置为true
isVisible = true;
Log.d(TAG, "setUserVisibleHint: true");
//设置显示最后一张图片(视频第一帧)
setLastImagePath();
//初始化子线程
initChildHandler();
//如果textureView可用
if (mTextureView.isAvailable()) {
openCamera();
} else {
initTextureView();
}
} else {
closeCamera();
return;
}
}
@Override
public void onResume() {
super.onResume();
if (isVisible) {
初始化子线程
initChildHandler();
if (mTextureView.isAvailable()) {
openCamera();
} else {
initTextureView();
}
}
}
//初始化监听控件
private void initView(View view){
mImageView = view.findViewById(R.id.image_show);
mTextureView = view.findViewById(R.id.textureView);
timer = view.findViewById(R.id.timer);
videoButton = view.findViewById(R.id.recording);
change = view.findViewById(R.id.change);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.recording:
if (isRecording) {
//再次按下将停止录制
stopRecorder();
isRecording = false;
} else {
//第一次按下将isRecording置为ture
//配置并开始录制
isRecording = true;
configSession();
startRecorder();
}
break;
case R.id.image_show:
openAlbum();
break;
case R.id.change:
changeCamera();
break;
}
}
//1.摄像头状态回调
private CameraDevice.StateCallback mCameraDeviceStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
//摄像头被打开
mCameraDevice = camera;
startPreview();
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
//摄像头断开
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
//异常
}
};
//2.录像时消息捕获回调
private CameraCaptureSession.CaptureCallback mSessionCaptureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
long timestamp, long frameNumber) {
super.onCaptureStarted(session, request, timestamp, frameNumber);
}
@Override
public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
@NonNull CaptureResult partialResult) {
super.onCaptureProgressed(session, request, partialResult);
}
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
super.onCaptureCompleted(session, request, result);
}
@Override
public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
@NonNull CaptureFailure failure) {
super.onCaptureFailed(session, request, failure);
}
};
//3.录像时会话状态回调
private CameraCaptureSession.StateCallback mSessionStateCallback = new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
mCameraCaptureSession = session;
updatePreview();
try {
//执行重复获取数据请求,等于一直获取数据呈现预览画面,mSessionCaptureCallback会返回此次操作的信息回调
mCameraCaptureSession.setRepeatingRequest(mPreviewCaptureRequest.build(),
mSessionCaptureCallback, mChildHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
}
};
//设置模式 闪光灯用
private void setUpCaptureRequestBuilder(CaptureRequest.Builder builder) {
builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
}
/**
* 初始化TextureView的纹理生成监听,只有纹理生成准备好了。才能去进行摄像头的初始化工作让TextureView接收摄像头预览画面
*/
private void initTextureView() {
mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
//可以使用纹理
initCameraManager();
selectCamera();
openCamera();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
//纹理尺寸变化
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
//纹理被销毁
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
//纹理更新
}
});
}
/**
* 计算需要的使用的摄像头分辨率
*
* @return
*/
private Size getMatchingSize() {
Size selectSize = null;
try {
CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(mCameraId);
StreamConfigurationMap streamConfigurationMap = cameraCharacteristics.get
(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
Size[] sizes = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG);
//这里是将预览铺满屏幕,所以直接获取屏幕分辨率
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
//屏幕分辨率宽
int deviceWidth = displayMetrics.widthPixels;
//屏幕分辨率高
int deviceHeight = displayMetrics.heightPixels;
/**
* 循环40次,让宽度范围从最小逐步增加,找到最符合屏幕宽度的分辨率,
* 你要是不放心那就增加循环,肯定会找到一个分辨率,不会出现此方法返回一个null的Size的情况
* ,但是循环越大后获取的分辨率就越不匹配
*/
for (int j = 1; j < 41; j++) {
for (int i = 0; i < sizes.length; i++) { //遍历所有Size
Size itemSize = sizes[i];
//判断当前Size高度小于屏幕宽度+j*5 && 判断当前Size高度大于屏幕宽度-j*5 && 判断当前Size宽度小于当前屏幕高度
if (itemSize.getHeight() < (deviceWidth + j * 5) && itemSize.getHeight() > (deviceWidth - j * 5)) {
if (selectSize != null) { //如果之前已经找到一个匹配的宽度
if (Math.abs(deviceHeight - itemSize.getWidth()) < Math.abs(deviceHeight - selectSize.getWidth())) { //求绝对值算出最接近设备高度的尺寸
selectSize = itemSize;
continue;
}
} else {
selectSize = itemSize;
}
}
}
if (selectSize != null) { //如果不等于null 说明已经找到了 跳出循环
break;
}
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
Log.e(TAG, "getMatchingSize: 选择的分辨率宽度=" + selectSize.getWidth());
Log.e(TAG, "getMatchingSize: 选择的分辨率高度=" + selectSize.getHeight());
return selectSize;
}
/**
* 初始化Camera2的相机管理,CameraManager用于获取摄像头分辨率,摄像头方向,摄像头id与打开摄像头的工作
*/
private void initCameraManager() {
mCameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);
}
/**
* 选择一颗我们需要使用的摄像头,主要是选择使用前摄还是后摄或者是外接摄像头
*/
private void selectCamera() {
if (mCameraManager != null) {
Log.e(TAG, "selectCamera: CameraManager is null");
}
try {
String[] cameraIdList = mCameraManager.getCameraIdList(); //获取当前设备的全部摄像头id集合
if (cameraIdList.length == 0) {
Log.e(TAG, "selectCamera: cameraIdList length is 0");
}
for (String cameraId : cameraIdList) {
//遍历所有摄像头
//屏幕方向
int rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
//设置拍照方向
// if (mCameraId.equals("1")) {
// rotation = 2;
// }
mPreviewCaptureRequest.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));
if (rotation == CameraCharacteristics.LENS_FACING_BACK) {
//这里选择了后摄像头
mCameraId = cameraId;
}
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@SuppressLint("MissingPermission")
private void openCamera() {
try {
if (mCameraManager == null) {
initCameraManager();
}
mCameraManager.openCamera(mCameraId, mCameraDeviceStateCallback, mChildHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void closeCamera() {
// 关闭预览就是关闭捕获会话
stopPreview();
// 关闭当前相机
if (null != mCameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
}
if (null != mMediaRecorder) {
mMediaRecorder.release();
mMediaRecorder = null;
}
if (mHandlerThread != null) {
stopBackgroundThread();
}
}
/**
* 开启预览
* 使用TextureView显示相机预览数据,
* 预览和拍照数据都是使用CameraCaptureSession会话来请求
*/
private void startPreview() {
stopPreview();
SurfaceTexture mSurfaceTexture = mTextureView.getSurfaceTexture();
Size cameraSize = getMatchingSize();
//设置TextureView的缓冲区大小
mSurfaceTexture.setDefaultBufferSize(cameraSize.getWidth(),
cameraSize.getHeight());
//获取Surface显示预览数据
Surface previewSurface = new Surface(mSurfaceTexture);
try {
//创建CaptureRequestBuilder,TEMPLATE_PREVIEW比表示预览请求
mPreviewCaptureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
//设置Surface作为预览数据的显示界面
mPreviewCaptureRequest.addTarget(previewSurface);
//创建相机捕获会话,第一个参数是捕获数据Surface列表,
// 第二个参数是CameraCaptureSession的状态回调接口,
//当他创建好后会回调onConfigured方法,第三个参数用来确定Callback在哪个线程执行
mCameraDevice.createCaptureSession(Collections.singletonList(previewSurface),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
mCameraCaptureSession = session;
updatePreview();
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
Toast.makeText(getActivity().getApplicationContext(), "Faileedsa ", Toast.LENGTH_SHORT).show();
}
}, mChildHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 更新预览
*/
private void updatePreview() {
if (null == mCameraDevice) {
return;
}
try {
setUpCaptureRequestBuilder(mPreviewCaptureRequest);
HandlerThread thread = new HandlerThread("CameraPreview");
thread.start();
mCameraCaptureSession.setRepeatingRequest(mPreviewCaptureRequest.build(), null, mChildHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 关闭预览
*/
private void stopPreview() {
//关闭预览就是关闭捕获会话
if (mCameraCaptureSession != null) {
mCameraCaptureSession.close();
mCameraCaptureSession = null;
}
}
/**
* 初始化子线程Handler,操作Camera2需要一个子线程的Handler
*/
private void initChildHandler() {
mHandlerThread = new HandlerThread("DangJunHaoDemo");
mHandlerThread.start();
mChildHandler = new Handler(mHandlerThread.getLooper());
}
/**
* 关闭线程
*/
public void stopBackgroundThread() {
if (mHandlerThread != null) {
//quitSafely 安全退出
mHandlerThread.quitSafely();
try {
mHandlerThread.join();
mHandlerThread = null;
mHandlerThread = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 配置录制视频相关数据
*/
private void configMediaRecorder() {
File file = new File(Environment.getExternalStorageDirectory() +
"/DCIM/camera/myMp4" + System.currentTimeMillis() + ".mp4");
if (file.exists()) {
file.delete();
}
if (mMediaRecorder == null) {
mMediaRecorder = new MediaRecorder();
}
//设置音频来源
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
//设置视频来源
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
//设置输出格式
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
//设置音频编码格式,请注意这里使用默认,实际app项目需要考虑兼容问题,应该选择AAC
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
//设置视频编码格式,请注意这里使用默认,实际app项目需要考虑兼容问题,应该选择H264
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
//设置比特率 一般是 1*分辨率 到 10*分辨率 之间波动。比特率越大视频越清晰但是视频文件也越大。
mMediaRecorder.setVideoEncodingBitRate(8 * 1024 * 1920);
//设置帧数 选择 30即可, 过大帧数也会让视频文件更大当然也会更流畅,但是没有多少实际提升。人眼极限也就30帧了。
mMediaRecorder.setVideoFrameRate(30);
Size size = getMatchingSize();
mMediaRecorder.setVideoSize(size.getWidth(), size.getHeight());
mMediaRecorder.setOrientationHint(90);
//如果是前置
if(mCameraId.equals("1")){
mMediaRecorder.setOrientationHint(270);
}
Surface surface = new Surface(mTextureView.getSurfaceTexture());
mMediaRecorder.setPreviewDisplay(surface);
mMediaRecorder.setOutputFile(file.getAbsolutePath());
try {
mMediaRecorder.prepare();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 配置录制视频时的CameraCaptureSession
*/
private void configSession() {
try {
if (mCameraCaptureSession != null) {
mCameraCaptureSession.stopRepeating();//停止预览,准备切换到录制视频
mCameraCaptureSession.close();//关闭预览的会话,需要重新创建录制视频的会话
mCameraCaptureSession = null;
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
configMediaRecorder();
Size cameraSize = getMatchingSize();
SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
surfaceTexture.setDefaultBufferSize(cameraSize.getWidth(), cameraSize.getHeight());
Surface previewSurface = new Surface(surfaceTexture);
Surface recorderSurface = mMediaRecorder.getSurface();//从获取录制视频需要的Surface
try {
mPreviewCaptureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewCaptureRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
mPreviewCaptureRequest.addTarget(previewSurface);
mPreviewCaptureRequest.addTarget(recorderSurface);
//请注意这里设置了Arrays.asList(previewSurface,recorderSurface) 2个Surface,很好理解录制视频也需要有画面预览,
// 第一个是预览的Surface,第二个是录制视频使用的Surface
mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, recorderSurface),
mSessionStateCallback, mChildHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 开始录制视频
*/
private void startRecorder() {
mMediaRecorder.start();
//开始计时
startTime();
}
/**
* 暂停录制视频(暂停后视频文件会自动保存)
*/
private void stopRecorder() {
if (mMediaRecorder != null) {
mMediaRecorder.stop();
mMediaRecorder.reset();
//停止计时
endTime();
}
broadcast();
setLastImagePath();
startPreview();
}
// 广播通知相册更新
public void broadcast() {
Log.d(TAG, "broadcast: success");
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/";
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri uri = Uri.fromFile(new File(path));
intent.setData(uri);
getActivity().sendBroadcast(intent);
}
// 改变前后摄像头
private void changeCamera() {
Log.d(TAG, "changeCamera: success");
if (mCameraId.equals(String.valueOf(CameraCharacteristics.LENS_FACING_BACK))) {
Toast.makeText(getContext(), "前置转后置", Toast.LENGTH_SHORT).show();
mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_FRONT);
} else {
Toast.makeText(getContext(), "后置转前置", Toast.LENGTH_SHORT).show();
mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_BACK);
}
mCameraDevice.close();
openCamera();
}
//找到最后一张的路径
private void setLastImagePath() {
Log.d(TAG, "setLastImagePath: success");
imageList = GetImageFilePath.getFilePath();
String string = imageList.get(imageList.size() - 1);
if (string.contains(".jpg")) {
setImageBitmap(string);
} else {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(string);
//获取第1帧
Bitmap bitmap = retriever.getFrameAtTime(1);
mImageView.setImageBitmap(bitmap);
}
}
// 设置缩略图显示
private void setImageBitmap(String path) {
Log.d(TAG, "setImageBitmap: success");
Bitmap bitmap = BitmapFactory.decodeFile(path);
//通过ImageView显示缩略图
mImageView.setImageBitmap(bitmap);
}
//打开相册
private void openAlbum() {
Log.d(TAG, "openAlbum: success");
Intent intent = new Intent();
// 在ImageShowActivity中直接从相册中遍历 不需要传递过去
//intent.putStringArrayListExtra("myList", imageList);
intent.setClass(getContext(), ImageShowActivity.class);
startActivity(intent);
}
private void startTime() {
timer.setBase(SystemClock.elapsedRealtime());//计时器清零
timer.start();
}
private void endTime(){
timer.stop();
timer.setBase(SystemClock.elapsedRealtime());//计时器清零
}
}
视频录制的流程跟拍照很相似
创建的请求不一样仅此而已
1.因为我在拍照的Fragment中获取过权限所以不在需要申请权限
2.因为使用的ViewPage加Fragment所以需要setUserVisibleHint函数来保证Fragment可见的时候在对其进行初始化,不然两个Fragment同时执行初始化相机打开相机等会报错.
3.初始化控件,在该类中用的是initView
4.监听控件onClick
5.初始化TextureView 监听 在demo中 initTextureVIew()
6.计算使用的摄像头分辨率 getMatchingSize()
7.初始化相机管理 initCameraManager()
只是获取 CameraManager
8.选择摄像头 主要是选择前后 selectCamera()
9.openCamera() 还是一样openCamera 的时候需要用到CameraDevice.StateCallback 在里面 开启预览
10.开启预览 这里是把创建预览请求mPreviewCaptureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewCaptureRequest.addTarget(previewSurface);
写到了startPreview()中捕获请求的回调CameraCaptureSession.StateCallback() 也写到这个方法里在回调中调用重复预览 repeatPreview()
11.重复预览 repeatPreview()
mCameraCaptureSession.setRepeatingRequest(mPreviewCaptureRequest.build(), null, mChildHandler);
mChildHandler这里是创建了一个子线程处理
12.因为录制视频需要停止预览stopPreview()
13.初始化子线程 initChildHandler() 以及关闭线程stopBackgroundThread()
14.配置录制视频的相关数据configMediaRecorder()
mMediaRecorder.setOrientationHint(270); 通过这个来处理保存的视频方向前置需要设为270 后置需要设置90
15.配置录制视频的CameraCaptureSession 这里跟拍照不一样configSession()
16.开始录制startRecorder()
停止录制stopRecorder()
17.广播刷新相册broadcast()
18. 改变前后摄像头changeCamera()
19.设置显示最后一样图片设置最后一张路径setLastImagePath() 把路径转图片显示setImageBitmap
20.打开相册 openAlbum()
21.录制视频需要用的计时器控件startTime() endTime()
总结几个回调使用与区别:
1.CameraDevice.StateCallback openCamera会用到
2.CameraCaptureSession.CaptureCallback 拍照或者是录像的时候用到
3.CameraCaptureSession.StateCallback 处理请求的回调
4.onRequestPermissionResult 权限回调
5.TextureView.SurfaceTextureListener() SurfaceView状态回调
这是我学习的过程不一定对 但是对小白肯定有帮助