本文章只寫了個類似微信的錄制視頻的按鈕,效果圖如下:
一、主要的功能:
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的,上面也只能貼圖片,哎,心塞