溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Android中PathMeasure仿支付寶支付動(dòng)畫

發(fā)布時(shí)間:2020-08-26 20:53:07 來源:腳本之家 閱讀:205 作者:xing-java 欄目:移動(dòng)開發(fā)

前言

在 Android 自定義 View 中,Path 可能用的比較多,PathMeasure 可能用的比較少,就我而言,以前也沒有使用過 PathMeasure 這個(gè) api,看到別人用 PathMeasure 和 ValueAnimator 結(jié)合在一起完成了很好的動(dòng)畫效果,于是我也學(xué)習(xí)下 PathMeasure ,此處記錄下。

PathMeasure

構(gòu)造器:

Android中PathMeasure仿支付寶支付動(dòng)畫

forceClosed 含義:

// 創(chuàng)建一個(gè) Path 對(duì)象
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); // 繪制線段路徑

 }

當(dāng) pathMeasure.setPath(path,false) 時(shí):

Android中PathMeasure仿支付寶支付動(dòng)畫

Android中PathMeasure仿支付寶支付動(dòng)畫

當(dāng) pathMeasure.setPath(path,true) 時(shí):

Android中PathMeasure仿支付寶支付動(dòng)畫

Android中PathMeasure仿支付寶支付動(dòng)畫

可以看到:當(dāng) forceClosed = true 時(shí), path 進(jìn)行了閉合,相應(yīng)的 path 長度也變長了,即 算上了斜邊的長度。

仿支付寶支付動(dòng)畫 View - LoadingView

效果:

Android中PathMeasure仿支付寶支付動(dòng)畫

思路:

繪制對(duì)號(hào),叉號(hào),主要是通過 ValueAnimator 結(jié)合 getSegment() 不斷繪制新的弧形段,其中,叉號(hào)由兩個(gè) path 組成,在第一個(gè) path 繪制完成時(shí),需要調(diào)用 pathMeasure.nextContour() 跳轉(zhuǎn)到另一個(gè) path。

getSegment() 將獲取的片段填充到 destPath 中,在 Android 4.4 及以下版本中,不能繪制,需要調(diào)用 destPath.reset(),destPath.line(0,0)

LoadingView 完整代碼:

public class LoadingView extends View {

  private final int DEFAULT_COLOR = Color.BLACK; // 默認(rèn)圓弧顏色

  private final int DEFAULT_STROKE_WIDTH = dp2Px(2); // 默認(rèn)圓弧寬度

  private final boolean DEFAULT_IS_SHOW_RESULT = false; // 默認(rèn)不顯示加載結(jié)果

  private final int DEFAULT_VIEW_WIDTH = dp2Px(50); // 控件默認(rèn)寬度

  private final int DEFAULT_VIEW_HEIGHT = dp2Px(50); // 控件默認(rèn)高度

  private int color; // 圓弧顏色

  private int strokeWidth;  // 圓弧寬度

  private boolean isShowResult;  // 是否顯示加載結(jié)果狀態(tài)

  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; // 上方頂點(diǎn)

  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());


  }


  /**
   * 測(cè)量控件的寬高,當(dāng)測(cè)量模式不是精確模式時(shí),設(shè)置默認(rèn)寬高
   *
   * @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);//旋轉(zhuǎn)rotateDelta=4的弧長
      canvas.drawArc(rectF, startAngle, endAngle, false, paint);
      // endAngle += 6 放在 drawArc()后面,是防止剛進(jìn)入時(shí),突兀的顯示了一段圓弧
      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,  // 加載成功,顯示對(duì)號(hào)
    LOAD_FAILED   // 加載失敗,顯示叉號(hào)
  }


  /**
   * 創(chuàng)建畫筆
   *
   * @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 轉(zhuǎn)換成 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

以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI