Android中PathMeasure仿支付宝支付动画
前言
在 Android 自定义 View 中,Path 可能用的比较多,PathMeasure 可能用的比较少,就我而言,以前也没有使用过 PathMeasure 这个 api,看到别人用 PathMeasure 和 ValueAnimator 结合在一起完成了很好的动画效果,于是我也学习下 PathMeasure ,此处记录下。
PathMeasure
构造器:
forceClosed 含义:
// 创建一个 Path 对象 path = new Path(); path.moveTo(20,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);//旋转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,paint); pathMeasure.getSegment(0,successValue * pathMeasure.getLength(),true); canvas.drawPath(destPath,paint); } else if (stateEnum == StateEnum.LOAD_FAILED) { canvas.drawCircle(0,paint); pathMeasure.setPath(rightFailPath,false); pathMeasure.getSegment(0,rightFailValue * pathMeasure.getLength(),true); if (rightFailValue == 1) { pathMeasure.setPath(leftFailPath,false); pathMeasure.nextContour(); pathMeasure.getSegment(0,leftFailValue * pathMeasure.getLength(),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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。
以上是来客网为你收集整理的Android中PathMeasure仿支付宝支付动画全部内容,希望文章能够帮你解决Android中PathMeasure仿支付宝支付动画所遇到的程序开发问题。
如果觉得来客网网站内容还不错,欢迎将来客网网站推荐给程序员好友。