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.PNGPath 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();
}
}
网友评论