平方X翻译说明:
仅供参考,对正确性概不负责,翻译不对的欢迎指正,勿喷。
译于20160718,原文android.support.v4.widget.MaterialProgressDrawable.java
翻译
MaterialProgressDrawable
class MaterialProgressDrawable(译注:注意不是public的哦)
extends Drawable
implements Animatable
Java.lang.Object
↳android.graphics.drawable.Drawable
↳android.support.v4.widget.MaterialProgressDrawable
类概述
为Material主题设置的Fancy(译注:花式的、花样的、奇特的)的进度条
嵌套类
类型 | 名称 | 说明 |
---|---|---|
public @interface | ProgressDrawableSize | 用于限制updateSizes(@ProgressDrawableSize int size)方法的参数 |
private static class | Ring | 环 |
常量
类型 | 常量 | 说明 |
---|---|---|
static final int | LARGE | 值:0 大样式 |
static final int | DEFAULT | 值:1 默认样式 |
公共构造方法
MaterialProgressDrawable(Context context, View parent) |
---|
重写的公共方法
返回值 | 方法 |
---|---|
void | draw(Canvas c) |
int | getAlpha() |
int | getIntrinsicHeight() |
int | getIntrinsicWidth() |
int | getOpacity() |
boolean | isRunning() |
void | setAlpha(int alpha) |
void | setColorFilter(ColorFilter colorFilter) |
void | start() |
void | stop() |
公共方法
返回值 | 方法 |
---|---|
void | setArrowScale(float scale) 设置箭头的比例 (译注:这里没写,但这个比例应该是0-1的,因为代码中有*mArrowScale) |
void | setBackgroundColor(int color) 设置背景色 |
void | setColorSchemeColors(int… colors) 设置进度条的颜色,可以传多个,依次显示。 同时第一个颜色也会被用于响应用户的swipe手势。 |
void | setProgressRotation(float rotation) 设置进度条的旋旋转,[0..1](译注:0-1代表顺时针旋转0-360度) |
void | setStartEndTrim(float startAngle, float endAngle) 设置进度条弧度的起始角。 (译注:指圆环的起始范围。顺时针,0为左边。如0-0.5表示上半圆,0.5-1表示下半圆。1-0.5表示下半圆。0-1就是整个圆了,一般要留有缺口。该方法可配合setProgressRotation(float rotation)使用) |
void | showArrow(boolean show) 设置为true以显示进度条 |
void | updateSizes(@ProgressDrawableSize int size) 设置进度条的整体大小。这将更新圆环的半径和宽度。 参数只能为LARGE=0,或default=1 |
使用说明
//①创建drawable并设给view
ImageView imageView=...;
progress = new MaterialProgressDrawable(getContext(),imageView);
imageView.setImageDrawable(progress);
//②设置相关属性
//背景
progress.setBackgroundColor(0xFF000000);
//颜色
int[] colors = {0xFFFF0000,0xFF00FF00,0xFF0000FF};
progress.setColorSchemeColors(colors);
//尺寸
progress.updateSizes(MaterialProgressDrawable.DEFAULT);
//旋转角度,0-1
progress.setProgressRotation(0f);
//圆环范围,0-1
progress.setStartEndTrim(0f, 1f);
//箭头大小,0-1
progress.setArrowScale(0f);
//透明度,0-255
progress.setAlpha(100);
//③start
progress.start();
源码分析
构造方法
调用构造方法后,要记得把这个drawable设给view,如imageView.setImageDrawable(drawable);
public static final int LARGE = 0;
public static final int DEFAULT = 1;
private final int[] COLORS = new int[] {
Color.BLACK
};
public MaterialProgressDrawable(Context context, View parent) {
mParent = parent;
mResources = context.getResources();
mRing = new Ring(mCallback);
mRing.setColors(COLORS);
updateSizes(DEFAULT);
setupAnimators();
}
初始化动画
private void setupAnimators() {
final Ring ring = mRing;
final Animation animation = new Animation() {
@Override
public void applyTransformation(float interpolatedTime, Transformation t) {
if (mFinishing) {
applyFinishTranslation(interpolatedTime, ring);
} else {
//初始化,最小弧度等于宽度
// The minProgressArc is calculated from 0 to create an
// angle that matches the stroke width.
final float minProgressArc = getMinProgressArc(ring);
final float startingEndTrim = ring.getStartingEndTrim();
final float startingTrim = ring.getStartingStartTrim();
final float startingRotation = ring.getStartingRotation();
//更新颜色,后面有补充1
updateRingColor(interpolatedTime, ring);
//前50%,移动start trim
// Moving the start trim only occurs in the first 50% of a
// single ring animation
if (interpolatedTime <= START_TRIM_DURATION_OFFSET) {
//这个计算使得在这50%的时间段内可以完成0-1的动画展
// scale the interpolatedTime so that the full
// transformation from 0 - 1 takes place in the
// remaining time
final float scaledTime = (interpolatedTime)
/ (1.0f - START_TRIM_DURATION_OFFSET);
//对MATERIAL_INTERPOLATOR的补充2
final float startTrim = startingTrim
+ ((MAX_PROGRESS_ARC - minProgressArc) * MATERIAL_INTERPOLATOR
.getInterpolation(scaledTime));
ring.setStartTrim(startTrim);
}
//后50%移动end trim
// Moving the end trim starts after 50% of a single ring
// animation completes
if (interpolatedTime > END_TRIM_START_DELAY_OFFSET) {
// scale the interpolatedTime so that the full
// transformation from 0 - 1 takes place in the
// remaining time
final float minArc = MAX_PROGRESS_ARC - minProgressArc;
float scaledTime = (interpolatedTime - START_TRIM_DURATION_OFFSET)
/ (1.0f - START_TRIM_DURATION_OFFSET);
final float endTrim = startingEndTrim
+ (minArc * MATERIAL_INTERPOLATOR.getInterpolation(scaledTime));
ring.setEndTrim(endTrim);
}
//补充3
final float rotation = startingRotation + (0.25f * interpolatedTime);
ring.setRotation(rotation);
float groupRotation = ((FULL_ROTATION / NUM_POINTS) * interpolatedTime)
+ (FULL_ROTATION * (mRotationCount / NUM_POINTS));
setRotation(groupRotation);
}
}
};
animation.setRepeatCount(Animation.INFINITE);
animation.setRepeatMode(Animation.RESTART);
animation.setInterpolator(LINEAR_INTERPOLATOR);
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
mRotationCount = 0;
}
@Override
public void onAnimationEnd(Animation animation) {
// do nothing
}
@Override
public void onAnimationRepeat(Animation animation) {
ring.storeOriginals();
//设置下一个颜色
ring.goToNextColor();
//结束角设为开始角
ring.setStartTrim(ring.getEndTrim());
if (mFinishing) {
// finished closing the last ring from the swipe gesture; go
// into progress mode
mFinishing = false;
animation.setDuration(ANIMATION_DURATION);
ring.setShowArrow(false);
} else {
//+1求余
mRotationCount = (mRotationCount + 1) % (NUM_POINTS);
}
}
});
mAnimation = animation;
}
补充1更新环的颜色。
这里要看一下这个方法
/**
*在动画的最后25%的时间内,更新环的颜色
*环的新颜色是当前环的颜色到下一个颜色的渐变
*/
private void updateRingColor(float interpolatedTime, Ring ring) {
if (interpolatedTime > COLOR_START_DELAY_OFFSET) {
// scale the interpolatedTime so that the full
// transformation from 0 - 1 takes place in the
// remaining time
ring.setColor(evaluateColorChange((interpolatedTime - COLOR_START_DELAY_OFFSET)
/ (1.0f - COLOR_START_DELAY_OFFSET), ring.getStartingColor(),
ring.getNextColor()));
}
}
//看一个渐变方法值得学习
// Adapted from ArgbEvaluator.java
private int evaluateColorChange(float fraction, int startValue, int endValue) {
int startInt = (Integer) startValue;
int startA = (startInt >> 24) & 0xff;
int startR = (startInt >> 16) & 0xff;
int startG = (startInt >> 8) & 0xff;
int startB = startInt & 0xff;
int endInt = (Integer) endValue;
int endA = (endInt >> 24) & 0xff;
int endR = (endInt >> 16) & 0xff;
int endG = (endInt >> 8) & 0xff;
int endB = endInt & 0xff;
return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
(int)((startR + (int)(fraction * (endR - startR))) << 16) |
(int)((startG + (int)(fraction * (endG - startG))) << 8) |
(int)((startB + (int)(fraction * (endB - startB))));
}
补充2
这个interpolator
private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator();
/**
*和android.R.interpolator#fast_out_slow_in一致的interpolator
*用查找表格来表示贝塞尔曲线在(0.0)到(1,1)的控制点
*/
public class FastOutSlowInInterpolator extends LookupTableInterpolator {}
abstract class LookupTableInterpolator implements Interpolator {
private final float[] mValues;
private final float mStepSize;
public LookupTableInterpolator(float[] values) {
mValues = values;
mStepSize = 1f / (mValues.length - 1);
}
@Override
public float getInterpolation(float input) {
if (input >= 1.0f) {
return 1.0f;
}
if (input <= 0f) {
return 0f;
}
//计算索引,用length-2,以防后面的越界,后面+1了
// Calculate index - We use min with length - 2 to avoid IndexOutOfBoundsException when
// we lerp (linearly interpolate) in the return statement
int position = Math.min((int) (input * (mValues.length - 1)), mValues.length - 2);
//因为表离散,计算小的偏移
// Calculate values to account for small offsets as the lookup table has discrete values
float quantized = position * mStepSize;
float diff = input - quantized;
float weight = diff / mStepSize;
// Linearly interpolate between the table values
return mValues[position] + weight * (mValues[position + 1] - mValues[position]);
//(译注:不会翻译,意思就是说,因为计算所求的input比例的位置,和查询表中的位置有差值,要根据这个差值求得相差的值。具体做法是位置*步长,求得位置所占比例,(input比例-位置所占比例)/步长,得到权重。(下一位置的值-当前位置的值)*权重,得到差值)
//示例如下[0,2,4,6,8]
//要求0.8位的值
//position=min(0.8*4,3)=3
//quantized=3*1/4=0.75,
//diff=0.8-0.75=0.05
//weight=0.05/0.25=0.2
//也就是说,和0.8相近的是0.75位置的数6,它和6相差0.05个步长,这个步长的值为
//(8-6)*0.2=0.4
//所以0.8的值应为6.4
//并不是很难,主要是数学的知识,我的数学似乎也不是很好……
}
}
补充3
这里设置了2个旋转,一个是环自己,一个是动画的旋转。
如果注释掉动画的旋转,环在慢慢变短、变长的同时,还会慢慢地旋转。
后面这个动画的旋转在draw方法中调用
c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY());
private static final float FULL_ROTATION = 1080.0f;
/** The number of points in the progress "star". */
private static final float NUM_POINTS = 5f;
float groupRotation = ((FULL_ROTATION / NUM_POINTS) * interpolatedTime)+ (FULL_ROTATION * (mRotationCount / NUM_POINTS));
//不是很明白,可能是这样的意思,在5次内转1080度,当前要转的度数就等于转次的度数乘以时间,加上已经转了多少圈对应的度数,即1080/5*时间+1080*(次数/5)
原理分析
看了上面的源码,我们整理一下,原理也是很清晰呢。
用动画控制环,
在动画中,让环先变短,再变长。
同时,让环和drawable都旋转。
环
draw
/**
* Draw the progress spinner
*/
public void draw(Canvas c, Rect bounds) {
final RectF arcBounds = mTempBounds;
arcBounds.set(bounds);
arcBounds.inset(mStrokeInset, mStrokeInset);
final float startAngle = (mStartTrim + mRotation) * 360;
final float endAngle = (mEndTrim + mRotation) * 360;
float sweepAngle = endAngle - startAngle;
mPaint.setColor(mCurrentColor);
//画弧,主要实现
c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint);
//画三角
drawTriangle(c, startAngle, sweepAngle, bounds);
if (mAlpha < 255) {
mCirclePaint.setColor(mBackgroundColor);
mCirclePaint.setAlpha(255 - mAlpha);
//画圆,就是背景的
c.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), bounds.width() / 2,
mCirclePaint);
}
}
drawTriangle
//看不懂,哈哈。看了好几遍还是看不懂,等我以后学成再来看。
private void drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds) {
if (mShowArrow) {
if (mArrow == null) {
mArrow = new Path();
mArrow.setFillType(Path.FillType.EVEN_ODD);
} else {
mArrow.reset();
}
// Adjust the position of the triangle so that it is inset as
// much as the arc, but also centered on the arc.
float inset = (int) mStrokeInset / 2 * mArrowScale;
float x = (float) (mRingCenterRadius * Math.cos(0) + bounds.exactCenterX());
float y = (float) (mRingCenterRadius * Math.sin(0) + bounds.exactCenterY());
// Update the path each time. This works around an issue in SKIA
// where concatenating a rotation matrix to a scale matrix
// ignored a starting negative rotation. This appears to have
// been fixed as of API 21.
mArrow.moveTo(0, 0);
mArrow.lineTo(mArrowWidth * mArrowScale, 0);
mArrow.lineTo((mArrowWidth * mArrowScale / 2), (mArrowHeight
* mArrowScale));
mArrow.offset(x - inset, y);
mArrow.close();
// draw a triangle
mArrowPaint.setColor(mCurrentColor);
c.rotate(startAngle + sweepAngle - ARROW_OFFSET_ANGLE, bounds.exactCenterX(),
bounds.exactCenterY());
c.drawPath(mArrow, mArrowPaint);
}
}