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