动画2-path动画

path动画有两种 1 view按照指定的路径运动 2动态绘path

ps:1需要用到获取控件位置的方法,2需要用到path相关知识(参考文章“”绘制技巧”中view基础知识)
获取控件的定位

/**
     * 获取控件位置的两种方法
     */
    private void location() {

        int[] startLocation = new int[2];
        iv.getLocationInWindow(startLocation);
        tvs.setText("left="+startLocation[0]+"top="+startLocation[1]);//包含了satusbar的高度

        iv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
             @Override
             public void onGlobalLayout() {
                 // 获取减少图标的位置
                    tvs.setText("left="+iv.getLeft()+"top="+iv.getTop()+"right="+iv.getRight()+"bottom="+iv.getBottom());//以activity根布局左和上为参考

                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                     iv.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                 }
             }
         });
    }

1添加购物车动画 文章来源(链接:https://www.jianshu.com/p/b7bacc6805c2

<RelativeLayout
        android:id="@+id/ll"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@id/iv">

        <TextView
            android:id="@+id/tv_add"
            android:layout_width="40dp"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:background="@color/red"
            android:text="+"
            android:textColor="@color/white"
            android:textSize="20sp" />

        <TextView
            android:id="@+id/tv_cart"
            android:layout_width="60dp"
            android:layout_height="50dp"
           android:layout_alignParentBottom="true"
            android:background="@color/blue"
            android:text="我是购物车" />
    </RelativeLayout>


 private void startpathAnimation() {
        final ImageView goods = new ImageView(this);
        goods.setImageResource(R.mipmap.ic_launcher_round);
        ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        goods.setLayoutParams(lp);
        mroot.addView(goods);
        // 控制点的位置
        int[] recyclerLocation = new int[2];
        mroot.getLocationInWindow(recyclerLocation);
        // 加入点的位置起始点
        int[] startLocation = new int[2];
        tvadd.getLocationInWindow(startLocation);
        // 购物车的位置终点
        int[] endLocation = new int[2];
        tvcard.getLocationInWindow(endLocation);
       // tvs.setText("rx"+recyclerLocation[0]+"ry"+recyclerLocation[1]+"sx="+startLocation[0]+"sy="+startLocation[1]+"ex="+endLocation[0]+"ey="+endLocation[1]);

        // TODO:  考虑到状态栏的问题,不然会往下偏移状态栏的高度
        int startX = startLocation[0] - recyclerLocation[0];
        int startY = startLocation[1] - recyclerLocation[1];
        // TODO:  和上面一样
        int endX = endLocation[0] - recyclerLocation[0];
        int endY = endLocation[1] - recyclerLocation[1];

        tvs.setText("sx="+startX+"sy="+startY+"ex="+endX+"ey="+endY);

        Path path=new Path();
        path.moveTo(startX,startY);
        // 使用二阶贝塞尔曲线:注意第一个起始坐标越大,贝塞尔曲线的横向距离就会越大,一般按照下面的式子取即可
        path.quadTo((startX + endX) / 2,startY, endX, endY);
        // mPathMeasure用来计算贝塞尔曲线的曲线长度和贝塞尔曲线中间插值的坐标,如果是true,path会形成一个闭环
        final PathMeasure pathMeasure = new PathMeasure(path, false);
        // 属性动画实现(从0到贝塞尔曲线的长度之间进行插值计算,获取中间过程的距离值)
        ValueAnimator valueAnimator=new ValueAnimator().ofFloat(0,pathMeasure.getLength());
        //根据距离计算动画时间
        int tempx=Math.abs(startX-endX);
        int tempy=Math.abs(startY-endY);

        int time= (int) (0.3*Math.sqrt(tempx*tempx+tempy*tempy));

        valueAnimator.setDuration(time);
        valueAnimator.start();
        valueAnimator.setInterpolator(new AccelerateInterpolator());
        final float[] mCurrentPosition=new float[2];
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value= (float) animation.getAnimatedValue();
                 pathMeasure.getPosTan(value,mCurrentPosition,null);
                 goods.setTranslationX(mCurrentPosition[0]);
                 goods.setTranslationY(mCurrentPosition[1]);
            }
        });

        valueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mroot.removeView(goods);//动画结束后删除做动画的控件
            }
        });

