首页 > 系统 > Android > 正文

Android中PathMeasure仿支付宝支付动画

2019-12-12 02:18:38
字体:
来源:转载
供稿:网友

前言

在 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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持武林网。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表