先说明一个问题,本记录没有解决CameraX前置拍摄视频,视频画面镜像问题,如果和我一样被这个困惑了的,就不要看了,我没解决,谁要是知道怎么解决麻烦评论去告诉我,谢了。(猜想:是不是要设置一个ImageAnalysis解析数据流,录屏时动态镜像啊。)
把想要的东西都封装在一个自定义view里面,使用时只要添加在activity的布局里面,外部无需关心调用。
值得一提的是,他切换相机会黑屏,原因是CameraX的切换摄像头逻辑是先解绑在绑定。我猜在解绑的时候,PreviewView的数据断流了,自己也没有保持最后一帧画面,就是黑屏的。那么他没保持最后一帧,那么我们自己保持就行了,关键PreviewView是提供了获取画面bitmap方法的。具体逻辑看下面的mPreviewView.getBitmap()相关逻辑代码。
还有就是他前置拍摄照片也是镜像的,需要我们设置一下
ImageCapture.Metadata metadata = new ImageCapture.Metadata();
metadata.setReversedHorizontal(lensFacing == CameraSelector.LENS_FACING_FRONT);
我就想不通了,你拍照有设置,为啥录视频没有,la ji。
最新的库
//相机录制
implementation "androidx.camera:camera-core:1.0.2"
implementation "androidx.camera:camera-camera2:1.0.2"
// If you want to additionally use the CameraX Lifecycle library
implementation "androidx.camera:camera-lifecycle:1.0.2"
// If you want to additionally use the CameraX View class
implementation "androidx.camera:camera-view:1.0.0-alpha29"
// If you want to additionally use the CameraX Extensions library
implementation "androidx.camera:camera-extensions:1.0.0-alpha29"
//promission
implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.5@aar'
权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
RecodingView
public class RecodingView extends RelativeLayout implements View.OnClickListener {
private static final double RATIO_4_3_VALUE = 4.0 / 3.0;
private static final double RATIO_16_9_VALUE = 16.0 / 9.0;
private boolean mIsBack = true;
private int lensFacing = CameraSelector.LENS_FACING_BACK;
private Context mContext;
private PreviewView mPreviewView;
private ProcessCameraProvider mCameraProvider;
private ImageView mBtnFlip;
private Camera camera;
private Preview mPreview;
private boolean first = true;
private ImageView mFlipIv;
private Bitmap mBitmapFlip;
private Handler mMainHandle = new Handler(Looper.getMainLooper());
private PreviewView.StreamState lastStreamState = PreviewView.StreamState.IDLE;
private RecordButton mRecordButton;
private ImageCapture mImageCapture;
private ExecutorService mCameraExecutor;
private VideoCapture mVideoCapture;
private int mCurrentState = RecordButton.NORMAL;
private ImageView mIvDelete;
private ImageView mIvOk;
private String mCurrentPicturePath;
private String mCurrentVideoPath;
private RelativeLayout mRlVideo;
private VideoView mVideoView;
public RecodingView(Context context) {
this(context, null);
}
public RecodingView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RecodingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setVisibility(GONE);
mContext = context;
mCameraExecutor = Executors.newSingleThreadExecutor();
inflate(context, R.layout.recoding_layout, this);
mBtnFlip = findViewById(R.id.mBtnFlip);
mBtnFlip.setOnClickListener(this);
mPreviewView = findViewById(R.id.previewView);
mFlipIv = findViewById(R.id.mFlipIv);
mIvDelete = findViewById(R.id.mIvDelete);
mIvDelete.setOnClickListener(this);
mIvOk = findViewById(R.id.mIvOk);
mIvOk.setOnClickListener(this);
mRlVideo = findViewById(R.id.mRlVideo);
findViewById(R.id.mIvBack).setOnClickListener(this);
mPreviewView.post(new Runnable() {
@Override
public void run() {
RxPermissions rxPermissions = new RxPermissions((Activity) mContext);
rxPermissions.request(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO).subscribe(new Consumer<Boolean>() {
@SuppressLint("CheckResult")
@Override
public void accept(Boolean aBoolean) throws Exception {
if (aBoolean == true) {
setVisibility(VISIBLE);
startCameraPreview();
}else{
rxPermissions.shouldShowRequestPermissionRationale((Activity) mContext,Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO).subscribe(new Consumer<Boolean>() {
@Override
public void accept(Boolean aBoolean) throws Exception {
if(aBoolean == false){
Toast.makeText(mContext, "您已永久禁止请去设置打开", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(mContext, "请同意所有权限", Toast.LENGTH_SHORT).show();
}
}
});
}
}
});
}
});
mRecordButton = findViewById(R.id.mRecordButton);
mRecordButton.setRecordButtonListener(new RecordButton.RecordButtonListener() {
@Override
public void onClick() {
takePicture();
}
@Override
public void onLongClick() {
takeVideo();
}
@SuppressLint("RestrictedApi")
@Override
public void onLongClickFinish(int result) {
switch (result) {
case RecordButton.NORMAL:
mCurrentState = RecordButton.NORMAL;
mVideoCapture.stopRecording();//停止录制
break;
case RecordButton.RECORD_SHORT:
mCurrentState = RecordButton.RECORD_SHORT;
mVideoCapture.stopRecording();//停止录制
break;
default:
}
}
});
}
/**
* 录制屏幕
*/
@SuppressLint("RestrictedApi")
private void takeVideo() {
if (mVideoCapture != null) {
File videoFile = createFile(Environment.getExternalStorageDirectory().getPath() + "/CameraX", "yyyy-MM-dd-HH-mm-ss-SSS", ".mp4");
VideoCapture.OutputFileOptions outputOptions = new VideoCapture.OutputFileOptions.Builder(videoFile)
.build();
if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
return;
}
mVideoCapture.startRecording(outputOptions, mCameraExecutor, new VideoCapture.OnVideoSavedCallback() {
@Override
public void onVideoSaved(@NonNull VideoCapture.OutputFileResults outputFileResults) {
mMainHandle.post(new Runnable() {
@Override
public void run() {
if (mCurrentState == RecordButton.NORMAL) {
//录制成功
setRecordView(false);
mCurrentVideoPath = outputFileResults.getSavedUri().getPath();
//展示视频
showVideoView(outputFileResults.getSavedUri());
} else {
//录制过短
Toast.makeText(mContext, "录制过短,重新录制", Toast.LENGTH_SHORT).show();
FileUtil.deleteFile(outputFileResults.getSavedUri().getPath());
}
}
});
}
@Override
public void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) {
}
});
}
}
/**
* 展示视频
* @param savedUri
*/
private void showVideoView(Uri savedUri) {
mVideoView = new VideoView(mContext);
mVideoView.setVideoURI(savedUri);
mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
mediaPlayer.start();
}
});
mRlVideo.addView(mVideoView,new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
mVideoView.start();
}
private void hideVideoView(){
if(mVideoView != null){
mVideoView.pause();
mVideoView = null;
}
mRlVideo.removeAllViews();
}
private void takePicture() {
if(mImageCapture != null){
File photoFile = createFile(Environment.getExternalStorageDirectory().getPath()+"/CameraX", "yyyy-MM-dd-HH-mm-ss-SSS", ".jpg");
ImageCapture.Metadata metadata = new ImageCapture.Metadata();
metadata.setReversedHorizontal(lensFacing == CameraSelector.LENS_FACING_FRONT);
ImageCapture.OutputFileOptions outputOptions = new ImageCapture.OutputFileOptions.Builder(photoFile)
.setMetadata(metadata)
.build();
mImageCapture.takePicture(outputOptions, mCameraExecutor, new ImageCapture.OnImageSavedCallback() {
@Override
public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
mMainHandle.post(new Runnable() {
@Override
public void run() {
try {
setRecordView(false);
mCurrentPicturePath = outputFileResults.getSavedUri().getPath();
Glide.with(mContext).load(mCurrentPicturePath).into(mFlipIv);
}catch (Exception e){
e.printStackTrace();
}
}
});
}
@Override
public void onError(@NonNull ImageCaptureException exception) {
}
});
}
}
private File createFile(String baseFolder, String format, String extension) {
if(!new File(baseFolder).exists()){
new File(baseFolder).mkdirs();
}
return new File(baseFolder, new SimpleDateFormat(format, Locale.US)
.format(System.currentTimeMillis()) + extension);
}
private void startCameraPreview() {
ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(mContext);
cameraProviderFuture.addListener(new Runnable() {
@Override
public void run() {
try {
mCameraProvider = cameraProviderFuture.get();
if(hasBackCamera()){
lensFacing = CameraSelector.LENS_FACING_BACK;
}else if(hasFrontCamera()){
lensFacing = CameraSelector.LENS_FACING_FRONT;
}else{
throw new IllegalArgumentException("Back and front camera are unavailable");
}
updateCameraSwitchButton();
bindCameraUseCases();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},ContextCompat.getMainExecutor(mContext));
}
@SuppressLint("RestrictedApi")
private void bindCameraUseCases() {
CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(lensFacing).build();
if(mPreview == null){
mPreview = new Preview.Builder()
.setTargetAspectRatio(aspectRatio())
// Set initial target rotation
.setTargetRotation(Surface.ROTATION_0)
.build();
}
mImageCapture = new ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
.setCameraSelector(cameraSelector)
// We request aspect ratio but no resolution to match preview config, but letting
// CameraX optimize for whatever specific resolution best fits our use cases
.setTargetAspectRatio(aspectRatio())
.setTargetRotation(mPreviewView.getDisplay().getRotation())
.build();
mVideoCapture = new VideoCapture.Builder()
.setTargetAspectRatio(aspectRatio())
.setTargetRotation(mPreviewView.getDisplay().getRotation())
.setCameraSelector(cameraSelector)
.build();
mCameraProvider.unbindAll();
try {
// A variable number of use-cases can be passed here -
// camera provides access to CameraControl & CameraInfo
camera = mCameraProvider.bindToLifecycle((LifecycleOwner) mContext, cameraSelector, mPreview,mImageCapture,mVideoCapture);
// Attach the viewfinder's surface provider to preview use case
if(first){
first = false;
mPreview.setSurfaceProvider(mPreviewView.getSurfaceProvider());
}
mPreviewView.getPreviewStreamState().observe((LifecycleOwner) mContext, new Observer<PreviewView.StreamState>() {
@Override
public void onChanged(PreviewView.StreamState streamState) {
//有可能会多次调用,那怎么办呢?结合规律,他会多次调用PreviewView.StreamState.IDLE或者PreviewView.StreamState.STREAMING
//所以很简单,我们记录上一次的状态如果是IDLE,而当前这一次回调是STREAMING,那么就是成功切换后的第一次调用。就只会执行一次
if(lastStreamState == PreviewView.StreamState.IDLE && streamState == PreviewView.StreamState.STREAMING){
flipImageViewRecycler();
}
lastStreamState = streamState;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 预览数据开始后,去掉假设的画面帧
*/
private void flipImageViewRecycler() {
mMainHandle.postDelayed(new Runnable() {
@Override
public void run() {
mBtnFlip.setEnabled(true);
mFlipIv.setImageBitmap(null);
if(mBitmapFlip != null){
mBitmapFlip.recycle();
mBitmapFlip = null;
}
}
},200);
}
private int aspectRatio() {
int width = getWidth();
int height = getHeight();
double previewRatio = (double)Math.max(width, height) / (double)Math.min(width, height);
if (Math.abs(previewRatio - RATIO_4_3_VALUE) <= Math.abs(previewRatio - RATIO_16_9_VALUE)) {
return AspectRatio.RATIO_4_3;
}
return AspectRatio.RATIO_16_9;
}
private void updateCameraSwitchButton() {
mBtnFlip.setEnabled(hasBackCamera() && hasFrontCamera());
}
private boolean hasFrontCamera() {
try {
return mCameraProvider.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) ? true: false;
} catch (CameraInfoUnavailableException e) {
e.printStackTrace();
return false;
}
}
private boolean hasBackCamera() {
try {
return mCameraProvider.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ? true: false;
} catch (CameraInfoUnavailableException e) {
e.printStackTrace();
return false;
}
}
@Override
public void onClick(View view) {
int id = view.getId();
if(id == R.id.mBtnFlip){
mBtnFlip.setEnabled(false);
if(lensFacing == CameraSelector.LENS_FACING_BACK){
lensFacing = CameraSelector.LENS_FACING_FRONT;
}else{
lensFacing = CameraSelector.LENS_FACING_BACK;
}
mBitmapFlip = mPreviewView.getBitmap();
mFlipIv.setImageBitmap(mBitmapFlip);
bindCameraUseCases();
}else if(id == R.id.mIvDelete){
setRecordView(true);
mFlipIv.setImageBitmap(null);
hideVideoView();
FileUtil.deleteFile(mCurrentPicturePath);
mCurrentPicturePath = null;
mCurrentVideoPath = null;
}else if(id == R.id.mIvOk){
successOption();
}else if(id == R.id.mIvBack){
((Activity)mContext).finish();
}
}
/**
* 拍摄成功后
*/
private void successOption() {
((Activity)mContext).finish();
//可在此做回调
Log.d("yanjin","path = "+ (TextUtils.isEmpty(mCurrentPicturePath)?mCurrentVideoPath:mCurrentPicturePath));
}
private void setRecordView(boolean flag){
if(flag){
mRecordButton.setVisibility(VISIBLE);
mIvDelete.setVisibility(GONE);
mIvOk.setVisibility(GONE);
}else{
mRecordButton.setVisibility(GONE);
mIvDelete.setVisibility(VISIBLE);
mIvOk.setVisibility(VISIBLE);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if(mCameraExecutor != null){
mCameraExecutor.shutdownNow();
}
}
}
布局recoding_layout.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"
android:background="@color/black">
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ImageView
android:id="@+id/mFlipIv"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<RelativeLayout
android:id="@+id/mRlVideo"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ImageView
android:id="@+id/mIvBack"
android:layout_width="35dp"
android:layout_height="35dp"
android:src="@mipmap/iv_back"
android:layout_marginTop="20dp"
android:layout_marginLeft="15dp" />
<ImageView
android:id="@+id/mBtnFlip"
android:layout_width="38dp"
android:layout_height="38dp"
android:layout_alignParentRight="true"
android:layout_marginTop="16dp"
android:layout_marginRight="15dp"
android:src="@mipmap/iv_flip_button"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="50dp">
<com.example.demo.recoding.view.RecordButton
android:id="@+id/mRecordButton"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_centerHorizontal="true" />
<View
android:id="@+id/mCenterFlagView"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_centerHorizontal="true"/>
<ImageView
android:id="@+id/mIvDelete"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@mipmap/iv_delete"
android:layout_toLeftOf="@id/mCenterFlagView"
android:layout_centerVertical="true"
android:layout_marginRight="30dp"
android:visibility="gone"/>
<ImageView
android:id="@+id/mIvOk"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@mipmap/iv_ok"
android:layout_toRightOf="@id/mCenterFlagView"
android:layout_centerVertical="true"
android:layout_marginLeft="30dp"
android:visibility="gone"/>
</RelativeLayout>
</RelativeLayout>
布局里面的图标,有兴趣可以去阿里图标库下载对应的替换上去。
RecordButton
public class RecordButton extends View implements View.OnTouchListener {
/**
* 返回参数:正常
*/
public static final int NORMAL = 0;
/**
* 返回参数:录制时间太短
*/
public static final int RECORD_SHORT = 1;
/**
* RecordButton的监听器
*/
public interface RecordButtonListener {
/**
* 点击事件监听
*/
void onClick();
/**
* 长按事件监听
*/
void onLongClick();
/**
* 长按事件结束
* @param result 返回值状态码 0:正常录制 1:录制时间过短
*/
void onLongClickFinish(int result);
}
/**
* 触摸延迟为300毫秒
*/
private static final long TOUCH_DELAY_DEFAULT = 300;
private long mTouchDelay = TOUCH_DELAY_DEFAULT;
/**
* 动画持续时间
*/
private static final long DURING_TIME = 200;
/**
* 录制时间15s
*/
private static final long RECORD_TIME_DEFAULT = 15000;
private long mRecordTime = RECORD_TIME_DEFAULT;
/**
* 录制过短时间
*/
private static final long MIN_RECORD_TIME_DEFAULT = 3000;
private long mMinRecordTime = MIN_RECORD_TIME_DEFAULT;
/**
* 缩放系数
* 取值于微信,暂不提供外部修改接口
* 可直接修改源码
*/
private static final float SCALE_NUM = 0.67f;
/**
* 初始状态下内外圆的比例
* 系数取值于微信,暂不提供外部修改接口
* 可直接修改源码
*/
private static final float INNER_EXTERNAL = 0.75f;
/**
* 触摸时间
*/
private long mTouchDown;
/**
* 触摸动作完成开关
*/
private volatile boolean isDone = false;
/**
* view的宽度
*/
private int mViewLength = 0;
/**
* 内圆,外圆,进度条画笔
*/
private Paint mInnerPaint, mExternalPaint, mProgressPaint;
/**
* progress的rectf
*/
private RectF mRectF;
/**
* 外圆的半径
*/
private int mExternalCircleRadius;
/**
* 内圆的半径
*/
private int mInnerCircleRadius;
/**
* 同心圆的中心点
*/
private int mCircleCenterX, mCircleCenterY;
/**
* progress的宽度
*/
private float mStrokeWidth = Utils.dp2px(5f);
/**
* 进度条的角度
*/
private float mSweepAngle = 0;
/**
* 触摸事件的监听器
*/
private RecordButtonListener mRecordButtonListener;
/**
* 延时任务
*/
private LongClickRunnable mLongClickRunnable;
private Handler mHandler = new Handler();
public RecordButton(Context context) {
this(context, null);
}
public RecordButton(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public RecordButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
/**
* 初始化画笔及其他参数
*
* @param context 上下文
* @param attrs 属性
*/
private void init(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecordButton);
mRecordTime = (long) a.getFloat(R.styleable.RecordButton_recordTime, RECORD_TIME_DEFAULT);
mTouchDelay = (long) a.getFloat(R.styleable.RecordButton_touchDelay, TOUCH_DELAY_DEFAULT);
mMinRecordTime = (long) a.getFloat(R.styleable.RecordButton_minRecordTime, MIN_RECORD_TIME_DEFAULT);
a.recycle();
//初始化内部圆的画笔
mInnerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mInnerPaint.setColor(getResources().getColor(R.color.innercircle));
//初始化外圈圆的画笔
mExternalPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mExternalPaint.setColor(getResources().getColor(R.color.externalcircle));
//初始化弧形画笔
mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mProgressPaint.setStyle(Paint.Style.STROKE);
mProgressPaint.setStrokeWidth(mStrokeWidth);
mProgressPaint.setColor(getResources().getColor(R.color.progress));
setBackgroundResource(R.color.background);
setOnTouchListener(this);
}
/**
* 重置参数
*/
private void reSetParameters() {
mExternalCircleRadius = (int) ((mViewLength / 2) * SCALE_NUM);
mInnerCircleRadius = (int) (mExternalCircleRadius * INNER_EXTERNAL);
mSweepAngle = 0;
mRectF = new RectF(mStrokeWidth / 2,
mStrokeWidth / 2,
mViewLength - (mStrokeWidth / 2),
mViewLength - (mStrokeWidth / 2));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int viewWidth = MeasureSpec.getSize(widthMeasureSpec);
int viewHeigth = MeasureSpec.getSize(heightMeasureSpec);
mViewLength = Math.min(viewWidth, viewHeigth);
mCircleCenterX = mViewLength / 2;
mCircleCenterY = mViewLength / 2;
reSetParameters();
setMeasuredDimension(mViewLength, mViewLength);
}
@Override
protected void onDraw(Canvas canvas) {
//画外圈圆
canvas.drawCircle(mCircleCenterX, mCircleCenterY, mExternalCircleRadius, mExternalPaint);
//画内圈圆
canvas.drawCircle(mCircleCenterX, mCircleCenterY, mInnerCircleRadius, mInnerPaint);
//画弧形
canvas.drawArc(mRectF, -90, mSweepAngle, false, mProgressPaint);
}
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (null == mRecordButtonListener) {
return false;
}
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
onActionDown();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
onActionUp();
break;
default:
}
return true;
}
/**
* 按下时执行操作
*/
private void onActionDown() {
if (null == mRecordButtonListener) {
return;
}
//清楚上一个正在执行的任务
if (null != mLongClickRunnable) {
mHandler.removeCallbacks(mLongClickRunnable);
}
//记录按下时间
mTouchDown = System.currentTimeMillis();
//完成置为false
isDone = false;
//提交延时任务
mLongClickRunnable = new LongClickRunnable();
mHandler.postDelayed(mLongClickRunnable, mTouchDelay);
}
/**
* 抬起时执行操作
* 手势抬起的时间用于点击判断
*/
private void onActionUp() {
//完成开关置为true
isDone = true;
//清除动画效果
clearAnimation();
long mTouchTime = System.currentTimeMillis() - mTouchDown;
//如果时间小于touch_delay就是点击事件
if (mTouchTime < mTouchDelay) {
mRecordButtonListener.onClick();
}
}
/**
* 结束动作后执行的逻辑
* 动画结束后的时间用于长按判断
*/
private void onActionEndAction() {
long mTouchTime = System.currentTimeMillis() - mTouchDown;
//完成开关置为true
isDone = true;
//执行结束动画效果
startEndCircleAnimation();
//如果触摸事件小于RECORD_SHORT_TIME就是录制过短
if (mTouchTime < mMinRecordTime) {
mRecordButtonListener.onLongClickFinish(RecordButton.RECORD_SHORT);
} else {
mRecordButtonListener.onLongClickFinish(RecordButton.NORMAL);
}
}
private class LongClickRunnable implements Runnable {
@Override
public void run() {
//如果延迟过后,触摸动作还没有结束
if (!isDone) {
//开启开始动画
startBeginCircleAnimation();
}
}
}
/**
* 开启开始动画
*/
private void startBeginCircleAnimation() {
//每次缩放动画之前重置参数,防止出现ui错误
reSetParameters();
//这里是内圈圆半径获取和赋值
ValueAnimator animator = ValueAnimator.ofInt(mInnerCircleRadius, (int) (mInnerCircleRadius * SCALE_NUM));
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mInnerCircleRadius = (int) valueAnimator.getAnimatedValue();
if (isDone) {
//如果在开始动画执行的过程中停止触摸动作,及时取消动画
valueAnimator.cancel();
}
//更新ui
postInvalidate();
}
});
//这里是外圈圆半径获取和赋值
ValueAnimator animator1 = ValueAnimator.ofInt(mExternalCircleRadius, (int) (mExternalCircleRadius / SCALE_NUM));
animator1.setInterpolator(new LinearInterpolator());
animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mExternalCircleRadius = (int) valueAnimator.getAnimatedValue();
if (isDone) {
//如果在开始动画执行的过程中停止触摸动作,及时取消动画
valueAnimator.cancel();
}
}
});
AnimatorSet set = new AnimatorSet();
set.playTogether(animator, animator1);
set.setDuration(DURING_TIME);
set.setInterpolator(new LinearInterpolator());
set.start();
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (isDone) {
return;
}
//开始进度条动画
startProgressAnimation();
//同时调用长按点击事件
if (mRecordButtonListener != null) {
mRecordButtonListener.onLongClick();
}
}
});
}
/**
* 开始进度条动画
*/
private void startProgressAnimation() {
//这里是进度条进度获取和赋值
ValueAnimator animator = ValueAnimator.ofFloat(0, 360);
animator.setDuration(mRecordTime);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mSweepAngle = isDone ? 0 : (float) valueAnimator.getAnimatedValue();
//更新ui
postInvalidate();
//如果动作结束了,结束动画
if (isDone) {
valueAnimator.cancel();
}
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
onActionEndAction();
}
});
animator.start();
}
/**
* 开启结束动画
*/
private void startEndCircleAnimation() {
//每次缩放动画之前重置参数,防止出现ui错误
reSetParameters();
//这里是内圈圆半径获取和赋值
ValueAnimator animator = ValueAnimator.ofInt((int) (mInnerCircleRadius * SCALE_NUM), mInnerCircleRadius);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mInnerCircleRadius = (int) valueAnimator.getAnimatedValue();
if (!isDone) {
//如果在结束动画播放过程中再次点击,及时停止动画
valueAnimator.cancel();
}
//更新ui
postInvalidate();
}
});
//这里是外圈圆半径获取和赋值
ValueAnimator animator1 = ValueAnimator.ofInt((int) (mExternalCircleRadius / SCALE_NUM), mExternalCircleRadius);
animator1.setInterpolator(new LinearInterpolator());
animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mExternalCircleRadius = (int) valueAnimator.getAnimatedValue();
if (!isDone) {
//如果在结束动画播放过程中再次点击,及时停止动画
valueAnimator.cancel();
}
}
});
AnimatorSet set = new AnimatorSet();
set.playTogether(animator, animator1);
set.setDuration(DURING_TIME);
set.setInterpolator(new LinearInterpolator());
set.start();
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
postInvalidate();
}
});
}
/**
* 设置触摸事件的监听器
*
* @param recordButtonListener 触摸监听器
*/
public void setRecordButtonListener(RecordButtonListener recordButtonListener) {
this.mRecordButtonListener = recordButtonListener;
}
/**
* 设置触摸延迟时间,区分点击还是长按
*
* @param touchDelay 触摸延迟时间(毫秒)
*/
public void setTouchDelay(long touchDelay) {
this.mTouchDelay = touchDelay;
}
/**
* 设置最长录制时间
*
* @param recordTime 最长录制时间(毫秒)
*/
public void setRecordTime(long recordTime) {
this.mRecordTime = recordTime;
}
/**
* 设置多长时间之内算是录制过短
*
* @param minRecordTime 录制过短时间(毫秒)
*/
public void setMinRecordTime(long minRecordTime) {
this.mMinRecordTime = minRecordTime+mTouchDelay+DURING_TIME;
}
}
FileUtil
public class FileUtil {
public static void deleteFile(String path){
try {
File file = new File(path);
if(file.exists()){
boolean delete = file.delete();
Log.d("yanjin","文件删除delete="+delete);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
最后activity里面调用就简单了
class RecodingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_recoding)
}
}
activity布局activity_recoding.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
tools:context=".recoding.RecodingActivity">
<com.example.demo.recoding.view.RecodingView
android:id="@+id/mRecodingView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
尽量在自定义view里面实现逻辑代码,当然这里缺一个点击ok后将数据返回的回调。