2购物车升级版,按照指定的路径运动

捕获.PNG
/**
绘制路径便于观察
*/
public class PathView extends View {

    private Paint paint;

    public PathView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    private void initView() {
        paint = new Paint();
        //抗锯齿
        paint.setAntiAlias(true);
        //防抖动
        paint.setDither(true);
        //设置画笔未实心
        paint.setStyle(Paint.Style.STROKE);
        //设置颜色
        paint.setColor(Color.GREEN);
        //设置画笔宽度
        paint.setStrokeWidth(3);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Path path = new Path();
        path.moveTo(60,60);
        path.lineTo(460,460);
        path.quadTo(660, 260, 860, 460); //订单
        path.cubicTo(160,660,960,1060,260,1260);
        canvas.drawPath(path,paint);
    }
}

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.eric.constrantapplication.Main2Activity">
  <com.example.eric.constrantapplication.view.PathView
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      />
  <ImageView
      android:id="@+id/iv"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@mipmap/ic_launcher_round"
      />
</android.support.constraint.ConstraintLayout>

在activity中

private void pathAnimate() {
        Path path = new Path();
        path.moveTo(60,60);
        path.lineTo(460,460);
        path.quadTo(660, 260, 860, 460);
        path.cubicTo(160,660,960,1060,260,1260);
        final PathMeasure pathMeasure = new PathMeasure(path, false);//是否闭合
        // 属性动画实现(从0到贝塞尔曲线的长度之间进行插值计算,获取中间过程的距离值)
        ValueAnimator valueAnimator=new ValueAnimator().ofFloat(0,pathMeasure.getLength());
        valueAnimator.setDuration(5000);
        valueAnimator.start();
        //valueAnimator.setInterpolator(new AccelerateInterpolator());
        final float[] mCurrentPosition=new float[2];
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value= (float) animation.getAnimatedValue();
                pathMeasure.getPosTan(value,mCurrentPosition,null);
                iv.setTranslationX(mCurrentPosition[0]-60);
                iv.setTranslationY(mCurrentPosition[1]-60);
            }
        });
    }

