开源项目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简介一个代有progress的Button,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纳米学位项目-源码是一个非常有价值的开源项目,对于想要学习和开发无人驾驶汽车技术的人来说,是一个非常好的资源。