开源项目android-process-button使用和源码分析 中,讲解了dmytrodanylyk大神的带进度显示的按钮。

今天再来介绍该作者的另一个开源项目circular-progress-button,效果更酷炫。

项目地址:
https://github.com/dmytrodanylyk/circular-progress-button
其中包含项目源码和示例代码。

运行效果图:

在分析该项目的源码之前,需要一些准备工作。关于Drawable,需要熟悉GradientDrawable和StateListDrawable类,ColorStateList类,以及如何继承Drawable类实现自己的drawable。对于动画,需要了解ValueAnimator和ObjectAnimator类的使用。

一、核心类的介绍
CircularProgressButton :圆形进度按钮,引用该开源项目时使用的控件。
MorphingAnimation :执行按钮的变换动画。比如从按钮变成圆环,按钮在不同状态之间的变换。
CircularProgressDrawable :圆环进度的Drawable,进度从0执行到100即结束。
CircularAnimatedDrawable :圆环动画的Drawable,使用该Drawable圆环会一直循环执行动画。

二、初始化
(1).成员变量介绍

private StrokeGradientDrawable background;// 背景
private CircularAnimatedDrawable mAnimatedDrawable;// 圆环动画
private CircularProgressDrawable mProgressDrawable;// 圆环进度
private ColorStateList mIdleColorState;// 默认
private ColorStateList mCompleteColorState;// 完成
private ColorStateList mErrorColorState;// 错误
private StateListDrawable mIdleStateDrawable;// 默认
private StateListDrawable mCompleteStateDrawable;// 完成
private StateListDrawable mErrorStateDrawable;// 错误
private String mIdleText;// 默认
private String mCompleteText;// 完成
private String mErrorText;// 错误
private String mProgressText;// 进度中
private enum State {
    IDLE,// 默认
    PROGRESS,// 进度中
    COMPLETE,// 完成
    ERROR// 错误
(2).成员变量初始化 

CircularProgressButton在构造方法中完成对成员变量的初始化操作。
通过getResources().getColorStateList(id)方法,根据id获取ColorStateList对象(参数id是在xml中定义的selector,其中包含了各个不同状态下的颜色值),然后赋值给相应的ColorStateList对象。再通过colorStateList.getColorForState(int[] stateSet, int defaultColor)方法取出各个状态下的color,调用stateListDrawable.addState(int[] stateSet, Drawable drawable)将颜色值添加到三个StateListDrawable对象中。参数stateSet同在xml中设置的state一致,包括state_enabled、state_focused、state_pressed等。
// 初始化mErrorStateDrawable
private void initErrorStateDrawable() {
    int colorPressed = getPressedColor(mErrorColorState);
    StrokeGradientDrawable drawablePressed = createDrawable(colorPressed);
    mErrorStateDrawable = new StateListDrawable();
    mErrorStateDrawable.addState(new int[]{android.R.attr.state_pressed}, drawablePressed.getGradientDrawable());
    mErrorStateDrawable.addState(StateSet.WILD_CARD, background.getGradientDrawable());
// 初始化mCompleteStateDrawable
private void initCompleteStateDrawable() {
    int colorPressed = getPressedColor(mCompleteColorState);
    StrokeGradientDrawable drawablePressed = createDrawable(colorPressed);
    mCompleteStateDrawable = new StateListDrawable();
    mCompleteStateDrawable.addState(new int[]{android.R.attr.state_pressed}, drawablePressed.getGradientDrawable());
    mCompleteStateDrawable.addState(StateSet.WILD_CARD, background.getGradientDrawable());
// 初始化mIdleStateDrawable
private void initIdleStateDrawable() {
    int colorNormal = getNormalColor(mIdleColorState);
    int colorPressed = getPressedColor(mIdleColorState);
    int colorFocused = getFocusedColor(mIdleColorState);
    int colorDisabled = getDisabledColor(mIdleColorState);
    if (background == null) {
        background = createDrawable(colorNormal);
    StrokeGradientDrawable drawableDisabled = createDrawable(colorDisabled);
    StrokeGradientDrawable drawableFocused = createDrawable(colorFocused);
    StrokeGradientDrawable drawablePressed = createDrawable(colorPressed);
    mIdleStateDrawable = new StateListDrawable();
    mIdleStateDrawable.addState(new int[]{android.R.attr.state_pressed}, drawablePressed.getGradientDrawable());
    mIdleStateDrawable.addState(new int[]{android.R.attr.state_focused}, drawableFocused.getGradientDrawable());
    mIdleStateDrawable.addState(new int[]{-android.R.attr.state_enabled}, drawableDisabled.getGradientDrawable());
    mIdleStateDrawable.addState(StateSet.WILD_CARD, background.getGradientDrawable());
private int getNormalColor(ColorStateList colorStateList) {
    return colorStateList.getColorForState(new int[]{android.R.attr.state_enabled}, 0);
private int getPressedColor(ColorStateList colorStateList) {
    return colorStateList.getColorForState(new int[]{android.R.attr.state_pressed}, 0);
private int getFocusedColor(ColorStateList colorStateList) {
    return colorStateList.getColorForState(new int[]{android.R.attr.state_focused}, 0);
private int getDisabledColor(ColorStateList colorStateList) {
    return colorStateList.getColorForState(new int[]{-android.R.attr.state_enabled}, 0);
}
关于显示文字、图标,进度条颜色值的初始化不再说明,很好理解。

三、执行进度变化
(1).setProgress(int progress)方法
根据当前进度值改变按钮的显示状态,需要调用setProgress(int progress)方法。

// 改变进度
public void setProgress(int progress) {
    mProgress = progress;
    if (mMorphingInProgress || getWidth() == 0) {
        return;
    mStateManager.saveProgress(this);
    // 判断进度mProgress和状态mState
    // mProgress是当前方法参数传递进来的;mState是在每一个动画执行结束后被赋值的
    if (mProgress >= mMaxProgress) {// 当前进度大于等于最大值
        if (mState == State.PROGRESS) {
            morphProgressToComplete();// 加载中 --> 完成
        } else if (mState == State.IDLE) {
            morphIdleToComplete();// 初始 --> 完成
    } else if (mProgress > IDLE_STATE_PROGRESS) {// 当前进度大于初始值,小于最大值
        if (mState == State.IDLE) {
            morphToProgress();// 初始 --> 加载中
        } else if (mState == State.PROGRESS) {
            invalidate();// 直接绘制
    } else if (mProgress == ERROR_STATE_PROGRESS) {// 当前进度等于错误值
        if (mState == State.PROGRESS) {
            morphProgressToError();// 加载中 --> 错误
        } else if (mState == State.IDLE) {
            morphIdleToError();// 初始 --> 错误
    } else if (mProgress == IDLE_STATE_PROGRESS) {// 当前进度等于初始值
        if (mState == State.COMPLETE) {
            morphCompleteToIdle();// 完成 --> 初始
        } else if (mState == State.PROGRESS) {
            morphProgressToIdle();// 加载中 --> 初始
        } else if (mState == State.ERROR) {
            morphErrorToIdle();// 错误 --> 初始
}
在方法内部,对传入的progress值和按钮当前的状态mState进行判断,调用相应的morphXXToXX()方法,来改变按钮的显示效果。

(2).morph()方法
setProgress(int progress)方法的核心,是调用各种不同的morph方法。

// 按钮的不同状态之间变换,使用该动画
private MorphingAnimation createMorphing() {
    mMorphingInProgress = true;
    MorphingAnimation animation = new MorphingAnimation(this, background);
    animation.setFromCornerRadius(mCornerRadius);
    animation.setToCornerRadius(mCornerRadius);
    animation.setFromWidth(getWidth());
    animation.setToWidth(getWidth());
    return animation;
// 按钮与圆环之间变换,使用该动画
// 相对于createMorphing()方法,增加了圆角和宽度的变化
private MorphingAnimation createProgressMorphing(float fromCorner, float toCorner, int fromWidth, int toWidth) {
    mMorphingInProgress = true;
    MorphingAnimation animation = new MorphingAnimation(this, background);
    animation.setFromCornerRadius(fromCorner);
    animation.setToCornerRadius(toCorner);
    animation.setPadding(mPaddingProgress);
    animation.setFromWidth(fromWidth);
    animation.setToWidth(toWidth);
    return animation;
// 初始 --> 加载中,由按钮变成圆环
private void morphToProgress() {
    setWidth(getWidth());
    setText(mProgressText);
    // CornerRadius变化:按钮的圆角mCornerRadius -> 圆环的高度getHeight()
    // Width变化:按钮的宽度getWidth() -> 圆环的宽度getHeight()
    MorphingAnimation animation = createProgressMorphing(mCornerRadius, getHeight(), getWidth(), getHeight());
    animation.setFromColor(getNormalColor(mIdleColorState));
    animation.setToColor(mColorProgress);
    animation.setFromStrokeColor(getNormalColor(mIdleColorState));
    animation.setToStrokeColor(mColorIndicatorBackground);
    animation.setListener(mProgressStateListener);
    animation.start();
// 加载中 --> 完成
private void morphProgressToComplete() {
    // CornerRadius变化:圆角的高度getHeight() -> 按钮的圆角mCornerRadius
    // Width变化:圆环的宽度getHeight() -> 按钮的宽度getWidth()
    MorphingAnimation animation = createProgressMorphing(getHeight(), mCornerRadius, getHeight(), getWidth());
    animation.setFromColor(mColorProgress);
    animation.setToColor(getNormalColor(mCompleteColorState));
    animation.setFromStrokeColor(mColorIndicator);
    animation.setToStrokeColor(getNormalColor(mCompleteColorState));
    animation.setListener(mCompleteStateListener);
    animation.start();
// 加载中 --> 错误
private void morphProgressToError() {
    MorphingAnimation animation = createProgressMorphing(getHeight(), mCornerRadius, getHeight(), getWidth());
    animation.setFromColor(mColorProgress);
    animation.setToColor(getNormalColor(mErrorColorState));
    animation.setFromStrokeColor(mColorIndicator);
    animation.setToStrokeColor(getNormalColor(mErrorColorState));
    animation.setListener(mErrorStateListener);
    animation.start();
// 加载中 --> 初始
private void morphProgressToIdle() {
    // CornerRadius变化:圆角的高度getHeight() -> 按钮的圆角mCornerRadius
    // Width变化:圆环的宽度getHeight() -> 按钮的宽度getWidth()
    MorphingAnimation animation = createProgressMorphing(getHeight(), mCornerRadius, getHeight(), getWidth());
    animation.setFromColor(mColorProgress);
    animation.setToColor(getNormalColor(mIdleColorState));
    animation.setFromStrokeColor(mColorIndicator);
    animation.setToStrokeColor(getNormalColor(mIdleColorState));
    animation.setListener(new OnAnimationEndListener() {
        @Override
        public void onAnimationEnd() {
            removeIcon();
            setText(mIdleText);
            mMorphingInProgress = false;
            mState = State.IDLE;
            mStateManager.checkState(CircularProgressButton.this);
    animation.start();
// 初始 --> 完成
private void morphIdleToComplete() {
    MorphingAnimation animation = createMorphing();
    animation.setFromColor(getNormalColor(mIdleColorState));
    animation.setToColor(getNormalColor(mCompleteColorState));
    animation.setFromStrokeColor(getNormalColor(mIdleColorState));
    animation.setToStrokeColor(getNormalColor(mCompleteColorState));
    animation.setListener(mCompleteStateListener);
    animation.start();
// 完成 --> 初始
private void morphCompleteToIdle() {
    MorphingAnimation animation = createMorphing();
    animation.setFromColor(getNormalColor(mCompleteColorState));
    animation.setToColor(getNormalColor(mIdleColorState));
    animation.setFromStrokeColor(getNormalColor(mCompleteColorState));
    animation.setToStrokeColor(getNormalColor(mIdleColorState));
    animation.setListener(mIdleStateListener);
    animation.start();
// 错误 --> 初始
private void morphErrorToIdle() {
    MorphingAnimation animation = createMorphing();
    animation.setFromColor(getNormalColor(mErrorColorState));
    animation.setToColor(getNormalColor(mIdleColorState));
    animation.setFromStrokeColor(getNormalColor(mErrorColorState));
    animation.setToStrokeColor(getNormalColor(mIdleColorState));
    animation.setListener(mIdleStateListener);
    animation.start();
// 初始 --> 错误
private void morphIdleToError() {
    MorphingAnimation animation = createMorphing();
    animation.setFromColor(getNormalColor(mIdleColorState));
    animation.setToColor(getNormalColor(mErrorColorState));
    animation.setFromStrokeColor(getNormalColor(mIdleColorState));
    animation.setToStrokeColor(getNormalColor(mErrorColorState));
    animation.setListener(mErrorStateListener);
    animation.start();
(3).MorphingAnimation类。 

在上面morph()方法中,执行按钮变换使用了MorphingAnimation类。按钮在不同状态之间的变换依赖此类来实现。该类通过各种参数,宽度、圆角、描边、内间距等,执行ValueAnimator动画,来改变按钮的背景色,圆角半径和描边颜色。执行ValueAnimator动画代码如下。
public void start() {
    // 大小变化的动画
    ValueAnimator widthAnimation = ValueAnimator.ofInt(mFromWidth, mToWidth);
    final GradientDrawable gradientDrawable = mDrawable.getGradientDrawable();
    widthAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            Integer value = (Integer) animation.getAnimatedValue();
            int leftOffset;
            int rightOffset;
            int padding;
            if (mFromWidth > mToWidth) {// 从按钮变成圆形进度
                leftOffset = (mFromWidth - value) / 2;
                rightOffset = mFromWidth - leftOffset;
                padding = (int) (mPadding * animation.getAnimatedFraction());
            } else {// 从圆形进度变成按钮
                leftOffset = (mToWidth - value) / 2;
                rightOffset = mToWidth - leftOffset;
                padding = (int) (mPadding - mPadding * animation.getAnimatedFraction());
            gradientDrawable
                    .setBounds(leftOffset + padding, padding, rightOffset - padding, mView.getHeight() - padding);
    // 背景色变化的动画
    ObjectAnimator bgColorAnimation = ObjectAnimator.ofInt(gradientDrawable, "color", mFromColor, mToColor);
    bgColorAnimation.setEvaluator(new ArgbEvaluator());
    // 描边色变化的动画
    ObjectAnimator strokeColorAnimation =
            ObjectAnimator.ofInt(mDrawable, "strokeColor", mFromStrokeColor, mToStrokeColor);
    strokeColorAnimation.setEvaluator(new ArgbEvaluator());
    // 圆角变化的动画
    ObjectAnimator cornerAnimation =
            ObjectAnimator.ofFloat(gradientDrawable, "cornerRadius", mFromCornerRadius, mToCornerRadius);
    AnimatorSet animatorSet = new AnimatorSet();
    animatorSet.setDuration(mDuration);
    animatorSet.playTogether(widthAnimation, bgColorAnimation, strokeColorAnimation, cornerAnimation);
    animatorSet.start();
(4).onDraw(Canvas canvas)方法 

在setProgress(int progress)方法中,当mProgress > IDLE_STATE_PROGRESS且mProgress < mMaxProgress,mState == State.PROGRESS时,直接执行invalidate()方法,触发onDraw(Canvas canvas)方法的调用,在onDraw(Canvas canvas)中来绘制圆环进度的变化。
圆环进度又分为两种,一直循环的进度和根据progress值绘制的进度,分别由drawIndeterminateProgress(Canvas canvas)和drawProgress(Canvas canvas)方法负责完成。
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    // 状态为加载中时,调用drawXXX()绘制圆环
    if (mProgress > 0 && mState == State.PROGRESS && !mMorphingInProgress) {
        if (mIndeterminateProgressMode) {
            drawIndeterminateProgress(canvas);
        } else {
            drawProgress(canvas);
// 绘制循环进度的圆环
private void drawIndeterminateProgress(Canvas canvas) {
    if (mAnimatedDrawable == null) {
        int offset = (getWidth() - getHeight()) / 2;
        mAnimatedDrawable = new CircularAnimatedDrawable(mColorIndicator, mStrokeWidth);
        // 左间距:按钮宽度的一半 - 圆环的半径
        int left = offset + mPaddingProgress;
        // 右间距:按钮的宽度 - 圆环距离按钮右侧的距离
        int right = getWidth() - offset - mPaddingProgress;
        // 下间距:圆环的直径
        int bottom = getHeight() - mPaddingProgress;
        // 上间距
        int top = mPaddingProgress;
        mAnimatedDrawable.setBounds(left, top, right, bottom);
        mAnimatedDrawable.setCallback(this);
        mAnimatedDrawable.start();
    } else {
        mAnimatedDrawable.draw(canvas);
// 根据进度值绘制圆环
private void drawProgress(Canvas canvas) {
    if (mProgressDrawable == null) {
        // offset:计算圆环左侧距离按钮的间距,这里的getHeight()代表圆环的直径
        int offset = (getWidth() - getHeight()) / 2;
        // size:圆环的直径
        int size = getHeight() - mPaddingProgress * 2;
        // 初始化CircularProgressDrawable,传入参数直径、描边宽度、描边颜色
        mProgressDrawable = new CircularProgressDrawable(size, mStrokeWidth, mColorIndicator);
        // 如果有padding值,再把padding加上。mPaddingProgress默认为0,指的是圆环和按钮之间的间距,大于0时圆环会变小
        int left = offset + mPaddingProgress;
        // 设置Bounds,在CircularProgressDrawable类中会用到Bounds的left和top
        mProgressDrawable.setBounds(left, mPaddingProgress, left, mPaddingProgress);
    // 计算进度条的弧度,最多360°,使用当前进度的比例*360
    float sweepAngle = ((float) mProgress / mMaxProgress) * 360;
    mProgressDrawable.setSweepAngle(sweepAngle);
    mProgressDrawable.draw(canvas);
(5).CircularProgressDrawable类 

在drawProgress(Canvas canvas)方法中使用了该类,根据当前的进度值,来绘制圆形进度条。内部的实现主要是重写draw(Canvas canvas)方法,使用android.graphics.Path类绘制弧形。
@Override
public void draw(Canvas canvas) {
    final Rect bounds = getBounds();
    if (mPath == null) {
        mPath = new Path();
    mPath.reset();
    // 画圆弧,mSweepAngle:精度条圆弧,0~360
    mPath.addArc(getRect(), mStartAngle, mSweepAngle);
    // 移动到按钮中间
    mPath.offset(bounds.left, bounds.top);
    // 绘制
    canvas.drawPath(mPath, createPaint());
private RectF getRect() {
    if (mRectF == null) {
        int index = mStrokeWidth / 2;
        mRectF = new RectF(index, index, getSize() - index, getSize() - index);
    return mRectF;
(6).CircularAnimatedDrawable类 

在drawIndeterminateProgress(Canvas canvas)方法中使用了该类,来绘制一直循环转动的圆形进度条。
CircularAnimatedDrawable由两个动画组合执行来绘制圆形进度,分别是mObjectAnimatorAngle和mObjectAnimatorSweep。
动画的执行可以分解开来看。采用注释掉代码的方式,一次只允许一个动画执行。会发现,只允许mObjectAnimatorAngle执行时,圆弧匀速旋转,弧度不变;只允许mObjectAnimatorSweep执行时,圆弧不旋转,但是弧度一直在发生改变。

mObjectAnimatorAngle和mObjectAnimatorSweep的初始化如下。

// 初始化动画,两个动画执行时都是在改变某个成员变量的值,然后在onDraw()方法中使用
private void setupAnimations() {
    // mObjectAnimatorAngle:圆弧整体匀速旋转的动画
    // mAngleProperty为成员变量mCurrentGlobalAngle赋值,范围0~360。mCurrentGlobalAngle匀速增加
    mObjectAnimatorAngle = ObjectAnimator.ofFloat(this, mAngleProperty, 360f);
    // 匀速
    mObjectAnimatorAngle.setInterpolator(ANGLE_INTERPOLATOR);
    mObjectAnimatorAngle.setDuration(ANGLE_ANIMATOR_DURATION);
    // 循环执行
    mObjectAnimatorAngle.setRepeatMode(ValueAnimator.RESTART);
    mObjectAnimatorAngle.setRepeatCount(ValueAnimator.INFINITE);
    // mObjectAnimatorSweep:圆弧的弧度变化的动画
    // mSweepProperty为成员变量mCurrentSweepAngle赋值,范围0~300。mCurrentSweepAngle减速增加
    mObjectAnimatorSweep = ObjectAnimator.ofFloat(this, mSweepProperty, 360f - MIN_SWEEP_ANGLE * 2);
    // 减速
    mObjectAnimatorSweep.setInterpolator(SWEEP_INTERPOLATOR);
    mObjectAnimatorSweep.setDuration(SWEEP_ANIMATOR_DURATION);
    // 循环执行
    mObjectAnimatorSweep.setRepeatMode(ValueAnimator.RESTART);
    mObjectAnimatorSweep.setRepeatCount(ValueAnimator.INFINITE);
    mObjectAnimatorSweep.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) {
        @Override
        public void onAnimationEnd(Animator animation) {
        @Override
        public void onAnimationCancel(Animator animation) {
        @Override
        public void onAnimationRepeat(Animator animation) {
            // 每轮动画结束时,调用该方法
            toggleAppearingMode();
// 切换显示模式
private void toggleAppearingMode() {
    // mModeAppearing值取反
    mModeAppearing = !mModeAppearing;
    if (mModeAppearing) {
        // mCurrentGlobalAngleOffset从0开始,每次增加60,直到最大值300,再从头开始。循环时每间隔一轮变一次
        mCurrentGlobalAngleOffset = (mCurrentGlobalAngleOffset + MIN_SWEEP_ANGLE * 2) % 360;
}
mObjectAnimatorAngle动画比较好理解。在执行时,mCurrentGlobalAngle每轮都是从0°递增到360°,而mCurrentGlobalAngle改变的是drawArc()中startAngle参数的值,从而达到视觉上圆弧匀速旋转的动画。如果只分解出mCurrentGlobalAngle动画产生的绘制,onDraw()相当于如下代码。
@Override
public void draw(Canvas canvas) {
    canvas.drawArc(fBounds, mCurrentGlobalAngle, 330, false, mPaint);
}
难点在于mObjectAnimatorSweep动画。圆弧在变化时,有两个极端状态,最大弧度(弧度为330°,显示效果上起点和终点间隔30°的灰色)和最小弧度(弧度为30°,显示效果上起点和终点之间为30°的蓝色)。
我们将动画的时间延长10倍,可以清晰的看到,圆弧最初显示的是最大弧度,圆弧的弧度减小,直到最小弧度为止。然后圆弧的弧度增加,直到最大弧度为止。接下来再进入上一步变化,依次循环下去。如果只分解出mObjectAnimatorSweep动画产生的绘制,onDraw()相当于如下代码。
@Override
public void draw(Canvas canvas) {
    // mObjectAnimatorSweep动画执行时,mCurrentSweepAngle由0~300逐渐增加
    float startAngle;
    float sweepAngle;
    if (!mModeAppearing) {// 弧度减小
        // mCurrentGlobalAngleOffset不变
        // startAngle递增
        startAngle = mCurrentSweepAngle - mCurrentGlobalAngleOffset;
        // sweepAngle递减
        sweepAngle = 360 - mCurrentSweepAngle - MIN_SWEEP_ANGLE;
    } else {// 弧度增加
        // mCurrentGlobalAngleOffset每次循环增加60
        // startAngle不变
        startAngle = -mCurrentGlobalAngleOffset;
        // sweepAngle递增
        sweepAngle = MIN_SWEEP_ANGLE + mCurrentSweepAngle;
    canvas.drawArc(fBounds, startAngle, sweepAngle, false, mPaint);
}
将成员变量mCurrentGlobalAngle添加到参数startAngle中,即完成了两个动画的组合。 在之前一篇博客 开源项目android-process-button使用和源码分析 中,讲解了dmytrodanylyk大神的带进度显示的按钮。今天再来介绍该作者的另一个开源项目circular-progress-button,效果更酷炫。项目地址:https://github.com/dmytrodanylyk/circular-progress-button其中包含
本文原创首发于公众号:ReactNative开发圈,转载需注明出处。 React Native 圆形进度条组件:react-native-circular-progress,圆形的进度条组件,支持动画,支持iOS和Android。 npm i --save react-native-circular-progress IOS需要手动Link下ReactART,用Xcode...
if (n[r]) return n[r].exports; var i = n[r] = {i: r, l: !1, exports: {}}; return t[r].call(i.exports, i, i.exports, e), i.l = !0, i.exports var n = {}; return e.m = t, e.c = n, e.d = f.
demo按钮效果图:一、定义ProgressButton的自定义属性在attrs文件中定义ProgressButton的基本属性:主要有进度条颜色、进度条的背景颜色、按钮正常和被点击状态时的颜色、按钮的边角半径和是否显示进度信息。<declare-styleable name="progressbutton"> <attr name="progressColor" format="color
第一种:circular-progress-button简介一个代有progressButton,github : circular-progress-button效果图: gradledependencies { compile 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3' }基本使用
在RN中,要实现环形进度条效果,一般都是需要借助chart图实现;最近做RN项目,经过折腾,发现react-native-circular-progress这个组件几乎能完美的解决。故分享出来,废话不多说了,直接上代码 1、react-native-circular-progress组件Circle.js的封装 import React, { PureComponent } from 'react'; import {StyleSheet, View, Dimensions} from
Android值Drawable系列:    [一起来说说那些你不知道的Drawable](http://blog.csdn.net/mr_dsw/article/details/50998681)    [Android实践之Drawable的使用](http://blog.csdn.net/mr_dsw/article/details/50999818)     [开源项目circula
无人驾驶汽车:udacity纳米学位项目-源码是一个开源项目,旨在帮助人们学习和开发无人驾驶汽车技术。该项目由Udacity公司开发,提供了一系列课程和实践项目,帮助学员了解无人驾驶汽车的基本原理、算法和技术,并通过实践项目来应用所学知识。 该项目的源码包含了许多实用的工具和库,包括机器学习算法、计算机视觉技术、传感器数据处理等。学员可以通过学习这些工具和库,了解无人驾驶汽车的各个方面,并开发自己的无人驾驶汽车应用程序。 总之,无人驾驶汽车:udacity纳米学位项目-源码是一个非常有价值的开源项目,对于想要学习和开发无人驾驶汽车技术的人来说,是一个非常好的资源。