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

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

(0)

相关推荐

  • Android开发之实现GridView支付宝九宫格

    先给大家展示下关于仿支付宝钱包首页中带有分割线的gridview,俗称九宫格 的效果图,怎么样是不是和你想象的一样啊.在你的预料之中就继续访问以下代码内容吧. 我们都知道ListView设置分割线是非常容易的,设置ListView的分割线颜色和宽度,只需要在布局中定义android:divider和android:dividerHeight属性即可.而GridView并没有这样的属性和方法,那我们改如何来做呢? 我们小编在做这个效果之前,也参考了其他的一些方案,比如说定义一个自定义的GridVi

  • android仿微信支付宝的支付密码输入框示例

    大家好,我是狸小华,萌汉子一枚.今天给大家带来的是仿微信/支付宝的密码输入框.这个效果也出来有一段时间了,所以搜索一下还是有不少的网友实现,但是,但是!经过一番查看后,我发现他们的实现分为两大类. 一,直接继承EditText,然后在ondraw里面做文章:二,EditText外面包一个viewGroup.我不喜欢这两种实现方式,觉着有些臃肿了,所以我详细介绍下我的实现方式:直接继承View,获取用户的输入,然后draw出来. 我们实现的是上面的密码输入框,这个键盘...系统自带的哦,调用用户输

  • Android自定义View仿支付宝输入六位密码功能

    跟选择银行卡界面类似,也是用一个PopupWindow,不过输入密码界面是一个自定义view,当输入六位密码完成后用回调在Activity中获取到输入的密码并以Toast显示密码.效果图如下: 自定义view布局效果图及代码如下: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/

  • Android中RecyclerView布局代替GridView实现类似支付宝的界面

    单纯使用GridView 通用的两种给GridView 添加分割线的方法:http://stackoverflow.com/questions/7132030/android-gridview-draw-dividers 给Gridview 添加分割线,也就是实现网格布局,不清楚谷歌为什么没有给Gridview 添加一个类似 ListView 的Divider 属性,因此就需要我们自己去添加分割线, 目前两种方法,第一种是 利用GridView 的  android:horizontalSpac

  • Android波纹扩散效果之仿支付宝咻一咻功能实现波纹扩散特效

    今年春节晚会没看尽兴,被支付宝集福给添了一段插曲,朋友们都在那数定时间段不停的咻一咻,哇,我咻到一个敬业福,不可能的,哈哈.那么咻一咻功能基于程序代码是怎么实现的呢?下面我们小编给大家分享本教程帮助大家学习Android波纹扩散效果之仿支付宝咻一咻功能实现波纹扩散特效,具体内容如下所示: 先来看看这个效果 这是我的在Only上添加的效果,说实话,Only现在都还只是半成品,台面都上不了,怪自己技术不行,也太懒了 PS:这个view也是我模仿了人家的效果,参考了人家的思路写的,不是纯手撸,罪过罪过

  • Android支付宝支付封装代码

    在做Android支付的时候肯定会用到支付宝支付, 根据官方给出的demo做起来非常费劲,所以我们需要一次简单的封装. 封装的代码也很简单,就是将官网给的demo提取出一个类来方便使用. public class Alipay { // 商户PID public static final String PARTNER = "123456789"; // 商户收款账号 public static final String SELLER = "qibin0506@gmail.co

  • Android app第三方支付宝支付接入教程

    支付宝的接入相对比较简单,看看支付宝官网的文档基本都能搞定,但是切记一点让你们的后台也要搞清楚支付宝的流程,重中之重. 1.注意事项 开发前一定要阅读支付宝官方文档 强烈建议签名等处理在后台处理,我这个是测试是在自己本地写的,不要吐槽 想获取支付宝合作商户ID,及支付宝公钥请点击支付宝链接,生成密钥及PKCS8转码工具在文档中 添加Android.permission.INTERNET权限和android.permission.ACCESS_NETWORK_STATE权限 要导入支付宝的包 2.

  • 新版Android studio导入微信支付和支付宝官方Demo问题解决大全

    最近项目要用到支付宝支付和微信支付,本想使用第三方支付框架ping++或者BeeCloud的,但是由于他们的收费问题,让我望而却步,而且公司给了相应的公钥.私钥和APPID等,所以就用下开放平台的呗. 进去倒腾了半天才发现一堆问题,Oh,我的天.完全不知所云,百度谷歌了一堆,都没找到足够的解决方案.好吧,自己来,这里也就把相关的东西分享给大家,如果有类似问题的可以考虑使用一下,不足的地方大牛就别喷了. 微信开放平台有文档和SDK,大家可以去自己查看下载,火箭:https://open.weixi

  • Android仿支付宝支付从底部弹窗效果

    我们再用支付宝支付的时候,会从底部弹上来一个对话框,让我们选择支付方式等等,今天我们就来慢慢实现这个功能 效果图 实现 主界面很简单,就是一个按钮,点击后跳到支付详情的Fragment中 package com.example.hfs.alipayuidemo; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.wi

  • Android支付宝和微信支付集成

    场景 随着移动支付的兴起,在我们的app'中,会经常有集成支付的需求.这时候一般都会采用微信和支付宝的sdk 来集成 (一)支付宝支付 在使用支付宝支付的过程中,我们是在服务器端生成订单,客户端访问接口,并得到订单信息,调用接口支付,支付成功后支付宝会分别 异步调用服务器端,并向客户端返回支付结果. 开发步骤: ①注册支付宝账号--进行实名认证--提交审核资料--审核通过 支付宝无线快捷支付接口: b.alipay.com/order/productDetail.htm?productId=20

随机推荐