3箭头绕圆环运动 箭头的方向与圆环一致(http://www.gcssloop.com/customview/Path_PathMeasure)

3.PNG
Path path = new Path();                                 // 创建 Path

path.addCircle(0, 0, 200, Path.Direction.CW);           // 添加一个圆形

PathMeasure measure = new PathMeasure(path, false);     // 创建 PathMeasure

currentValue += 0.005;                                  // 计算当前的位置在总长度上的比例[0,1]
if (currentValue >= 1) {
    currentValue = 0;
}

// 获取当前位置的坐标以及趋势的矩阵
measure.getMatrix(measure.getLength() * currentValue, mMatrix, PathMeasure.TANGENT_MATRIX_FLAG | PathMeasure.POSITION_MATRIX_FLAG);

mMatrix.preTranslate(-mBitmap.getWidth() / 2, -mBitmap.getHeight() / 2);   // <-- 将图片绘制中心调整到与当前点重合(注意:此处是前乘pre)

canvas.drawPath(path, mDeafultPaint);                                   // 绘制 Path
canvas.drawBitmap(mBitmap, mMatrix, mDeafultPaint);                     // 绘制箭头

invalidate();              

4支付宝支付成功 失败的动态效果 文章来源(https://www.jb51.net/article/99966.htm

捕获.PNG

首先我们需要了解PathMeasure这个类,这个类我们可以理解为用来管理Path。我们主要看几个方法。
PathMeasure(): 构造方法 ,实例化一个对象
PathMeasure(Path path,boolean isClosed):传入Path对象和是否闭合,path对象不能为空
getLength():获取当前轮廓、外形的总长度, 如果没有设置Path对象,返回0
getSegment(float startD,float stopD,Path dst,boolean startWithMoveTo):调用这个方法,我们可以获取到指定范围内的一段轮廓,存入到dst参数中。所以,这个方法传入的参数分别为长度起始值、结束值、装这一段路径的Path对象、是否MoveTo。另外,这个方法返回值为Boolean类型,如果getLength为0的话,返回false,或者startD > stopD,同样返回false。
setPath(Path path , boolean isClosed):给当前PathMeasure对象设置Path
nextContour():移动到下一个轮廓(path)

package com.mintmedical.wavedemo;
 
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
 
/**
 * Created by MooreLi on 2016/12/12.
 */
 
public class ResultAnimation extends View implements ValueAnimator.AnimatorUpdateListener {
 private Context mContext;
 /**
  * paint对象
  */
 private Paint mPaint;
 /**
  * Path和对应的空Path用来填充
  */
 private Path mPathCircle;
 private Path mPathCircleDst;
 private Path mPathRight;
 private Path mPathRightDst;
 private Path mPathWrong1;
 private Path mPathWrong2;
 private Path mPathWrong1Dst;
 private Path mPathWrong2Dst;
 /**
  * Path管理
  */
 private PathMeasure mPathMeasure;
 /**
  * 动画
  */
 private ValueAnimator mCircleAnimator;
 private ValueAnimator mRightAnimator;
 private ValueAnimator mWrong1Animator;
 private ValueAnimator mWrong2Animator;
 /**
  * 当前绘制进度占总Path长度百分比
  */
 private float mCirclePercent;
 private float mRightPercent;
 private float mWrong1Percent;
 private float mWrong2Percent;
 /**
  * 线宽
  */
 private int mLineWidth;
 /**
  * 正确动画 错误动画
  */
 public static final int RESULT_RIGHT = 1;
 public static final int RESULT_WRONG = 2;
 /**
  * 当前结果类型
  */
 private int mResultType = RESULT_WRONG;
 
 public ResultAnimation(Context context) {
  super(context);
  mContext = context;
  init();
 }
 
 public ResultAnimation(Context context, AttributeSet attrs) {
  super(context, attrs);
  mContext = context;
  init();
 }
 
 public ResultAnimation(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  mContext = context;
  init();
 }
 
 private void init() {
  mLineWidth = dp2px(3);
  mPaint = new Paint();
  mPaint.setAntiAlias(true);
  mPaint.setStrokeWidth(mLineWidth);
  mPaint.setStyle(Paint.Style.STROKE);
  mPaint.setColor(Color.GREEN);
 
  initPath();
 }
 
 private void initPath() {
  mPathCircle = new Path();
  mPathCircleDst = new Path();
  mPathRight = new Path();
  mPathRightDst = new Path();
  mPathWrong1 = new Path();
  mPathWrong2 = new Path();
  mPathWrong1Dst = new Path();
  mPathWrong2Dst = new Path();
 
  mPathMeasure = new PathMeasure();
 
  //实例化对象
  mCircleAnimator = ValueAnimator.ofFloat(0, 1);
  //设置时长为1000ms
  mCircleAnimator.setDuration(1000);
  //开始动画
  mCircleAnimator.start();
  //设置动画监听
  mCircleAnimator.addUpdateListener(this);
 
  mRightAnimator = ValueAnimator.ofFloat(0, 1);
  mRightAnimator.setDuration(500);
  mRightAnimator.addUpdateListener(this);
 
  mWrong1Animator = ValueAnimator.ofFloat(0, 1);
  mWrong1Animator.setDuration(300);
  mWrong1Animator.addUpdateListener(this);
  mWrong2Animator = ValueAnimator.ofFloat(0, 1);
  mWrong2Animator.setDuration(300);
  mWrong2Animator.addUpdateListener(this);
 
 }
 
 @Override
 protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);
  if (mResultType == RESULT_RIGHT) {
   mPaint.setColor(Color.GREEN);
  } else {
   mPaint.setColor(Color.RED);
  }
 
  //画圆
  mPathCircle.addCircle(getWidth() / 2, getWidth() / 2, getWidth() / 2 - mLineWidth, Path.Direction.CW);
  mPathMeasure.setPath(mPathCircle, false);
  mPathMeasure.getSegment(0, mCirclePercent * mPathMeasure.getLength(), mPathCircleDst, true);
  canvas.drawPath(mPathCircleDst, mPaint);
  if (mResultType == RESULT_RIGHT) {
   //画对勾
   mPathRight.moveTo(getWidth() / 4, getWidth() / 2);
   mPathRight.lineTo(getWidth() / 2, getWidth() / 4 * 3);
   mPathRight.lineTo(getWidth() / 4 * 3, getWidth() / 4);
   if (mCirclePercent == 1) {
    mPathMeasure.nextContour();
    mPathMeasure.setPath(mPathRight, false);
    mPathMeasure.getSegment(0, mRightPercent * mPathMeasure.getLength(), mPathRightDst, true);
    canvas.drawPath(mPathRightDst, mPaint);
   }
  } else {
   mPathWrong1.moveTo(getWidth() / 4 * 3, getWidth() / 4);
   mPathWrong1.lineTo(getWidth() / 4, getWidth() / 4 * 3);
 
   mPathWrong2.moveTo(getWidth() / 4, getWidth() / 4);
   mPathWrong2.lineTo(getWidth() / 4 * 3, getWidth() / 4 * 3);
 
   if (mCirclePercent == 1) {
    mPathMeasure.nextContour();
    mPathMeasure.setPath(mPathWrong1, false);
    mPathMeasure.getSegment(0, mWrong1Percent * mPathMeasure.getLength(), mPathWrong1Dst, true);
    canvas.drawPath(mPathWrong1Dst, mPaint);
   }
   if (mWrong1Percent == 1) {
    mPathMeasure.nextContour();
    mPathMeasure.setPath(mPathWrong2, false);
    mPathMeasure.getSegment(0, mWrong2Percent * mPathMeasure.getLength(), mPathWrong2Dst, true);
    canvas.drawPath(mPathWrong2Dst, mPaint);
   }
  }
 }
 
 private int dp2px(int dp) {
  float scale = mContext.getResources().getDisplayMetrics().density;
  return (int) (scale * dp + 0.5f);
 }
 
 @Override
 public void onAnimationUpdate(ValueAnimator animation) {
  //圆形动画
  if (animation.equals(mCircleAnimator)) {
   mCirclePercent = (float) animation.getAnimatedValue();
   invalidate();
   Log.e("TEST","percent:"+mCirclePercent);
   if (mCirclePercent == 1) {
    if (mResultType == RESULT_RIGHT)
     mRightAnimator.start();
    else
     mWrong1Animator.start();
   }
  }
  //正确时 对勾动画
  else if (animation.equals(mRightAnimator)) {
   mRightPercent = (float) animation.getAnimatedValue();
   invalidate();
  }
  //错误时 右侧动画
  else if (animation.equals(mWrong1Animator)) {
   mWrong1Percent = (float) animation.getAnimatedValue();
   invalidate();
   if (mWrong1Percent == 1) {
    mWrong2Animator.start();
   }
  }
  //错误时 左侧动画
  else if (animation.equals(mWrong2Animator)) {
   mWrong2Percent = (float) animation.getAnimatedValue();
   invalidate();
  }
 }
 
 public void setmResultType(int mResultType) {
  this.mResultType = mResultType;
  invalidate();
 }
 
 /**
  * 固定写死了宽高,可重新手动调配
  *
  * @param widthMeasureSpec
  * @param heightMeasureSpec
  */
 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  setMeasuredDimension(dp2px(50), dp2px(50));
 }
}

