Android自定義View(防抖音 RecordButton) 原

RecordButton


import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.Xfermode;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import com.applenew.tmt.utils.ScreenUtils;

/**
 * Record Button
 * <p>
 * Created by peng on 2018/9/12.
 */

public class RecordButton extends View {

    private Context mContext;

    /**
     * 繪製中間的畫筆
     */
    private Paint mRectPaint;

    /**
     * 圓環畫筆
     */
    private Paint mCirclePaint;

    /**
     * 矩形圓角(corner的值等於矩形寬度的一半時,矩形就是圓形了)
     */
    private float corner;

    /**
     * 圓環半徑
     */
    private float circleRadius;

    /**
     * 圓環寬度
     */
    private float circleStrokeWidth;

    /**
     * 矩形寬
     */
    private float rectWidth;

    /**
     * 圓環內半徑
     */
    private float mMinCircleRadius;

    /**
     * 最大圓環半徑
     */
    private float mMaxCircleRadius;

    /**
     * 最小矩形寬
     */
    private float mMinRectWidth;

    /**
     * 最大矩形寬
     */
    private float mMaxRectWidth;

    /**
     * 最小圓角
     */
    private float mMinCorner;

    /**
     * 最大圓角
     */
    private float mMaxCorner;

    /**
     * 最小圓環寬度
     */
    private float mMinCircleStrokeWidth;

    /**
     * 最大圓環寬度
     */
    private float mMaxCircleStrokeWidth;

    /**
     * 矩形
     */
    private RectF mRectF = new RectF();

    /**
     * 自定義枚舉類,標明錄製幾種狀態,默認ORIGIN
     */
    private RecordMode mRecordMode = RecordMode.ORIGIN;

    /**
     * 開始動畫集合
     */
    private AnimatorSet mBeginAnimatorSet = new AnimatorSet();

    /**
     * 結束動畫集合
     */
    private AnimatorSet mEndAnimatorSet = new AnimatorSet();

    /**
     * {@link (PorterDuff.Mode)}
     */
    private Xfermode mXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);


    private Handler mHandler = new Handler();


    private ClickRunnable mClickRunnable = new ClickRunnable();

    /**
     * 自定義錄製按鈕監聽接口
     */
    private OnRecordStateChangedListener mOnRecordStateChangedListener;

    private float mInitX;

    private float mInitY;

    /**
     * Down X
     */
    private float mDownRawX;

    /**
     * Down Y
     */
    private float mDownRawY;

    /**
     * 滑動比例,抖音根據滑動 調整焦距
     */
    private float mInfectionPoint;

