Android自定义view之仿微信录制视频按钮
本文章只写了个类似微信的录制视频的按钮,效果图如下:
一、主要的功能:
1.长按显示进度条,单击事件,录制完成回调
2.最大时间和最小时间控制
3.进度条宽度,颜色设置
二、实现思路
该自定义View主要有三块组成,白色内圆,浅色大圆,圆形进度条;长按一段时间,内圆缩小0.75倍,外圆放大1.33倍,进度条显示更新,松开手内圆,外圆统一恢复到原来大小;长按时间达到最大,影藏进度条,,同样内圆外圆恢复到原来大小;动画主要用到属性动画中的ValueAnimator,在一定时间内匀速改变内圆,外圆半径,和圆形进度条的绘制角度,最后调用invalidate()重新绘制,起到动画的作用。
三、代码分析
@Override protected void onDraw(final Canvas canvas) { super.onDraw(canvas); //绘制外圆 canvas.drawCircle(mWidth/2,mHeight/2,mBigRadius,mBigCirclePaint); //绘制内圆 canvas.drawCircle(mWidth/2,mHeight/2,mSmallRadius,mSmallCirclePaint); //录制的过程中绘制进度条 if(isRecording){ drawProgress(canvas); } }
绘制内外圆,isRecording表示录制的情况下才参与绘制,相当于显示
@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: isPressed=true; mStartTime=System.currentTimeMillis(); Message mMessage=Message.obtain(); mMessage.what=WHAT_LONG_CLICK; mHandler.sendMessageDelayed(mMessage,mLongClickTime); break; case MotionEvent.ACTION_UP: isPressed=false; isRecording=false; mEndTime=System.currentTimeMillis(); if(mEndTime-mStartTime<mLongClickTime){ mHandler.removeMessages(WHAT_LONG_CLICK); if(onClickListener!=null) onClickListener.onClick(); }else{ startAnimation(mBigRadius,mInitBitRadius,mSmallRadius,mInitSmallRadius);//手指离开时动画复原 if(mProgressAni!=null&&mProgressAni.getCurrentPlayTime()/1000<mMinTime&&!isMaxTime){ if(onLongClickListener!=null){ onLongClickListener.onNoMinRecord(mMinTime); } mProgressAni.cancel(); }else{ //录制完成 if(onLongClickListener!=null&&!isMaxTime){ onLongClickListener.onRecordFinishedListener(); } } } break; } return true; }
长按的事件是通过handler发送延时消息实现的,按下的时候就发送,当手指离开,记录按下和离开的时间间隔,达到一定时间即为长按,否则直接移除消息,长按事件失效,此时情况就是点击事件;
private Handler mHandler=new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what){ case WHAT_LONG_CLICK: //长按事件触发 if(onLongClickListener!=null) { onLongClickListener.onLongClick(); } //内外圆动画,内圆缩小,外圆放大 startAnimation(mBigRadius,mBigRadius*1.33f,mSmallRadius,mSmallRadius*0.7f); break; } } } ;
handler里面处理的即为长按的触发事件,此时开始startAnimation
private void startAnimation(float bigStart,float bigEnd, float smallStart,float smallEnd) { ValueAnimator bigObjAni=ValueAnimator.ofFloat(bigStart,bigEnd); bigObjAni.setDuration(150); bigObjAni.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mBigRadius= (float) animation.getAnimatedValue(); invalidate(); } }); ValueAnimator smallObjAni=ValueAnimator.ofFloat(smallStart,smallEnd); smallObjAni.setDuration(150); smallObjAni.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mSmallRadius= (float) animation.getAnimatedValue(); invalidate(); } }); bigObjAni.start(); smallObjAni.start(); smallObjAni.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { isRecording=false; } @Override public void onAnimationEnd(Animator animation) { //开始绘制圆形进度 if(isPressed){ isRecording=true; isMaxTime=false; startProgressAnimation(); } } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); }
ValueAnimator.ofFloat(bigStart,bigEnd);让圆的半径从bigStart到bigEnd动态变化,
设置addUpdateListener监听,获取正在变化的值重新赋值给圆的半径,调用 invalidate重新绘制,从而起到动画的效果,在开始录制的动画结束后,来绘制圆形进度条,
/** * 绘制圆形进度 * @param canvas */ private void drawProgress(Canvas canvas) { mProgressCirclePaint.setStrokeWidth(mProgressW); mProgressCirclePaint.setStyle(Paint.Style.STROKE); //用于定义的圆弧的形状和大小的界限 RectF oval = new RectF(mWidth/2-(mBigRadius-mProgressW/2), mHeight/2-(mBigRadius-mProgressW/2), mWidth/2+(mBigRadius-mProgressW/2),mHeight/2+(mBigRadius-mProgressW/2)); //根据进度画圆弧 canvas.drawArc(oval, -90, mCurrentProgress, false, mProgressCirclePaint); }
RectF限制圆弧的绘制范围,mCurrentProgress绘制的角度0~360f之间变化,同样可以利用ValueAnimator,来在0~360f之间不断改变,然后不断更新绘制,起到进度条动态更新的效果
四、全部代码
package com.yus.ycamera; import android.animation.Animator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; /** * Created by yufs on 2017/7/4. */ public class CircleButtonView extends View{ private static final int WHAT_LONG_CLICK = 1; private Paint mBigCirclePaint; private Paint mSmallCirclePaint; private Paint mProgressCirclePaint; private int mHeight;//当前View的高 private int mWidth;//当前View的宽 private float mInitBitRadius; private float mInitSmallRadius; private float mBigRadius; private float mSmallRadius; private long mStartTime; private long mEndTime; private Context mContext; private boolean isRecording;//录制状态 private boolean isMaxTime;//达到最大录制时间 private float mCurrentProgress;//当前进度 private long mLongClickTime=500;//长按最短时间(毫秒), private int mTime=5;//录制最大时间s private int mMinTime=3;//录制最短时间 private int mProgressColor;//进度条颜色 private float mProgressW=18f;//圆环宽度 private boolean isPressed;//当前手指处于按压状态 private ValueAnimator mProgressAni;//圆弧进度变化 public CircleButtonView(Context context ) { super(context); init(context,null); } public CircleButtonView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context,attrs); } public CircleButtonView(Context context, AttributeSet attrs) { super(context, attrs); init(context,attrs); } private void init(Context context,AttributeSet attrs) { this.mContext=context; TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleButtonView); mMinTime=a.getInt(R.styleable.CircleButtonView_minTime,0); mTime=a.getInt(R.styleable.CircleButtonView_maxTime,10); mProgressW=a.getDimension(R.styleable.CircleButtonView_progressWidth,12f); mProgressColor=a.getColor(R.styleable.CircleButtonView_progressColor,Color.parseColor("#6ABF66")); a.recycle(); //初始画笔抗锯齿、颜色 mBigCirclePaint=new Paint(Paint.ANTI_ALIAS_FLAG); mBigCirclePaint.setColor(Color.parseColor("#DDDDDD")); mSmallCirclePaint=new Paint(Paint.ANTI_ALIAS_FLAG); mSmallCirclePaint.setColor(Color.parseColor("#FFFFFF")); mProgressCirclePaint=new Paint(Paint.ANTI_ALIAS_FLAG); mProgressCirclePaint.setColor(mProgressColor); mProgressAni= ValueAnimator.ofFloat(0, 360f); mProgressAni.setDuration(mTime*1000); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mWidth = MeasureSpec.getSize(widthMeasureSpec); mHeight=MeasureSpec.getSize(heightMeasureSpec); mInitBitRadius=mBigRadius= mWidth/2*0.75f; mInitSmallRadius=mSmallRadius= mBigRadius*0.75f; } @Override protected void onDraw(final Canvas canvas) { super.onDraw(canvas); //绘制外圆 canvas.drawCircle(mWidth/2,mHeight/2,mBigRadius,mBigCirclePaint); //绘制内圆 canvas.drawCircle(mWidth/2,mHeight/2,mSmallRadius,mSmallCirclePaint); //录制的过程中绘制进度条 if(isRecording){ drawProgress(canvas); } } /** * 绘制圆形进度 * @param canvas */ private void drawProgress(Canvas canvas) { mProgressCirclePaint.setStrokeWidth(mProgressW); mProgressCirclePaint.setStyle(Paint.Style.STROKE); //用于定义的圆弧的形状和大小的界限 RectF oval = new RectF(mWidth/2-(mBigRadius-mProgressW/2), mHeight/2-(mBigRadius-mProgressW/2), mWidth/2+(mBigRadius-mProgressW/2),mHeight/2+(mBigRadius-mProgressW/2)); //根据进度画圆弧 canvas.drawArc(oval, -90, mCurrentProgress, false, mProgressCirclePaint); } private Handler mHandler=new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what){ case WHAT_LONG_CLICK: //长按事件触发 if(onLongClickListener!=null) { onLongClickListener.onLongClick(); } //内外圆动画,内圆缩小,外圆放大 startAnimation(mBigRadius,mBigRadius*1.33f,mSmallRadius,mSmallRadius*0.7f); break; } } } ; @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: isPressed=true; mStartTime=System.currentTimeMillis(); Message mMessage=Message.obtain(); mMessage.what=WHAT_LONG_CLICK; mHandler.sendMessageDelayed(mMessage,mLongClickTime); break; case MotionEvent.ACTION_UP: isPressed=false; isRecording=false; mEndTime=System.currentTimeMillis(); if(mEndTime-mStartTime<mLongClickTime){ mHandler.removeMessages(WHAT_LONG_CLICK); if(onClickListener!=null) onClickListener.onClick(); }else{ startAnimation(mBigRadius,mInitBitRadius,mSmallRadius,mInitSmallRadius);//手指离开时动画复原 if(mProgressAni!=null&&mProgressAni.getCurrentPlayTime()/1000<mMinTime&&!isMaxTime){ if(onLongClickListener!=null){ onLongClickListener.onNoMinRecord(mMinTime); } mProgressAni.cancel(); }else{ //录制完成 if(onLongClickListener!=null&&!isMaxTime){ onLongClickListener.onRecordFinishedListener(); } } } break; } return true; } private void startAnimation(float bigStart,float bigEnd, float smallStart,float smallEnd) { ValueAnimator bigObjAni=ValueAnimator.ofFloat(bigStart,bigEnd); bigObjAni.setDuration(150); bigObjAni.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mBigRadius= (float) animation.getAnimatedValue(); invalidate(); } }); ValueAnimator smallObjAni=ValueAnimator.ofFloat(smallStart,smallEnd); smallObjAni.setDuration(150); smallObjAni.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mSmallRadius= (float) animation.getAnimatedValue(); invalidate(); } }); bigObjAni.start(); smallObjAni.start(); smallObjAni.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { isRecording=false; } @Override public void onAnimationEnd(Animator animation) { //开始绘制圆形进度 if(isPressed){ isRecording=true; isMaxTime=false; startProgressAnimation(); } } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); } /** * 圆形进度变化动画 */ private void startProgressAnimation() { mProgressAni.start(); mProgressAni.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCurrentProgress= (float) animation.getAnimatedValue(); invalidate(); } }); mProgressAni.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { //录制动画结束时,即为录制全部完成 if(onLongClickListener!=null&&isPressed){ isPressed=false; isMaxTime=true; onLongClickListener.onRecordFinishedListener(); startAnimation(mBigRadius,mInitBitRadius,mSmallRadius,mInitSmallRadius); //影藏进度进度条 mCurrentProgress=0; invalidate(); } } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); } /** * 长按监听器 */ public interface OnLongClickListener{ void onLongClick(); //未达到最小录制时间 void onNoMinRecord(int currentTime); //录制完成 void onRecordFinishedListener(); } public OnLongClickListener onLongClickListener; public void setOnLongClickListener(OnLongClickListener onLongClickListener) { this.onLongClickListener = onLongClickListener; } /** * 点击监听器 */ public interface OnClickListener{ void onClick(); } public OnClickListener onClickListener; public void setOnClickListener(OnClickListener onClickListener) { this.onClickListener = onClickListener; } }
属性文件
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="CircleButtonView"> <attr name="minTime" format="integer"></attr> <attr name="maxTime" format="integer"></attr> <attr name="progressColor" format="color"></attr> <attr name="progressWidth" format="dimension"></attr> </declare-styleable> </resources>
全部的大家可以下载源码查看,有什么问题,欢迎提出,后续会将此控件应用到小视频的录制上面,下一遍记录小视频录制,还有就是个人在demo演示的时候都没有找到个将视频转gif的,上面也只能贴图片,哎,心塞