版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wanggang514260663/article/details/85121855
首先先上效果图。
对于PathMeasure
不属性的可以先看看这篇文章安卓自定义View进阶-PathMeasure。
由于上面文章讲解的很清晰,所以这里不再说明PathMeasure
使用。
绘制搜索放大镜效果
首先需要确定放大镜的渐变过程
1、一个完整的放大镜(由一个圆+一个线条手柄)
2、放大镜逐渐从圆开始到手柄逐渐消失
3、从手柄终点开始绘制圆弧(loading效果)
所以其实完整的应该是一大一小两个圆环,在圆环中间有一段线条作为手柄。
之后通过getLength
获取圆弧的总长度,之后使用getSegment
获取需要绘制的部分,动态绘制,就可以出现渐变效果了。
下面是完整的代码。(代码只在于实现功能,切勿在项目直接使用)
public class SearchView extends View {
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Path searchPath;
private Path circlePath;
private PathMeasure pathMeasure;
private float searchAnimationValue = 0F;
private float circleAnimationValue = 0F;
private float reverseSearchAnimationValue = 0F;
private ValueAnimator reverseSearchAnimator;
private ValueAnimator circleAnimator;
private ValueAnimator searchAnimator;
private static final int TYPE_NORMAL = 1;
private static final int TYPE_PRE_LOADING = 2;
private static final int TYPE_LOADING = 3;
private static final int TYPE_RESET_NORMAL = 4;
private int currentType = TYPE_NORMAL;
private int width;
private int height;
private int radius;
public SearchView(Context context, AttributeSet attrs) {
super(context, attrs);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.parseColor("#FFFFFF"));
paint.setStrokeWidth(18);
paint.setStrokeCap(Paint.Cap.ROUND);
reverseSearchAnimator = ValueAnimator.ofFloat(1, 0);
reverseSearchAnimator.setDuration(2000);
reverseSearchAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
reverseSearchAnimationValue = (float) animation.getAnimatedValue();
invalidate();
}
});
reverseSearchAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
currentType = TYPE_NORMAL;
searchAnimator.start();
}
});
circleAnimator = ValueAnimator.ofFloat(0, 1);
circleAnimator.setDuration(2000);
circleAnimator.setRepeatCount(3);
circleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
circleAnimationValue = (float) animation.getAnimatedValue();
invalidate();
}
});
circleAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
currentType = TYPE_RESET_NORMAL;
reverseSearchAnimator.start();
}
});
searchAnimator = ValueAnimator.ofFloat(0, 1);
searchAnimator.setDuration(2000);
searchAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
searchAnimationValue = (float) animation.getAnimatedValue();
invalidate();
}
});
searchAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
currentType = TYPE_PRE_LOADING;
}
@Override
public void onAnimationEnd(Animator animation) {
currentType = TYPE_LOADING;
circleAnimator.start();
}
});
searchAnimator.start();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(widthMeasureSpec, MeasureSpec.makeMeasureSpec(widthMeasureSpec, MeasureSpec.EXACTLY));
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
radius = width / 4 * 3;
}
public void startAnimator() {
searchAnimator.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.parseColor("#db2d43"));
canvas.translate(getWidth() / 2, getHeight() / 2);
pathMeasure = new PathMeasure();
//绘制放大镜
searchPath = new Path();
searchPath.addArc(new RectF(-radius / 4, -radius / 4, radius / 4, radius / 4), 45, 359.9F);
circlePath = new Path();
circlePath.addArc(new RectF(-radius / 2, -radius / 2, radius / 2, radius / 2), 45, -359.9F);
pathMeasure.setPath(circlePath, false);
float[] pos = new float[2];
float[] tan = new float[2];
pathMeasure.getPosTan(0, pos, tan);
searchPath.lineTo(pos[0], pos[1]);
switch (currentType) {
case TYPE_NORMAL:
canvas.drawPath(searchPath, paint);
break;
case TYPE_PRE_LOADING:
Path dst = new Path();
pathMeasure.setPath(searchPath, false);
pathMeasure.getSegment(pathMeasure.getLength() * searchAnimationValue, pathMeasure.getLength(), dst, true);
canvas.drawPath(dst, paint);
break;
case TYPE_LOADING:
Path dst2 = new Path();
pathMeasure.setPath(circlePath, false);
float stop = pathMeasure.getLength() * circleAnimationValue;
float start = (float) (stop - ((0.5 - Math.abs(circleAnimationValue - 0.5)) * getWidth()));
pathMeasure.getSegment(start, stop, dst2, true);
canvas.drawPath(dst2, paint);
break;
case TYPE_RESET_NORMAL:
Path dst3 = new Path();
pathMeasure.setPath(searchPath, false);
pathMeasure.getSegment(pathMeasure.getLength() * reverseSearchAnimationValue, pathMeasure.getLength(), dst3, true);
canvas.drawPath(dst3, paint);
break;
}
}
}
绘制圆弧计时器效果
public class ScrollBoxView extends View {
private int width, height;
private static final int TYPE_NORMAL = 1;
private static final int TYPE_LOADING = 2;
private static final int TYPE_REVERSE_LOADING = 3;
private int stateType = TYPE_NORMAL;
private Paint bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint ovalPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint ballPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Path ovalLeftPath = new Path();
private Path ovalRightPath = new Path();
private Path ballPath = new Path();
private int radius;
private float ballHeight;
private int ballMaxRadius;
private ValueAnimator ovalAnimator;
private ValueAnimator reverseOvalAnimator;
private float ovalAnimatorValue;
private int reverseOvalAnimatorValue;
private Matrix mMatrix;
private int currentBallRadius;
private PathMeasure pathOvalMeasure = new PathMeasure();
public ScrollBoxView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
bgPaint.setColor(Color.parseColor("#207e82"));
bgPaint.setStrokeWidth(10F);
bgPaint.setStyle(Paint.Style.STROKE);
ovalPaint.setColor(Color.parseColor("#FFFFFF"));
ovalPaint.setStrokeWidth(10F);
ovalPaint.setStyle(Paint.Style.STROKE);
ballPaint.setColor(Color.WHITE);
ballPaint.setStyle(Paint.Style.FILL);
ballPaint.setStrokeCap(Paint.Cap.ROUND);
reverseOvalAnimator = ValueAnimator.ofInt(0, 255);
reverseOvalAnimator.setDuration(4000);
reverseOvalAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
reverseOvalAnimatorValue = (int) animation.getAnimatedValue();
invalidate();
}
});
reverseOvalAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
stateType = TYPE_NORMAL;
ovalAnimator.start();
}
});
ovalAnimator = ValueAnimator.ofFloat(0, 1);
ovalAnimator.setDuration(4000);
ovalAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
ovalAnimatorValue = (float) animation.getAnimatedValue();
invalidate();
}
});
ovalAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
stateType = TYPE_LOADING;
}
@Override
public void onAnimationEnd(Animator animation) {
stateType = TYPE_REVERSE_LOADING;
reverseOvalAnimator.start();
}
});
ovalAnimator.start();
mMatrix = new Matrix();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(widthMeasureSpec, MeasureSpec.makeMeasureSpec(widthMeasureSpec, MeasureSpec.EXACTLY));
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
width = w;
height = h;
radius = width * 3 / 4 / 2;
ballHeight = width / 5 * 2;
ballMaxRadius = radius / 8 / 2;
currentBallRadius = ballMaxRadius;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getWidth() / 2, getHeight() / 2);
canvas.rotate(-270);
mMatrix.reset();
canvas.drawColor(Color.parseColor("#1d5464"));
ovalLeftPath.addRoundRect(new RectF(-radius, -radius / 2, radius, radius / 2), radius / 2, radius / 2, Path.Direction.CCW);
ovalRightPath.addRoundRect(new RectF(-radius, -radius / 2, radius, radius / 2), radius / 2, radius / 2, Path.Direction.CW);
switch (stateType) {
case TYPE_NORMAL:
canvas.drawPath(ovalLeftPath, ovalPaint);
canvas.drawCircle(-ballHeight / 2, 0, ballMaxRadius, ballPaint);
break;
case TYPE_LOADING:
pathOvalMeasure.setPath(ovalLeftPath, false);
Path dst = new Path();
pathOvalMeasure.getSegment(pathOvalMeasure.getLength() / 2 * ovalAnimatorValue, pathOvalMeasure.getLength() / 2, dst, true);
pathOvalMeasure.setPath(ovalRightPath, false);
Path dst2 = new Path();
pathOvalMeasure.getSegment(pathOvalMeasure.getLength() / 2 * ovalAnimatorValue, pathOvalMeasure.getLength() / 2, dst2, true);
canvas.drawPath(ovalLeftPath, bgPaint);
canvas.drawPath(dst, ovalPaint);
canvas.drawPath(dst2, ovalPaint);
if (-ballHeight / 2 + ballHeight * ovalAnimatorValue > ballHeight / 4) {
ballPaint.setAlpha((int) (255 - ovalAnimatorValue / 1.5 * 255));
}
if (ovalAnimatorValue > 0.99F) {
ballPaint.setAlpha((int) (255 - ovalAnimatorValue * 255));
}
canvas.drawCircle(-ballHeight / 2 + ballHeight * ovalAnimatorValue, 0, currentBallRadius, ballPaint);
break;
case TYPE_REVERSE_LOADING:
ovalPaint.setAlpha(reverseOvalAnimatorValue);
canvas.drawPath(ovalLeftPath, ovalPaint);
ballPaint.setAlpha(reverseOvalAnimatorValue);
canvas.drawCircle(-ballHeight / 2, 0, ballMaxRadius, ballPaint);
break;
}
}
}