class AnimationControl {
private View mView;
private int angle;
//只用到4个属性
private ObjectAnimator[] animatorList = new ObjectAnimator[4];
private AnimatorSet animationSet;
public Boolean stop = true;
//默认值
private long defaultMoveDistance = 300;
private long defaultScale = 2;
private long defaultDurtion = 3000;
public AnimationControl(View mView) {
this.mView = mView;
//此方法中实现了随机游走,并根据传入参数决定进行随机游走动画还是触摸反馈动画
public void initDefaultAnimation(final Boolean isOpposite) {
long scale = defaultScale;
long durtion = defaultDurtion;
animationSet = new AnimatorSet();
if (isOpposite) {
scale += defaultScale;
} else {
angle = new Random().nextInt(360);
double moveX = defaultMoveDistance * Math.cos(angle);
double moveY = defaultMoveDistance * Math.sin(angle);
animatorList[0] = animatorList[0].ofFloat(mView,"scaleX", 1f, scale, 1f);
animatorList[1] = animatorList[1].ofFloat(mView,"scaleY", 1f, scale, 1f);
animatorList[2] = animatorList[2].ofFloat(mView,"translationX", mView.getTranslationX(), (float) moveX );
animatorList[3] = animatorList[3].ofFloat(mView,"translationY", mView.getTranslationY(), (float) moveY );
if (isOpposite) {
durtion = defaultDurtion/2;
animationSet.setDuration(durtion);
} else {
animationSet.setDuration(durtion);
animationSet.setInterpolator(new AccelerateDecelerateInterpolator());
if (isOpposite) {
animationSet.playTogether(animatorList[0],animatorList[1]);
} else {
animationSet.playTogether(animatorList);
animationSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
@Override
public void onAnimationEnd(Animator animation) {
//在监听器中监听动画是否停止,如果没有停止递归调用startAnmiation方法开始下一次游走的动画
if(stop) {
return;
} else {
startAnmiation(isOpposite);
@Override
public void onAnimationCancel(Animator animation) {
@Override
public void onAnimationRepeat(Animator animation) {
//开始动画
public void startAnmiation(Boolean isOpposite) {
stop = false;
initDefaultAnimation(isOpposite);
animationSet.start();
//为动画设置属性,这里传入的参数是随机游走的范围和放大的倍数,如有需要,可以扩展这些参数
public void setAnmiationPropoty(long distance,long scale){
defaultMoveDistance = distance;
defaultScale = scale;
//停止动画
public void stopAnmiation() {
stop = true;
animationSet.cancel();
public void stopBeforeAndOppositeMove() {
stopAnmiation();
startAnmiation(true);
随机游走原理
实际上原理很简单,我们只要把每次游走的路径变成随机的就可以,如何定义随机呢?由于是2D平面,我们只需要随机出一个角度x,并定义游走的范围L(实际上也可以用随机数来初始化一个半径,这里我们用给定的值),那么在x轴方向上的移动的距离就是L* cosX,y轴则为L *sinX,剩下的就交给属性动画和递归就可以了。代码如下:
//此方法中实现了随机游走,并根据传入参数决定进行随机游走动画还是触摸反馈动画
public void initDefaultAnimation(final Boolean isOpposite) {
long scale = defaultScale;
long durtion = defaultDurtion;
animationSet = new AnimatorSet();
if (isOpposite) {
scale += defaultScale;
} else {
//随机出一个角度
angle = new Random().nextInt(360);
//在x,y轴上分别移动的距离
double moveX = defaultMoveDistance * Math.cos(angle);
double moveY = defaultMoveDistance * Math.sin(angle);
animatorList[0] = animatorList[0].ofFloat(mView,"scaleX", 1f, scale, 1f);
animatorList[1] = animatorList[1].ofFloat(mView,"scaleY", 1f, scale, 1f);
animatorList[2] = animatorList[2].ofFloat(mView,"translationX", mView.getTranslationX(), (float) moveX );
animatorList[3] = animatorList[3].ofFloat(mView,"translationY", mView.getTranslationY(), (float) moveY );
if (isOpposite) {
durtion = defaultDurtion/2;
animationSet.setDuration(durtion);
} else {
animationSet.setDuration(durtion);
//设置加速度控制器
animationSet.setInterpolator(new AccelerateDecelerateInterpolator());
if (isOpposite) {
animationSet.playTogether(animatorList[0],animatorList[1]);
} else {
animationSet.playTogether(animatorList);
animationSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
@Override
public void onAnimationEnd(Animator animation) {
//在监听器中监听动画是否停止,如果没有停止递归调用startAnmiation方法开始下一次游走的动画
//每次动画start之前一定会再次初始化属性
if(stop) {
return;
} else {
startAnmiation(isOpposite);
@Override
public void onAnimationCancel(Animator animation) {
@Override
public void onAnimationRepeat(Animator animation) {
触摸反馈+跟随效果
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
final int X = (int) event.getRawX();
final int Y = (int) event.getRawY();
if (!mAnimationControl.stop) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = X;
lastY = Y;
isOpposite = true;
mAnimationControl.stopBeforeAndOppositeMove();
break;
case MotionEvent.ACTION_MOVE:
if (isOpposite) {
//计算移动的距离
int offX = X - lastX;
int offY = Y - lastY;
((View)getParent()).scrollBy(-offX,-offY);
lastX = X;
lastY = Y;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (isOpposite) {
isOpposite = false;
mAnimationControl.stopAnmiation();
mAnimationControl.startAnmiation(false);
break;
return true;
可以看到只有当动画处在非stop的情况下才有触摸反馈和跟随动画
1.触摸反馈
只需要关心MotionEvent.ACTION_DOWN
,这里面我们调用了控制器的mAnimationControl.stopBeforeAndOppositeMove()
方法开始了一段新的动画
2.跟随动画
我们需要记录上次移动的位置并计算出移动的偏移量,这是因为scrollBy
方法是根据当前位置移动的,同时我们在MotionEvent.ACTION_UP
,方法中又恢复了随机游走动画。
自定义view全部源码
这个自定义view完全可以当作一个普通的view来用,只不过你可以通过startRandomMove()
和stopRandomMove()
来启动和停止动画,动画里面可以做的事情很多,可以根据自己的需求定制动画和触摸反馈。
package com.example.mac.myapplication;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import java.util.Random;
* Created by zhangxiang on 2017/7/16.
public class RandomMoveView extends View {
private Context mContext;
private AnimationControl mAnimationControl;
private Boolean isOpposite;
private Boolean isStop = true;
private int lastX, lastY;
public RandomMoveView(Context context) {
this(context,null);
public RandomMoveView(Context context, @Nullable AttributeSet attrs) {
super(context,attrs);
mContext = context;
mAnimationControl = new AnimationControl(this);
public void startRandomMove() {
if (isStop) {
mAnimationControl.startAnmiation(false);
isStop = false;
public void stopRandomMove() {
if (!isStop) {
mAnimationControl.stopAnmiation();
isStop = true;
public void setAnmiationPropoty(long distance,long scale) {
mAnimationControl.setAnmiationPropoty(distance,scale);
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
final int X = (int) event.getRawX();
final int Y = (int) event.getRawY();
if (!mAnimationControl.stop) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = X;
lastY = Y;
isOpposite = true;
mAnimationControl.stopBeforeAndOppositeMove();
break;
case MotionEvent.ACTION_MOVE:
if (isOpposite) {
//计算移动的距离
int offX = X - lastX;
int offY = Y - lastY;
((View)getParent()).scrollBy(-offX,-offY);
lastX = X;
lastY = Y;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (isOpposite) {
isOpposite = false;
mAnimationControl.stopAnmiation();
mAnimationControl.startAnmiation(false);
break;
return true;
class AnimationControl {
private View mView;
private int angle;
//只用到4个属性
private ObjectAnimator[] animatorList = new ObjectAnimator[4];
private AnimatorSet animationSet;
public Boolean stop = true;
//默认值
private long defaultMoveDistance = 300;
private long defaultScale = 2;
private long defaultDurtion = 3000;
public AnimationControl(View mView) {
this.mView = mView;
//此方法中实现了随机游走,并根据传入参数决定进行随机游走动画还是触摸反馈动画
public void initDefaultAnimation(final Boolean isOpposite) {
long scale = defaultScale;
long durtion = defaultDurtion;
animationSet = new AnimatorSet();
if (isOpposite) {
scale += defaultScale;
} else {
//随机出一个角度
angle = new Random().nextInt(360);
//在x,y轴上分别移动的距离
double moveX = defaultMoveDistance * Math.cos(angle);
double moveY = defaultMoveDistance * Math.sin(angle);
animatorList[0] = animatorList[0].ofFloat(mView,"scaleX", 1f, scale, 1f);
animatorList[1] = animatorList[1].ofFloat(mView,"scaleY", 1f, scale, 1f);
animatorList[2] = animatorList[2].ofFloat(mView,"translationX", mView.getTranslationX(), (float) moveX );
animatorList[3] = animatorList[3].ofFloat(mView,"translationY", mView.getTranslationY(), (float) moveY );
if (isOpposite) {
durtion = defaultDurtion/2;
animationSet.setDuration(durtion);
} else {
animationSet.setDuration(durtion);
//设置加速度控制器
animationSet.setInterpolator(new AccelerateDecelerateInterpolator());
if (isOpposite) {
animationSet.playTogether(animatorList[0],animatorList[1]);
} else {
animationSet.playTogether(animatorList);
animationSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
@Override
public void onAnimationEnd(Animator animation) {
//在监听器中监听动画是否停止,如果没有停止递归调用startAnmiation方法开始下一次游走的动画
//每次动画start之前一定会再次初始化属性
if(stop) {
return;
} else {
startAnmiation(isOpposite);
@Override
public void onAnimationCancel(Animator animation) {
@Override
public void onAnimationRepeat(Animator animation) {
//开始动画
public void startAnmiation(Boolean isOpposite) {
stop = false;
initDefaultAnimation(isOpposite);
animationSet.start();
//为动画设置属性,这里传入的参数是随机游走的范围和放大的倍数,如有需要,可以扩展这些参数
public void setAnmiationPropoty(long distance,long scale){
defaultMoveDistance = distance;
defaultScale = scale;
//停止动画
public void stopAnmiation() {
stop = true;
animationSet.cancel();
public void stopBeforeAndOppositeMove() {
stopAnmiation();
startAnmiation(true);