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();
    }
}

如果对大家有帮助,请点个赞以鼓励我前进~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349