(点击上方公众号,可快速关注)
来源:伯乐在线专栏作者 - joe
链接:http://android.jobbole.com/85135/
点击 → 了解如何加入专栏作者
啦啦啦,今天给大家带来最近弄的CircleProgress相关的效果。这里的效果图可能还看不出是UC浏览器的那个下拉刷新的效果,不过首先还是要说说这个进度条,在下一篇中将实现真正的下拉刷新!
话不多说,直接上图:
progress_静态.png
progress_动态.gif
特点:就是一个进度条
1、可以设置多种颜色。
2、可以显示多种状态(LOADING、SUCCESS、ERROR,其实远不止这几种)
3、可以控制是否显示箭头
相关准备工作
知识点:
1.Canvas里面相关方法
2.drawArc()画圆弧的方法
3.drawPath()画路径的方法
4.属性动画使用
结果的钩钩或者那个叉叉还有那个箭头都是使用drawPath()来完成的。
在onDraw里面对应有四个相关的方法:
1.drawArc(Canvas canvas):画对应的进度
2.drawTriangle(Canvas c, float startAngle, float sweepAngle):画箭头
3.drawHook(Canvas canvas):画钩钩
4.drawError(Canvas canvas):画叉叉
三个动画控制:
两个来控制进度条的 startAngle和sweepAngle,一个用来控制画钩钩或者画叉叉的时候的渐变效果!
相关代码
三支画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Cap.ROUND);
mPaint.setStrokeWidth(mBorderWidth);
mPaint.setColor(mColors[mCurrentColorIndex]);
mHookPaint = new Paint(mPaint);
mArrowPaint = new Paint(mPaint);
三个动画
private void setupAnimations() {
mObjectAnimatorAngle = ObjectAnimator.ofFloat(this, mAngleProperty, 360f);
mObjectAnimatorAngle.setInterpolator(ANGLE_INTERPOLATOR);
mObjectAnimatorAngle.setDuration(ANGLE_ANIMATOR_DURATION);
mObjectAnimatorAngle.setRepeatMode(ValueAnimator.RESTART);
mObjectAnimatorAngle.setRepeatCount(ValueAnimator.INFINITE);
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 SimpleAnimatorListener() {
@Override
public void onAnimationRepeat(Animator animation) {
toggleAppearingMode();
}
});
fractionAnimator = ValueAnimator.ofInt(0, 255);
fractionAnimator.setInterpolator(ANGLE_INTERPOLATOR);
fractionAnimator.setDuration(100);
fractionAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
fraction = animation.getAnimatedFraction();
mHookPaint.setAlpha((Integer) animation.getAnimatedValue());
invalidate();
}
});
}
四个draw相关方法
private void drawError(Canvas canvas) {
mError.reset();
mError.moveTo(fBounds.centerX() + fBounds.width() * 0.2f * fraction, fBounds.centerY() - fBounds.height() * 0.2f * fraction);
mError.lineTo(fBounds.centerX() - fBounds.width() * 0.2f * fraction, fBounds.centerY() + fBounds.height() * 0.2f * fraction);
mError.moveTo(fBounds.centerX() - fBounds.width() * 0.2f * fraction, fBounds.centerY() - fBounds.height() * 0.2f * fraction);
mError.lineTo(fBounds.centerX() + fBounds.width() * 0.2f * fraction, fBounds.centerY() + fBounds.height() * 0.2f * fraction);
mHookPaint.setColor(mColors[3]);
canvas.drawPath(mError, mHookPaint);
canvas.drawArc(fBounds, 0, 360, false, mHookPaint);
}
private void drawHook(Canvas canvas) {
mHook.reset();
mHook.moveTo(fBounds.centerX() - fBounds.width() * 0.25f * fraction, fBounds.centerY());
mHook.lineTo(fBounds.centerX() - fBounds.width() * 0.1f * fraction, fBounds.centerY() + fBounds.height() * 0.18f * fraction);
mHook.lineTo(fBounds.centerX() + fBounds.width() * 0.25f * fraction, fBounds.centerY() - fBounds.height() * 0.20f * fraction);
mHookPaint.setColor(mColors[0]);
canvas.drawPath(mHook, mHookPaint);
canvas.drawArc(fBounds, 0, 360, false, mHookPaint);
}
private void drawArc(Canvas canvas) {
float startAngle = mCurrentGlobalAngle - mCurrentGlobalAngleOffset;
float sweepAngle = mCurrentSweepAngle;
if (mModeAppearing) {
mPaint.setColor(gradient(mColors[mCurrentColorIndex], mColors[mNextColorIndex],
mCurrentSweepAngle / (360 - MIN_SWEEP_ANGLE * 2)));
sweepAngle += MIN_SWEEP_ANGLE;
} else {
startAngle = startAngle + sweepAngle;
sweepAngle = 360 - sweepAngle - MIN_SWEEP_ANGLE;
}
canvas.drawArc(fBounds, startAngle, sweepAngle, false, mPaint);
if (showArrow) {
drawTriangle(canvas, startAngle, sweepAngle);
}
}
public void drawTriangle(Canvas c, float startAngle, float sweepAngle) {
if (mArrow == null) {
mArrow = new Path();
mArrow.setFillType(Path.FillType.EVEN_ODD);
} else {
mArrow.reset();
}
float x = (float) (mRingCenterRadius * Math.cos(0) + fBounds.centerX());
float y = (float) (mRingCenterRadius * Math.sin(0) + fBounds.centerY());
mArrow.moveTo(0, 0);
mArrow.lineTo(ARROW_WIDTH * mArrowScale, 0);
mArrow.lineTo((ARROW_WIDTH * mArrowScale / 2), (ARROW_HEIGHT
* mArrowScale));
mArrow.offset(x, y);
mArrow.close();
c.rotate(startAngle + sweepAngle, fBounds.centerX(),
fBounds.centerY());
c.drawPath(mArrow, mPaint);
}
上面的代码就是相关核心的方法了,其实对应的进度条效果就是控制 startAngle和sweepAngle这两个对应的字段,然后不断的调用drawArc()方法。
对于画钩钩或者画叉叉,就是一个ValueAnimator,通过百分比控制缩放和画笔的透明度。
对于drawTriangle()方法,如果你觉得很眼熟的话也很正常,其实这个就是在SwipeRefreshLayout里面抄过来的。。。。。
一开始,我很纠结这个箭头怎么才能跟着进度条一起旋转,自己写的也是有各种问题,另外mArrowScale这个参数在里面其实没有使用的。
如果没有offset偏移量,那么那个path肯定是画在左上角的。
x=mRingCenterRadius +fBounds.centerX();
y=fBounds.centerY();
通过这个一设置,这个path其实就到了右边的中间靠着圆弧的内侧一点去了(因为这里的半径减去了圆弧自己的宽度。。),这么一来,再根据相关的角度旋转角度,就有一种跟着进度条一直转的效果了!
对于drawArc()方法,主要是控制startAngle和sweepAngle这两个变量,mCurrentGlobalAngle的变化范围是(0~360),而mCurrentSweepAngle的变化范围是(0~360f – MIN_SWEEP_ANGLE * 2),为什么要减去两个最小值呢?因为sweepAngle总会加一个或者总会减去一个最小值,所以最小间距还是MIN_SWEEP_ANGLE。
至于什么时候加什么时候减呢?这里有一个变量值mModeAppearing提供记录!那就是当mObjectAnimatorSweep的动画重复的时候,就需要切换一下了。
mObjectAnimatorSweep.addListener(new SimpleAnimatorListener() {
@Override
public void onAnimationRepeat(Animator animation) {
toggleAppearingMode();
}
});
private void toggleAppearingMode() {
mModeAppearing = !mModeAppearing;
if (mModeAppearing) {
mCurrentColorIndex = ++mCurrentColorIndex % 4;
mNextColorIndex = ++mNextColorIndex % 4;
mCurrentGlobalAngleOffset = (mCurrentGlobalAngleOffset + MIN_SWEEP_ANGLE * 2) % 360;
}
}
最终效果图
下一篇介绍剩余的下拉刷新部分,还有就是两个圆圈的过度效果。。
相关的代码请移步 我的github。。。
专栏作者简介( 点击 → 加入专栏作者 )
joe:90后程序猿。。
打赏支持作者写出更多好文章,谢谢!
关注「安卓开发精选」
看更多精选安卓技术文章
↓↓↓