欢迎来到代码驿站!

Android代码

当前位置:首页 > 移动开发 > Android代码

Android中PathMeasure仿支付宝支付动画

时间:2021-07-08 14:48:03|栏目:Android代码|点击:

前言

在 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

上一篇:Android实现京东秒杀界面

栏    目:Android代码

下一篇:Android实现隐藏手机底部虚拟按键

本文标题:Android中PathMeasure仿支付宝支付动画

本文地址:http://www.codeinn.net/misctech/154284.html

推荐教程

广告投放 | 联系我们 | 版权申明

重要申明:本站所有的文章、图片、评论等,均由网友发表或上传并维护或收集自网络,属个人行为,与本站立场无关。

如果侵犯了您的权利,请与我们联系,我们将在24小时内进行处理、任何非本站因素导致的法律后果,本站均不负任何责任。

联系QQ:914707363 | 邮箱:codeinn#126.com(#换成@)

Copyright © 2020 代码驿站 版权所有