自定义进度条:跑道轨迹效果进度条

原因

UI需要一个有跑道轨迹效果的进度条,但是搜索看到的,普遍都是圆形或者水平的进度条,根本不符合要求,只能直接自定义一个

新增属性

在attrs.xml下新增属性

<declare-styleable name="TrackProgressView">
        <!--        内圆水平宽度-->
        <attr name="innerWidth" format="dimension" />
        <!--        圆半径-->
        <attr name="radius" format="dimension" />
        <!--        背景色-->
        <attr name="bgColor" format="reference" />
        <!--        轨迹宽度-->
        <attr name="trackWidth" format="dimension" />
        <!--        轨迹颜色-->
        <attr name="trackColor" format="reference" />
        <!--        最大进度-->
        <attr name="maxProgress" format="integer" />
        <!--        当前进度-->
        <attr name="currentProgress" format="integer" />
        <!--        进度条颜色-->
        <attr name="progressColor" format="integer" />
        <!--        字体颜色-->
        <attr name="textColor" format="reference" />
        <!--        字体大小-->
        <attr name="textSize" format="dimension" />
        <!--        提示文字-->
        <attr name="tipText" format="string" />
        <!--        是否显示文字-->
        <attr name="showText" format="boolean" />
        <!--        是否显示提示-->
        <attr name="showTips" format="boolean" />

自定义View

分析

