Android进阶之路 - 静态会员进度条

年后这个新版本加入了VIP模块,有幸正好由我来负责,可以再积累一下这方面的知识。

那段时间看了一本书,书中说到初级码农的特性之一就是完全集中于某些功能,忽略了了很多成长机会,所以重复性劳作带来的成长值有限,大家应该去接触更广、更深的内容

进度条Blog

  • Android进阶之路 - 静态等级进度条
  • Android进阶之路 - 静态会员进度条

静态进度条一般只用于实现显示效果,无其他任何交互行为
在这里插入图片描述

年过半许

    • 基础概要
      • 功能分析
      • 组件解析
    • 开发实践
      • 自定义属性
      • 自定义控件
      • 静态使用方式
    • 项目场景
      • 圆角尺寸与设计图不符?
      • 设置对应进度值后,显示异常?

基础概要

起初想着自己写一下自定义控件就行,但是通过查询相关控件后发现自己考虑还是有限,所以直接借鉴了早期前辈写的控件,毕竟考虑比我当前全面,功能扩展也多一些

功能分析

在这里插入图片描述

要实现类似进度条,起码有以下几点要考虑到

  • 需绘制双进度条:一条 width 默认为控件宽度,用于背景效果;一条 width 需进行计算,实时显示当前进度
  • 进度条计算:计算的规则至少需要当前进度值最大值,否则无法进行基础计算
  • 双进度条配置不同:对应 Paint 画笔,除大部分配置相同外,颜色至少要不同
  • padding考虑:有些控件为了显示效果更佳,会加入padding设置,这时候计算widthheight时要考虑到padding尺寸

通过以上简单分析,我们可以分析出我们需要的一些基本自定义属性,例如

  • 当前成长值、最大成长值
  • 背景进度条颜色、当前进度条颜色

组件解析

看了不少进度条控件,最后还是选了 ZzHorizontalProgressBar ,虽然并不是多么出名,但自己用的舒服就好,下面简单介绍一下我对这款控件的理解

作用范围

  • 支持 静态设置、动态设置
  • 支持 三种显示模式,矩形,圆角形等
  • 支持 自定义圆角大小
  • 支持 渐变色
  • 支持 双进度条
  • 支持 成长值回调
  • 支持 设置进度条边框、边框色

属性说明

在这里插入图片描述

有兴趣的主要看这部分方法的实现就好,对应的分别是绘制进度条背景、绘制当前进度条、绘制边框

在这里插入图片描述

以矩形为例,一起看看 绘制进度条背景、绘制当前进度条

绘制进度条背景实现简单,可以简单了解

在这里插入图片描述

绘制当前进度条,除了公共部分,可以直接看else部分(如果有渐变需求的可以看看 if 部分)

在这里插入图片描述


开发实践

为了减少外部引用带来的影响,我将 ZzHorizontalProgressBar 的核心部分 copy 了出来 ,主要有自定义属性+自定义控件,为了方便 Demo 效果采用了静态设置方式,建议自行根据开发场景选择对应方式(在实际项目中我是结合静态+动态的方式来实现效果)

自定义属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ZzHorizontalProgressBar">
        <attr name="zpb_padding" format="dimension" />
        <attr name="zpb_bg_color" format="color|reference" />
        <attr name="zpb_pb_color" format="color|reference" />
        <attr name="zpb_second_pb_color" format="color|reference" />
        <attr name="zpb_max" format="integer" />
        <attr name="zpb_progress" format="integer" />
        <attr name="zpb_show_zero_point" format="boolean" />
        <attr name="zpb_show_second_progress" format="boolean" />
        <attr name="zpb_second_progress" format="integer" />
        <attr name="zpb_show_second_point_shape" format="enum">
            <enum name="point" value="0"/>
            <enum name="line" value="1"/>
        </attr>
        <attr name="zpb_open_gradient" format="boolean" />
        <attr name="zpb_gradient_from" format="color|reference" />
        <attr name="zpb_gradient_to" format="color|reference" />
        <attr name="zpb_open_second_gradient" format="boolean" />
        <attr name="zpb_second_gradient_from" format="color|reference" />
        <attr name="zpb_second_gradient_to" format="color|reference" />
        <attr name="zpb_show_mode" format="enum" >
            <enum name="round" value="0"/>
            <enum name="rect" value="1"/>
            <enum name="round_rect" value="2"/>
        </attr>
        <attr name="zpb_round_rect_radius" format="dimension|reference"/>
        <attr name="zpb_draw_border" format="boolean"/>
        <attr name="zpb_border_width" format="dimension|reference"/>
        <attr name="zpb_border_color" format="color|reference"/>

    </declare-styleable>
</resources>

自定义控件

为表尊重,保持作者原始注释

Tip:因类内有小千行代码,建议使用者直接copy该类

package com.example.lineprogress;

import android.content.Context;
import android.content.res.TypedArray;
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.ColorInt;
import androidx.annotation.IntDef;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


/**
 * 水平进度条
 *
 * Created by 周卓 on 2016/9/22.
 */
public class ZzHorizontalProgressBar extends View {

    public static final int SHOW_MODE_ROUND = 0;
    public static final int SHOW_MODE_RECT = 1;
    public static final int SHOW_MODE_ROUND_RECT = 2;

    public static final int SHAPE_POINT = 0;
    public static final int SHAPE_LINE = 1;

    private int mMax;
    private int mProgress;
    private int mBgColor;
    private int mProgressColor;
    private int mPadding;
    private boolean mOpenGradient;
    private int mGradientFrom;
    private int mGradientTo;
    private boolean mShowSecondProgress;
    private int mSecondProgress;
    private int mSecondProgressShape;
    private boolean mShowZeroPoint;

