实现效果如下图:
实现原理:
通过属性动画和canvas结合实现效果;
首先在attrs中自定义属性:
proogress_start_color:圆环渐变开始颜色;
progress_end_color:圆环渐变结束颜色;
progress_width:环的宽度;
radius:圆环半径;
<declare-styleable name="MdStyleProgress">
<attr name="progress_start_color" format="color"/>
<attr name="progress_end_color" format="color"/>
<attr name="progress_width" format="dimension"/>
<attr name="radius" format="dimension"/>
</declare-styleable>
开始自定义View:
首先在自定义View中获取定义的属性值:
//获取自定义属性
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.MdStyleProgress);
int indexCount = typedArray.getIndexCount();
for (int i = 0; i < indexCount; i++) {
int attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.MdStyleProgress_progress_start_color:
mProgressStartColor = typedArray.getColor(attr, PROGRESS_START_COLOR);
break;
case R.styleable.MdStyleProgress_progress_end_color:
mProgressEndColor = typedArray.getColor(attr, PROGRESS_END_COLOR);
break;
case R.styleable.MdStyleProgress_progress_width:
mProgressWidth = (int) typedArray.getDimension(attr, mProgressWidth);
break;
case R.styleable.MdStyleProgress_radius:
mRadius = (int) typedArray.getDimension(attr, mRadius);
break;
}
}
//回收TypedArray对象
typedArray.recycle();
根据获取到的值配置画笔:
private void setPaint() {
progressPaint = new Paint();
progressPaint.setAntiAlias(true);
progressPaint.setDither(true);
progressPaint.setStyle(Paint.Style.STROKE);
progressPaint.setStrokeWidth(mProgressWidth);
progressPaint.setStrokeCap(Paint.Cap.ROUND);
//设置渐变色
LinearGradient linearGradient = new LinearGradient(0, 0, mRadius*2, mRadius*2, mProgressStartColor, mProgressEndColor, Shader.TileMode.CLAMP);
progressPaint.setShader(linearGradient);
}
将画笔设置抗锯齿,并且设置线冒为圆形,而后通过LinearGradient为画笔设置了从左上到右下的渐变色,色值是从自定义属性中取得的值;
确定圆环的范围:
mRectF = new RectF(mProgressWidth / 2, mProgressWidth / 2, (mRadius) * 2 + mProgressWidth / 2, (mRadius) * 2 + mProgressWidth / 2);
我们以整个控件的中心点为中心,mProgressWidth / 2开始到(mRadius) * 2 + mProgressWidth / 2为整个圆的显示区域;实际圆所占用的区域长宽便是(mRadius) * 2;
然后我们重写onMeasure方法,测量view的宽高,并处理padding:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = getPaddingLeft() + mProgressWidth + mRadius * 2 + getPaddingRight();
widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
int heightSize = getPaddingTop() + mProgressWidth + mRadius * 2 + getPaddingBottom();
heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
代码中我们把上下左右的padding距离分别加在了view的宽高中;
最后重写onDraw,画出圆环:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.translate(getPaddingLeft(), getPaddingTop());
canvas.drawArc(mRectF, startAngle, sweepAngle, false, progressPaint);
canvas.restore();
}
代码中我们留出了上边和左边的padding距离,而后通过drawArc画出扇形,至此扇形圆环已经画出来了;
让圆环动起来:
圆环的动画效果是:
1.圆环逆时针逐渐伸出,伸出到40%的长度后停止(从0度逆时针伸出);
2.圆环开始旋转,直到头部到达开始的位置(0度);
3.圆环尾部收缩,直到圆环长度收缩完毕;
4.进入下一次循环;
我们采用属性动画的方式让圆环动起来,分析动画:
圆环需要旋转一圈,并且最后需要收缩尾部,所以我们定义动画的长度为360+360*0.4=504;
设置动画次数为无限重复;
设置重复方式为从头开始;
设置插值器为线性插值器;
设置动画时间为1200ms;
定义动画:
private void initAnimator() {
mAnimator = new ValueAnimator();
mAnimator = ValueAnimator.ofInt(0, 504); //504是360 (转一圈)+ 360*0.4(收缩尾巴)
mAnimator.setRepeatCount(ValueAnimator.INFINITE);
mAnimator.setDuration(1200);
mAnimator.setRepeatMode(ValueAnimator.RESTART);
mAnimator.setInterpolator(new LinearInterpolator());
// 设置动画的回调
mAnimatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
//开始弧线逐渐增长
if (value < 144) {
startAngle = INITI_ALANGLE - value;
//ui备注说为了动画平滑
if (value < 5) {
sweepAngle = 5;
} else {
sweepAngle = value;
}
}
//到了40%长度
else if (value >= 144 && value < 360) {
sweepAngle = 144;
startAngle = INITI_ALANGLE - value;
}
//收缩尾巴
else if (value > 360) {
if (sweepAngle > 5) {
sweepAngle = 144 - (value - 360);
}
}
Log.d("MDProgress", "start:" + startAngle + " value:" + value);
invalidate();
}
};
}
我们通过设置ValueAnimator.AnimatorUpdateListener监听动画的每一帧回调,从而在这里来改变画布:
在动画值小于360*0.4=144时,我们让sweepAngle = value;,让扇形圆环慢慢增长;
到了144时,我们改变扇形圆环的开始角度startAngle ,让圆环旋转起来;
转到360度时,再次改变sweepAngle,让扇形圆环 逐渐缩短,完成整个动画过程;
对外暴露方法,便于调用:
public void startAnimator() {
mAnimator.addUpdateListener(mAnimatorUpdateListener);
mAnimator.start();
}
public void stopAnimator() {
mAnimator.removeAllUpdateListeners();
mAnimator.end();
}
使用方式:
xml中:
设置自定义属性命名空间:
xmlns:app="http://schemas.android.com/apk/res-auto"
定义布局:
<view.MdStyleProgress
android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tv_content"
android:layout_marginTop="48dp"
android:layout_centerHorizontal="true"
app:progress_width="3dp"
app:radius="15dp" />
Activity中开始动画:
progress.startAnimator();