//    private ScrollDirection mScrollDirection;


    /**
     * Cancel Flag
     */
    private boolean mHasCancel = false;

    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);
        mContext = context;
        init();
    }

    /**
     * 初始化
     */
    private void init() {
        setLayerType(LAYER_TYPE_HARDWARE, null);
        mRectPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mRectPaint.setStyle(Paint.Style.FILL);
        mRectPaint.setColor(Color.parseColor("#F91069")); //內部錄製按鈕顏色

        mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mCirclePaint.setColor(Color.parseColor("#66F6A623"));

        mMinCircleStrokeWidth = ScreenUtils.dip2px(mContext, 3);
        mMaxCircleStrokeWidth = ScreenUtils.dip2px(mContext, 12);
        circleStrokeWidth = mMinCircleStrokeWidth;
        mCirclePaint.setStrokeWidth(circleStrokeWidth);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        int width = getMeasuredWidth();
        int height = getMeasuredHeight();

        int centerX = width / 2;
        int centerY = height / 2;

        mMaxRectWidth = width / 3;
        mMinRectWidth = mMaxRectWidth * 0.6f;

        mMinCircleRadius = mMaxRectWidth / 2 + mMinCircleStrokeWidth + ScreenUtils.dip2px(mContext, 5);
        mMaxCircleRadius = width / 2 - mMaxCircleStrokeWidth;

        mMinCorner = ScreenUtils.dip2px(mContext, 5);
        mMaxCorner = mMaxRectWidth / 2;

        if (rectWidth == 0) {
            rectWidth = mMaxRectWidth;
        }
        if (circleRadius == 0) {
            circleRadius = mMinCircleRadius;
        }
        if (corner == 0) {
            corner = rectWidth / 2;
        }

        //初始圓環外邊界
        mCirclePaint.setColor(Color.parseColor("#F6A623"));
        canvas.drawCircle(centerX, centerY, circleRadius, mCirclePaint);
        mCirclePaint.setXfermode(mXfermode);
        //初始圓環內邊界
        mCirclePaint.setColor(Color.parseColor("#9900FF"));
        canvas.drawCircle(centerX, centerY, circleRadius - circleStrokeWidth, mCirclePaint);
        mCirclePaint.setXfermode(null);

        //初始畫內圓
        mRectF.left = centerX - rectWidth / 2;
        mRectF.right = centerX + rectWidth / 2;
        mRectF.top = centerY - rectWidth / 2;
        mRectF.bottom = centerY + rectWidth / 2;
        canvas.drawRoundRect(mRectF, corner, corner, mRectPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (mRecordMode == RecordMode.ORIGIN && inBeginRange(event)) {
                    mDownRawX = event.getRawX();
                    mDownRawY = event.getRawY();
                    mHandler.postDelayed(mClickRunnable, 200);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (!mHasCancel) {
                    if (mRecordMode == RecordMode.LONG_CLICK) {
//                        ScrollDirection mOldDirection = mScrollDirection;
//                        float oldY = getY();
//                        setX(mInitX+event.getRawX()-mDownRawX);
//                        setY(mInitY+event.getRawY()-mDownRawY);
//                        float newY = getY();

//                        if (newY <= oldY) {
//                            mScrollDirection = ScrollDirection.UP;
//                        } else {
//                            mScrollDirection = ScrollDirection.DOWN;
//                        }

//                        if (mOldDirection != mScrollDirection) {
//                            mInfectionPoint = oldY;
//                        }
                        float zoomPercentage = (mInfectionPoint - getY()) / mInitY;
                        if (mOnRecordStateChangedListener != null) {
                            mOnRecordStateChangedListener.onZoom(zoomPercentage);
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (!mHasCancel) {
                    if (mRecordMode == RecordMode.LONG_CLICK) {
                        if (mOnRecordStateChangedListener != null) {
                            mOnRecordStateChangedListener.onRecordStop();
                        }
                        resetLongClick();
                    } else if (mRecordMode == RecordMode.ORIGIN && inBeginRange(event)) {
                        mHandler.removeCallbacks(mClickRunnable);
                        mRecordMode = RecordMode.SINGLE_CLICK;
                        if (mOnRecordStateChangedListener != null) {
                            mOnRecordStateChangedListener.onRecordStart();
                        }
//                        startBeginAnimation();
                    } else if (mRecordMode == RecordMode.SINGLE_CLICK && inEndRange(event)) {
                        if (mOnRecordStateChangedListener != null) {
                            mOnRecordStateChangedListener.onRecordStop();
                        }
                        resetSingleClick();
                    }
                } else {
                    mHasCancel = false;
                }
                break;
            default:
                break;
        }
        return true;
    }

    private boolean inBeginRange(MotionEvent event) {
        int centerX = getMeasuredWidth() / 2;
        int centerY = getMeasuredHeight() / 2;
        int minX = (int) (centerX - mMinCircleRadius);
        int maxX = (int) (centerX + mMinCircleRadius);
        int minY = (int) (centerY - mMinCircleRadius);
        int maxY = (int) (centerY + mMinCircleRadius);
        boolean isXInRange = event.getX() >= minX && event.getX() <= maxX;
        boolean isYInRange = event.getY() >= minY && event.getY() <= maxY;
        return isXInRange && isYInRange;
    }

    private boolean inEndRange(MotionEvent event) {
        int minX = 0;
        int maxX = getMeasuredWidth();
        int minY = 0;
        int maxY = getMeasuredHeight();
        boolean isXInRange = event.getX() >= minX && event.getX() <= maxX;
        boolean isYInRange = event.getY() >= minY && event.getY() <= maxY;
        return isXInRange && isYInRange;
    }

    private void resetLongClick() {
        mRecordMode = RecordMode.ORIGIN;
        mBeginAnimatorSet.cancel();
        startEndAnimation();
        setX(mInitX);
        setY(mInitY);
    }

    private void resetSingleClick() {
        mRecordMode = RecordMode.ORIGIN;
        mBeginAnimatorSet.cancel();
        startEndAnimation();
    }

    public void reset() {
        if (mRecordMode == RecordMode.LONG_CLICK) {
            resetLongClick();
            mRecordMode = RecordMode.ORIGIN;
        } else if (mRecordMode == RecordMode.SINGLE_CLICK) {
            resetSingleClick();
            mRecordMode = RecordMode.ORIGIN;
        } else if (mRecordMode == RecordMode.ORIGIN) {
            if (mBeginAnimatorSet.isRunning()) {
                mHasCancel = true;
                mBeginAnimatorSet.cancel();
                startEndAnimation();
                mHandler.removeCallbacks(mClickRunnable);
                mRecordMode = RecordMode.ORIGIN;
            }
        }

    }

    public void startClockRecord() {
        if (mRecordMode == RecordMode.ORIGIN) {
            startBeginAnimation();
            mRecordMode = RecordMode.SINGLE_CLICK;
        }
    }

    public void startBeginAnimation() {
        AnimatorSet startAnimatorSet = new AnimatorSet();
        //矩形動畫
        ObjectAnimator cornerAnimator = ObjectAnimator.ofFloat(this, "corner",
                mMaxCorner, mMinCorner)
                .setDuration(500);
        ObjectAnimator rectSizeAnimator = ObjectAnimator.ofFloat(this, "rectWidth",
                mMaxRectWidth, mMinRectWidth)
                .setDuration(500);

        //外圈圓環
        ObjectAnimator radiusAnimator = ObjectAnimator.ofFloat(this, "circleRadius",
                mMinCircleRadius, mMaxCircleRadius)
                .setDuration(500);
        startAnimatorSet.playTogether(cornerAnimator, rectSizeAnimator, radiusAnimator);

        //圓環抖動 動畫
        ObjectAnimator circleWidthAnimator = ObjectAnimator.ofFloat(this, "circleStrokeWidth",
                mMinCircleStrokeWidth, mMaxCircleStrokeWidth, mMinCircleStrokeWidth)
                .setDuration(1500);
        circleWidthAnimator.setRepeatCount(ObjectAnimator.INFINITE);

        mBeginAnimatorSet.playSequentially(startAnimatorSet, circleWidthAnimator);
        mBeginAnimatorSet.start();
    }

    private void startEndAnimation() {
        ObjectAnimator cornerAnimator = ObjectAnimator.ofFloat(this, "corner",
                mMinCorner, mMaxCorner)
                .setDuration(500);
        ObjectAnimator rectSizeAnimator = ObjectAnimator.ofFloat(this, "rectWidth",
                mMinRectWidth, mMaxRectWidth)
                .setDuration(500);
        ObjectAnimator radiusAnimator = ObjectAnimator.ofFloat(this, "circleRadius",
                mMaxCircleRadius, mMinCircleRadius)
                .setDuration(500);
        ObjectAnimator circleWidthAnimator = ObjectAnimator.ofFloat(this, "circleStrokeWidth",
                mMaxCircleStrokeWidth, mMinCircleStrokeWidth)
                .setDuration(500);

        mEndAnimatorSet.playTogether(cornerAnimator, rectSizeAnimator, radiusAnimator, circleWidthAnimator);
        mEndAnimatorSet.start();
    }

    public void setCorner(float corner) {
        this.corner = corner;
        invalidate();
    }

    public void setCircleRadius(float circleRadius) {
        this.circleRadius = circleRadius;
    }

    public void setCircleStrokeWidth(float circleStrokeWidth) {
        this.circleStrokeWidth = circleStrokeWidth;
        invalidate();
    }

    public void setRectWidth(float rectWidth) {
        this.rectWidth = rectWidth;
    }

    class ClickRunnable implements Runnable {

        @Override
        public void run() {
            if (!mHasCancel) {
                mRecordMode = RecordMode.LONG_CLICK;
                mInitX = getX();
                mInitY = getY();
                mInfectionPoint = mInitY;
                if (mOnRecordStateChangedListener != null) {
                    mOnRecordStateChangedListener.onLongPressRecordStart();
                    startBeginAnimation();
                }
//                mScrollDirection = ScrollDirection.UP;
            }
        }
    }

    public void setOnRecordStateChangedListener(OnRecordStateChangedListener listener) {
        this.mOnRecordStateChangedListener = listener;
    }

    public interface OnRecordStateChangedListener {

        /**
         * 開始錄製
         */
        void onRecordStart();

        /**
         * 開始長按錄製
         */
        void onLongPressRecordStart();

        /**
         * 結束錄製
         */
        void onRecordStop();

        /**
         * 縮放百分比
         *
         * @param percentage 百分比值 0%~100% 對應縮放支持的最小和最大值 默認最小1.0
         */
        void onZoom(float percentage);
    }

    public enum RecordMode {
        /**
         * 單擊錄製模式
         */
        SINGLE_CLICK,
        /**
         * 長按錄製模式
         */
        LONG_CLICK,
        /**
         * 初始化
         */
        ORIGIN;

        RecordMode() {

        }
    }

    private enum ScrollDirection {
        /**
         * 滑動方向 上
         */
        UP,
        /**
         * 滑動方向 下
         */
        DOWN;

        ScrollDirection() {

        }
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章