    private Paint mSecondProgressPaint;
    private Paint mSecondGradientPaint;
    private Paint mProgressPaint;
    private Paint mGradientPaint;
    private Paint mBgPaint;
    private boolean mOpenSecondGradient;
    private int mSecondGradientFrom;
    private int mSecondGradientTo;
    private int mSecondProgressColor;

    private int mRadius;
    private boolean mDrawBorder = false;
    private int mBorderColor;
    private int mBorderWidth;

    private int mShowMode = 0;
    private Paint mBorderPaint;

    @IntDef({SHOW_MODE_ROUND, SHOW_MODE_RECT, SHOW_MODE_ROUND_RECT})
    @Target({ElementType.PARAMETER, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    private @interface ShowMode {
    }

    @IntDef({SHAPE_POINT, SHAPE_LINE})
    @Target({ElementType.PARAMETER, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    private @interface SecondProgressShape {
    }

    private OnProgressChangedListener mOnProgressChangedListener;

    public interface OnProgressChangedListener {
        void onProgressChanged(ZzHorizontalProgressBar progressBar, int max, int progress);

        void onSecondProgressChanged(ZzHorizontalProgressBar progressBar, int max, int progress);
    }

    public ZzHorizontalProgressBar(Context context) {
        super(context);
        init(context, null);
    }

    public ZzHorizontalProgressBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public ZzHorizontalProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        initAttrs(context, attrs);
        initPaths();
    }

    private void initAttrs(Context context, AttributeSet attrs) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ZzHorizontalProgressBar);
        mMax = a.getInteger(R.styleable.ZzHorizontalProgressBar_zpb_max, 100);
        mProgress = a.getInteger(R.styleable.ZzHorizontalProgressBar_zpb_progress, 0);
        mBgColor = a.getColor(R.styleable.ZzHorizontalProgressBar_zpb_bg_color, 0xff3F51B5);
        mProgressColor = a.getColor(R.styleable.ZzHorizontalProgressBar_zpb_pb_color, 0xffFF4081);
        mSecondProgressColor = a.getColor(R.styleable.ZzHorizontalProgressBar_zpb_second_pb_color, 0xffFF4081);
        mPadding = a.getDimensionPixelSize(R.styleable.ZzHorizontalProgressBar_zpb_padding, 0);
        mShowZeroPoint = a.getBoolean(R.styleable.ZzHorizontalProgressBar_zpb_show_zero_point, false);
        mShowSecondProgress = a.getBoolean(R.styleable.ZzHorizontalProgressBar_zpb_show_second_progress, false);
        mSecondProgress = a.getInteger(R.styleable.ZzHorizontalProgressBar_zpb_second_progress, 0);
        mSecondProgressShape = a.getInteger(R.styleable.ZzHorizontalProgressBar_zpb_show_second_point_shape, SHAPE_POINT);
        mOpenGradient = a.getBoolean(R.styleable.ZzHorizontalProgressBar_zpb_open_gradient, false);
        mGradientFrom = a.getColor(R.styleable.ZzHorizontalProgressBar_zpb_gradient_from, 0xffFF4081);
        mGradientTo = a.getColor(R.styleable.ZzHorizontalProgressBar_zpb_gradient_to, 0xffFF4081);
        mOpenSecondGradient = a.getBoolean(R.styleable.ZzHorizontalProgressBar_zpb_open_second_gradient, false);
        mShowMode = a.getInt(R.styleable.ZzHorizontalProgressBar_zpb_show_mode, SHOW_MODE_ROUND);
        mSecondGradientFrom = a.getColor(R.styleable.ZzHorizontalProgressBar_zpb_second_gradient_from, 0xffFF4081);
        mSecondGradientTo = a.getColor(R.styleable.ZzHorizontalProgressBar_zpb_second_gradient_to, 0xffFF4081);
        mRadius = a.getDimensionPixelSize(R.styleable.ZzHorizontalProgressBar_zpb_round_rect_radius, 20);
        mDrawBorder = a.getBoolean(R.styleable.ZzHorizontalProgressBar_zpb_draw_border, false);
        mBorderWidth = a.getDimensionPixelSize(R.styleable.ZzHorizontalProgressBar_zpb_border_width, 1);
        mBorderColor = a.getColor(R.styleable.ZzHorizontalProgressBar_zpb_border_color, 0xffff001f);
        a.recycle();
    }

    private void initPaths() {
        //常规进度条效果
        mProgressPaint = new Paint();
        mProgressPaint.setColor(mProgressColor);
        mProgressPaint.setStyle(Paint.Style.FILL);
        mProgressPaint.setAntiAlias(true);

        mSecondProgressPaint = new Paint();
        mSecondProgressPaint.setColor(mSecondProgressColor);
        mSecondProgressPaint.setStyle(Paint.Style.FILL);
        mSecondProgressPaint.setAntiAlias(true);

        //渐变效果
        mGradientPaint = new Paint();
        mGradientPaint.setStyle(Paint.Style.FILL);
        mGradientPaint.setAntiAlias(true);

        mSecondGradientPaint = new Paint();
        mSecondGradientPaint.setStyle(Paint.Style.FILL);
        mSecondGradientPaint.setAntiAlias(true);

        //背景效果
        mBgPaint = new Paint();
        mBgPaint.setColor(mBgColor);
        mBgPaint.setStyle(Paint.Style.FILL);
        mBgPaint.setAntiAlias(true);

        mBorderPaint = new Paint();
        mBorderPaint.setColor(mBorderColor);
        mBorderPaint.setStyle(Paint.Style.STROKE);
        mBorderPaint.setStrokeWidth(mBorderWidth);
        mBorderPaint.setAntiAlias(true);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        switch (mShowMode) {
            case SHOW_MODE_ROUND:
                //half circle
                drawBackgroundCircleMode(canvas);
                drawProgressCircleMode(canvas);
                drawBorderCircleMode(canvas);
                break;
            case SHOW_MODE_RECT:
                //rect
                drawBackgroundRectMode(canvas);
                drawProgressRectMode(canvas);
                drawBorderRectMode(canvas);
                break;
            case SHOW_MODE_ROUND_RECT:
                //custom radius
                drawBackgroundRoundRectMode(canvas);
                drawProgressRoundRectMode(canvas);
                drawBorderRoundRect(canvas);
                break;
        }
    }

    /**
     * 绘制半圆形进度
     */
    private void drawProgressCircleMode(Canvas canvas) {
        int width = getWidth();
        float percent = 0;
        if (mMax != 0) {
            percent = mProgress * 1.0f / mMax;
        }
        int progressHeight = getHeight() - mPadding * 2;
        if (mOpenGradient) {
            int progressWidth = width - mPadding * 2;
            float dx = progressWidth * percent;

            int[] colors = new int[2];
            float[] positions = new float[2];
            //from color
            colors[0] = mGradientFrom;
            positions[0] = 0;
            //to color
            colors[1] = mGradientTo;
            positions[1] = 1;
            LinearGradient shader = new LinearGradient(
                    mPadding + progressHeight / 2.0f, mPadding, mPadding + progressHeight / 2.0f + dx, mPadding + progressHeight,
                    colors,
                    positions,
                    Shader.TileMode.MIRROR);
            //gradient
            mGradientPaint.setShader(shader);

            int radius = width > getHeight() ? getHeight() / 2 : width / 2;
            if (dx < getHeight()) {
                //left circle
                if (mProgress == 0) {
                    if (mShowZeroPoint) {
                        canvas.drawCircle(mPadding + progressHeight / 2.0f, mPadding + progressHeight / 2.0f, progressHeight / 2.0f, mGradientPaint);
                    }
                } else {
                    canvas.drawCircle(mPadding + progressHeight / 2.0f, mPadding + progressHeight / 2.0f, progressHeight / 2.0f, mGradientPaint);
                }

            } else {
                //progress line
                RectF rectF = new RectF(mPadding, mPadding, mPadding + dx, mPadding + progressHeight);
                canvas.drawRoundRect(rectF, radius, radius, mGradientPaint);
            }

        } else {
            int progressWidth = width - mPadding * 2 - progressHeight;
            float dx = progressWidth * percent;
            mProgressPaint.setColor(mProgressColor);
            float left = mPadding + progressHeight / 2.0f;
            //left circle
            if (mProgress == 0) {
                if (mShowZeroPoint) {
                    canvas.drawCircle(left, left, progressHeight / 2.0f, mProgressPaint);
                }
            } else {
                canvas.drawCircle(left, left, progressHeight / 2.0f, mProgressPaint);
            }
            //right circle
            if (mProgress == 0) {
                if (mShowZeroPoint) {
                    canvas.drawCircle(left + dx, left, progressHeight / 2.0f, mProgressPaint);
                }
            } else {
                canvas.drawCircle(left + dx, left, progressHeight / 2.0f, mProgressPaint);
            }
            //middle line
            RectF rectF = new RectF(left, mPadding, left + dx, mPadding + progressHeight);
            canvas.drawRect(rectF, mProgressPaint);
        }

        //draw second progress
        if (mShowSecondProgress) {
            float secondPercent = 0;
            if (mMax != 0) {
                secondPercent = mSecondProgress * 1.0f / mMax;
            }
            int secondProgressHeight = getHeight() - mPadding * 2;
            if (mOpenSecondGradient) {
                int secondProgressWidth = width - mPadding * 2;
                float secondDx = secondProgressWidth * secondPercent;

                int[] secondColors = new int[2];
                float[] secondPositions = new float[2];
                //from color
                secondColors[0] = mSecondGradientFrom;
                secondPositions[0] = 0;
                //to color
                secondColors[1] = mSecondGradientTo;
                secondPositions[1] = 1;
                LinearGradient secondShader = new LinearGradient(
                        mPadding + secondProgressHeight / 2.0f, mPadding, mPadding + secondProgressHeight / 2.0f + secondDx, mPadding + secondProgressHeight,
                        secondColors,
                        secondPositions,
                        Shader.TileMode.MIRROR);
                //gradient
                mSecondGradientPaint.setShader(secondShader);

                int secondRadius = width > getHeight() ? getHeight() / 2 : width / 2;
                if (secondDx < getHeight()) {
                    //left circle
                    if (mSecondProgress == 0) {
                        if (mShowZeroPoint) {
                            canvas.drawCircle(mPadding + secondProgressHeight / 2.0f, mPadding + secondProgressHeight / 2.0f, secondProgressHeight / 2.0f, mSecondGradientPaint);
                        }
                    } else {
                        canvas.drawCircle(mPadding + secondProgressHeight / 2.0f, mPadding + secondProgressHeight / 2.0f, secondProgressHeight / 2.0f, mSecondGradientPaint);
                    }
                } else {
                    //progress line
                    RectF rectF = new RectF(mPadding, mPadding, mPadding + secondDx, mPadding + secondProgressHeight);
                    canvas.drawRoundRect(rectF, secondRadius, secondRadius, mSecondGradientPaint);
                }
            } else {
                //no gradient
                if (mSecondProgressShape == 0) {
                    //point shape
                    int secondProgressWidth = width - mPadding * 2;
                    float secondDx = secondProgressWidth * secondPercent;
                    //progress line
                    float px = mPadding + secondProgressHeight / 2.0f + secondDx;
                    if (px < width - mPadding - secondProgressHeight / 2.0f) {
                        if (mSecondProgress == 0) {
                            if (mShowZeroPoint) {
                                canvas.drawCircle(px, mPadding + secondProgressHeight / 2.0f, secondProgressHeight / 2.0f, mSecondProgressPaint);
                            }
                        } else {
                            canvas.drawCircle(px, mPadding + secondProgressHeight / 2.0f, secondProgressHeight / 2.0f, mSecondProgressPaint);
                        }
                    } else {
                        canvas.drawCircle(px - secondProgressHeight, mPadding + secondProgressHeight / 2.0f, secondProgressHeight / 2.0f, mSecondProgressPaint);
                    }

                } else {
                    //line shape
                    int secondProgressWidth = width - mPadding * 2 - secondProgressHeight;
                    float dx = secondProgressWidth * secondPercent;
                    mSecondProgressPaint.setColor(mSecondProgressColor);
                    //left circle
                    if (mSecondProgress == 0) {
                        if (mShowZeroPoint) {
                            canvas.drawCircle(mPadding + secondProgressHeight / 2.0f, mPadding + secondProgressHeight / 2.0f, secondProgressHeight / 2.0f, mSecondProgressPaint);
                        }
                    } else {
                        canvas.drawCircle(mPadding + secondProgressHeight / 2.0f, mPadding + secondProgressHeight / 2.0f, secondProgressHeight / 2.0f, mSecondProgressPaint);
                    }
                    //right circle
                    if (mSecondProgress == 0) {
                        if (mShowZeroPoint) {
                            canvas.drawCircle(mPadding + secondProgressHeight / 2.0f + dx, mPadding + secondProgressHeight / 2.0f, secondProgressHeight / 2.0f, mSecondProgressPaint);
                        }
                    } else {
                        canvas.drawCircle(mPadding + secondProgressHeight / 2.0f + dx, mPadding + secondProgressHeight / 2.0f, secondProgressHeight / 2.0f, mSecondProgressPaint);
                    }
                    //middle line
                    RectF rectF = new RectF(mPadding + secondProgressHeight / 2.0f, mPadding, mPadding + secondProgressHeight / 2.0f + dx, mPadding + secondProgressHeight);
                    canvas.drawRect(rectF, mSecondProgressPaint);
                }
            }
        }

    }

    /**
     * 绘制方形进度
     */
    private void drawProgressRectMode(Canvas canvas) {
        int width = getWidth();
        float percent = 0;
        if (mMax != 0) {
            percent = mProgress * 1.0f / mMax;
        }
        int progressHeight = getHeight() - mPadding * 2;
        if (mOpenGradient) {
            int progressWidth = width - mPadding * 2;
            float mDx = progressWidth * percent;

            int[] colors = new int[2];
            float[] positions = new float[2];
            //from color
            colors[0] = mGradientFrom;
            positions[0] = 0;
            //to color
            colors[1] = mGradientTo;
            positions[1] = 1;
            LinearGradient shader = new LinearGradient(
                    mPadding + progressHeight / 2.0f, mPadding, mPadding + progressHeight / 2.0f + mDx, mPadding + progressHeight,
                    colors,
                    positions,
                    Shader.TileMode.MIRROR);
            //gradient
            mGradientPaint.setShader(shader);

            //progress line
            RectF rectF = new RectF(mPadding, mPadding, mPadding + mDx, mPadding + progressHeight);
            canvas.drawRect(rectF, mGradientPaint);
        } else {
            int progressWidth = width - mPadding * 2;
            float dx = progressWidth * percent;
            mProgressPaint.setColor(mProgressColor);
            RectF rectF = new RectF(mPadding, mPadding, mPadding + dx, mPadding + progressHeight);
            canvas.drawRect(rectF, mProgressPaint);
        }

        //draw second progress
        if (mShowSecondProgress) {
            float secondPercent = 0;
            if (mMax != 0) {
                secondPercent = mSecondProgress * 1.0f / mMax;
            }
            int secondProgressHeight = getHeight() - mPadding * 2;
            if (mOpenSecondGradient) {
                int secondProgressWidth = width - mPadding * 2;
                float secondDx = secondProgressWidth * secondPercent;

                int[] secondColors = new int[2];
                float[] secondPositions = new float[2];
                //from color
                secondColors[0] = mSecondGradientFrom;
                secondPositions[0] = 0;
                //to color
                secondColors[1] = mSecondGradientTo;
                secondPositions[1] = 1;
                LinearGradient secondShader = new LinearGradient(
                        mPadding + secondProgressHeight / 2.0f, mPadding, mPadding + secondProgressHeight / 2.0f + secondDx, mPadding + secondProgressHeight,
                        secondColors,
                        secondPositions,
                        Shader.TileMode.MIRROR);
                //gradient
                mSecondGradientPaint.setShader(secondShader);

                //progress line
                RectF rectF = new RectF(mPadding, mPadding, mPadding + secondDx, mPadding + secondProgressHeight);
                canvas.drawRect(rectF, mSecondGradientPaint);
            } else {
                //no gradient
                //line shape
                int secondProgressWidth = width - mPadding * 2;
                float dx = secondProgressWidth * secondPercent;
                mSecondProgressPaint.setColor(mSecondProgressColor);
                RectF rectF = new RectF(mPadding, mPadding, mPadding + dx, mPadding + secondProgressHeight);
                canvas.drawRect(rectF, mSecondProgressPaint);
            }
        }

    }

    /**
     * 绘制圆角矩形进度
     */
    private void drawProgressRoundRectMode(Canvas canvas) {
        int width = getWidth();
        float percent = 0;
        if (mMax != 0) {
            percent = mProgress * 1.0f / mMax;
        }
        int progressHeight = getHeight() - mPadding * 2;
        int progressWidth = width - mPadding * 2 - mBorderWidth;
        float dx = progressWidth * percent;
        if (mOpenGradient) {
            int[] colors = new int[2];
            float[] positions = new float[2];
            //from color
            colors[0] = mGradientFrom;
            positions[0] = 0;
            //to color
            colors[1] = mGradientTo;
            positions[1] = 1;
            float left = mPadding + progressHeight / 2.0f;
            LinearGradient shader = new LinearGradient(
                    left, mPadding, left + dx, mPadding + progressHeight,
                    colors,
                    positions,
                    Shader.TileMode.MIRROR);
            //gradient
            mGradientPaint.setShader(shader);
            //progress line
            float rectLeft = mPadding + mBorderWidth / 2.0f;
            float rectTop = mPadding + mBorderWidth / 2.0f;
            RectF rectF = new RectF(rectLeft, rectTop, rectLeft + dx, getHeight() - rectTop);
            canvas.drawRoundRect(rectF, mRadius, mRadius, mGradientPaint);
        } else {
            mProgressPaint.setColor(mProgressColor);
            float rectLeft = mPadding + mBorderWidth / 2.0f;
            float rectTop = mPadding + mBorderWidth / 2.0f;
            RectF rectF = new RectF(rectLeft, rectTop, rectLeft + dx, getHeight() - rectTop);
            canvas.drawRoundRect(rectF, mRadius, mRadius, mProgressPaint);
        }

        //draw second progress
        if (mShowSecondProgress) {
            float secondPercent = 0;
            if (mMax != 0) {
                secondPercent = mSecondProgress * 1.0f / mMax;
            }
            int secondProgressHeight = getHeight() - mPadding * 2;
            int secondProgressWidth = width - mPadding * 2 - mBorderWidth;
            float secondDx = secondProgressWidth * secondPercent;
            if (mOpenSecondGradient) {
                int[] secondColors = new int[2];
                float[] secondPositions = new float[2];
                //from color
                secondColors[0] = mSecondGradientFrom;
                secondPositions[0] = 0;
                //to color
                secondColors[1] = mSecondGradientTo;
                secondPositions[1] = 1;
                float left = mPadding + secondProgressHeight / 2.0f;
                LinearGradient secondShader = new LinearGradient(
                        left, mPadding, left + secondDx, mPadding + secondProgressHeight,
                        secondColors,
                        secondPositions,
                        Shader.TileMode.MIRROR);
                //gradient
                mSecondGradientPaint.setShader(secondShader);

                //progress line
                float rectLeft = mPadding + mBorderWidth / 2.0f;
                float rectTop = mPadding + mBorderWidth / 2.0f;
                RectF rectF = new RectF(rectLeft, rectTop, rectLeft + secondDx, getHeight() - rectTop);
                canvas.drawRoundRect(rectF, mRadius, mRadius, mSecondGradientPaint);
            } else {
                //no gradient
                //line shape
                mSecondProgressPaint.setColor(mSecondProgressColor);
                float rectLeft = mPadding + mBorderWidth / 2.0f;
                float rectTop = mPadding + mBorderWidth / 2.0f;
                RectF rectF = new RectF(rectLeft, rectTop, rectLeft + secondDx, getHeight() - rectTop);
                canvas.drawRoundRect(rectF, mRadius, mRadius, mSecondProgressPaint);
            }
        }

    }

    /**
     * 绘制半圆形背景
     */
    private void drawBackgroundCircleMode(Canvas canvas) {
        int bgHeight = getHeight();
        int width = getWidth();
        //left circle
        canvas.drawCircle(bgHeight / 2.0f, bgHeight / 2.0f, bgHeight / 2.0f, mBgPaint);
        //right circle
        canvas.drawCircle(width - bgHeight / 2.0f, bgHeight / 2.0f, bgHeight / 2.0f, mBgPaint);
        //middle line
        RectF rectF = new RectF(bgHeight / 2.0f, 0, width - bgHeight / 2.0f, bgHeight);
        canvas.drawRect(rectF, mBgPaint);
    }

    /**
     * 绘制半圆形边框
     */
    private void drawBorderCircleMode(Canvas canvas) {
        if (mDrawBorder) {
            int bgHeight = getHeight();
            int width = getWidth();
            RectF rect = new RectF(0, 0, width, bgHeight);
            canvas.drawRoundRect(rect, bgHeight / 2.0f, bgHeight / 2.0f, mBorderPaint);
        }
    }

    /**
     * 绘制半方形边框
     */
    private void drawBorderRectMode(Canvas canvas) {
        if (mDrawBorder) {
            int bgHeight = getHeight();
            int width = getWidth();
            RectF rect = new RectF(0, 0, width, bgHeight);
            canvas.drawRect(rect, mBorderPaint);
        }
    }

    /**
     * 绘制圆角矩形边框
     */
    private void drawBorderRoundRect(Canvas canvas) {
        if (mDrawBorder) {
            int bgHeight = getHeight();
            int width = getWidth();
            RectF rect = new RectF(mBorderWidth / 2.0f, mBorderWidth / 2.0f, width - mBorderWidth / 2.0f, bgHeight - mBorderWidth / 2.0f);
            canvas.drawRoundRect(rect, mRadius, mRadius, mBorderPaint);
        }
    }

    /**
     * 绘制方形背景
     */
    private void drawBackgroundRectMode(Canvas canvas) {
        int bgHeight = getHeight();
        int width = getWidth();
        RectF rectF = new RectF(0, 0, width, bgHeight);
        canvas.drawRect(rectF, mBgPaint);
    }

    /**
     * 绘制圆角矩形背景
     */
    private void drawBackgroundRoundRectMode(Canvas canvas) {
        int bgHeight = getHeight();
        int width = getWidth();
        RectF rectF = new RectF(mBorderWidth / 2.0f, mBorderWidth / 2.0f, width - mBorderWidth / 2.0f, bgHeight - mBorderWidth / 2.0f);
        canvas.drawRoundRect(rectF, mRadius, mRadius, mBgPaint);
    }

    /**
     * 获取最大值
     *
     * @return 最大值
     */
    public int getMax() {
        return mMax;
    }

    /**
     * 设置最大值
     *
     * @param max 最大值
     */
    public void setMax(int max) {
        this.mMax = max;
        invalidate();
    }

    /**
     * 获取一级进度值
     *
     * @return 进度值
     */
    public int getProgress() {
        return mProgress;
    }

    /**
     * 设置一级进度值
     *
     * @param progress 进度值
     */
    public void setProgress(int progress) {
        if (progress < 0) {
            this.mProgress = 0;
        } else if (progress > mMax) {
            this.mProgress = mMax;
        } else {
            this.mProgress = progress;
        }
        invalidate();
        if (mOnProgressChangedListener != null) {
            mOnProgressChangedListener.onProgressChanged(this, mMax, this.mProgress);
        }
    }

    /**
     * 是否显示二级进度条
     *
     * @return 是/否
     */
    public boolean isShowSecondProgress() {
        return mShowSecondProgress;
    }

    /**
     * 设置是否显示二级进度条
     *
     * @param showSecondProgress 是/否
     */
    public void setShowSecondProgress(boolean showSecondProgress) {
        this.mShowSecondProgress = showSecondProgress;
        invalidate();
    }

    /**
     * 获取二级进度条进度
     *
     * @return 进度值
     */
    public int getSecondProgress() {
        return mSecondProgress;
    }

    /**
     * 设置二级进度条进度
     *
     * @param secondProgress 进度值
     */
    public void setSecondProgress(int secondProgress) {
        if (secondProgress < 0) {
            this.mSecondProgress = 0;
        } else if (secondProgress > mMax) {
            this.mSecondProgress = mMax;
        } else {
            this.mSecondProgress = secondProgress;
        }
        invalidate();
        if (mOnProgressChangedListener != null) {
            mOnProgressChangedListener.onSecondProgressChanged(this, mMax, this.mSecondProgress);
        }
    }

    /**
     * 获取二级进度条形状
     *
     * @return 形状,点:{@link #SHAPE_POINT} 线:{@link #SHAPE_LINE}
     */
    public int getSecondProgressShape() {
        return mSecondProgressShape;
    }

    /**
     * 设置二级进度条形状
     *
     * @param secondProgressShape 形状,点:{@link #SHAPE_POINT} 线:{@link #SHAPE_LINE}
     */
    public void setSecondProgressShape(@SecondProgressShape int secondProgressShape) {
        this.mSecondProgressShape = secondProgressShape;
        invalidate();
    }

    /**
     * 获取背景色
     *
     * @return 颜色值
     */
    public int getBgColor() {
        return mBgColor;
    }

    /**
     * 设置背景色
     *
     * @param bgColor 颜色值
     */
    public void setBgColor(@ColorInt int bgColor) {
        this.mBgColor = bgColor;
        mBgPaint.setColor(bgColor);
        invalidate();
    }

    /**
     * 获取二级渐变是否启用
     *
     * @return 是/否
     */
    public boolean isOpenSecondGradient() {
        return mOpenSecondGradient;
    }

    /**
     * 设置二级渐变是否启用
     *
     * @param openSecondGradient 是/否
     */
    public void setOpenSecondGradient(boolean openSecondGradient) {
        this.mOpenSecondGradient = openSecondGradient;
        invalidate();
    }

    public int getSecondGradientFrom() {
        return mSecondGradientFrom;
    }

    public int getSecondGradientTo() {
        return mSecondGradientTo;
    }

    /**
     * 获取二级进度条颜色
     *
     * @return 颜色值
     */
    public int getSecondProgressColor() {
        return mSecondProgressColor;
    }

    /**
     * 设置二级进度条颜色
     *
     * @param secondProgressColor 颜色值
     */
    public void setSecondProgressColor(@ColorInt int secondProgressColor) {
        this.mSecondProgressColor = secondProgressColor;
        mSecondProgressPaint.setColor(secondProgressColor);
        invalidate();
    }

    /**
     * 获取一级进度条颜色
     *
     * @return 颜色值
     */
    public int getProgressColor() {
        return mProgressColor;
    }

    /**
     * 设置一级进度条颜色
     *
     * @param progressColor 颜色值
     */
    public void setProgressColor(@ColorInt int progressColor) {
        this.mProgressColor = progressColor;
        mProgressPaint.setColor(progressColor);
        invalidate();
    }

    /**
     * 获取内边距
     *
     * @return 边距值
     */
    public int getPadding() {
        return mPadding;
    }

    /**
     * 设置内边距
     *
     * @param padding 边距值
     */
    public void setPadding(int padding) {
        this.mPadding = padding;
        invalidate();
    }

    /**
     * 设置显示模式
     *
     * @param showMode 显示模式,0:半圆,1:方形,2:圆角矩形
     */
    public void setShowMode(@ShowMode int showMode) {
        switch (showMode) {
            case SHOW_MODE_ROUND:
                this.mShowMode = 0;
                break;
            case SHOW_MODE_RECT:
                this.mShowMode = 1;
                break;
            case SHOW_MODE_ROUND_RECT:
                this.mShowMode = 2;
                break;
        }
        invalidate();
    }

    /**
     * 获取进度百分比,int类型
     *
     * @return percentage value
     */
    public int getPercentage() {
        if (mMax == 0) {
            return 0;
        }
        return (int) (mProgress * 100.0f / mMax + 0.5f);
    }

    /**
     * 获取进度百分比,float类型
     *
     * @return percentage value
     */
    public float getPercentageFloat() {
        if (mMax == 0) {
            return 0f;
        }
        return mProgress * 100.0f / mMax;
    }

    /**
     * 一级渐变色是否启用
     *
     * @return 是/否
     */
    public boolean isOpenGradient() {
        return mOpenGradient;
    }

    /**
     * 设置一级渐变色是否启用
     *
     * @param openGradient 是/否
     */
    public void setOpenGradient(boolean openGradient) {
        this.mOpenGradient = openGradient;
        invalidate();
    }

    public int getGradientFrom() {
        return mGradientFrom;
    }

    public int getGradientTo() {
        return mGradientTo;
    }

    /**
     * 设置边框颜色
     *
     * @param borderColor 颜色值
     */
    public void setBorderColor(@ColorInt int borderColor) {
        this.mBorderColor = borderColor;
        this.mBorderPaint.setColor(this.mBorderColor);
        invalidate();
    }

    /**
     * 设置一级进度条的渐变色
     *
     * @param from 起始颜色
     * @param to   结束颜色
     */
    public void setGradientColor(int from, int to) {
        this.mGradientFrom = from;
        this.mGradientTo = to;
        invalidate();
    }

    /**
     * 设置二级进度条的渐变色
     *
     * @param from 起始颜色
     * @param to   结束颜色
     */
    public void setSecondGradientColor(int from, int to) {
        this.mSecondGradientFrom = from;
        this.mSecondGradientTo = to;
        invalidate();
    }

    /**
     * 设置一级进度条的渐变色和边框颜色
     *
     * @param from        起始颜色
     * @param to          结束颜色
     * @param borderColor 边框颜色
     */
    public void setGradientColorAndBorderColor(int from, int to, int borderColor) {
        this.mGradientFrom = from;
        this.mGradientTo = to;
        this.mBorderColor = borderColor;
        this.mBorderPaint.setColor(this.mBorderColor);
        invalidate();
    }

    /**
     * 获取边框颜色
     *
     * @return 颜色值
     */
    public int getBorderColor() {
        return mBorderColor;
    }

    /**
     * 设置进度变化监听(包括一级和二级进度)
     *
     * @param onProgressChangedListener 进度值变化回调
     */
    public void setOnProgressChangedListener(OnProgressChangedListener onProgressChangedListener) {
        this.mOnProgressChangedListener = onProgressChangedListener;
    }

}

静态使用方式

如采用动态控件设置方式,则需要在对应组件内进行设置(因当前采用的是静态方式,可忽略MainActivity

package com.example.lineprogress

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

    }
}

