话不多说,先洗脑,安利!!!https://github.com/ddwhan0123/Useful-Open-Source-Android 旅游都在更啊!!
这些天都在浪几乎没撸代码,然后今天下午找了个下午茶时间捯饬了个自定义View来实现 很多APP都有却没怎么公开的一个“进度通知的View”
实现power by:https://dribbble.com/LeslyPyram
先上下原设计:
用圆+线条+颜色的变化来告知用户你现在的物件到哪了(这是我自己买的东西的截图,看实现就好)
再贴下,今天实现的效果:
颜色不管,别的大致就差不多“翻版了”(间距和球体空心实心只是个人爱好问题,这不是模仿难点)
来看下项目目录
东西很简单,就一个控件就能搞定,先来看看如何使用
<stepperindicator.com.stepperindicatordemo.MyStepperIndicator
android:id="@+id/mySI"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/button"
android:layout_marginLeft="32dp"
android:layout_marginRight="32dp"
android:layout_marginTop="32dp"
app:stpi_stepCount="5" />
在布局文件里设置下总的“进程数”就可以了。
如果你要设置球的颜色,大小,边框粗细,如下:
app:stpi_circleColor
app:stpi_circleRadius
app:stpi_circleStrokeWidth
当然,你也有其他的自定义选项,这里不做全部介绍了,你可以看 attrs.xml文件内的标签,命名很规范,一看就懂
看完怎么用,来看下怎么实现的
public class MyStepperIndicator extends View {
private static final float EXPAND_MARK = 1.3f;
private static final int DEFAULT_ANIMATION_DURATION = 250;
private Resources resources;
private Paint circlePaint;
private Paint linePaint, lineDonePaint, lineDoneAnimatedPaint;
private Paint indicatorPaint;
private float circleRadius;
private float animProgress;
private float checkRadius;
private float animIndicatorRadius;
private float indicatorRadius;
private float lineLength;
private float lineMargin;
private float animCheckRadius;
private int animDuration;
private int stepCount;
private int currentStep;
private Bitmap doneIcon;
private float[] indicators;
private List<Path> linePathList = new ArrayList<>();
private AnimatorSet animatorSet;
private ObjectAnimator lineAnimator, indicatorAnimator, checkAnimator;
private int previousStep;
//构造函数
public MyStepperIndicator(Context context) {
this(context, null);
}
public MyStepperIndicator(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyStepperIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public MyStepperIndicator(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
//获取项目资源用
resources = getResources();
// 默认值
int defaultCircleColor = ContextCompat.getColor(context, R.color.stpi_default_circle_color);
float defaultCircleRadius = resources.getDimension(R.dimen.stpi_default_circle_radius);
float defaultCircleStrokeWidth = resources.getDimension(R.dimen.stpi_default_circle_stroke_width);
int defaultIndicatorColor = ContextCompat.getColor(context, R.color.stpi_default_indicator_color);
float defaultIndicatorRadius = resources.getDimension(R.dimen.stpi_default_indicator_radius);
float defaultLineStrokeWidth = resources.getDimension(R.dimen.stpi_default_line_stroke_width);
float defaultLineMargin = resources.getDimension(R.dimen.stpi_default_line_margin);
int defaultLineColor = ContextCompat.getColor(context, R.color.stpi_default_line_color);
int defaultLineDoneColor = ContextCompat.getColor(context, R.color.stpi_default_line_done_color);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.StepperIndicator, defStyleAttr, 0);
circlePaint = new Paint();
circlePaint.setStrokeWidth(a.getDimension(R.styleable.StepperIndicator_stpi_circleStrokeWidth, defaultCircleStrokeWidth));
circlePaint.setStyle(Paint.Style.STROKE);
circlePaint.setColor(a.getColor(R.styleable.StepperIndicator_stpi_circleColor, defaultCircleColor));
circlePaint.setAntiAlias(true);
indicatorPaint = new Paint(circlePaint);
indicatorPaint.setStyle(Paint.Style.FILL);
indicatorPaint.setColor(a.getColor(R.styleable.StepperIndicator_stpi_indicatorColor, defaultIndicatorColor));
indicatorPaint.setAntiAlias(true);
linePaint = new Paint();
linePaint.setStrokeWidth(a.getDimension(R.styleable.StepperIndicator_stpi_lineStrokeWidth, defaultLineStrokeWidth));
linePaint.setStrokeCap(Paint.Cap.ROUND);
linePaint.setStyle(Paint.Style.STROKE);
linePaint.setColor(a.getColor(R.styleable.StepperIndicator_stpi_lineColor, defaultLineColor));
linePaint.setAntiAlias(true);
lineDonePaint = new Paint(linePaint);
lineDonePaint.setColor(a.getColor(R.styleable.StepperIndicator_stpi_lineDoneColor, defaultLineDoneColor));
lineDoneAnimatedPaint = new Paint(lineDonePaint);
circleRadius = a.getDimension(R.styleable.StepperIndicator_stpi_circleRadius, defaultCircleRadius);
checkRadius = circleRadius + circlePaint.getStrokeWidth() / 2f;
indicatorRadius = a.getDimension(R.styleable.StepperIndicator_stpi_indicatorRadius, defaultIndicatorRadius);
animIndicatorRadius = indicatorRadius;
animCheckRadius = checkRadius;
lineMargin = a.getDimension(R.styleable.StepperIndicator_stpi_lineMargin, defaultLineMargin);
setStepCount(a.getInteger(R.styleable.StepperIndicator_stpi_stepCount, 2));
animDuration = a.getInteger(R.styleable.StepperIndicator_stpi_animDuration, DEFAULT_ANIMATION_DURATION);
a.recycle();
doneIcon = BitmapFactory.decodeResource(resources, R.drawable.ic_done_white_18dp);
if (isInEditMode())
currentStep = Math.max((int) Math.ceil(stepCount / 2f), 1);
}
public void setStepCount(int stepCount) {
if (stepCount < 2)
throw new IllegalArgumentException("stepCount must be >= 2");
this.stepCount = stepCount;
currentStep = 0;
compute();
invalidate();
}
private void compute() {
indicators = new float[stepCount];
linePathList.clear();
float startX = circleRadius * EXPAND_MARK + circlePaint.getStrokeWidth() / 2f;
// 计算指标和线路长度的位置
float divider = (getMeasuredWidth() - startX * 2f) / (stepCount - 1);
lineLength = divider - (circleRadius * 2f + circlePaint.getStrokeWidth()) - (lineMargin * 2);
// 计算圈和线的位置
for (int i = 0; i < indicators.length; i++)
indicators[i] = startX + divider * i;
for (int i = 0; i < indicators.length - 1; i++) {
float position = ((indicators[i] + indicators[i + 1]) / 2) - lineLength / 2;
final Path linePath = new Path();
linePath.moveTo(position, getMeasuredHeight() / 2);
linePath.lineTo(position + lineLength, getMeasuredHeight() / 2);
linePathList.add(linePath);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Log.d("--->onSizeChanged", "w = " + w + " h = " + h + " oldw = " + oldw + " oldh = " + oldh);
compute();
}
//控件绘图
@SuppressWarnings("ConstantConditions")
@Override
protected void onDraw(Canvas canvas) {
float centerY = getMeasuredHeight() / 2f;
// 目前绘制动画步骤n-1 n,或从n + 1到n
boolean inAnimation = false;
boolean inLineAnimation = false;
boolean inIndicatorAnimation = false;
boolean inCheckAnimation = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
inAnimation = animatorSet != null && animatorSet.isRunning();
inLineAnimation = lineAnimator != null && lineAnimator.isRunning();
inIndicatorAnimation = indicatorAnimator != null && indicatorAnimator.isRunning();
inCheckAnimation = checkAnimator != null && checkAnimator.isRunning();
}
boolean drawToNext = previousStep == currentStep - 1;
boolean drawFromNext = previousStep == currentStep + 1;
for (int i = 0; i < indicators.length; i++) {
final float indicator = indicators[i];
boolean drawCheck = i < currentStep || (drawFromNext && i == currentStep);
// 画圈
canvas.drawCircle(indicator, centerY, circleRadius, circlePaint);
// 如果当前步骤,或回来,下一步还有返回动画
if ((i == currentStep && !drawFromNext) || (i == previousStep && drawFromNext && inAnimation)) {
// Draw animated indicator
canvas.drawCircle(indicator, centerY, animIndicatorRadius, indicatorPaint);
}
// 画对勾
if (drawCheck) {
float radius = checkRadius;
if ((i == previousStep && drawToNext)
|| (i == currentStep && drawFromNext))
radius = animCheckRadius;
canvas.drawCircle(indicator, centerY, radius, indicatorPaint);
if (!isInEditMode()) {
if ((i != previousStep && i != currentStep) || (!inCheckAnimation && !(i == currentStep && !inAnimation)))
canvas.drawBitmap(doneIcon, indicator - (doneIcon.getWidth() / 2), centerY - (doneIcon.getHeight() / 2), null);
}
}
// 画线
if (i < linePathList.size()) {
if (i >= currentStep) {
canvas.drawPath(linePathList.get(i), linePaint);
if (i == currentStep && drawFromNext && (inLineAnimation || inIndicatorAnimation)) // Coming back from n+1
canvas.drawPath(linePathList.get(i), lineDoneAnimatedPaint);
} else {
if (i == currentStep - 1 && drawToNext && inLineAnimation) {
// Going to n+1
canvas.drawPath(linePathList.get(i), linePaint);
canvas.drawPath(linePathList.get(i), lineDoneAnimatedPaint);
} else
canvas.drawPath(linePathList.get(i), lineDonePaint);
}
}
}
}
//控件尺寸
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int desiredHeight = (int) Math.ceil((circleRadius * EXPAND_MARK * 2) + circlePaint.getStrokeWidth());
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width = widthMode == MeasureSpec.EXACTLY ? widthSize : getSuggestedMinimumWidth();
int height = heightMode == MeasureSpec.EXACTLY ? heightSize : desiredHeight;
setMeasuredDimension(width, height);
}
public int getStepCount() {
return stepCount;
}
public int getCurrentStep() {
return currentStep;
}
/** * 设置现在的位置 * * @param currentStep a value between 0 (inclusive) and stepCount (inclusive) */
@UiThread
public void setCurrentStep(int currentStep) {
if (currentStep < 0 || currentStep > stepCount)
throw new IllegalArgumentException("Invalid step value " + currentStep);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (animatorSet != null)
animatorSet.cancel();
animatorSet = null;
lineAnimator = null;
indicatorAnimator = null;
if (currentStep == this.currentStep + 1) {
previousStep = this.currentStep;
animatorSet = new AnimatorSet();
// 画新的线
lineAnimator = ObjectAnimator.ofFloat(MyStepperIndicator.this, "animProgress", 1.0f, 0.0f);
// 标记
checkAnimator = ObjectAnimator
.ofFloat(MyStepperIndicator.this, "animCheckRadius", indicatorRadius, checkRadius * EXPAND_MARK, checkRadius);
// 最后到达目标"进度"
animIndicatorRadius = 0;
indicatorAnimator = ObjectAnimator
.ofFloat(MyStepperIndicator.this, "animIndicatorRadius", 0f, indicatorRadius * 1.4f, indicatorRadius);
animatorSet.play(lineAnimator).with(checkAnimator).before(indicatorAnimator);
} else if (currentStep == this.currentStep - 1) {
//回退操作
previousStep = this.currentStep;
animatorSet = new AnimatorSet();
indicatorAnimator = ObjectAnimator.ofFloat(MyStepperIndicator.this, "animIndicatorRadius", indicatorRadius, 0f);
animProgress = 1.0f;
lineDoneAnimatedPaint.setPathEffect(null);
lineAnimator = ObjectAnimator.ofFloat(MyStepperIndicator.this, "animProgress", 0.0f, 1.0f);
animCheckRadius = checkRadius;
checkAnimator = ObjectAnimator.ofFloat(MyStepperIndicator.this, "animCheckRadius", checkRadius, indicatorRadius);
animatorSet.playSequentially(indicatorAnimator, lineAnimator, checkAnimator);
}
if (animatorSet != null) {
lineAnimator.setDuration(Math.min(500, animDuration));
lineAnimator.setInterpolator(new DecelerateInterpolator());
indicatorAnimator.setDuration(lineAnimator.getDuration() / 2);
checkAnimator.setDuration(lineAnimator.getDuration() / 2);
animatorSet.start();
}
}
this.currentStep = currentStep;
invalidate();
}
public void setAnimIndicatorRadius(float animIndicatorRadius) {
this.animIndicatorRadius = animIndicatorRadius;
invalidate();
}
public void setAnimCheckRadius(float animCheckRadius) {
this.animCheckRadius = animCheckRadius;
invalidate();
}
}
重要步骤已经写在里面了,这里做下详细解释
1.初始化一系列需要的参数(从xml获得或者通过set方法)
2.位置计算
3.在draw方法里根据获取的参数进行一系列的绘制,绘制内容为 点,圆 线。
然后就是一系列逻辑和动画了(实现概念很基础,但是基础很重要)
setCurrentStep方法为最为关键的动画实现方法。根据当前“进度”值currentStep作为逻辑判断的标准。
源码地址:https://github.com/ddwhan0123/BlogSample/blob/master/StepperIndicatorDemo/StepperIndicatorDemo.zip?raw=true
有问题可以微信我(活人)
谢谢阅读