1、一个圆分垂直分割成2个半圆,半圆之间一条直线
2、计算圆上坐标点:根据角度 (i),半径(mRadius),获取坐标点
公式:x 坐标 = mRadius*Math.cos(Math.toRadians(i)
y 坐标 = mRadius * Math.sin(Math.toRadians(i)
3、角度从-180 到 180,顺时针计算每一个角度坐标
4、当角度 等于-90度,添加一条从左往右的直线坐标点
5、当角度 等于90度,添加一条从右往左的坐标点
6、把所有坐标点连在一起,组成一个圆角矩形
7、轨道坐标点,就是在圆角矩形加上宽度(水平方向 y + 或 - 宽度,垂直方向x +或 -宽度)

实现步骤

1、需要继承View
2、计算内圆角矩形坐标点
3、根据圆角矩形坐标点获取轨道坐标点

最终实现源码

/**
 * @package com.poso2o.lottery_shop.ui.custom
 * @filename TrackProgressView
 * @author:sen
 * @date 2021/3/2  5:15 PM
 * @function: 轨迹进度条
 */
public class TrackProgressView extends View {

    private int mWidth = 100;
    private int mRadius = 50;
    private int mTrackWidth = 20;
    /**
     * 背景色
     */
    private int mBgColor = 0;
    /**
     * 轨迹动画颜色
     */
    private int mTrackColor = 0;
    /**
     * 进度颜色
     */
    private int mProgressColor = 0;
    private int mTextColor = 0;
    /**
     * 最大进度
     */
    private int mMaxProgress = 100;
    /**
     * 当前进度
     */
    private int mCurrentProgress = 10;
    private int mTextSize = 0;
    private String mTipsText = "未来3天预约率";
    private boolean mShowText = true;
    private boolean mShowTips = true;
    private float rote = 0f;
    private int lastIndex = 0;

    private Paint mBgPaint = new Paint();
    private Paint mTrackPaint = new Paint();
    private Paint mProgressPaint = new Paint();
    private Paint mTextPaint = new Paint();
    /**
     * 圆角矩形
     */
    private List<Point> mPathPointList = new ArrayList<Point>();
    private Path mBgPath = new Path();
    /**
     * 轨迹
     */
    private List<Point> mTrackPointList = new ArrayList<Point>();
    private Path mTrackPath = new Path();
    /**
     * 进度
     */
    private Path mProgressPath = new Path();


    public TrackProgressView(Context context) {
        this(context, null);
    }

    public TrackProgressView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TrackProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //默认参数
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TrackProgressView, defStyleAttr, 0);

        //内圆矩形背景色
        mBgColor = typedArray.getColor(R.styleable.TrackProgressView_bgColor, getResources().getColor(R.color.red));
        //内圆半径
        mRadius = typedArray.getDimensionPixelSize(R.styleable.TrackProgressView_radius, 50);
        //内圆水平宽度
        mWidth = typedArray.getDimensionPixelSize(R.styleable.TrackProgressView_innerWidth, 100);
//        mWidth = 2 * mRadius;
        LogUtils.INSTANCE.d("mWidth>>>>" + mWidth);
        //轨迹宽度
        mTrackWidth = typedArray.getDimensionPixelSize(R.styleable.TrackProgressView_trackWidth, 20);
        //轨迹颜色
        mTrackColor = typedArray.getColor(R.styleable.TrackProgressView_trackColor, getResources().getColor(R.color.orange));

        //进度条颜色
        mProgressColor = typedArray.getColor(R.styleable.TrackProgressView_progressColor, getResources().getColor(R.color.white));
        //最大进度
        mMaxProgress = typedArray.getInteger(R.styleable.TrackProgressView_maxProgress, 100);
        //当前进度
        mCurrentProgress = typedArray.getInteger(R.styleable.TrackProgressView_currentProgress, 10);

        //字体颜色
        mTextSize = typedArray.getDimensionPixelSize(R.styleable.TrackProgressView_textSize, 20);
        mTextColor = typedArray.getColor(R.styleable.TrackProgressView_textColor, getResources().getColor(R.color.white));
        mShowText = typedArray.getBoolean(R.styleable.TrackProgressView_showText, true);
        mShowTips = typedArray.getBoolean(R.styleable.TrackProgressView_showTips, true);
        mTipsText = typedArray.getString(R.styleable.TrackProgressView_tipText);
        if (TextUtils.isEmpty(mTipsText)) {
            mTipsText = "未来3天预约率";
        }
        typedArray.recycle();

        initPaint();

        //内部圆角矩形
        initList();
        float startX = 0;
        float startY = 0;
        //圆角矩形路径
        for (int i = 0; i < mPathPointList.size(); i++) {
            Point point = mPathPointList.get(i);
            if (i == 0) {
                startX = point.getX();
                startY = point.getY();
                mBgPath.moveTo(startX, startY);
            }
            mBgPath.quadTo(startX, startY, point.getX(), point.getY());
            startX = point.getX();
            startY = point.getY();
        }

        //外部边框
        for (int i = 0; i < mTrackPointList.size(); i++) {
            Point point = mTrackPointList.get(i);
            if (i == 0) {
                startX = point.getX();
                startY = point.getY();
                mTrackPath.moveTo(startX, startY);
            }
            mTrackPath.quadTo(startX, startY, point.getX(), point.getY());
            startX = point.getX();
            startY = point.getY();
        }

        LogUtils.INSTANCE.d("size =" + (mTrackPointList.size()) + ",mCurrentProgress =  " + mCurrentProgress);

        setMaxProgress(mMaxProgress);
        setProgress(mCurrentProgress, false);
    }

    private void initList() {
        for (int i = -180; i <= 180; i++) {
            int outRadius = mRadius + mTrackWidth;
            float pointX = (float) (outRadius + mRadius * Math.cos(Math.toRadians(i)));
            float pointY = (float) (outRadius + mRadius * Math.sin(Math.toRadians(i)));
            //外边框 半径 = 内 + 宽度/2
            float outX = (float) (outRadius + (mRadius + mTrackWidth / 2) * Math.cos(Math.toRadians(i)));
            float outY = (float) (outRadius + (mRadius + mTrackWidth / 2) * Math.sin(Math.toRadians(i)));

            //左半圆 左上扇形 -180~-90 ,左下扇形 90~180
            //右半圆 右上扇形 -90~0 右下扇形 0~90
//            LogUtils.INSTANCE.d("角度 = " + i + ",pointX = " + pointX + ", pointY = " + pointY);
            if (i >= -180 && i <= -90) {
                if (i == -90) {
                    //添加一条横线 270个点
                    int count = 0;
                    //占比
                    for (int j = 0; j <= mWidth; j++) {
                        count = j;
                        mPathPointList.add(new Point(pointX + count, pointY));
                        //上边框
                        mTrackPointList.add(new Point(outX + count, outY));
                    }
//                    LogUtils.INSTANCE.d("count = "+ count);
                    continue;
                } else {
                    //左上扇形
                    mPathPointList.add(new Point(pointX, pointY));
                    //
                    mTrackPointList.add(new Point(outX, outY));
                }
            }

            if (i >= -90 && i <= 0) {
                //右上扇形
                mPathPointList.add(new Point(pointX + mWidth, pointY));
                //右边框
                mTrackPointList.add(new Point(outX + mWidth, outY));
            }
            if (i >= 0 && i <= 90) {
                //右下扇形
                if (i == 90) {
                    //添加一条横线
                    int count = 0;
                    for (int j = mWidth; j >= 0; j--) {
                        count = j;
                        mPathPointList.add(new Point(pointX + count, pointY));
                        //
                        mTrackPointList.add(new Point(outX + count, outY));
                    }
                    continue;
                } else {
                    mPathPointList.add(new Point(pointX + mWidth, pointY));
                    //边框
                    mTrackPointList.add(new Point(outX + mWidth, outY));
                }
            }

            if (i >= 90 && i <= 180) {
                //左下扇形
                mPathPointList.add(new Point(pointX, pointY));
                //
                mTrackPointList.add(new Point(outX, outY));
            }
        }
    }

    private void initPaint() {
        //圆角矩形
        mBgPaint.setColor(mBgColor);
        mBgPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        //轨迹
        mTrackPaint.setColor(mTrackColor);
        mTrackPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        mTrackPaint.setStyle(Paint.Style.STROKE);
        mTrackPaint.setStrokeWidth(mTrackWidth);
        //进度
        mProgressPaint.setColor(mProgressColor);
        mProgressPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        mProgressPaint.setStyle(Paint.Style.STROKE);
        mProgressPaint.setStrokeWidth(mTrackWidth);

        //文字
        mTextPaint.setColor(mTextColor);
        mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG | Paint.FAKE_BOLD_TEXT_FLAG);
//        mTextPaint.setStyle(Paint.Style.STROKE);
        mTextPaint.setTextSize(mTextSize);
    }

    public void setMaxProgress(int maxProgress) {
        this.mMaxProgress = maxProgress;
        //计算百分比
        rote = mTrackPointList.size() * 1f / mMaxProgress * 1f;
        LogUtils.INSTANCE.d("size =" + (mTrackPointList.size()) + ",mMaxProgress = " + maxProgress + " , rote = " + rote);
        lastIndex = 0;
    }

    public void resetProgress() {
        lastIndex = 0;
        this.mCurrentProgress = 0;
        //重新绘制
        mProgressPath = new Path();
        Point point = mTrackPointList.get(0);
        mProgressPath.moveTo(point.getX(), point.getY());
        invalidate();
    }

    private void setProgress(int progress, boolean refresh) {
        if (progress > mMaxProgress) {
            this.mCurrentProgress = mMaxProgress;
        } else {
            this.mCurrentProgress = progress;
        }
        if (progress == 0) {
            //重置
            resetProgress();
            return;
        }
        mProgressPaint.setColor(mProgressColor);
        LogUtils.INSTANCE.d("size =" + (mTrackPointList.size()) + ",mCurrentProgress =  " + mCurrentProgress);
        //根据百分比,获取当前索引
        int maxIndex = (int) (mCurrentProgress * rote);
        LogUtils.INSTANCE.d("lastIndex =  " + lastIndex);
        LogUtils.INSTANCE.d("maxIndex =  " + maxIndex);
        if (maxIndex >= mTrackPointList.size()) {
            maxIndex = mTrackPointList.size() - 1;
        }
        float startX = 0;
        float startY = 0;
        for (int i = lastIndex; i <= maxIndex; i++) {
            Point point = mTrackPointList.get(i);
            if (i == lastIndex) {
                startX = point.getX();
                startY = point.getY();
                mProgressPath.moveTo(startX, startY);
            }
            mProgressPath.quadTo(startX, startY, point.getX(), point.getY());
            startX = point.getX();
            startY = point.getY();
        }
        lastIndex = maxIndex;
        if (refresh) {
            //重新绘制
            invalidate();
        }
    }

    public void setProgress(int progress) {
        setProgress(progress, true);
    }

    public void setTipsText(String tipsText) {
        this.mTipsText = tipsText;
        invalidate();
    }

    public void setShowText(boolean showText){
        this.mShowText = showText;
        invalidate();
    }

    public void setShowTips(boolean showTips){
        this.mShowTips = showTips;
        invalidate();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width;
        int height;
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        LogUtils.INSTANCE.d("onMeasure::widthMode=" + widthMode + "; widthSize="
                + widthSize);
        LogUtils.INSTANCE.d("onMeasure::heightMode=" + heightMode + "; heightSize="
                + heightSize);
        int outRadius = mRadius + mTrackWidth;
        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            //外边框直径 + 内圆矩形宽度
            width = (int) (getPaddingLeft() + (mWidth + outRadius * 2) + getPaddingRight());
            textStartX = width / 2;
        }
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            //外边框直径
            height = (int) (getPaddingTop() + (outRadius * 2) + getPaddingBottom());
            textStartY = height / 2;
        }
        setMeasuredDimension(width, height);
        LogUtils.INSTANCE.d("onMeasure>>>>");

    }

    private int textStartX = 0;
    private int textStartY = 0;

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //内圆角矩形
        canvas.drawPath(mBgPath, mBgPaint);
        //轨迹边框
        canvas.drawPath(mTrackPath, mTrackPaint);
        mProgressPaint.setColor(mProgressColor);
        //进度
        canvas.drawPath(mProgressPath, mProgressPaint);
        //进度百分比文字 ,居中,文字宽度,高度
        if (mShowText) {
            Rect textRect = new Rect();
            String textName = String.format("%.2f", (mCurrentProgress * 1f / mMaxProgress) * 100.0) + "%";
            mTextPaint.setTextSize(mTextSize);
            mTextPaint.getTextBounds(textName, 0, textName.length(), textRect);
            if (mShowTips) {
                canvas.drawText(textName, textStartX - textRect.width() / 2, textStartY, mTextPaint);
            } else {
                //居中
                canvas.drawText(textName, textStartX - textRect.width() / 2, textStartY + textRect.height() / 2, mTextPaint);
            }
        }
        if (mShowTips) {
            //进度提示文字
            mTextPaint.setTextSize(mTextSize / 2);
            Rect textRect2 = new Rect();
            mTextPaint.getTextBounds(mTipsText, 0, mTipsText.length(), textRect2);
            if (mShowText) {
                canvas.drawText(mTipsText, textStartX - textRect2.width() / 2, textStartY + (int) (1.5 * textRect2.height()), mTextPaint);
            } else {
                //居中
                canvas.drawText(mTipsText, textStartX - textRect2.width() / 2, textStartY + textRect2.height() / 2, mTextPaint);
            }
        }
    }


    static class Point {
        private float x;
        private float y;

        public Point(float x, float y) {
            this.x = x;
            this.y = y;
        }

        public float getX() {
            return x;
        }

        public float getY() {
            return y;
        }
    }
}

使用

1、布局中添加TrackProgressView

<com.poso2o.lottery_shop.ui.custom.TrackProgressView
               android:id="@+id/trackProgressView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="10dp"
                app:bgColor="@color/main_color"
                app:currentProgress="1"
                app:innerWidth="50dp"
                app:maxProgress="100"
                app:progressColor="@color/white"
                app:radius="20dp"
                app:showTips="true"
                app:showText="true"
                app:textColor="@color/white"
                app:textSize="20dp"
                app:tipText="未来5天预约率"
                app:trackColor="@color/red"
                app:trackWidth="10dp" />

2、代码中操作进度

trackProgressView.setProgress(progressCount)
        btnAddProgress.setOnClickListener {
            progressCount += 10
            trackProgressView.setProgress(progressCount)
        }

3、效果在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值