• Android studio实现自定义圆形进度条 带刻度进度条 计步效果 时速表 水波纹效果


    原文链接
    效果图

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    点击重置后:
    在这里插入图片描述
    该项目总共实现了三种圆形进度条效果

    1. CircleProgress:圆形进度条,可以实现仿 QQ 健康计步器的效果,支持配置进度条背景色、宽度、起始角度,支持进度条渐变
    2. DialProgress:类似 CircleProgress,但是支持刻度
    3. WaveProgress:实现了水波纹效果的圆形进度条,不支持渐变和起始角度配置,如需此功能可参考 CircleProgress 自行实现。
      所有进度条都是可以手动触摸变化百分比的
      我的代码和依赖,我没有加leakcanary库,没有app.activity
      依赖是出现报错后自己添加的,不一定适用所有项目
     buildTypes {
            debug {
                buildConfigField "boolean", "DEBUG", "true"
    
            }
            release {
                buildConfigField "boolean", "DEBUG", "false"
                }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
      defaultConfig {
       buildConfigField "boolean", "DEBUG", "true"
       }
    
    • 1
    • 2
    • 3
    values /

    attrs.xml

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <!-- 是否开启抗锯齿 -->
        <attr name="antiAlias" format="boolean" />
        <!-- 圆弧起始角度,3点钟方向为0,顺时针递增,小于0或大于360进行取余 -->
        <attr name="startAngle" format="float" />
        <!-- 圆弧度数 -->
        <attr name="sweepAngle" format="float" />
        <!-- 设置动画时间 -->
        <attr name="animTime" format="integer" />
        <!-- 绘制内容的数值 -->
        <attr name="maxValue" format="float" />
        <attr name="value" format="float" />
        <!-- 绘制内容的单位 -->
        <attr name="unit" format="string|reference" />
        <attr name="unitSize" format="dimension" />
        <attr name="unitColor" format="color|reference" />
        <!-- 绘制内容相应的提示语 -->
        <attr name="hint" format="string|reference" />
        <attr name="hintSize" format="dimension" />
        <attr name="hintColor" format="color|reference" />
        <!-- 精度,默认为0 -->
        <attr name="precision" format="integer" />
        <attr name="valueSize" format="dimension" />
        <attr name="valueColor" format="color|reference" />
        <!-- 圆弧颜色,设置多个可实现渐变 -->
        <attr name="arcColor1" format="color|reference" />
        <attr name="arcColor2" format="color|reference" />
        <attr name="arcColor3" format="color|reference" />
        <!-- 背景圆弧颜色,默认白色 -->
        <attr name="bgArcColor" format="color|reference" />
        <!-- 圆弧宽度 -->
        <attr name="arcWidth" format="dimension" />
        <!-- 圆弧颜色, -->
        <attr name="arcColors" format="color|reference" />
        <!-- 文字的偏移量。相对于圆半径而言,默认三分之一 -->
        <attr name="textOffsetPercentInRadius" format="float" />
    
        <!-- 圆形进度条 -->
        <declare-styleable name="CircleProgressBar">
            <attr name="antiAlias" />
            <attr name="startAngle" />
            <attr name="sweepAngle" />
            <attr name="animTime" />
            <attr name="maxValue" />
            <attr name="value" />
            <attr name="precision" />
            <attr name="valueSize" />
            <attr name="valueColor" />
            <attr name="textOffsetPercentInRadius" />
            <!-- 绘制内容相应的提示语 -->
            <attr name="hint" />
            <attr name="hintSize" />
            <attr name="hintColor" />
            <!-- 绘制内容的单位 -->
            <attr name="unit" />
            <attr name="unitSize" />
            <attr name="unitColor" />
            <!-- 圆弧宽度 -->
            <attr name="arcWidth" />
            <attr name="arcColors" />
            <!-- 背景圆弧颜色 -->
            <attr name="bgArcColor" />
            <!-- 背景圆弧宽度 -->
            <attr name="bgArcWidth" format="dimension" />
        </declare-styleable>
    
        <declare-styleable name="DialProgress">
            <attr name="antiAlias" />
            <attr name="startAngle" />
            <attr name="sweepAngle" />
            <attr name="animTime" />
            <attr name="maxValue" />
            <attr name="value" />
            <attr name="precision" />
            <attr name="valueSize" />
            <attr name="valueColor" />
            <attr name="textOffsetPercentInRadius" />
            <!-- 绘制内容的单位 -->
            <attr name="unit" />
            <attr name="unitSize" />
            <attr name="unitColor" />
            <!-- 绘制内容相应的提示语 -->
            <attr name="hint" />
            <attr name="hintSize" />
            <attr name="hintColor" />
            <!-- 圆弧的宽度 -->
            <attr name="arcWidth" />
            <!-- 刻度的宽度 -->
            <attr name="dialWidth" format="dimension|reference" />
            <!-- 刻度之间的间隔 -->
            <attr name="dialIntervalDegree" format="integer" />
            <!-- 圆弧颜色, -->
            <attr name="arcColors" />
            <!-- 背景圆弧线颜色 -->
            <attr name="bgArcColor" />
            <!-- 刻度线颜色 -->
            <attr name="dialColor" format="color|reference" />
        </declare-styleable>
    
        <declare-styleable name="WaveProgress">
            <!-- 是否开启抗锯齿 -->
            <attr name="antiAlias" />
            <!-- 深色水波动画时间 -->
            <attr name="darkWaveAnimTime" format="integer" />
            <!-- 浅色水波动画时间 -->
            <attr name="lightWaveAnimTime" format="integer" />
            <!-- 最大值 -->
            <attr name="maxValue" />
            <!-- 当前值 -->
            <attr name="value" />
            <attr name="valueColor" />
            <attr name="valueSize" />
            <!-- 绘制内容相应的提示语 -->
            <attr name="hint" />
            <attr name="hintSize" />
            <attr name="hintColor" />
            <!-- 圆环宽度 -->
            <attr name="circleWidth" format="dimension" />
            <!-- 圆环颜色 -->
            <attr name="circleColor" format="color|reference" />
            <!-- 背景圆环颜色 -->
            <attr name="bgCircleColor" format="color|reference" />
            <!-- 锁定水波不随圆环进度改变,默认锁定在50%-->
            <attr name="lockWave" format="boolean" />
            <!-- 水波数量 -->
            <attr name="waveNum" format="integer" />
            <!-- 水波高度,峰值和谷值之和 -->
            <attr name="waveHeight" format="dimension" />
            <!-- 深色水波颜色 -->
            <attr name="darkWaveColor" format="color|reference" />
            <!-- 是否显示浅色水波 -->
            <attr name="showLightWave" format="boolean" />
            <!-- 浅色水波颜色 -->
            <attr name="lightWaveColor" format="color|reference" />
            <!-- 浅色水波的方向 -->
            <attr name="lightWaveDirect" format="enum">
                <enum name="L2R" value="0" />
                <enum name="R2L" value="1" />
            </attr>
        </declare-styleable>
    </resources>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142

    color.xml

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <color name="black">#FF000000</color>
        <color name="white">#FFFFFFFF</color>
        <color name="colorPrimary">#3F51B5</color>
        <color name="colorPrimaryDark">#303F9F</color>
        <color name="colorAccent">#FF4081</color>
    
        <color name="dark">#803cbcb7</color>
        <color name="light">#800de6e8</color>
    
        <color name="green">#00FF00</color>
        <color name="blue">#EE9A00</color>
        <color name="red">#EE0000</color>
    
        <integer-array name="gradient_arc_color">
            <item>@color/green</item>
            <item>@color/blue</item>
            <item>@color/red</item>
        </integer-array>
    </resources>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    dimens.xml

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <dimen name="small">5dp</dimen>
        <dimen name="medium">10dp</dimen>
        <dimen name="normal">15dp</dimen>
        <dimen name="large">20dp</dimen>
        <dimen name="xlarge">25dp</dimen>
        <dimen name="xxlarge">30dp</dimen>
    
        <!-- text size -->
        <dimen name="text_size_35">35sp</dimen>
        <dimen name="text_size_34">34sp</dimen>
        <dimen name="text_size_33">33sp</dimen>
        <dimen name="text_size_32">32sp</dimen>
        <dimen name="text_size_31">31sp</dimen>
        <dimen name="text_size_30">30sp</dimen>
        <dimen name="text_size_29">29sp</dimen>
        <dimen name="text_size_28">28sp</dimen>
        <dimen name="text_size_26">26sp</dimen>
        <dimen name="text_size_25">25sp</dimen>
        <dimen name="text_size_24">24sp</dimen>
        <dimen name="text_size_23">23sp</dimen>
        <dimen name="text_size_22">22sp</dimen>
        <dimen name="text_size_21">21sp</dimen>
        <dimen name="text_size_20">20sp</dimen>
        <dimen name="text_size_19">19sp</dimen>
        <dimen name="text_size_18">18sp</dimen>
        <dimen name="text_size_17">17sp</dimen>
        <dimen name="text_size_16">16sp</dimen>
        <dimen name="text_size_15">15sp</dimen>
        <dimen name="text_size_14">14sp</dimen>
        <dimen name="text_size_13">13sp</dimen>
        <dimen name="text_size_12">12sp</dimen>
        <dimen name="text_size_11">11sp</dimen>
        <dimen name="text_size_10">10sp</dimen>
        <dimen name="text_size_9">9sp</dimen>
        <dimen name="text_size_8">8sp</dimen>
        <dimen name="text_size_7">7sp</dimen>
    
        <!-- Default screen margins, per the Android Design guidelines. -->
        <dimen name="activity_horizontal_margin">16dp</dimen>
        <dimen name="activity_vertical_margin">16dp</dimen>
    </resources>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    layout /

    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <ScrollView 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:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.example.circularwaterripple.MainActivity">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
    
            <Button
                android:id="@+id/btn_reset_all"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="重置" />
    
            <com.example.circularwaterripple.CircleProgress
                android:id="@+id/circle_progress_bar1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                app:antiAlias="true"
                app:arcWidth="@dimen/small"
                app:bgArcColor="@color/colorAccent"
                app:bgArcWidth="@dimen/small"
                app:hint="截止当前已走"
                app:hintSize="15sp"
                app:maxValue="10000"
                app:startAngle="135"
                app:sweepAngle="270"
                app:unit="步"
                app:unitSize="15sp"
                app:value="10000"
                app:valueSize="25sp"/>
    
            <com.example.circularwaterripple.CircleProgress
                android:id="@+id/circle_progress_bar2"
                android:layout_width="100dp"
                android:layout_height="200dp"
                android:layout_gravity="center_horizontal"
                app:antiAlias="true"
                app:arcWidth="@dimen/small"
                app:bgArcColor="@color/colorAccent"
                app:bgArcWidth="@dimen/small"
                app:hint="百分比"
                app:hintSize="@dimen/text_size_15"
                app:maxValue="100"
                app:startAngle="135"
                app:sweepAngle="270"
                app:textOffsetPercentInRadius="0.5"
                app:unit="%"
                app:unitSize="@dimen/text_size_15"
                app:value="75"
                app:valueSize="@dimen/text_size_20"
                tools:ignore="MissingClass" />
    
            <com.example.circularwaterripple.CircleProgress
                android:id="@+id/circle_progress_bar3"
                android:layout_width="200dp"
                android:layout_height="200dp"
                android:layout_gravity="center_horizontal"
                app:antiAlias="true"
                app:arcWidth="@dimen/small"
                app:bgArcColor="@android:color/darker_gray"
                app:bgArcWidth="@dimen/small"
                app:hint="当前进度"
                app:hintSize="@dimen/text_size_25"
                app:maxValue="100"
                app:startAngle="270"
                app:sweepAngle="360"
                app:unit="%"
                app:unitSize="@dimen/text_size_25"
                app:value="100"
                app:valueSize="@dimen/text_size_35" />
    
            <com.example.circularwaterripple.DialProgress
                android:id="@+id/dial_progress_bar"
                android:layout_width="300dp"
                android:layout_height="300dp"
                android:layout_gravity="center_horizontal"
                android:padding="@dimen/medium"
                app:animTime="1000"
                app:arcColors="@array/gradient_arc_color"
                app:arcWidth="@dimen/large"
                app:dialIntervalDegree="3"
                app:dialWidth="2dp"
                app:hint="当前时速"
                app:hintSize="@dimen/text_size_25"
                app:maxValue="300"
                app:startAngle="135"
                app:sweepAngle="270"
                app:unit="km/h"
                app:unitSize="@dimen/text_size_25"
                app:value="300"
                app:valueSize="@dimen/text_size_35" />
    
            <com.example.circularwaterripple.WaveProgress
                android:id="@+id/wave_progress_bar"
                android:layout_width="300dp"
                android:layout_height="300dp"
                android:layout_gravity="center_horizontal"
                app:darkWaveAnimTime="1000"
                app:darkWaveColor="@color/dark"
                app:lightWaveAnimTime="2000"
                app:lightWaveColor="@color/light"
                app:lightWaveDirect="R2L"
                app:lockWave="false"
                app:valueSize="@dimen/text_size_35"
                app:waveHeight="30dp"
                app:waveNum="1"
                tools:ignore="ExtraText" />
        </LinearLayout>
    </ScrollView>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117

    activity.test.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <com.example.circularwaterripple.CircleProgress
            android:layout_width="400dp"
            android:layout_height="400dp"
            app:waveHeight="100dp" />
    </LinearLayout>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    activity

    CircleProgress 圆形进度条,类似 QQ 健康中运动步数的 UI 控件

    import android.animation.ValueAnimator;
    import android.content.Context;
    import android.content.res.Resources;
    import android.content.res.TypedArray;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Point;
    import android.graphics.RectF;
    import android.graphics.SweepGradient;
    import android.graphics.Typeface;
    import android.text.TextPaint;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.View;
    
     // 圆形进度条,类似 QQ 健康中运动步数的 UI 控件
    public class CircleProgress extends View {
        private static final String TAG = CircleProgress.class.getSimpleName();
        private Context mContext;
    
        //默认大小
        private int mDefaultSize;
        //是否开启抗锯齿
        private boolean antiAlias;
        //绘制提示
        private TextPaint mHintPaint;
        private CharSequence mHint;
        private int mHintColor;
        private float mHintSize;
        private float mHintOffset;
    
        //绘制单位
        private TextPaint mUnitPaint;
        private CharSequence mUnit;
        private int mUnitColor;
        private float mUnitSize;
        private float mUnitOffset;
    
        //绘制数值
        private TextPaint mValuePaint;
        private float mValue;
        private float mMaxValue;
        private float mValueOffset;
        private int mPrecision;
        private String mPrecisionFormat;
        private int mValueColor;
        private float mValueSize;
    
        //绘制圆弧
        private Paint mArcPaint;
        private float mArcWidth;
        private float mStartAngle, mSweepAngle;
        private RectF mRectF;
        //渐变的颜色是360度,如果只显示270,那么则会缺失部分颜色
        private SweepGradient mSweepGradient;
        private int[] mGradientColors = {Color.GREEN, Color.YELLOW, Color.RED};
        //当前进度,[0.0f,1.0f]
        private float mPercent;
        //动画时间
        private long mAnimTime;
        //属性动画
        private ValueAnimator mAnimator;
    
        //绘制背景圆弧
        private Paint mBgArcPaint;
        private int mBgArcColor;
        private float mBgArcWidth;
    
        //圆心坐标,半径
        private Point mCenterPoint;
        private float mRadius;
        private float mTextOffsetPercentInRadius;
    
        public CircleProgress(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(context, attrs);
        }
    
        private void init(Context context, AttributeSet attrs) {
            mContext = context;
            mDefaultSize = MiscUtil.dipToPx(mContext, Constant.DEFAULT_SIZE);
            mAnimator = new ValueAnimator();
            mRectF = new RectF();
            mCenterPoint = new Point();
            initAttrs(attrs);
            initPaint();
            setValue(mValue);
        }
    
        private void initAttrs(AttributeSet attrs) {
            /*
            从mContext中获取与CircleProgressBar相关的属性。
            obtainStyledAttributes是一个用于从给定的attrs和指定的styleable资源ID数组中获取属性的方法
             */
            TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.CircleProgressBar);
    
            /*
            从TypedArray中获取CircleProgressBar_antiAlias属性,并将其值赋给antiAlias。
            如果该属性在XML中未定义,那么将使用默认值Constant.ANTI_ALIAS。
            getBoolean是一个方法,用于从TypedArray中获取布尔类型的属性
             */
            antiAlias = typedArray.getBoolean(R.styleable.CircleProgressBar_antiAlias, Constant.ANTI_ALIAS);
    
            /*
            从TypedArray中获取CircleProgressBar_hint属性,并将其值赋给mHint。
            getString是一个方法,用于从TypedArray中获取字符串类型的属性
             */
            mHint = typedArray.getString(R.styleable.CircleProgressBar_hint);
    
            /*
            从TypedArray中获取CircleProgressBar_hintColor属性,并将其值赋给mHintColor。
            如果该属性在XML中未定义,那么将使用默认值Color.BLACK。
            getColor是一个方法,用于从TypedArray中获取颜色类型的属性
             */
            mHintColor = typedArray.getColor(R.styleable.CircleProgressBar_hintColor, Color.BLACK);
    
            /*
            从TypedArray中获取CircleProgressBar_hintSize属性,并将其值赋给mHintSize。
            如果该属性在XML中未定义,那么将使用默认值Constant.DEFAULT_HINT_SIZE。
            getDimension是一个方法,用于从TypedArray中获取尺寸类型的属性
             */
            mHintSize = typedArray.getDimension(R.styleable.CircleProgressBar_hintSize, Constant.DEFAULT_HINT_SIZE);
    
            /*
            从 TypedArray 中获取一个浮点数。这个浮点数的键是 R.styleable.CircleProgressBar_value,
            如果在这个 TypedArray 中找不到这个键,那么就会返回默认值 Constant.DEFAULT_VALUE。
            getFloat 方法将把这个键对应的值转换为浮点数,并且把这个浮点数赋值给 mValue
             */
            mValue = typedArray.getFloat(R.styleable.CircleProgressBar_value, Constant.DEFAULT_VALUE);
            mMaxValue = typedArray.getFloat(R.styleable.CircleProgressBar_maxValue, Constant.DEFAULT_MAX_VALUE);
            //内容数值精度格式
            //从typedArray中获取CircleProgressBar_precision的整数值,如果CircleProgressBar_precision在typedArray中不存在,那么就会使用默认值0
            mPrecision = typedArray.getInt(R.styleable.CircleProgressBar_precision, 0);
    
            //MiscUtil.getPrecisionFormat()方法根据mPrecision的值返回一个格式化对象,用于后续的格式化操作
            mPrecisionFormat = MiscUtil.getPrecisionFormat(mPrecision);
    
            //getColor是一个方法,用于从TypedArray中获取颜色类型的属性
            mValueColor = typedArray.getColor(R.styleable.CircleProgressBar_valueColor, Color.BLACK);
    
            //getDimension是一个方法,用于从TypedArray中获取尺寸类型的属性
            mValueSize = typedArray.getDimension(R.styleable.CircleProgressBar_valueSize, Constant.DEFAULT_VALUE_SIZE);
    
            //getString是一个方法,用于从TypedArray中获取字符串类型的属性
            mUnit = typedArray.getString(R.styleable.CircleProgressBar_unit);
    
            //getColor是一个方法,用于从TypedArray中获取颜色类型的属性
            mUnitColor = typedArray.getColor(R.styleable.CircleProgressBar_unitColor, Color.BLACK);
    
            getDimension是一个方法,用于从TypedArray中获取尺寸类型的属性
            mUnitSize = typedArray.getDimension(R.styleable.CircleProgressBar_unitSize, Constant.DEFAULT_UNIT_SIZE);
    
            mArcWidth = typedArray.getDimension(R.styleable.CircleProgressBar_arcWidth, Constant.DEFAULT_ARC_WIDTH);
            mStartAngle = typedArray.getFloat(R.styleable.CircleProgressBar_startAngle, Constant.DEFAULT_START_ANGLE);
            mSweepAngle = typedArray.getFloat(R.styleable.CircleProgressBar_sweepAngle, Constant.DEFAULT_SWEEP_ANGLE);
    
            mBgArcColor = typedArray.getColor(R.styleable.CircleProgressBar_bgArcColor, Color.WHITE);
            mBgArcWidth = typedArray.getDimension(R.styleable.CircleProgressBar_bgArcWidth, Constant.DEFAULT_ARC_WIDTH);
            mTextOffsetPercentInRadius = typedArray.getFloat(R.styleable.CircleProgressBar_textOffsetPercentInRadius, 0.33f);
    
            //mPercent = typedArray.getFloat(R.styleable.CircleProgressBar_percent, 0);
            mAnimTime = typedArray.getInt(R.styleable.CircleProgressBar_animTime, Constant.DEFAULT_ANIM_TIME);
    
            //获取一个颜色数组的ID
            int gradientArcColors = typedArray.getResourceId(R.styleable.CircleProgressBar_arcColors, 0);
            if (gradientArcColors != 0) {
                try {
                    int[] gradientColors = getResources().getIntArray(gradientArcColors);
                    if (gradientColors.length == 0) {//如果渐变色为数组为0,则尝试以单色读取色值
                        int color = getResources().getColor(gradientArcColors);
                        mGradientColors = new int[2];
                        mGradientColors[0] = color;
                        mGradientColors[1] = color;
                    } else if (gradientColors.length == 1) {//如果渐变数组只有一种颜色,默认设为两种相同颜色
                        mGradientColors = new int[2];
                        mGradientColors[0] = gradientColors[0];
                        mGradientColors[1] = gradientColors[0];
                    } else {
                        mGradientColors = gradientColors;
                    }
                } catch (Resources.NotFoundException e) {
                    throw new Resources.NotFoundException("the give resource not found.");
                }
            }
    
            typedArray.recycle();
        }
    
        private void initPaint() {
            mHintPaint = new TextPaint();
            // 设置抗锯齿,会消耗较大资源,绘制图形速度会变慢。
            mHintPaint.setAntiAlias(antiAlias);
            // 设置绘制文字大小
            mHintPaint.setTextSize(mHintSize);
            // 设置画笔颜色
            mHintPaint.setColor(mHintColor);
            // 从中间向两边绘制,不需要再次计算文字
            mHintPaint.setTextAlign(Paint.Align.CENTER);
    
            mValuePaint = new TextPaint();
            mValuePaint.setAntiAlias(antiAlias);
            mValuePaint.setTextSize(mValueSize);
            mValuePaint.setColor(mValueColor);
            // 设置Typeface对象,即字体风格,包括粗体,斜体以及衬线体,非衬线体等
            //Typeface 是一个表示字体类型的类
            mValuePaint.setTypeface(Typeface.DEFAULT_BOLD);
            //setTextAlign() 方法并传入 Paint.Align.CENTER,可以将 mValuePaint 的文本对齐方式设置为居中对齐
            mValuePaint.setTextAlign(Paint.Align.CENTER);
    
            mUnitPaint = new TextPaint();
            mUnitPaint.setAntiAlias(antiAlias);
            mUnitPaint.setTextSize(mUnitSize);
            mUnitPaint.setColor(mUnitColor);
            mUnitPaint.setTextAlign(Paint.Align.CENTER);
    
            mArcPaint = new Paint();
            mArcPaint.setAntiAlias(antiAlias);
    
            // 设置画笔的样式,为FILL,FILL_OR_STROKE,或STROKE
            mArcPaint.setStyle(Paint.Style.STROKE);
    
            // 设置画笔粗细
            mArcPaint.setStrokeWidth(mArcWidth);
    
            // 当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式,如圆形样式
            // Cap.ROUND,或方形样式 Cap.SQUARE
            mArcPaint.setStrokeCap(Paint.Cap.ROUND);
    
            mBgArcPaint = new Paint();
            mBgArcPaint.setAntiAlias(antiAlias);
            mBgArcPaint.setColor(mBgArcColor);
            mBgArcPaint.setStyle(Paint.Style.STROKE);
            mBgArcPaint.setStrokeWidth(mBgArcWidth);
            mBgArcPaint.setStrokeCap(Paint.Cap.ROUND);
        }
    
        /*
        调用了 MiscUtil.measure 方法来计算宽度和高度,然后使用 setMeasuredDimension 方法设置 View 的大小。
        setMeasuredDimension 的参数是测量后的宽度和高度。
         */
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            setMeasuredDimension(MiscUtil.measure(widthMeasureSpec, mDefaultSize),
                    MiscUtil.measure(heightMeasureSpec, mDefaultSize));
        }
    
        //尺寸改变
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            Log.d(TAG, "onSizeChanged: w = " + w + "; h = " + h + "; oldw = " + oldw + "; oldh = " + oldh);
            //求圆弧和背景圆弧的最大宽度
            float maxArcWidth = Math.max(mArcWidth, mBgArcWidth);
            //求最小值作为实际值
            int minSize = Math.min(w - getPaddingLeft() - getPaddingRight() - 2 * (int) maxArcWidth,
                    h - getPaddingTop() - getPaddingBottom() - 2 * (int) maxArcWidth);
            //减去圆弧的宽度,否则会造成部分圆弧绘制在外围
            mRadius = minSize / 2;
            //获取圆的相关参数
            mCenterPoint.x = w / 2;
            mCenterPoint.y = h / 2;
            //绘制圆弧的边界
            mRectF.left = mCenterPoint.x - mRadius - maxArcWidth / 2;
            mRectF.top = mCenterPoint.y - mRadius - maxArcWidth / 2;
            mRectF.right = mCenterPoint.x + mRadius + maxArcWidth / 2;
            mRectF.bottom = mCenterPoint.y + mRadius + maxArcWidth / 2;
            //计算文字绘制时的 baseline
            //由于文字的baseline、descent、ascent等属性只与textSize和typeface有关,所以此时可以直接计算
            //若value、hint、unit由同一个画笔绘制或者需要动态设置文字的大小,则需要在每次更新后再次计算
            mValueOffset = mCenterPoint.y + getBaselineOffsetFromY(mValuePaint);
            mHintOffset = mCenterPoint.y - mRadius * mTextOffsetPercentInRadius + getBaselineOffsetFromY(mHintPaint);
            mUnitOffset = mCenterPoint.y + mRadius * mTextOffsetPercentInRadius + getBaselineOffsetFromY(mUnitPaint);
            updateArcPaint();
            Log.d(TAG, "onSizeChanged: 控件大小 = " + "(" + w + ", " + h + ")"
                    + "圆心坐标 = " + mCenterPoint.toString()
                    + ";圆半径 = " + mRadius
                    + ";圆的外接矩形 = " + mRectF.toString());
        }
    
        private float getBaselineOffsetFromY(Paint paint) {
            return MiscUtil.measureTextHeight(paint) / 2;
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            drawText(canvas);
            drawArc(canvas);
        }
    
    
         // 绘制内容文字
        private void drawText(Canvas canvas) {
            // 计算文字宽度,由于Paint已设置为居中绘制,故此处不需要重新计算
            // float textWidth = mValuePaint.measureText(mValue.toString());
            // float x = mCenterPoint.x - textWidth / 2;
            canvas.drawText(String.format(mPrecisionFormat, mValue), mCenterPoint.x, mValueOffset, mValuePaint);
    
            if (mHint != null) {
                canvas.drawText(mHint.toString(), mCenterPoint.x, mHintOffset, mHintPaint);
            }
    
            if (mUnit != null) {
                canvas.drawText(mUnit.toString(), mCenterPoint.x, mUnitOffset, mUnitPaint);
            }
        }
    
        private void drawArc(Canvas canvas) {
            // 绘制背景圆弧
            // 从进度圆弧结束的地方开始重新绘制,优化性能
            //save保存当前的绘图状态
            canvas.save();
            float currentAngle = mSweepAngle * mPercent;
            //在画布(Canvas)上应用旋转操作。旋转的中心点是(mCenterPoint.x, mCenterPoint.y),旋转的角度是mStartAngle
            canvas.rotate(mStartAngle, mCenterPoint.x, mCenterPoint.y);
    
            //在画布上绘制一个弧形。
            //弧形的边界是由mRectF定义的,起始角度是currentAngle,扫过的角度是mSweepAngle - currentAngle + 2。
            //这个弧形不会闭合,因为参数中的false表示不闭合
            canvas.drawArc(mRectF, currentAngle, mSweepAngle - currentAngle + 2, false, mBgArcPaint);
    
            // 第一个参数 oval 为 RectF 类型,即圆弧显示区域
            // startAngle 和 sweepAngle  均为 float 类型,分别表示圆弧起始角度和圆弧度数
            // 3点钟方向为0度,顺时针递增
            // 如果 startAngle < 0 或者 > 360,则相当于 startAngle % 360
            // useCenter:如果为True时,在绘制圆弧时将圆心包括在内,通常用来绘制扇形
            canvas.drawArc(mRectF, 2, currentAngle, false, mArcPaint);
    
            //恢复到之前保存的图形状态时
            canvas.restore();
        }
    
         //更新圆弧画笔
        private void updateArcPaint() {
            // 设置渐变
            //SweepGradient是Android中的一种Shader(着色器),它创建的渐变效果是以一个指定的中心点进行的扫描渐变
            mSweepGradient = new SweepGradient(mCenterPoint.x, mCenterPoint.y, mGradientColors, null);
    
            //设置Shader,给圆弧添加颜色渐变效果
            mArcPaint.setShader(mSweepGradient);
        }
    
        public boolean isAntiAlias() {
            return antiAlias;
        }
    
        public CharSequence getHint() {
            return mHint;
        }
    
        public void setHint(CharSequence hint) {
            mHint = hint;
        }
    
        public CharSequence getUnit() {
            return mUnit;
        }
    
        public void setUnit(CharSequence unit) {
            mUnit = unit;
        }
    
        public float getValue() {
            return mValue;
        }
    
    
         // 设置当前值
        public void setValue(float value) {
            if (value > mMaxValue) {
                value = mMaxValue;
            }
            float start = mPercent;
            float end = value / mMaxValue;
            startAnimator(start, end, mAnimTime);
        }
    
        private void startAnimator(float start, float end, long animTime) {
            //ValueAnimator对象用于动画的创建,ValueAnimator.ofFloat(start, end)表示这个动画将会在start和end之间进行变化
            mAnimator = ValueAnimator.ofFloat(start, end);
    
            //设置动画的持续时间
            mAnimator.setDuration(animTime);
    
            //addUpdateListener是添加一个监听器,监听动画的更新
            mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
    
                    //animation.getAnimatedValue(): 这是调用动画对象的getAnimatedValue()方法。这个方法通常返回动画的当前值
                    mPercent = (float) animation.getAnimatedValue();
                    mValue = mPercent * mMaxValue;
    
                    //检查是否处于调试模式
                    if (BuildConfig.DEBUG) {
                        Log.d(TAG, "onAnimationUpdate: percent = " + mPercent
                                + ";currentAngle = " + (mSweepAngle * mPercent)
                                + ";value = " + mValue);
                    }
                    //告诉Android系统这个视图需要被重新绘制
                    invalidate();
                }
            });
    
            //启动了 mAnimator
            mAnimator.start();
        }
    
       // 获取最大值
    
        public float getMaxValue() {
            return mMaxValue;
        }
    
        //设置最大值
        public void setMaxValue(float maxValue) {
            mMaxValue = maxValue;
        }
    
        // 获取精度
        public int getPrecision() {
            return mPrecision;
        }
    
        public void setPrecision(int precision) {
            mPrecision = precision;
            mPrecisionFormat = MiscUtil.getPrecisionFormat(precision);
        }
    
        public int[] getGradientColors() {
            return mGradientColors;
        }
    
        // 设置渐变
        public void setGradientColors(int[] gradientColors) {
            mGradientColors = gradientColors;
            updateArcPaint();
        }
    
        public long getAnimTime() {
            return mAnimTime;
        }
    
        public void setAnimTime(long animTime) {
            mAnimTime = animTime;
        }
    
    
         // 重置
    
        public void reset() {
            startAnimator(mPercent, 0.0f, 1000L);
        }
    
        @Override
        protected void onDetachedFromWindow() {
            super.onDetachedFromWindow();
            //释放资源
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421
    • 422
    • 423
    • 424
    • 425
    • 426
    • 427
    • 428
    • 429
    • 430
    • 431
    • 432
    • 433
    • 434
    • 435
    • 436
    • 437
    • 438
    • 439
    • 440
    • 441
    • 442
    • 443
    • 444
    • 445
    • 446
    • 447
    • 448
    • 449
    • 450
    • 451
    • 452
    • 453
    • 454
    • 455
    • 456
    • 457
    • 458
    • 459
    • 460
    • 461
    • 462

    Constant

    
    public class Constant {
    
        public static final boolean ANTI_ALIAS = true;
    
        public static final int DEFAULT_SIZE = 150;
        public static final int DEFAULT_START_ANGLE = 270;
        public static final int DEFAULT_SWEEP_ANGLE = 360;
    
        public static final int DEFAULT_ANIM_TIME = 1000;
    
        public static final int DEFAULT_MAX_VALUE = 100;
        public static final int DEFAULT_VALUE = 50;
    
        public static final int DEFAULT_HINT_SIZE = 15;
        public static final int DEFAULT_UNIT_SIZE = 30;
        public static final int DEFAULT_VALUE_SIZE = 15;
    
        public static final int DEFAULT_ARC_WIDTH = 15;
    
        public static final int DEFAULT_WAVE_HEIGHT = 40;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    DialProgress 带有刻度的圆形进度条

    import android.animation.ValueAnimator;
    import android.content.Context;
    import android.content.res.Resources;
    import android.content.res.TypedArray;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Point;
    import android.graphics.RectF;
    import android.graphics.SweepGradient;
    import android.graphics.Typeface;
    import android.text.TextPaint;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.View;
    
     // 带有刻度的圆形进度条
    public class DialProgress extends View {
    
        private static final String TAG = DialProgress.class.getSimpleName();
        private Context mContext;
    
        //圆心坐标
        private Point mCenterPoint;
        private float mRadius;
        private float mTextOffsetPercentInRadius;
    
        private boolean antiAlias;
        //绘制提示
        private TextPaint mHintPaint;
        private CharSequence mHint;
        private int mHintColor;
        private float mHintSize;
        private float mHintOffset;
    
        //绘制数值
        private Paint mValuePaint;
        private int mValueColor;
        private float mMaxValue;
        private float mValue;
        private float mValueSize;
        private float mValueOffset;
        private String mPrecisionFormat;
    
        //绘制单位
        private Paint mUnitPaint;
        private float mUnitSize;
        private int mUnitColor;
        private float mUnitOffset;
        private CharSequence mUnit;
        //前景圆弧
        private Paint mArcPaint;
        private float mArcWidth;
        private int mDialIntervalDegree;
        private float mStartAngle, mSweepAngle;
        private RectF mRectF;
        //渐变
        private int[] mGradientColors = {Color.GREEN, Color.YELLOW, Color.RED};
        //当前进度,[0.0f,1.0f]
        private float mPercent;
        //动画时间
        private long mAnimTime;
        //属性动画
        private ValueAnimator mAnimator;
    
        //背景圆弧
        private Paint mBgArcPaint;
        private int mBgArcColor;
    
        //刻度线颜色
        private Paint mDialPaint;
        private float mDialWidth;
        private int mDialColor;
    
        private int mDefaultSize;
    
        public DialProgress(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(context, attrs);
        }
    
        private void init(Context context, AttributeSet attrs) {
            mContext = context;
            //将常量 Constant.DEFAULT_SIZE 的值从设备独立像素(dp)转换为屏幕的实际像素(px),然后将结果赋值给 mDefaultSize
            mDefaultSize = MiscUtil.dipToPx(context, Constant.DEFAULT_SIZE);
            mRectF = new RectF();
            mCenterPoint = new Point();
            initConfig(context, attrs);
            initPaint();
            setValue(mValue);
        }
    
        private void initConfig(Context context, AttributeSet attrs) {
            //obtainStyledAttributes是一个用于从给定的attrs和指定的styleable资源ID数组中获取属性的方法
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DialProgress);
            //getBoolean是一个方法,用于从TypedArray中获取布尔类型的属性
            antiAlias = typedArray.getBoolean(R.styleable.DialProgress_antiAlias, true);
            mMaxValue = typedArray.getFloat(R.styleable.DialProgress_maxValue, Constant.DEFAULT_MAX_VALUE);
            mValue = typedArray.getFloat(R.styleable.DialProgress_value, Constant.DEFAULT_VALUE);
            mValueSize = typedArray.getDimension(R.styleable.DialProgress_valueSize, Constant.DEFAULT_VALUE_SIZE);
            mValueColor = typedArray.getColor(R.styleable.DialProgress_valueColor, Color.BLACK);
            mDialIntervalDegree = typedArray.getInt(R.styleable.DialProgress_dialIntervalDegree, 10);
            int precision = typedArray.getInt(R.styleable.DialProgress_precision, 0);
            mPrecisionFormat = MiscUtil.getPrecisionFormat(precision);
    
            mUnit = typedArray.getString(R.styleable.DialProgress_unit);
            mUnitColor = typedArray.getColor(R.styleable.DialProgress_unitColor, Color.BLACK);
            mUnitSize = typedArray.getDimension(R.styleable.DialProgress_unitSize, Constant.DEFAULT_UNIT_SIZE);
    
            //getString是一个方法,用于从TypedArray中获取字符串类型的属性
            mHint = typedArray.getString(R.styleable.DialProgress_hint);
    
            //getColor是一个方法,用于从TypedArray中获取颜色类型的属性
            mHintColor = typedArray.getColor(R.styleable.DialProgress_hintColor, Color.BLACK);
            mHintSize = typedArray.getDimension(R.styleable.DialProgress_hintSize, Constant.DEFAULT_HINT_SIZE);
    
            //getDimension是一个方法,用于从TypedArray中获取尺寸类型的属性
            mArcWidth = typedArray.getDimension(R.styleable.DialProgress_arcWidth, Constant.DEFAULT_ARC_WIDTH);
    
            mStartAngle = typedArray.getFloat(R.styleable.DialProgress_startAngle, Constant.DEFAULT_START_ANGLE);
            mSweepAngle = typedArray.getFloat(R.styleable.DialProgress_sweepAngle, Constant.DEFAULT_SWEEP_ANGLE);
    
            mAnimTime = typedArray.getInt(R.styleable.DialProgress_animTime, Constant.DEFAULT_ANIM_TIME);
    
            mBgArcColor = typedArray.getColor(R.styleable.DialProgress_bgArcColor, Color.GRAY);
            mDialWidth = typedArray.getDimension(R.styleable.DialProgress_dialWidth, 2);
            mDialColor = typedArray.getColor(R.styleable.DialProgress_dialColor, Color.WHITE);
    
            mTextOffsetPercentInRadius = typedArray.getFloat(R.styleable.DialProgress_textOffsetPercentInRadius, 0.33f);
    
            //调用TypedArray的 getResourceId 方法。这个方法接受两个参数:一个是资源名,另一个是默认值
            int gradientArcColors = typedArray.getResourceId(R.styleable.DialProgress_arcColors, 0);
            if (gradientArcColors != 0) {
                try {
                    //getResources获取资源
                    int[] gradientColors = getResources().getIntArray(gradientArcColors);
                    if (gradientColors.length == 0) {
                        int color = getResources().getColor(gradientArcColors);
                        mGradientColors = new int[2];
                        mGradientColors[0] = color;
                        mGradientColors[1] = color;
                    } else if (gradientColors.length == 1) {
                        mGradientColors = new int[2];
                        mGradientColors[0] = gradientColors[0];
                        mGradientColors[1] = gradientColors[0];
                    } else {
                        mGradientColors = gradientColors;
                    }
                } catch (Resources.NotFoundException e) {
                    throw new Resources.NotFoundException("the give resource not found.");
                }
            }
            typedArray.recycle();
        }
    
        private void initPaint() {
            mHintPaint = new TextPaint();
            // 设置抗锯齿,会消耗较大资源,绘制图形速度会变慢。
            mHintPaint.setAntiAlias(antiAlias);
            // 设置绘制文字大小
            mHintPaint.setTextSize(mHintSize);
            // 设置画笔颜色
            mHintPaint.setColor(mHintColor);
            // 从中间向两边绘制,不需要再次计算文字
            mHintPaint.setTextAlign(Paint.Align.CENTER);
    
            mValuePaint = new Paint();
            mValuePaint.setAntiAlias(antiAlias);
            mValuePaint.setTextSize(mValueSize);
            mValuePaint.setColor(mValueColor);
            //设置字体类型,粗体斜体等
            mValuePaint.setTypeface(Typeface.DEFAULT_BOLD);
            mValuePaint.setTextAlign(Paint.Align.CENTER);
    
            mUnitPaint = new Paint();
            mUnitPaint.setAntiAlias(antiAlias);
            mUnitPaint.setTextSize(mUnitSize);
            mUnitPaint.setColor(mUnitColor);
            mUnitPaint.setTextAlign(Paint.Align.CENTER);
    
            mArcPaint = new Paint();
            mArcPaint.setAntiAlias(antiAlias);
            mArcPaint.setStyle(Paint.Style.STROKE);
            mArcPaint.setStrokeWidth(mArcWidth);
            mArcPaint.setStrokeCap(Paint.Cap.BUTT);
    
            mBgArcPaint = new Paint();
            mBgArcPaint.setAntiAlias(antiAlias);
            //设置画笔的样式,为FILL,FILL_OR_STROKE,或STROKE
            mBgArcPaint.setStyle(Paint.Style.STROKE);
            //设置画笔粗细
            mBgArcPaint.setStrokeWidth(mArcWidth);
            // 当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式,如圆形样式
            mBgArcPaint.setStrokeCap(Paint.Cap.BUTT);
            mBgArcPaint.setColor(mBgArcColor);
    
            mDialPaint = new Paint();
            // 设置抗锯齿
            mDialPaint.setAntiAlias(antiAlias);
            mDialPaint.setColor(mDialColor);
            //设置画笔粗细
            mDialPaint.setStrokeWidth(mDialWidth);
        }
    
        // 更新圆弧画笔
        private void updateArcPaint() {
            // 设置渐变
            // 渐变的颜色是360度,如果只显示270,那么则会缺失部分颜色
            SweepGradient sweepGradient = new SweepGradient(mCenterPoint.x, mCenterPoint.y, mGradientColors, null);
            mArcPaint.setShader(sweepGradient);
        }
    
        /*
           调用了 MiscUtil.measure 方法来计算宽度和高度,然后使用 setMeasuredDimension 方法设置 View 的大小。
           setMeasuredDimension 的参数是测量后的宽度和高度。
        */
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            setMeasuredDimension(MiscUtil.measure(widthMeasureSpec, mDefaultSize),
                    MiscUtil.measure(heightMeasureSpec, mDefaultSize));
        }
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            Log.d(TAG, "onSizeChanged: w = " + w + "; h = " + h + "; oldw = " + oldw + "; oldh = " + oldh);
            //求最小值作为实际值
            int minSize = Math.min(getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - 2 * (int) mArcWidth,
                    getMeasuredHeight() - getPaddingTop() - getPaddingBottom() - 2 * (int) mArcWidth);
            mRadius = minSize / 2;
            mCenterPoint.x = getMeasuredWidth() / 2;
            mCenterPoint.y = getMeasuredHeight() / 2;
            //绘制圆弧的边界
            mRectF.left = mCenterPoint.x - mRadius - mArcWidth / 2;
            mRectF.top = mCenterPoint.y - mRadius - mArcWidth / 2;
            mRectF.right = mCenterPoint.x + mRadius + mArcWidth / 2;
            mRectF.bottom = mCenterPoint.y + mRadius + mArcWidth / 2;
    
            mValueOffset = mCenterPoint.y + getBaselineOffsetFromY(mValuePaint);
            mHintOffset = mCenterPoint.y - mRadius * mTextOffsetPercentInRadius + getBaselineOffsetFromY(mHintPaint);
            mUnitOffset = mCenterPoint.y + mRadius * mTextOffsetPercentInRadius + getBaselineOffsetFromY(mUnitPaint);
    
            updateArcPaint();
            Log.d(TAG, "onMeasure: 控件大小 = " + "(" + getMeasuredWidth() + ", " + getMeasuredHeight() + ")"
                    + ";圆心坐标 = " + mCenterPoint.toString()
                    + ";圆半径 = " + mRadius
                    + ";圆的外接矩形 = " + mRectF.toString());
        }
    
        private float getBaselineOffsetFromY(Paint paint) {
            return MiscUtil.measureTextHeight(paint) / 2;
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            drawArc(canvas);
            drawDial(canvas);
            drawText(canvas);
        }
    
        private void drawArc(Canvas canvas) {
            // 绘制背景圆弧
            // 从进度圆弧结束的地方开始重新绘制,优化性能
            float currentAngle = mSweepAngle * mPercent;
            //save保存当前的绘图状态
            canvas.save();
            //在画布(Canvas)上应用旋转操作。旋转的中心点是(mCenterPoint.x, mCenterPoint.y),旋转的角度是270
            canvas.rotate(mStartAngle, mCenterPoint.x, mCenterPoint.y);
            canvas.drawArc(mRectF, currentAngle, mSweepAngle - currentAngle, false, mBgArcPaint);
            // 第一个参数 oval 为 RectF 类型,即圆弧显示区域
            // startAngle 和 sweepAngle  均为 float 类型,分别表示圆弧起始角度和圆弧度数
            // 3点钟方向为0度,顺时针递增
            // 如果 startAngle < 0 或者 > 360,则相当于 startAngle % 360
            // useCenter:如果为True时,在绘制圆弧时将圆心包括在内,通常用来绘制扇形
            canvas.drawArc(mRectF, 0, currentAngle, false, mArcPaint);
            //恢复到之前保存的图形状态时
            canvas.restore();
        }
    
        private void drawDial(Canvas canvas) {
            int total = (int) (mSweepAngle / mDialIntervalDegree);
            //save保存当前的绘图状态
            canvas.save();
            //在画布(Canvas)上应用旋转操作。旋转的中心点是(mCenterPoint.x, mCenterPoint.y),旋转的角度是mStartAngle
            canvas.rotate(mStartAngle, mCenterPoint.x, mCenterPoint.y);
            for (int i = 0; i <= total; i++) {
                //drawLine方法则用于在画布上绘制一条线
                canvas.drawLine(mCenterPoint.x + mRadius, mCenterPoint.y, mCenterPoint.x + mRadius + mArcWidth, mCenterPoint.y, mDialPaint);
                canvas.rotate(mDialIntervalDegree, mCenterPoint.x, mCenterPoint.y);
            }
            //恢复到之前保存的图形状态时
            canvas.restore();
        }
    
        private void drawText(Canvas canvas) {
            canvas.drawText(String.format(mPrecisionFormat, mValue), mCenterPoint.x, mValueOffset, mValuePaint);
    
            if (mUnit != null) {
                canvas.drawText(mUnit.toString(), mCenterPoint.x, mUnitOffset, mUnitPaint);
            }
    
            if (mHint != null) {
                canvas.drawText(mHint.toString(), mCenterPoint.x, mHintOffset, mHintPaint);
            }
        }
    
        public float getMaxValue() {
            return mMaxValue;
        }
    
        public void setMaxValue(float maxValue) {
            mMaxValue = maxValue;
        }
    
         // 设置当前值
        public void setValue(float value) {
            if (value > mMaxValue) {
                value = mMaxValue;
            }
            float start = mPercent;
            float end = value / mMaxValue;
            startAnimator(start, end, mAnimTime);
        }
    
        private void startAnimator(float start, float end, long animTime) {
            //ValueAnimator对象用于动画的创建,ValueAnimator.ofFloat(start, end)表示这个动画将会在start和end之间进行变化
            mAnimator = ValueAnimator.ofFloat(start, end);
    
            //设置动画的持续时间
            mAnimator.setDuration(animTime);
    
            //addUpdateListener是添加一个监听器,监听动画的更新
            mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    //animation.getAnimatedValue(): 这是调用动画对象的getAnimatedValue()方法。这个方法通常返回动画的当前值
                    mPercent = (float) animation.getAnimatedValue();
                    mValue = mPercent * mMaxValue;
                    //检查是否处于调试模式
                    if (BuildConfig.DEBUG) {
                        Log.d(TAG, "onAnimationUpdate: percent = " + mPercent
                                + ";currentAngle = " + (mSweepAngle * mPercent)
                                + ";value = " + mValue);
                    }
                    //告诉Android系统这个视图需要被重新绘制
                    invalidate();
                }
            });
            //启动了 mAnimator
            mAnimator.start();
        }
    
        public int[] getGradientColors() {
            return mGradientColors;
        }
    
        public void setGradientColors(int[] gradientColors) {
            mGradientColors = gradientColors;
            updateArcPaint();
        }
    
        public void reset() {
            startAnimator(mPercent, 0.0f, 1000L);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367

    MainActivity

    import android.annotation.SuppressLint;
    import android.graphics.Color;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    import androidx.appcompat.app.AppCompatActivity;
    import java.util.Random;
    
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        private final static int[] COLORS = new int[]{Color.RED, Color.YELLOW, Color.GREEN, Color.BLUE};
    
        private Button mBtnResetAll;
        private CircleProgress mCircleProgress1, mCircleProgress2, mCircleProgress3;
        private DialProgress mDialProgress;
        private WaveProgress mWaveProgress;
        private Random mRandom;
    
        @SuppressLint("MissingInflatedId")
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            mBtnResetAll = (Button) findViewById(R.id.btn_reset_all);
            mCircleProgress1 = (CircleProgress) findViewById(R.id.circle_progress_bar1);
            mCircleProgress2 = (CircleProgress) findViewById(R.id.circle_progress_bar2);
            mCircleProgress3 = (CircleProgress) findViewById(R.id.circle_progress_bar3);
            mDialProgress = (DialProgress) findViewById(R.id.dial_progress_bar);
            mWaveProgress = (WaveProgress) findViewById(R.id.wave_progress_bar);
    
            mBtnResetAll.setOnClickListener(this);
            mCircleProgress1.setOnClickListener(this);
            mCircleProgress2.setOnClickListener(this);
            mCircleProgress3.setOnClickListener(this);
            mDialProgress.setOnClickListener(this);
            mWaveProgress.setOnClickListener(this);
    
            mRandom = new Random();
        }
    
        @Override
        public void onClick(View v) {
            if  (v.getId() == R.id.btn_reset_all) {
    
                mCircleProgress1.reset();
                mCircleProgress2.reset();
                mCircleProgress3.reset();
                mDialProgress.reset();
                mWaveProgress.reset();
            }
                else if (v.getId() == R.id.circle_progress_bar1) {
                mCircleProgress1.setValue(mRandom.nextInt((int) mCircleProgress1.getMaxValue()));
            }
            else if (v.getId() == R.id.circle_progress_bar2) {
                mCircleProgress2.setValue(mRandom.nextFloat() * mCircleProgress2.getMaxValue());
            }
            else if (v.getId() == R.id.circle_progress_bar3) {
                //在代码中动态改变渐变色,可能会导致颜色跳跃
                mCircleProgress3.setGradientColors(COLORS);
                mCircleProgress3.setValue(mRandom.nextFloat() * mCircleProgress3.getMaxValue());
            }
    
            else if (v.getId() == R.id.dial_progress_bar) {
                mDialProgress.setValue(mRandom.nextFloat() * mDialProgress.getMaxValue());
            }
            else if (v.getId() == R.id.wave_progress_bar){
                    mWaveProgress.setValue(mRandom.nextFloat() * mWaveProgress.getMaxValue());
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    WaveProgress 圆形水波进度条

    import android.animation.Animator;
    import android.animation.ValueAnimator;
    import android.annotation.TargetApi;
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Path;
    import android.graphics.Point;
    import android.graphics.RectF;
    import android.os.Build;
    import android.text.TextPaint;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.View;
    import android.view.animation.LinearInterpolator;
    
    
     // 水波进度条
    public class WaveProgress extends View {
    
        private static final String TAG = WaveProgress.class.getSimpleName();
    
        //浅色波浪方向
        private static final int L2R = 0;
        private static final int R2L = 1;
    
        private int mDefaultSize;
        //圆心
        private Point mCenterPoint;
        //半径
        private float mRadius;
        //圆的外接矩形
        private RectF mRectF;
        //深色波浪移动距离
        private float mDarkWaveOffset;
        //浅色波浪移动距离
        private float mLightWaveOffset;
        //浅色波浪方向
        private boolean isR2L;
        //是否锁定波浪不随进度移动
        private boolean lockWave;
    
        //是否开启抗锯齿
        private boolean antiAlias;
        //最大值
        private float mMaxValue;
        //当前值
        private float mValue;
        //当前进度
        private float mPercent;
    
        //绘制提示
        private TextPaint mHintPaint;
        private CharSequence mHint;
        private int mHintColor;
        private float mHintSize;
    
        private Paint mPercentPaint;
        private float mValueSize;
        private int mValueColor;
    
        //圆环宽度
        private float mCircleWidth;
        //圆环
        private Paint mCirclePaint;
        //圆环颜色
        private int mCircleColor;
        //背景圆环颜色
        private int mBgCircleColor;
    
        //水波路径
        private Path mWaveLimitPath;
        private Path mWavePath;
        //水波高度
        private float mWaveHeight;
        //水波数量
        private int mWaveNum;
        //深色水波
        private Paint mWavePaint;
        //深色水波颜色
        private int mDarkWaveColor;
        //浅色水波颜色
        private int mLightWaveColor;
    
        //深色水波贝塞尔曲线上的起始点、控制点
        private Point[] mDarkPoints;
        //浅色水波贝塞尔曲线上的起始点、控制点
        private Point[] mLightPoints;
    
        //贝塞尔曲线点的总个数
        private int mAllPointCount;
        private int mHalfPointCount;
    
        private ValueAnimator mProgressAnimator;
        private long mDarkWaveAnimTime;
        private ValueAnimator mDarkWaveAnimator;
        private long mLightWaveAnimTime;
        private ValueAnimator mLightWaveAnimator;
    
        public WaveProgress(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(context, attrs);
        }
    
        private void init(Context context, AttributeSet attrs) {
            //将常量 Constant.DEFAULT_SIZE 的值从设备独立像素(dp)转换为屏幕的实际像素(px),然后将结果赋值给 mDefaultSize
            mDefaultSize = MiscUtil.dipToPx(context, Constant.DEFAULT_SIZE);
            mRectF = new RectF();
            mCenterPoint = new Point();
    
            initAttrs(context, attrs);
            initPaint();
            initPath();
        }
    
        private void initAttrs(Context context, AttributeSet attrs) {
            //obtainStyledAttributes是一个用于从给定的attrs和指定的styleable资源ID数组中获取属性的方法
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.WaveProgress);
            //getBoolean是一个方法,用于从TypedArray中获取布尔类型的属性
            antiAlias = typedArray.getBoolean(R.styleable.WaveProgress_antiAlias, true);
            mDarkWaveAnimTime = typedArray.getInt(R.styleable.WaveProgress_darkWaveAnimTime, Constant.DEFAULT_ANIM_TIME);
            mLightWaveAnimTime = typedArray.getInt(R.styleable.WaveProgress_lightWaveAnimTime, Constant.DEFAULT_ANIM_TIME);
            mMaxValue = typedArray.getFloat(R.styleable.WaveProgress_maxValue, Constant.DEFAULT_MAX_VALUE);
            mValue = typedArray.getFloat(R.styleable.WaveProgress_value, Constant.DEFAULT_VALUE);
    
            //getDimension是一个方法,用于从TypedArray中获取尺寸类型的属性
            mValueSize = typedArray.getDimension(R.styleable.WaveProgress_valueSize, Constant.DEFAULT_VALUE_SIZE);
            //getColor是一个方法,用于从TypedArray中获取颜色类型的属性
            mValueColor = typedArray.getColor(R.styleable.WaveProgress_valueColor, Color.BLACK);
            //getString是一个方法,用于从TypedArray中获取字符串类型的属性
            mHint = typedArray.getString(R.styleable.WaveProgress_hint);
            mHintColor = typedArray.getColor(R.styleable.WaveProgress_hintColor, Color.BLACK);
            mHintSize = typedArray.getDimension(R.styleable.WaveProgress_hintSize, Constant.DEFAULT_HINT_SIZE);
    
            mCircleWidth = typedArray.getDimension(R.styleable.WaveProgress_circleWidth, Constant.DEFAULT_ARC_WIDTH);
            mCircleColor = typedArray.getColor(R.styleable.WaveProgress_circleColor, Color.GREEN);
            mBgCircleColor = typedArray.getColor(R.styleable.WaveProgress_bgCircleColor, Color.WHITE);
    
            mWaveHeight = typedArray.getDimension(R.styleable.WaveProgress_waveHeight, Constant.DEFAULT_WAVE_HEIGHT);
            mWaveNum = typedArray.getInt(R.styleable.WaveProgress_waveNum, 1);
            //getResources获取资源
            mDarkWaveColor = typedArray.getColor(R.styleable.WaveProgress_darkWaveColor,
                    getResources().getColor(android.R.color.holo_blue_dark));
            mLightWaveColor = typedArray.getColor(R.styleable.WaveProgress_lightWaveColor,
                    getResources().getColor(android.R.color.holo_green_light));
    
            isR2L = typedArray.getInt(R.styleable.WaveProgress_lightWaveDirect, R2L) == R2L;
            lockWave = typedArray.getBoolean(R.styleable.WaveProgress_lockWave, false);
    
            typedArray.recycle();
        }
    
        private void initPaint() {
            mHintPaint = new TextPaint();
            // 设置抗锯齿,会消耗较大资源,绘制图形速度会变慢。
            mHintPaint.setAntiAlias(antiAlias);
            // 设置绘制文字大小
            mHintPaint.setTextSize(mHintSize);
            // 设置画笔颜色
            mHintPaint.setColor(mHintColor);
            //setTextAlign() 方法并传入 Paint.Align.CENTER,可以将 mValuePaint 的文本对齐方式设置为居中对齐
            mHintPaint.setTextAlign(Paint.Align.CENTER);
    
            mCirclePaint = new Paint();
            // 设置抗锯齿
            mCirclePaint.setAntiAlias(antiAlias);
            //设置画笔粗细
            mCirclePaint.setStrokeWidth(mCircleWidth);
            //设置画笔的样式,为FILL,FILL_OR_STROKE,或STROKE
            mCirclePaint.setStyle(Paint.Style.STROKE);
            // 当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式,如圆形样式
            mCirclePaint.setStrokeCap(Paint.Cap.ROUND);
    
            mWavePaint = new Paint();
            mWavePaint.setAntiAlias(antiAlias);
            mWavePaint.setStyle(Paint.Style.FILL);
    
            mPercentPaint = new Paint();
            mPercentPaint.setTextAlign(Paint.Align.CENTER);
            mPercentPaint.setAntiAlias(antiAlias);
            mPercentPaint.setColor(mValueColor);
            mPercentPaint.setTextSize(mValueSize);
        }
    
        private void initPath() {
            mWaveLimitPath = new Path();
            mWavePath = new Path();
        }
    
        /*
        调用了 MiscUtil.measure 方法来计算宽度和高度,然后使用 setMeasuredDimension 方法设置 View 的大小。
        setMeasuredDimension 的参数是测量后的宽度和高度。
         */
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            setMeasuredDimension(MiscUtil.measure(widthMeasureSpec, mDefaultSize),
                    MiscUtil.measure(heightMeasureSpec, mDefaultSize));
        }
    
        //尺寸改变
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            Log.d(TAG, "onSizeChanged: w = " + w + "; h = " + h + "; oldw = " + oldw + "; oldh = " + oldh);
            //求最小值作为实际值
            int minSize = Math.min(getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - 2 * (int) mCircleWidth,
                    getMeasuredHeight() - getPaddingTop() - getPaddingBottom() - 2 * (int) mCircleWidth);
            mRadius = minSize / 2;
            mCenterPoint.x = getMeasuredWidth() / 2;
            mCenterPoint.y = getMeasuredHeight() / 2;
            //绘制圆弧的边界
            mRectF.left = mCenterPoint.x - mRadius - mCircleWidth / 2;
            mRectF.top = mCenterPoint.y - mRadius - mCircleWidth / 2;
            mRectF.right = mCenterPoint.x + mRadius + mCircleWidth / 2;
            mRectF.bottom = mCenterPoint.y + mRadius + mCircleWidth / 2;
            Log.d(TAG, "onSizeChanged: 控件大小 = " + "(" + getMeasuredWidth() + ", " + getMeasuredHeight() + ")"
                    + ";圆心坐标 = " + mCenterPoint.toString()
                    + ";圆半径 = " + mRadius
                    + ";圆的外接矩形 = " + mRectF.toString());
            initWavePoints();
            //开始动画
            setValue(mValue);
            startWaveAnimator();
        }
    
        private void initWavePoints() {
            //当前波浪宽度
            float waveWidth = (mRadius * 2) / mWaveNum;
            mAllPointCount = 8 * mWaveNum + 1;
            mHalfPointCount = mAllPointCount / 2;
            mDarkPoints = getPoint(false, waveWidth);
            mLightPoints = getPoint(isR2L, waveWidth);
        }
    
    
        // 从左往右或者从右往左获取贝塞尔点
        private Point[] getPoint(boolean isR2L, float waveWidth) {
            Point[] points = new Point[mAllPointCount];
            //第1个点特殊处理,即数组的中点
            points[mHalfPointCount] = new Point((int) (mCenterPoint.x + (isR2L ? mRadius : -mRadius)), mCenterPoint.y);
            //屏幕内的贝塞尔曲线点
            for (int i = mHalfPointCount + 1; i < mAllPointCount; i += 4) {
                float width = points[mHalfPointCount].x + waveWidth * (i / 4 - mWaveNum);
                points[i] = new Point((int) (waveWidth / 4 + width), (int) (mCenterPoint.y - mWaveHeight));
                points[i + 1] = new Point((int) (waveWidth / 2 + width), mCenterPoint.y);
                points[i + 2] = new Point((int) (waveWidth * 3 / 4 + width), (int) (mCenterPoint.y + mWaveHeight));
                points[i + 3] = new Point((int) (waveWidth + width), mCenterPoint.y);
            }
            //屏幕外的贝塞尔曲线点
            for (int i = 0; i < mHalfPointCount; i++) {
                int reverse = mAllPointCount - i - 1;
                points[i] = new Point((isR2L ? 2 : 1) * points[mHalfPointCount].x - points[reverse].x,
                        points[mHalfPointCount].y * 2 - points[reverse].y);
            }
            //对从右向左的贝塞尔点数组反序,方便后续处理
            return isR2L ? MiscUtil.reverse(points) : points;
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            drawCircle(canvas);
            drawLightWave(canvas);
            drawDarkWave(canvas);
            drawProgress(canvas);
        }
    
         // 绘制圆环
        private void drawCircle(Canvas canvas) {
            //save保存当前的绘图状态
            canvas.save();
            //在画布(Canvas)上应用旋转操作。旋转的中心点是(mCenterPoint.x, mCenterPoint.y),旋转的角度是270
            canvas.rotate(270, mCenterPoint.x, mCenterPoint.y);
    
            int currentAngle = (int) (360 * mPercent);
            //画背景圆环
            mCirclePaint.setColor(mBgCircleColor);
            canvas.drawArc(mRectF, currentAngle, 360 - currentAngle, false, mCirclePaint);
            //画圆环
            mCirclePaint.setColor(mCircleColor);
            canvas.drawArc(mRectF, 0, currentAngle, false, mCirclePaint);
            //恢复到之前保存的图形状态时
            canvas.restore();
        }
    
    
        // 绘制深色波浪(贝塞尔曲线)
        private void drawDarkWave(Canvas canvas) {
            mWavePaint.setColor(mDarkWaveColor);
            drawWave(canvas, mWavePaint, mDarkPoints, mDarkWaveOffset);
        }
    
    
         // 绘制浅色波浪(贝塞尔曲线)
        private void drawLightWave(Canvas canvas) {
            mWavePaint.setColor(mLightWaveColor);
            //从右向左的水波位移应该被减去
            drawWave(canvas, mWavePaint, mLightPoints, isR2L ? -mLightWaveOffset : mLightWaveOffset);
        }
    
        @TargetApi(Build.VERSION_CODES.KITKAT)
        private void drawWave(Canvas canvas, Paint paint, Point[] points, float waveOffset) {
            //reset()用于重置或清理对象状态的方法
            mWaveLimitPath.reset();
            mWavePath.reset();
            float height = lockWave ? 0 : mRadius - 2 * mRadius * mPercent;
            //moveTo和lineTo绘制出水波区域矩形
            //moveTo() 方法将路径的结束点移动到指定的位置,并以此为新的开始进行新的绘制
            mWavePath.moveTo(points[0].x + waveOffset, points[0].y + height);
    
            for (int i = 1; i < mAllPointCount; i += 2) {
                //quadTo() 方法在 mWavePath 上绘制一个二次贝塞尔曲线
                mWavePath.quadTo(points[i].x + waveOffset, points[i].y + height,
                        points[i + 1].x + waveOffset, points[i + 1].y + height);
            }
            //mWavePath.lineTo(points[mAllPointCount - 1].x, points[mAllPointCount - 1].y + height);
            //不管如何移动,波浪与圆路径的交集底部永远固定,否则会造成上移的时候底部为空的情况
            //lineTo()方法用于在当前的绘画位置开始一条新的线,然后移动到指定的坐标
            mWavePath.lineTo(points[mAllPointCount - 1].x, mCenterPoint.y + mRadius);
            mWavePath.lineTo(points[0].x, mCenterPoint.y + mRadius);
            //关闭路径,即连接路径的起点和终点
            mWavePath.close();
            //addCircle()方法用于在当前的绘画位置添加一个圆形
            mWaveLimitPath.addCircle(mCenterPoint.x, mCenterPoint.y, mRadius, Path.Direction.CW);
            //取该圆与波浪路径的交集,形成波浪在圆内的效果
            mWaveLimitPath.op(mWavePath, Path.Op.INTERSECT);
            canvas.drawPath(mWaveLimitPath, paint);
        }
    
        //前一次绘制时的进度
        private float mPrePercent;
        //当前进度值
        private String mPercentValue;
    
        private void drawProgress(Canvas canvas) {
            float y = mCenterPoint.y - (mPercentPaint.descent() + mPercentPaint.ascent()) / 2;
            if (BuildConfig.DEBUG) {
                Log.d(TAG, "mPercent = " + mPercent + "; mPrePercent = " + mPrePercent);
            }
            if (mPrePercent == 0.0f || Math.abs(mPercent - mPrePercent) >= 0.01f) {
                //String类的format方法,该方法将一个格式化的字符串返回
                //"%.0f%%"表示一个浮点数应该被格式化为没有小数位的百分数
                mPercentValue = String.format("%.0f%%", mPercent * 100);
                mPrePercent = mPercent;
            }
            canvas.drawText(mPercentValue, mCenterPoint.x, y, mPercentPaint);
    
            if (mHint != null) {
                //descent和ascent两个函数可能分别返回字体的下沉(即,基线以下的最低点)和上升(基线以上的最高点)
                float hy = mCenterPoint.y * 2 / 3 - (mHintPaint.descent() + mHintPaint.ascent()) / 2;
                canvas.drawText(mHint.toString(), mCenterPoint.x, hy, mHintPaint);
            }
        }
    
        public float getMaxValue() {
            return mMaxValue;
        }
    
        public void setMaxValue(float maxValue) {
            mMaxValue = maxValue;
        }
    
        public float getValue() {
            return mValue;
        }
    
    
         // 设置当前值
        public void setValue(float value) {
            if (value > mMaxValue) {
                value = mMaxValue;
            }
            float start = mPercent;
            float end = value / mMaxValue;
            Log.d(TAG, "setValue, value = " + value + ";start = " + start + "; end = " + end);
            startAnimator(start, end, mDarkWaveAnimTime);
        }
    
        private void startAnimator(final float start, float end, long animTime) {
            Log.d(TAG, "startAnimator,value = " + mValue
                    + ";start = " + start + ";end = " + end + ";time = " + animTime);
            //当start=0且end=0时,不需要启动动画
            if (start == 0 && end == 0) {
                return;
            }
            //ValueAnimator对象用于动画的创建,ValueAnimator.ofFloat(start, end)表示这个动画将会在start和end之间进行变化
            mProgressAnimator = ValueAnimator.ofFloat(start, end);
            //设置动画的持续时间
            mProgressAnimator.setDuration(animTime);
            //addUpdateListener是添加一个监听器,监听动画的更新
            mProgressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    //animation.getAnimatedValue()从名为 animation 的对象中获取当前动画的动画值
                    mPercent = (float) animation.getAnimatedValue();
                    if (mPercent == 0.0f || mPercent == 1.0f) {
                        stopWaveAnimator();
                    } else {
                        startWaveAnimator();
                    }
                    mValue = mPercent * mMaxValue;
                    if (BuildConfig.DEBUG) {
                        Log.d(TAG, "onAnimationUpdate: percent = " + mPercent
                                + ";value = " + mValue);
                    }
                    //告诉Android系统这个视图需要被重新绘制
                    invalidate();
                }
            });
            //启动了 mAnimator
            mProgressAnimator.start();
        }
    
        private void startWaveAnimator() {
            startLightWaveAnimator();
            startDarkWaveAnimator();
        }
    
        private void stopWaveAnimator() {
            //调用isRunning()方法检查mDarkWaveAnimator是否正在运行。如果动画正在播放,则此方法将返回true,否则返回false
            if (mDarkWaveAnimator != null && mDarkWaveAnimator.isRunning()) {
                //调用cancel()方法取消正在运行的动画
                mDarkWaveAnimator.cancel();
                //调用removeAllUpdateListeners()方法移除所有注册的更新监听器
                mDarkWaveAnimator.removeAllUpdateListeners();
                mDarkWaveAnimator = null;
            }
            if (mLightWaveAnimator != null && mLightWaveAnimator.isRunning()) {
                mLightWaveAnimator.cancel();
                mLightWaveAnimator.removeAllUpdateListeners();
                mLightWaveAnimator = null;
            }
        }
    
    
        //开始浅色动画
        private void startLightWaveAnimator() {
            if (mLightWaveAnimator != null && mLightWaveAnimator.isRunning()) {
                return;
            }
            mLightWaveAnimator = ValueAnimator.ofFloat(0, 2 * mRadius);
            //设置了动画的持续时间,单位是毫秒
            mLightWaveAnimator.setDuration(mLightWaveAnimTime);
            //设置了动画的重复次数  无限重复(ValueAnimator.INFINITE
            mLightWaveAnimator.setRepeatCount(ValueAnimator.INFINITE);
            //设置了动画的插值器
            mLightWaveAnimator.setInterpolator(new LinearInterpolator());
            //添加了一个更新监听器
            mLightWaveAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    //从一个动画(animation)中获取一个值,然后将该值赋给一个变量(mLightWaveOffset)
                    mLightWaveOffset = (float) animation.getAnimatedValue();
                    //postInvalidate()用于将组件的无效区域(invalid regions)发送到事件队列中,以便在稍后进行重绘(repaint)。
                    postInvalidate();
                }
            });
            mLightWaveAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
                    mLightWaveOffset = 0;
                }
    
                @Override
                public void onAnimationEnd(Animator animation) {
    
                }
    
                @Override
                public void onAnimationCancel(Animator animation) {
    
                }
    
                @Override
                public void onAnimationRepeat(Animator animation) {
    
                }
            });
            mLightWaveAnimator.start();
        }
    
        //开始深色动画
        private void startDarkWaveAnimator() {
            if (mDarkWaveAnimator != null && mDarkWaveAnimator.isRunning()) {
                return;
            }
            mDarkWaveAnimator = ValueAnimator.ofFloat(0, 2 * mRadius);
            //设置了动画的持续时间,单位是毫秒
            mDarkWaveAnimator.setDuration(mDarkWaveAnimTime);
            //设置了动画的重复次数  无限重复(ValueAnimator.INFINITE
            mDarkWaveAnimator.setRepeatCount(ValueAnimator.INFINITE);
            //设置了动画的插值器
            mDarkWaveAnimator.setInterpolator(new LinearInterpolator());
            //添加了一个更新监听器
            mDarkWaveAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    //从一个动画(animation)中获取一个值,然后将该值赋给一个变量(mLightWaveOffset)
                    mDarkWaveOffset = (float) animation.getAnimatedValue();
                    //postInvalidate()用于将组件的无效区域(invalid regions)发送到事件队列中,以便在稍后进行重绘(repaint)。
                    postInvalidate();
                }
            });
            mDarkWaveAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
                    mDarkWaveOffset = 0;
                }
    
                @Override
                public void onAnimationEnd(Animator animation) {
    
                }
    
                @Override
                public void onAnimationCancel(Animator animation) {
    
                }
    
                @Override
                public void onAnimationRepeat(Animator animation) {
    
                }
            });
            mDarkWaveAnimator.start();
        }
    
        @Override
        protected void onDetachedFromWindow() {
            super.onDetachedFromWindow();
            stopWaveAnimator();
            //调用isRunning()方法检查mDarkWaveAnimator是否正在运行。如果动画正在播放,则此方法将返回true,否则返回false
            if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
                //调用cancel()方法取消正在运行的动画
                mProgressAnimator.cancel();
                //调用removeAllUpdateListeners()方法移除所有注册的更新监听器
                mProgressAnimator.removeAllUpdateListeners();
                mProgressAnimator = null;
            }
        }
        public void reset() {
            startAnimator(mPercent, 0.0f, 1000L);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421
    • 422
    • 423
    • 424
    • 425
    • 426
    • 427
    • 428
    • 429
    • 430
    • 431
    • 432
    • 433
    • 434
    • 435
    • 436
    • 437
    • 438
    • 439
    • 440
    • 441
    • 442
    • 443
    • 444
    • 445
    • 446
    • 447
    • 448
    • 449
    • 450
    • 451
    • 452
    • 453
    • 454
    • 455
    • 456
    • 457
    • 458
    • 459
    • 460
    • 461
    • 462
    • 463
    • 464
    • 465
    • 466
    • 467
    • 468
    • 469
    • 470
    • 471
    • 472
    • 473
    • 474
    • 475
    • 476
    • 477
    • 478
    • 479
    • 480
    • 481
    • 482
    • 483
    • 484
    • 485
    • 486
    • 487
    • 488
    • 489
    • 490
    • 491
    • 492
    • 493
    • 494
    • 495
    • 496
    • 497
    • 498
    • 499
    • 500
    • 501
    • 502
    • 503
    • 504
    • 505
    • 506
    • 507
    • 508
    • 509
    • 510
    • 511
    • 512
    • 513
    • 514
    • 515
    • 516
    • 517
    • 518
    • 519
    • 520
    • 521
    • 522
    • 523
    • 524
    • 525
    • 526
    • 527
    • 528
    • 529
    • 530
    • 531
    • 532
    • 533
    • 534
    • 535
    • 536
    • 537
    • 538
    • 539
    • 540
    • 541
    • 542
    • 543
    • 544
    • 545
    • 546
    • 547

    MiscUtil

    import android.content.Context;
    import android.graphics.Paint;
    import android.view.View;
    
    public class MiscUtil {
         //测量 View
        public static int measure(int measureSpec, int defaultSize) {
            int result = defaultSize;
            int specMode = View.MeasureSpec.getMode(measureSpec);
            int specSize = View.MeasureSpec.getSize(measureSpec);
    
            if (specMode == View.MeasureSpec.EXACTLY) {
                result = specSize;
            } else if (specMode == View.MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
            return result;
        }
    
        // dip 转换成px
        public static int dipToPx(Context context, float dip) {
            float density = context.getResources().getDisplayMetrics().density;
            return (int) (dip * density + 0.5f * (dip >= 0 ? 1 : -1));
        }
    
         // 获取数值精度格式化字符串
        public static String getPrecisionFormat(int precision) {
            return "%." + precision + "f";
        }
    
         // 反转数组
        public static <T> T[] reverse(T[] arrays) {
            if (arrays == null) {
                return null;
            }
            int length = arrays.length;
            for (int i = 0; i < length / 2; i++) {
                T t = arrays[i];
                arrays[i] = arrays[length - i - 1];
                arrays[length - i - 1] = t;
            }
            return arrays;
        }
    
         // 测量文字高度
        public static float measureTextHeight(Paint paint) {
            Paint.FontMetrics fontMetrics = paint.getFontMetrics();
            return (Math.abs(fontMetrics.ascent) - fontMetrics.descent);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
  • 相关阅读:
    MySQL数据库修改字段编码
    【C++】移动距离
    如何基于Django中的WebSockets和异步视图来实现实时通信功能
    程序员宝藏
    无人机-地面站
    vue-计算属性
    pyqt5 应用的主题样式!
    No module named google.protobuf
    配置windows环境下独立浏览器爬虫方案【不依赖系统环境与chrome】
    MyBatis 判断条件为不等于的问题(<if test=“变量!= ‘1‘.toString()“> xxx </if>
  • 原文地址:https://blog.csdn.net/weixin_74239923/article/details/132697166