前言
在 Android 自定义 View 中,Path 可能用的比较多,PathMeasure 可能用的比较少,就我而言,以前也没有使用过 PathMeasure 这个 api,看到别人用 PathMeasure 和 ValueAnimator 结合在一起完成了很好的动画效果,于是我也学习下 PathMeasure ,此处记录下。
PathMeasure
构造器:
forceClosed 含义:
// 创建一个 Path 对象 path = new Path(); path.moveTo(20, 20); path.lineTo(200, 20); path.lineTo(200, 400);
在onDraw(Canvas canvas) 中绘制 path
@Override protected void onDraw(Canvas canvas) { destPath.reset(); destPath.lineTo(0, 0); pathMeasure.setPath(path, true); Log.e("debug", "PathMeasure.getLength() = " + pathMeasure.getLength()); pathMeasure.getSegment(0, pathMeasure.getLength() * curValue, destPath, true); canvas.drawPath(destPath, paint); // 绘制线段路径 }
当 pathMeasure.setPath(path,false) 时:
当 pathMeasure.setPath(path,true) 时:
可以看到:当 forceClosed = true 时, path 进行了闭合,相应的 path 长度也变长了,即 算上了斜边的长度。
仿支付宝支付动画 View - LoadingView
效果:
思路:
绘制对号,叉号,主要是通过 ValueAnimator 结合 getSegment() 不断绘制新的弧形段,其中,叉号由两个 path 组成,在第一个 path 绘制完成时,需要调用 pathMeasure.nextContour() 跳转到另一个 path。
getSegment() 将获取的片段填充到 destPath 中,在 Android 4.4 及以下版本中,不能绘制,需要调用 destPath.reset(),destPath.line(0,0)
LoadingView 完整代码:
public class LoadingView extends View { private final int DEFAULT_COLOR = Color.BLACK; // 默认圆弧颜色 private final int DEFAULT_STROKE_WIDTH = dp2Px(2); // 默认圆弧宽度 private final boolean DEFAULT_IS_SHOW_RESULT = false; // 默认不显示加载结果 private final int DEFAULT_VIEW_WIDTH = dp2Px(50); // 控件默认宽度 private final int DEFAULT_VIEW_HEIGHT = dp2Px(50); // 控件默认高度 private int color; // 圆弧颜色 private int strokeWidth; // 圆弧宽度 private boolean isShowResult; // 是否显示加载结果状态 private Paint paint; // 画笔 private int mWidth; // 控件宽度 private int mHeight; // 控件高度 private int radius; // 圆弧所在圆的半径 private int halfStrokeWidth; // 画笔宽度的一半 private int rotateDelta = 4; private int curAngle = 0; private int minAngle = -90; private int startAngle = -90; // 上方顶点 private int endAngle = 0; private RectF rectF; private StateEnum stateEnum = StateEnum.LOADING; private Path successPath; private Path rightFailPath; private Path leftFailPath; private ValueAnimator successAnimator; private ValueAnimator rightFailAnimator; private ValueAnimator leftFailAnimator; private PathMeasure pathMeasure; private float successValue; private float rightFailValue; private float leftFailValue; private Path destPath; private AnimatorSet animatorSet; public LoadingView(Context context) { this(context, null); } public LoadingView(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } private void init(Context context, AttributeSet attrs) { TypedArray typedArray = null; try { typedArray = context.obtainStyledAttributes(attrs, R.styleable.LoadingView); color = typedArray.getColor(R.styleable.LoadingView_color, DEFAULT_COLOR); strokeWidth = (int) typedArray.getDimension(R.styleable.LoadingView_storkeWidth, DEFAULT_STROKE_WIDTH); isShowResult = typedArray.getBoolean(R.styleable.LoadingView_isShowResult, DEFAULT_IS_SHOW_RESULT); } catch (Exception e) { e.printStackTrace(); } finally { if (typedArray != null) { typedArray.recycle(); } } paint = createPaint(color, strokeWidth, Paint.Style.STROKE); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; Log.i("debug", "getMeasureWidth() = " + getMeasuredWidth()); Log.i("debug", "getMeasureHeight() = " + getMeasuredHeight()); radius = Math.min(mWidth, mHeight) / 2; halfStrokeWidth = strokeWidth / 2; rectF = new RectF(halfStrokeWidth - radius, halfStrokeWidth - radius, radius - halfStrokeWidth, radius - halfStrokeWidth); // success path successPath = new Path(); successPath.moveTo(-radius * 2 / 3f, 0f); successPath.lineTo(-radius / 8f, radius / 2f); successPath.lineTo(radius / 2, -radius / 3); // fail path ,right top to left bottom rightFailPath = new Path(); rightFailPath.moveTo(radius / 3f, -radius / 3f); rightFailPath.lineTo(-radius / 3f, radius / 3f); // fail path, left top to right bottom leftFailPath = new Path(); leftFailPath.moveTo(-radius / 3f, -radius / 3f); leftFailPath.lineTo(radius / 3f, radius / 3f); pathMeasure = new PathMeasure(); destPath = new Path(); initSuccessAnimator(); initFailAnimator(); } private void initSuccessAnimator() { // pathMeasure.setPath(successPath, false); successAnimator = ValueAnimator.ofFloat(0, 1f); successAnimator.setDuration(1000); successAnimator.setInterpolator(new LinearInterpolator()); successAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { successValue = (float) animation.getAnimatedValue(); invalidate(); } }); } private void initFailAnimator() { // pathMeasure.setPath(rightFailPath, false); rightFailAnimator = ValueAnimator.ofFloat(0, 1f); rightFailAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { rightFailValue = (float) animation.getAnimatedValue(); invalidate(); } }); // pathMeasure.setPath(leftFailPath, false); leftFailAnimator = ValueAnimator.ofFloat(0, 1f); leftFailAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { leftFailValue = (float) animation.getAnimatedValue(); invalidate(); } }); animatorSet = new AnimatorSet(); animatorSet.play(leftFailAnimator).after(rightFailAnimator); animatorSet.setDuration(500); animatorSet.setInterpolator(new LinearInterpolator()); } /** * 测量控件的宽高,当测量模式不是精确模式时,设置默认宽高 * * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) { widthMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_VIEW_WIDTH, MeasureSpec.EXACTLY); } if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) { heightMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_VIEW_HEIGHT, MeasureSpec.EXACTLY); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onDraw(Canvas canvas) { canvas.save(); canvas.translate(mWidth / 2, mHeight / 2); destPath.reset(); destPath.lineTo(0, 0); // destPath if (stateEnum == StateEnum.LOADING) { if (endAngle >= 300 || startAngle > minAngle) { startAngle += 6; if (endAngle > 20) { endAngle -= 6; } } if (startAngle > minAngle + 300) { minAngle = startAngle; endAngle = 20; } canvas.rotate(curAngle += rotateDelta, 0, 0);//旋转rotateDelta=4的弧长 canvas.drawArc(rectF, startAngle, endAngle, false, paint); // endAngle += 6 放在 drawArc()后面,是防止刚进入时,突兀的显示了一段圆弧 if (startAngle == minAngle) { endAngle += 6; } invalidate(); } if (isShowResult) { if (stateEnum == StateEnum.LOAD_SUCCESS) { pathMeasure.setPath(successPath, false); canvas.drawCircle(0, 0, radius - halfStrokeWidth, paint); pathMeasure.getSegment(0, successValue * pathMeasure.getLength(), destPath, true); canvas.drawPath(destPath, paint); } else if (stateEnum == StateEnum.LOAD_FAILED) { canvas.drawCircle(0, 0, radius - halfStrokeWidth, paint); pathMeasure.setPath(rightFailPath, false); pathMeasure.getSegment(0, rightFailValue * pathMeasure.getLength(), destPath, true); if (rightFailValue == 1) { pathMeasure.setPath(leftFailPath, false); pathMeasure.nextContour(); pathMeasure.getSegment(0, leftFailValue * pathMeasure.getLength(), destPath, true); } canvas.drawPath(destPath, paint); } } canvas.restore(); } public void updateState(StateEnum stateEnum) { this.stateEnum = stateEnum; if (stateEnum == StateEnum.LOAD_SUCCESS) { successAnimator.start(); } else if (stateEnum == StateEnum.LOAD_FAILED) { animatorSet.start(); } } public enum StateEnum { LOADING, // 正在加载 LOAD_SUCCESS, // 加载成功,显示对号 LOAD_FAILED // 加载失败,显示叉号 } /** * 创建画笔 * * @param color 画笔颜色 * @param strokeWidth 画笔宽度 * @param style 画笔样式 * @return */ private Paint createPaint(int color, int strokeWidth, Paint.Style style) { Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setStrokeCap(Paint.Cap.ROUND); paint.setStrokeJoin(Paint.Join.ROUND); paint.setColor(color); paint.setStrokeWidth(strokeWidth); paint.setStyle(style); return paint; } /** * dp 转换成 px * * @param dpValue * @return */ private int dp2Px(int dpValue) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics()); } }
github : https://github.com/xing16/LoadingView
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持呐喊教程。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:notice#nhooo.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。