Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android中PathMeasure仿支付寶支付動畫

Android中PathMeasure仿支付寶支付動畫

編輯:關於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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持本站。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved