android自定義view實現進度條動畫、按鈕漸變及錄製狀態控制

不得不說自定義view繪製各種圖形及它們的動畫是令人有成就感的事情。該篇來分享之前項目中寫的一個視頻錄製按鈕的自定義view,該view有錄製、暫停(結束)等狀態的控制,各種狀態轉變的動畫,以及錄製進度展示。先看效果(圓圈的小白色段是最小錄製時長的標記點):
錄製效果
代碼的內容其實主要就是繪製進度條、錄製按鈕的切換動畫(圓角正方形—>圓—>圓角正方形)。還是那句話,計算好每個組件的座標(半徑)事情就完成了一半,所以關鍵在於計算,這裏的圖形都簡單計算並不複雜,只是要稍微細心點。註釋都寫得比較詳細,就不一一解釋了,具體看代碼:

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import com.robot.common.frame.BaseApp;
import com.robot.common.utils.PixelUtil;

/**
 * 視頻錄製控制、進度
 *
 * @author ly
 * date 2020/3/3 11:02
 */
public class RecordProgressView extends View implements View.OnClickListener {

    private static final float changeRatio = 5f;
    //最小錄製時長標記的弧長
    private static final int minPAngle = 2;

    private OnRecordStatusListener onRecordStatusListener;
    private Paint paint;
    //內圈錄製、暫停按鈕顏色
    private int circleInsideColor;
    //進度圈顏色
    private int circleOutsideColor;
    //進度顏色
    private int circleProgressColor;
    private float progressW;
    //進度條圓圈半徑
    private float circleProgressR;
    //內實心圓圈半徑
    private float circleInsideR;
    //錄製按鈕寬高
    private float pauseRectW;

    //動態變量內圈半徑(逐漸變小或變大)
    private float curCircleInsideR;

    private State state = State.READY;
    //暫停按鈕rect
    private RectF pauseRect;
    private int pauseRadius;
    private RectF progressRect;
    private float progress;
    private float minProgress = 90;
    private float minCircleR;
    private LinearGradient linearGradientCircle;
    private LinearGradient linearGradientRect;

    public enum State {
        READY, RECORDING, PAUSE, FINISH
    }

    public RecordProgressView(Context context) {
        super(context);
        init();
    }

    public RecordProgressView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public RecordProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        circleInsideColor = 0xffE9B763;
        circleOutsideColor = 0x55ffffff;
        circleProgressColor = 0xffffffff;
        paint = new Paint();
        paint.setAntiAlias(true);//抗鋸齒
        pauseRadius = PixelUtil.dp2px(5);

        setOnClickListener();
    }

    public float getProgress() {
        return progress;
    }

    /**
     * @param progress 已錄製時長/最大時長
     * @author ly on 2020/3/3 16:41
     */
    public void setProgress(float progress) {
        this.progress = progress * 360;
        if (this.progress >= 360) {//表示錄製達到最大時長,自動結束
            this.progress = 360;
            state = State.FINISH;
            if (onRecordStatusListener != null)
                onRecordStatusListener.onFinish();
        }
        invalidate();
    }

    /**
     * @param minProgress 最小錄製時長/最大時長
     * @author ly on 2020/3/3 16:41
     */
    public void setMinProgress(float minProgress) {
        this.minProgress = minProgress * 360;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (circleProgressR == 0) {
            int w = getWidth();
            progressW = w * 0.061f;
            circleProgressR = (w - progressW) / 2f;
            circleInsideR = (w * 0.83f) / 2;
            pauseRectW = w * 0.37f;
            curCircleInsideR = circleInsideR;
            //矩形對角線的一半
//            minCircleR = (float) (Math.sqrt(Math.pow(pauseRectW, 2) * 2) / 2f);
            minCircleR = pauseRectW / 2;

            pauseRect = new RectF(w / 2f - pauseRectW / 2
                    , w / 2f - pauseRectW / 2
                    , w / 2f + pauseRectW / 2
                    , w / 2f + pauseRectW / 2);
            progressRect = new RectF(0 + progressW / 2, 0 + progressW / 2, w - progressW / 2, w - progressW / 2);

            float offsetX = (float) Math.sqrt(Math.pow(curCircleInsideR, 2) / 2);

            //圓的左上到右下方向漸變
            linearGradientCircle = new LinearGradient(
                    curCircleInsideR - offsetX, curCircleInsideR - offsetX, w - offsetX, w - offsetX,
                    0xff7360FF, 0xff9663F3,
                    Shader.TileMode.MIRROR);

            linearGradientRect = new LinearGradient(
                    pauseRect.left, pauseRect.top, pauseRect.right, pauseRect.bottom,
                    0xff7360FF, 0xff9663F3,
                    Shader.TileMode.MIRROR);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //畫外圈進度圈、進度
        paint.setShader(null);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(circleOutsideColor);
        paint.setStrokeWidth(progressW);//設置畫筆粗細
        canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, circleProgressR, paint);

        if (minProgress - minPAngle > 0 && BaseApp.get().isDebug()) {
            paint.setColor(circleProgressColor);
            canvas.drawArc(progressRect, minProgress - 90, minPAngle, false, paint);
        }

        if (state != State.READY) {
            //進度就是圓弧
            //時鐘3點的方向爲0度,順時鐘方向爲正,-90是圓弧的開始點,即12點位置開始,
            //sweepAngle掃過的角度,調整該值即可實現進度順時針加載(0-360)
            paint.setColor(circleProgressColor);
            canvas.drawArc(progressRect, -90, progress, false, paint);
        }

        //畫內部圓/暫停按鈕
        paint.setColor(circleInsideColor);
        paint.setStyle(Paint.Style.FILL);
        paint.setShader(linearGradientCircle);
        if (state == State.RECORDING) {//錄製中畫矩形和進度
            if (curCircleInsideR > minCircleR) {
                canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, curCircleInsideR, paint);
                changeCircleR(false);
            } else {
                paint.setShader(linearGradientRect);
                canvas.drawRoundRect(pauseRect, pauseRadius, pauseRadius, paint);
            }
        } else {
            canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, curCircleInsideR, paint);
            if (curCircleInsideR < circleInsideR)
                changeCircleR(true);
        }
    }


    public void resetState() {
        state = State.READY;
        progress = 0;
        invalidate();
    }

    public void setState(State state) {
        this.state = state;
        invalidate();
    }

    public void setOnClickListener() {
        setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (state) {
            //由開始狀態變爲錄製狀態,點擊後由圓變爲矩形
            case READY:
            case PAUSE:
                state = State.RECORDING;
                if (onRecordStatusListener != null)
                    onRecordStatusListener.onRecording();

                invalidate();
                break;
            //錄製中,點擊後由矩形變爲圓
            case RECORDING:
                if (progress < minProgress) {
                    state = State.PAUSE;
                    if (onRecordStatusListener != null)
                        onRecordStatusListener.onPause();
                } else {
                    state = State.FINISH;
                    if (onRecordStatusListener != null)
                        onRecordStatusListener.onFinish();

                }
                invalidate();
                break;
            default:

                break;
        }
    }

    private void changeCircleR(boolean is2Big) {
        if (is2Big) {//變大
            curCircleInsideR += changeRatio;
            if (curCircleInsideR >= circleInsideR)
                curCircleInsideR = circleInsideR;
        } else {//變小
            curCircleInsideR -= changeRatio;
            if (curCircleInsideR <= minCircleR)
                curCircleInsideR = minCircleR;
        }
        invalidate();
    }

    public void setOnRecordStatusListener(OnRecordStatusListener onRecordStatusListener) {
        this.onRecordStatusListener = onRecordStatusListener;
    }

    public interface OnRecordStatusListener {
        void onRecording();

        void onPause();

        void onFinish();
    }
}

如果對大家有幫助,請點個贊以鼓勵我前進~

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