先說明一個問題墓毒,本記錄沒有解決CameraX前置拍攝視頻,視頻畫面鏡像問題亲怠,如果和我一樣被這個困惑了的所计,就不要看了,我沒解決团秽,誰要是知道怎么解決麻煩評論去告訴我主胧,謝了叭首。(猜想:是不是要設(shè)置一個ImageAnalysis解析數(shù)據(jù)流,錄屏時動態(tài)鏡像啊踪栋。)
把想要的東西都封裝在一個自定義view里面焙格,使用時只要添加在activity的布局里面,外部無需關(guān)心調(diào)用夷都。
值得一提的是眷唉,他切換相機會黑屏,原因是CameraX的切換攝像頭邏輯是先解綁在綁定囤官。我猜在解綁的時候冬阳,PreviewView的數(shù)據(jù)斷流了,自己也沒有保持最后一幀畫面治拿,就是黑屏的摩泪。那么他沒保持最后一幀,那么我們自己保持就行了劫谅,關(guān)鍵PreviewView是提供了獲取畫面bitmap方法的见坑。具體邏輯看下面的mPreviewView.getBitmap()相關(guān)邏輯代碼。
還有就是他前置拍攝照片也是鏡像的捏检,需要我們設(shè)置一下
ImageCapture.Metadata metadata = new ImageCapture.Metadata();
metadata.setReversedHorizontal(lensFacing == CameraSelector.LENS_FACING_FRONT);
我就想不通了荞驴,你拍照有設(shè)置,為啥錄視頻沒有贯城,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'
權(quán)限
<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, "您已永久禁止請去設(shè)置打開", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(mContext, "請同意所有權(quán)限", 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) {
//有可能會多次調(diào)用能犯,那怎么辦呢鲫骗?結(jié)合規(guī)律,他會多次調(diào)用PreviewView.StreamState.IDLE或者PreviewView.StreamState.STREAMING
//所以很簡單踩晶,我們記錄上一次的狀態(tài)如果是IDLE执泰,而當前這一次回調(diào)是STREAMING,那么就是成功切換后的第一次調(diào)用渡蜻。就只會執(zhí)行一次
if(lastStreamState == PreviewView.StreamState.IDLE && streamState == PreviewView.StreamState.STREAMING){
flipImageViewRecycler();
}
lastStreamState = streamState;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 預覽數(shù)據(jù)開始后术吝,去掉假設(shè)的畫面幀
*/
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();
//可在此做回調(diào)
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>
布局里面的圖標,有興趣可以去阿里圖標庫下載對應(yīng)的替換上去茸苇。
RecordButton
public class RecordButton extends View implements View.OnTouchListener {
/**
* 返回參數(shù):正常
*/
public static final int NORMAL = 0;
/**
* 返回參數(shù):錄制時間太短
*/
public static final int RECORD_SHORT = 1;
/**
* RecordButton的監(jiān)聽器
*/
public interface RecordButtonListener {
/**
* 點擊事件監(jiān)聽
*/
void onClick();
/**
* 長按事件監(jiān)聽
*/
void onLongClick();
/**
* 長按事件結(jié)束
* @param result 返回值狀態(tài)碼 0:正常錄制 1:錄制時間過短
*/
void onLongClickFinish(int result);
}
/**
* 觸摸延遲為300毫秒
*/
private static final long TOUCH_DELAY_DEFAULT = 300;
private long mTouchDelay = TOUCH_DELAY_DEFAULT;
/**
* 動畫持續(xù)時間
*/
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;
/**
* 縮放系數(shù)
* 取值于微信排苍,暫不提供外部修改接口
* 可直接修改源碼
*/
private static final float SCALE_NUM = 0.67f;
/**
* 初始狀態(tài)下內(nèi)外圓的比例
* 系數(shù)取值于微信,暫不提供外部修改接口
* 可直接修改源碼
*/
private static final float INNER_EXTERNAL = 0.75f;
/**
* 觸摸時間
*/
private long mTouchDown;
/**
* 觸摸動作完成開關(guān)
*/
private volatile boolean isDone = false;
/**
* view的寬度
*/
private int mViewLength = 0;
/**
* 內(nèi)圓学密,外圓淘衙,進度條畫筆
*/
private Paint mInnerPaint, mExternalPaint, mProgressPaint;
/**
* progress的rectf
*/
private RectF mRectF;
/**
* 外圓的半徑
*/
private int mExternalCircleRadius;
/**
* 內(nèi)圓的半徑
*/
private int mInnerCircleRadius;
/**
* 同心圓的中心點
*/
private int mCircleCenterX, mCircleCenterY;
/**
* progress的寬度
*/
private float mStrokeWidth = Utils.dp2px(5f);
/**
* 進度條的角度
*/
private float mSweepAngle = 0;
/**
* 觸摸事件的監(jiān)聽器
*/
private RecordButtonListener mRecordButtonListener;
/**
* 延時任務(wù)
*/
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);
}
/**
* 初始化畫筆及其他參數(shù)
*
* @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();
//初始化內(nèi)部圓的畫筆
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);
}
/**
* 重置參數(shù)
*/
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);
//畫內(nèi)圈圓
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;
}
/**
* 按下時執(zhí)行操作
*/
private void onActionDown() {
if (null == mRecordButtonListener) {
return;
}
//清楚上一個正在執(zhí)行的任務(wù)
if (null != mLongClickRunnable) {
mHandler.removeCallbacks(mLongClickRunnable);
}
//記錄按下時間
mTouchDown = System.currentTimeMillis();
//完成置為false
isDone = false;
//提交延時任務(wù)
mLongClickRunnable = new LongClickRunnable();
mHandler.postDelayed(mLongClickRunnable, mTouchDelay);
}
/**
* 抬起時執(zhí)行操作
* 手勢抬起的時間用于點擊判斷
*/
private void onActionUp() {
//完成開關(guān)置為true
isDone = true;
//清除動畫效果
clearAnimation();
long mTouchTime = System.currentTimeMillis() - mTouchDown;
//如果時間小于touch_delay就是點擊事件
if (mTouchTime < mTouchDelay) {
mRecordButtonListener.onClick();
}
}
/**
* 結(jié)束動作后執(zhí)行的邏輯
* 動畫結(jié)束后的時間用于長按判斷
*/
private void onActionEndAction() {
long mTouchTime = System.currentTimeMillis() - mTouchDown;
//完成開關(guān)置為true
isDone = true;
//執(zhí)行結(jié)束動畫效果
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() {
//如果延遲過后,觸摸動作還沒有結(jié)束
if (!isDone) {
//開啟開始動畫
startBeginCircleAnimation();
}
}
}
/**
* 開啟開始動畫
*/
private void startBeginCircleAnimation() {
//每次縮放動畫之前重置參數(shù)腻暮,防止出現(xiàn)ui錯誤
reSetParameters();
//這里是內(nèi)圈圓半徑獲取和賦值
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) {
//如果在開始動畫執(zhí)行的過程中停止觸摸動作幔翰,及時取消動畫
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) {
//如果在開始動畫執(zhí)行的過程中停止觸摸動作漩氨,及時取消動畫
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();
//同時調(diào)用長按點擊事件
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();
//如果動作結(jié)束了,結(jié)束動畫
if (isDone) {
valueAnimator.cancel();
}
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
onActionEndAction();
}
});
animator.start();
}
/**
* 開啟結(jié)束動畫
*/
private void startEndCircleAnimation() {
//每次縮放動畫之前重置參數(shù)遗增,防止出現(xiàn)ui錯誤
reSetParameters();
//這里是內(nèi)圈圓半徑獲取和賦值
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) {
//如果在結(jié)束動畫播放過程中再次點擊叫惊,及時停止動畫
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) {
//如果在結(jié)束動畫播放過程中再次點擊,及時停止動畫
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();
}
});
}
/**
* 設(shè)置觸摸事件的監(jiān)聽器
*
* @param recordButtonListener 觸摸監(jiān)聽器
*/
public void setRecordButtonListener(RecordButtonListener recordButtonListener) {
this.mRecordButtonListener = recordButtonListener;
}
/**
* 設(shè)置觸摸延遲時間做修,區(qū)分點擊還是長按
*
* @param touchDelay 觸摸延遲時間(毫秒)
*/
public void setTouchDelay(long touchDelay) {
this.mTouchDelay = touchDelay;
}
/**
* 設(shè)置最長錄制時間
*
* @param recordTime 最長錄制時間(毫秒)
*/
public void setRecordTime(long recordTime) {
this.mRecordTime = recordTime;
}
/**
* 設(shè)置多長時間之內(nèi)算是錄制過短
*
* @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里面調(diào)用就簡單了
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里面實現(xiàn)邏輯代碼霍狰,當然這里缺一個點擊ok后將數(shù)據(jù)返回的回調(diào)。