activity_main

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <com.example.lineprogress.ZzHorizontalProgressBar
        android:layout_width="match_parent"
        android:layout_height="10dp"
        android:layout_marginHorizontal="30dp"
        android:layout_marginTop="20dp"
        app:zpb_bg_color="#7F683F"
        app:zpb_max="100"
        app:zpb_pb_color="#FFDFA8"
        app:zpb_progress="19"
        app:zpb_show_mode="round" />

    <com.example.lineprogress.ZzHorizontalProgressBar
        android:layout_width="match_parent"
        android:layout_height="10dp"
        android:layout_marginHorizontal="30dp"
        android:layout_marginTop="20dp"
        app:zpb_bg_color="#7F683F"
        app:zpb_max="100"
        app:zpb_pb_color="#FFDFA8"
        app:zpb_progress="19"
        app:zpb_show_mode="rect" />

    <com.example.lineprogress.ZzHorizontalProgressBar
        android:layout_width="match_parent"
        android:layout_height="10dp"
        android:layout_marginHorizontal="30dp"
        android:layout_marginTop="20dp"
        app:zpb_bg_color="#7F683F"
        app:zpb_max="100"
        app:zpb_pb_color="#FFDFA8"
        app:zpb_progress="19"
        app:zpb_show_mode="round_rect" />