5与支付宝效果类似的sercahView(http://www.gcssloop.com/customview/Path_PathMeasure

s1.PNG
s2.PNG

始状态 初始状态,没有任何动效,只显示一个搜索标志 🔍
准备搜索 放大镜图标逐渐变化为一个点
正在搜索 围绕这一个圆环运动,并且线段长度会周期性变化
准备结束 从一个点逐渐变化成为放大镜图标

public class SearchView extends View {

    // 画笔
    private Paint mPaint;

    // View 宽高
    private int mViewWidth;
    private int mViewHeight;

    public SearchView(Context context) {
        this(context,null);
    }

    public SearchView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initAll();
    }

    public void initAll() {

        initPaint();

        initPath();

        initListener();

        initHandler();

        initAnimator();

        // 进入开始动画
        mCurrentState = State.STARTING;
        mStartingAnimator.start();

    }

    // 这个视图拥有的状态
    public static enum State {
        NONE,
        STARTING,
        SEARCHING,
        ENDING
    }

    // 当前的状态(非常重要)
    private State mCurrentState = State.NONE;

    // 放大镜与外部圆环
    private Path path_srarch;
    private Path path_circle;

    // 测量Path 并截取部分的工具
    private PathMeasure mMeasure;

    // 默认的动效周期 2s
    private int defaultDuration = 2000;

    // 控制各个过程的动画
    private ValueAnimator mStartingAnimator;
    private ValueAnimator mSearchingAnimator;
    private ValueAnimator mEndingAnimator;

    // 动画数值(用于控制动画状态,因为同一时间内只允许有一种状态出现,具体数值处理取决于当前状态)
    private float mAnimatorValue = 0;

    // 动效过程监听器
    private ValueAnimator.AnimatorUpdateListener mUpdateListener;
    private Animator.AnimatorListener mAnimatorListener;

    // 用于控制动画状态转换
    private Handler mAnimatorHandler;

    // 判断是否已经搜索结束
    private boolean isOver = false;

    private int count = 0;



    private void initPaint() {
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(Color.WHITE);
        mPaint.setStrokeWidth(15);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setAntiAlias(true);
    }

    private void initPath() {
        path_srarch = new Path();
        path_circle = new Path();

        mMeasure = new PathMeasure();

        // 注意,不要到360度,否则内部会自动优化,测量不能取到需要的数值
        RectF oval1 = new RectF(-50, -50, 50, 50);          // 放大镜圆环
        path_srarch.addArc(oval1, 45, 359.9f);

        RectF oval2 = new RectF(-100, -100, 100, 100);      // 外部圆环
        path_circle.addArc(oval2, 45, -359.9f);

        float[] pos = new float[2];

        mMeasure.setPath(path_circle, false);               // 放大镜把手的位置
        mMeasure.getPosTan(0, pos, null);

        path_srarch.lineTo(pos[0], pos[1]);                 // 放大镜把手

        Log.i("TAG", "pos=" + pos[0] + ":" + pos[1]);
    }

    private void initListener() {
        mUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mAnimatorValue = (float) animation.getAnimatedValue();
                invalidate();
            }
        };

        mAnimatorListener = new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                // getHandle发消息通知动画状态更新
                mAnimatorHandler.sendEmptyMessage(0);
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        };
    }

    private void initHandler() {
        mAnimatorHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (mCurrentState) {
                    case STARTING:
                        // 从开始动画转换好搜索动画
                        isOver = false;
                        mCurrentState = State.SEARCHING;
                        mStartingAnimator.removeAllListeners();
                        mSearchingAnimator.start();
                        break;
                    case SEARCHING:
                        if (!isOver) {  // 如果搜索未结束 则继续执行搜索动画
                            mSearchingAnimator.start();
                            Log.e("Update", "RESTART");

                            count++;
                            if (count>2){       // count大于2则进入结束状态
                                isOver = true;
                            }
                        } else {        // 如果搜索已经结束 则进入结束动画
                            mCurrentState = State.ENDING;
                            mEndingAnimator.start();
                        }
                        break;
                    case ENDING:
                        // 从结束动画转变为无状态
                        mCurrentState = State.NONE;
                        break;
                }
            }
        };
    }

    private void initAnimator() {
        mStartingAnimator = ValueAnimator.ofFloat(0, 1).setDuration(defaultDuration);
        mSearchingAnimator = ValueAnimator.ofFloat(0, 1).setDuration(defaultDuration);
        mEndingAnimator = ValueAnimator.ofFloat(1, 0).setDuration(defaultDuration);

        mStartingAnimator.addUpdateListener(mUpdateListener);
        mSearchingAnimator.addUpdateListener(mUpdateListener);
        mEndingAnimator.addUpdateListener(mUpdateListener);

        mStartingAnimator.addListener(mAnimatorListener);
        mSearchingAnimator.addListener(mAnimatorListener);
        mEndingAnimator.addListener(mAnimatorListener);
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mViewWidth = w;
        mViewHeight = h;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        drawSearch(canvas);
    }

    private void drawSearch(Canvas canvas) {

        mPaint.setColor(Color.WHITE);


        canvas.translate(mViewWidth / 2, mViewHeight / 2);

        canvas.drawColor(Color.parseColor("#0082D7"));

        switch (mCurrentState) {
            case NONE:
                canvas.drawPath(path_srarch, mPaint);
                break;
            case STARTING://由放大镜变成点
                mMeasure.setPath(path_srarch, false);
                Path dst = new Path();
                mMeasure.getSegment(mMeasure.getLength() * mAnimatorValue, mMeasure.getLength(), dst, true);
                canvas.drawPath(dst, mPaint);
                break;
            case SEARCHING://点绕圆环运动,长度会变(start-stop差值在变)
                mMeasure.setPath(path_circle, false);
                Path dst2 = new Path();
                float stop = mMeasure.getLength() * mAnimatorValue;
                float start = (float) (stop - ((0.5 - Math.abs(mAnimatorValue - 0.5)) * 200f));
                mMeasure.getSegment(start, stop, dst2, true);
                canvas.drawPath(dst2, mPaint);
                break;
            case ENDING://
                mMeasure.setPath(path_srarch, false);
                Path dst3 = new Path();
                mMeasure.getSegment(mMeasure.getLength() * mAnimatorValue, mMeasure.getLength(), dst3, true);
                canvas.drawPath(dst3, mPaint);
                break;
        }
    }
}

