实现:
首先dynamicanimation库:
implementation 'androidx.dynamicanimation:dynamicanimation:1.0.0'
引入viewBinding:
viewBinding{
enabled true
}
布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/spring_bt0"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginTop="10dp"
android:background="@drawable/icon_qiqiu"
android:gravity="center"
android:paddingBottom="25dp"
android:text="拖动"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv1"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginTop="10dp"
android:background="@drawable/icon_denglong"
android:gravity="center"
android:paddingBottom="25dp"
android:text="生"
android:textColor="#ffffff" />
<TextView
android:id="@+id/tv2"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginTop="10dp"
android:background="@drawable/icon_denglong"
android:gravity="center"
android:paddingBottom="25dp"
android:text="日"
android:textColor="#ffffff" />
<TextView
android:id="@+id/tv3"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginTop="10dp"
android:background="@drawable/icon_denglong"
android:gravity="center"
android:paddingBottom="25dp"
android:text="快"
android:textColor="#ffffff" />
<TextView
android:id="@+id/tv4"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginTop="10dp"
android:background="@drawable/icon_denglong"
android:gravity="center"
android:paddingBottom="25dp"
android:text="乐"
android:textColor="#ffffff" />
</LinearLayout>
</RelativeLayout>
添加视图:
private PointF startPosition = new PointF();
private VelocityTracker velocityTracker;
private float[] remain = new float[2];
private SpringAnimation[] springAnimationXs = new SpringAnimation[4];
private SpringAnimation[] springAnimationYs = new SpringAnimation[4];
private SpringAnimation springAnimationXRoot, springAnimationYRoot;
private ActivityMainBinding binding;
springAnimationXRoot = new SpringAnimation(binding.springBt0, DynamicAnimation.TRANSLATION_X);
springAnimationYRoot = new SpringAnimation(binding.springBt0, DynamicAnimation.TRANSLATION_Y);
springAnimationXs[0] = new SpringAnimation(binding.tv1, DynamicAnimation.TRANSLATION_X);
springAnimationYs[0] = new SpringAnimation(binding.tv1, DynamicAnimation.TRANSLATION_Y);
springAnimationXs[1] = new SpringAnimation(binding.tv2, DynamicAnimation.TRANSLATION_X);
springAnimationYs[1] = new SpringAnimation(binding.tv2, DynamicAnimation.TRANSLATION_Y);
springAnimationXs[2] = new SpringAnimation(binding.tv3, DynamicAnimation.TRANSLATION_X);
springAnimationYs[2] = new SpringAnimation(binding.tv3, DynamicAnimation.TRANSLATION_Y);
springAnimationXs[3] = new SpringAnimation(binding.tv4, DynamicAnimation.TRANSLATION_X);
springAnimationYs[3] = new SpringAnimation(binding.tv4, DynamicAnimation.TRANSLATION_Y);
监听事件:
springAnimationXRoot.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {
@Override
public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) {
springAnimationXs[0].animateToFinalPosition(value);
remain[0] = springAnimationXRoot.getSpring().getFinalPosition() - value;
}
});
springAnimationYRoot.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {
@Override
public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) {
springAnimationYs[0].animateToFinalPosition(value);
remain[1] = springAnimationYRoot.getSpring().getFinalPosition() - value;
}
});
for (int i = 0; i < springAnimationXs.length - 1; i++) {
final int next = i + 1;
springAnimationXs[i].addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {
@Override
public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) {
springAnimationXs[next].animateToFinalPosition(value);
}
});
springAnimationYs[i].addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {
@Override
public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) {
springAnimationYs[next].animateToFinalPosition(value);
}
});
}
binding.springBt0.setOnTouchListener(this);
阻尼、钢性效果设置:
float percent1 = 50 / 100f;//阻尼百分比
float percent2 = 1 / 100f;//钢性百分比
//root spring
SpringForce springRootX = springAnimationXRoot.getSpring();
SpringForce springRootY = springAnimationYRoot.getSpring();
if (springRootX == null) {
springRootX = new SpringForce();
springAnimationXRoot.setSpring(springRootX);
}
if (springRootY == null) {
springRootY = new SpringForce();
springAnimationYRoot.setSpring(springRootY);
}
float highestBouncy = SpringForce.DAMPING_RATIO_HIGH_BOUNCY;
float lowestBouncy = SpringForce.DAMPING_RATIO_NO_BOUNCY;
float dampingRatio = (lowestBouncy - highestBouncy) * percent1;
springRootX.setDampingRatio(dampingRatio);
springRootY.setDampingRatio(dampingRatio);
float highestStiffness = SpringForce.STIFFNESS_HIGH;
float lowestStiffness = SpringForce.STIFFNESS_VERY_LOW;
float stiffness = (highestStiffness - lowestStiffness) * percent2;
springAnimationXRoot.getSpring().setStiffness(stiffness);
springAnimationYRoot.getSpring().setStiffness(stiffness);
for (int i = 0; i < springAnimationXs.length; i++) {
SpringForce springX = springAnimationXs[i].getSpring();
SpringForce springY = springAnimationYs[i].getSpring();
if (springX == null) {
springX = new SpringForce();
springAnimationXs[i].setSpring(springX);
}
if (springY == null) {
springY = new SpringForce();
springAnimationYs[i].setSpring(springY);
}
springX.setDampingRatio(dampingRatio);
springY.setDampingRatio(dampingRatio);
springAnimationXs[i].getSpring().setStiffness(stiffness);
springAnimationYs[i].getSpring().setStiffness(stiffness);
}
触摸事件:
@Override
public boolean onTouch(View v, MotionEvent event) {
//滑动速度
int index = event.getActionIndex();
int action = event.getActionMasked();
int pointerId = event.getPointerId(index);
switch (action) {
case MotionEvent.ACTION_DOWN:
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain();
} else {
velocityTracker.clear();
}
velocityTracker.addMovement(event);
break;
case MotionEvent.ACTION_MOVE:
//跟随手指移动
float distanceX = event.getRawX() - startPosition.x;
float distanceY = event.getRawY() - startPosition.y;
moveViews(distanceX, distanceY);
//速度计算
velocityTracker.addMovement(event);
velocityTracker.computeCurrentVelocity(1000);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
float velocityX = velocityTracker.getXVelocity(pointerId);
float velocityY = velocityTracker.getYVelocity(pointerId);
startSpringAnimationWithVelocity(velocityX, velocityY);
velocityTracker.recycle();
velocityTracker = null;
break;
}
startPosition.set(event.getRawX(), event.getRawY());
return true;
}
移动视图与开始弹簧动画:
/**
* 移动视图
*
* @param distanceX dx
* @param distanceY dy
*/
private void moveViews(float distanceX, float distanceY) {
springAnimationXRoot.animateToFinalPosition(binding.springBt0.getTranslationX() + distanceX + remain[0]);
springAnimationYRoot.animateToFinalPosition(binding.springBt0.getTranslationY() + distanceY + remain[1]);
}
/**
* 开始弹簧动画
*
* @param velocityX vx
* @param velocityY vy
*/
public void startSpringAnimationWithVelocity(float velocityX, float velocityY) {
springAnimationXRoot.setStartVelocity(velocityX);
springAnimationYRoot.setStartVelocity(velocityY);
springAnimationXRoot.animateToFinalPosition(binding.springBt0.getTranslationX() + remain[0]);
springAnimationYRoot.animateToFinalPosition(binding.springBt0.getTranslationY() + remain[1]);
}
完整代码:
public class MainActivity extends AppCompatActivity implements View.OnTouchListener {
private PointF startPosition = new PointF();
private VelocityTracker velocityTracker;
private float[] remain = new float[2];
private SpringAnimation[] springAnimationXs = new SpringAnimation[4];
private SpringAnimation[] springAnimationYs = new SpringAnimation[4];
private SpringAnimation springAnimationXRoot, springAnimationYRoot;
private ActivityMainBinding binding;
@SuppressLint("ClickableViewAccessibility")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
springAnimationXRoot = new SpringAnimation(binding.springBt0, DynamicAnimation.TRANSLATION_X);
springAnimationYRoot = new SpringAnimation(binding.springBt0, DynamicAnimation.TRANSLATION_Y);
springAnimationXs[0] = new SpringAnimation(binding.tv1, DynamicAnimation.TRANSLATION_X);
springAnimationYs[0] = new SpringAnimation(binding.tv1, DynamicAnimation.TRANSLATION_Y);
springAnimationXs[1] = new SpringAnimation(binding.tv2, DynamicAnimation.TRANSLATION_X);
springAnimationYs[1] = new SpringAnimation(binding.tv2, DynamicAnimation.TRANSLATION_Y);
springAnimationXs[2] = new SpringAnimation(binding.tv3, DynamicAnimation.TRANSLATION_X);
springAnimationYs[2] = new SpringAnimation(binding.tv3, DynamicAnimation.TRANSLATION_Y);
springAnimationXs[3] = new SpringAnimation(binding.tv4, DynamicAnimation.TRANSLATION_X);
springAnimationYs[3] = new SpringAnimation(binding.tv4, DynamicAnimation.TRANSLATION_Y);
springAnimationXRoot.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {
@Override
public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) {
springAnimationXs[0].animateToFinalPosition(value);
remain[0] = springAnimationXRoot.getSpring().getFinalPosition() - value;
}
});
springAnimationYRoot.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {
@Override
public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) {
springAnimationYs[0].animateToFinalPosition(value);
remain[1] = springAnimationYRoot.getSpring().getFinalPosition() - value;
}
});
for (int i = 0; i < springAnimationXs.length - 1; i++) {
final int next = i + 1;
springAnimationXs[i].addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {
@Override
public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) {
springAnimationXs[next].animateToFinalPosition(value);
}
});
springAnimationYs[i].addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {
@Override
public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) {
springAnimationYs[next].animateToFinalPosition(value);
}
});
}
binding.springBt0.setOnTouchListener(this);
float percent1 = 50 / 100f;//阻尼百分比
float percent2 = 1 / 100f;//钢性百分比
//root spring
SpringForce springRootX = springAnimationXRoot.getSpring();
SpringForce springRootY = springAnimationYRoot.getSpring();
if (springRootX == null) {
springRootX = new SpringForce();
springAnimationXRoot.setSpring(springRootX);
}
if (springRootY == null) {
springRootY = new SpringForce();
springAnimationYRoot.setSpring(springRootY);
}
float highestBouncy = SpringForce.DAMPING_RATIO_HIGH_BOUNCY;
float lowestBouncy = SpringForce.DAMPING_RATIO_NO_BOUNCY;
float dampingRatio = (lowestBouncy - highestBouncy) * percent1;
springRootX.setDampingRatio(dampingRatio);
springRootY.setDampingRatio(dampingRatio);
float highestStiffness = SpringForce.STIFFNESS_HIGH;
float lowestStiffness = SpringForce.STIFFNESS_VERY_LOW;
float stiffness = (highestStiffness - lowestStiffness) * percent2;
springAnimationXRoot.getSpring().setStiffness(stiffness);
springAnimationYRoot.getSpring().setStiffness(stiffness);
for (int i = 0; i < springAnimationXs.length; i++) {
SpringForce springX = springAnimationXs[i].getSpring();
SpringForce springY = springAnimationYs[i].getSpring();
if (springX == null) {
springX = new SpringForce();
springAnimationXs[i].setSpring(springX);
}
if (springY == null) {
springY = new SpringForce();
springAnimationYs[i].setSpring(springY);
}
springX.setDampingRatio(dampingRatio);
springY.setDampingRatio(dampingRatio);
springAnimationXs[i].getSpring().setStiffness(stiffness);
springAnimationYs[i].getSpring().setStiffness(stiffness);
}
binding.tv1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "生日快乐", Toast.LENGTH_SHORT).show();
}
});
}
/**
* 移动视图
*
* @param distanceX dx
* @param distanceY dy
*/
private void moveViews(float distanceX, float distanceY) {
springAnimationXRoot.animateToFinalPosition(binding.springBt0.getTranslationX() + distanceX + remain[0]);
springAnimationYRoot.animateToFinalPosition(binding.springBt0.getTranslationY() + distanceY + remain[1]);
}
/**
* 开始弹簧动画
*
* @param velocityX vx
* @param velocityY vy
*/
public void startSpringAnimationWithVelocity(float velocityX, float velocityY) {
springAnimationXRoot.setStartVelocity(velocityX);
springAnimationYRoot.setStartVelocity(velocityY);
springAnimationXRoot.animateToFinalPosition(binding.springBt0.getTranslationX() + remain[0]);
springAnimationYRoot.animateToFinalPosition(binding.springBt0.getTranslationY() + remain[1]);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
//滑动速度
int index = event.getActionIndex();
int action = event.getActionMasked();
int pointerId = event.getPointerId(index);
switch (action) {
case MotionEvent.ACTION_DOWN:
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain();
} else {
velocityTracker.clear();
}
velocityTracker.addMovement(event);
break;
case MotionEvent.ACTION_MOVE:
//跟随手指移动
float distanceX = event.getRawX() - startPosition.x;
float distanceY = event.getRawY() - startPosition.y;
moveViews(distanceX, distanceY);
//速度计算
velocityTracker.addMovement(event);
velocityTracker.computeCurrentVelocity(1000);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
float velocityX = velocityTracker.getXVelocity(pointerId);
float velocityY = velocityTracker.getYVelocity(pointerId);
startSpringAnimationWithVelocity(velocityX, velocityY);
velocityTracker.recycle();
velocityTracker = null;
break;
}
startPosition.set(event.getRawX(), event.getRawY());
return true;
}
}
图片资源自行下载:https://www.iconfont.cn/