</androidx.appcompat.widget.LinearLayoutCompat>

项目场景

仅记录我在项目中使用该控件时所遇到的问题

圆角尺寸与设计图不符?

  1. 设置显示模式为 SHOW_MODE_ROUND_RECT
progressLine.setShowMode(SHOW_MODE_ROUND_RECT)
  1. 通过静态方式在xml中设置app:zpb_round_rect_radius尺寸
app:zpb_round_rect_radius="5dp"

设置对应进度值后,显示异常?

有可能显示为0,有可能显示满值,或者也有可能出现别的场景(到时候可以留言哈)

项目伪代码,接口数据一般采用 String 类型接收,为防止直接转换Int报错,可以先转Double再转Int

 progressLine.progress = floor(info?.growthValue?.toDouble() ?: 0.00).toInt()
 progressLine.max = floor(data?.nextGrowthValue?.toDouble() ?: 0.00).toInt()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/603961.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于51单片机的智能台灯proteus仿真设计( proteus仿真+程序+原理图+报告+讲解视频)

基于51单片机的红外光敏检测智能台灯控制系统仿真( proteus仿真程序原理图报告讲解视频&#xff09; 1.主要功能&#xff1a; 基于51单片机的红外检测光照检测智能台灯仿真设计 1、检测光照强度并显示在数码管上。 2、具备红外检测人体功能。 3、灯光控制模式分为自动模式…

抓取Google时被屏蔽怎么办?如何避免?

在当今数字化时代&#xff0c;数据采集和网络爬取已成为许多企业和个人必不可少的业务活动。对于爬取搜索引擎数据&#xff0c;特别是Google&#xff0c;使用代理IP是常见的手段。然而&#xff0c;使用代理抓取Google并不是一件轻松的事情&#xff0c;有许多常见的误区可能会导…

vue 语法2

【5】条件渲染和列表渲染 &#xff08;1&#xff09;条件渲染v-if v-else-if v-else 条件渲染根据表达式的真假值来渲染不同的元素或组件。 v-if&#xff1a;当表达式的值为真时&#xff0c;渲染该元素或组件。 v-else-if&#xff1a;当前面的 v-if 或 v-else-if 的表达式为假…

【C++】STL — vector的接口讲解 +详细模拟实现

前言: 本章我们将学习STL中另一个重要的类模板vector… vector是表示可变大小数组的序列容器。就像数组一样&#xff0c;vector也采用的连续存储空间来存储元素。但是又不像数组&#xff0c;它的大小是可以动态改变的本质讲&#xff0c;vector使用动态分配数组来存储它的元素v…

智慧公厕的核心技术详解:物联网、云计算、大数据、自动化控制

公共厕所是城市的重要组成部分&#xff0c;而智慧公厕的建设和管理正成为城市发展的重要方向。智慧公厕的核心技术即是物联网、云计算、大数据和自动化控制。下面将以智慧公厕源头实力厂家广州中期科技有限公司&#xff0c;大量精品案例项目现场实景实图实例&#xff0c;详细介…

Sealos急速部署生产用k8s集群

最近一段时间部署k8s全部使用sealos了&#xff0c;整体使用感觉良好&#xff0c;基本没有什么坑。推荐给大家。 使用 Sealos&#xff0c;可以安装一个不包含任何组件的裸 Kubernetes 集群。 最大的好处是提供 99 年证书&#xff0c;用到我跑路是足够了。不用像之前kubeadm安装…

【计算机科学速成课】笔记一

文章目录 写在前面1.计算机的早期历史2.电子计算机3.布尔运算和逻辑门4.二进制5.算术逻辑单元-ALU6.寄存器和内存 写在前面 所有的一切源于这样一个网站——CS自学指南。 这是新手小白入门计算机科学必要了解的知识——【计算机科学速成课】[40集全/精校] - Crash Course Comp…

地平线的花样年华

北京车展在这个喧闹的“五一”假期落幕了&#xff0c;它留给我们许多思考。 虽然社会面的传播焦点落在了“网红”两个字上&#xff0c;但技术的更新依然如暗流涌动&#xff0c;给这届北京车展写下注脚。整个过程前后&#xff0c;最重要和吸引了最多目光的&#xff0c;是智驾&a…

2024蓝桥杯CTF writeUP--cc

给了个网页&#xff0c;里面有加密算法&#xff0c;密钥&#xff0c;密文 使用在线解码工具 CTF最全在线工具整理_在线ctf工具-CSDN博客 将输出的密文&#xff0c;密钥&#xff0c;vi&#xff0c;加密方式一一对应

Linux变量的认识及环境变量配置详解

文章目录 1、变量的划分2、局部变量3、全局变量4、环境变量4.1、概述4.2、配置临时环境变量4.3、配置永久环境变量4.3.1、用户级配置文件1&#xff09;配置方法一&#xff1a;~/.bashrc文件2&#xff09;配置方法二&#xff1a;~/.profile文件3&#xff09;配置方法三&#xff…

生产制造中刀具管理系统,帮助工厂不再频繁换刀

一、刀具管理的定义与重要性 刀具管理是指对生产过程中使用的各种刀具进行计划、采购、存储、分配、使用、监控、维修和报废等全过程的管理。刀具作为制造过程中的直接工具&#xff0c;其性能、质量和使用效率直接影响产品的加工精度、表面质量和生产效率。因此&#xff0c;建…

ansible—playbook的template、tags、roles模块

目录 一、template 1、简介 2、template模块实例 1.先准备一个以.j2结尾的template模板文件&#xff0c;设置引用的变量&#xff0c;ansible上要先安装httpd 2、修改主机清单文件&#xff0c;使用主机变量定义一个变量名相同而值不同的变量 3、主机添加hosts 4、编写pla…

【漏洞复现】金和OA FileDownLoad接口处存在任意文件读取漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

词袋法TFIDF

Tf-idf⽂本特征提取 TF-IDF的主要思想是&#xff1a;如果某个词或短语在⼀篇⽂章中出现的概率⾼&#xff0c;并且在其他⽂章中很少出现&#xff0c;则认为此词或者短语具有很好的类别区分能⼒&#xff0c;适合⽤来分类。TF-IDF作⽤&#xff1a;⽤以评估⼀字词对于⼀个⽂件集或…

数据结构-线性表-链表-2.3-1

设计一个递归算法&#xff0c;删除不带头结点的单链表L中所有值为x的结点。 void del(Linkllist &L&#xff0c;int x){LNode *p;if(LNULL){return;}if(L->datax){pL;LL->next;;free(p);del(L,x);}else{del(L->next,x);} } 时间复杂度为O(n)

Linux系统编程--网络编程

一、OSI网络七层模型 OSI模型将整个网络通信过程分解为七个层次&#xff0c;每个层次都为网络通信提供了特定的功能。以下是OSI模型的七个层次&#xff0c;从上到下依次是&#xff1a; 应用层&#xff08;Application Layer&#xff09;&#xff1a;为应用软件提供网络服务&am…

盘点四种计算数组中元素值为1的个数的方法

目录 一、引言 二、方法一&#xff1a;基础循环遍历 三、方法二&#xff1a;列表推导式 四、方法三&#xff1a;使用内置函数sum和生成器表达式 五、方法四&#xff1a;使用NumPy库 六、性能比较 七、性能结果分析与讨论 八、最佳实践 九、总结 一、引言 在编程和数…

Linux:进程通信(二)信号的保存

目录 一、信号的处理是否是立即处理的&#xff1f; 二、信号如何保存 1、阻塞、未决、递达 2、信号集 3、信号集操作函数 4、sigprocmask函数 5、sigpending 函数 上篇文章我们讲解了信号的产生&#xff1a;Linux&#xff1a;进程信号&#xff08;一&#xff09;信号的产…

7天精通Web APIs——-Bom操作(理论+实战)(第五天)

一、window对象 1.1 window对象和bom的关系 首先理解dom和bom之间的关系 显然bom的范围比较大 bom的全称为浏览器对象模型 window是bom的核心对象&#xff0c;window里面有很多属性和方法&#xff0c;用于实现浏览器与 JavaScript 代码之间的交互。作为 JavaScript 的全局对…

项目管理-项目绩效域2/2

项目管理&#xff1a;每天进步一点点~ 活到老&#xff0c;学到老 ヾ(◍∇◍)&#xff89;&#xff9e; 何时学习都不晚&#xff0c;加油 八大绩效域包括&#xff1a;“团干部 策划开公交” 团队、干系人、不确定性、测试、规划、开发方法与生命周期、项目工作、交付。 上节…
最新文章