6通过更改起始坐标营造移动的效果,例如充电的波浪

20160328094655217.gif
 public class GreenWave extends View{
    private Paint mPaint;
    private Path mPath;
    private int mItemWaveLength = 400;
    private int dx;
    public GreenWave(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPath = new Path();
        mPaint = new Paint();
        mPaint.setColor(Color.GREEN);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPath.reset();
        int originY = 300;
       int halfWaveLen = mItemWaveLength/2;
        mPath.moveTo(-mItemWaveLength+dx,originY+dx/2);//originY+dx为了波浪垂直位移
        //循环绘制让波纹水平铺满屏幕
        for (int i = -mItemWaveLength;i<=getWidth()+mItemWaveLength;i+=mItemWaveLength){
            mPath.rQuadTo(halfWaveLen/2,-100,halfWaveLen,0);
            mPath.rQuadTo(halfWaveLen/2,100,halfWaveLen,0);
        }

        mPath.lineTo(getWidth(),getHeight());
        mPath.lineTo(0,getHeight());
        mPath.close();//闭合之后整体绿色

        canvas.drawPath(mPath,mPaint);
    }

    public void startAnim(){
        ValueAnimator animator = ValueAnimator.ofInt(0,mItemWaveLength);
        animator.setDuration(2000);
        //animator.setRepeatMode(ValueAnimator.REVERSE);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                dx = (int)animation.getAnimatedValue();
                postInvalidate();
            }
        });
        animator.start();
    }

    }

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容