这里有新鲜出炉的精品教程,程序狗速度看过来!
Android是一种基于Linux的自由及开放源代码的操作系统,主要使用于移动设备,如智能手机和平板电脑,由Google公司和开放手机联盟领导及开发。尚未有统一中文名称,中国大陆地区较多人使用“安卓”或“安致”。
这篇文章主要为大家详细介绍了Android中PathMeasure仿支付宝支付动画,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
前言
在 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
来源: http://www.phperz.com/article/17/0828/344197.html