Android Spring动画–基于物理的动画
在本教程中,我们将实现基于Spring的动画,这些动画是我们android应用程序中支持库的一部分。
Spring Animation是Android Physics Based动画API的一部分。
Android Spring动画
Android Spring Animation会根据弹簧特性(潮湿度,刚度和弹性)对视图进行动画处理。
一旦在build.gradle的dependencies部分中添加以下依赖项,即可在项目中实现Spring Animation:
dependencies {
implementation 'com.android.support:support-dynamic-animation:27.1.1'
}
要创建Spring动画,我们需要创建一个SpringAnimation类,并传递动画类型-平移/旋转/缩放以及动画的最终位置和动画速度。
我们还可以设置其他属性,例如刚度和阻尼。
现实生活中的弹簧回到其最终位置时会反弹。
阻尼越高,刚度越低,则其振荡/反弹越多。
要为视图设置动画,必须始终在之前分配视图的最终位置。
要开始动画,我们调用" start()"或者" animateToFinalPosition(Float finalPosition)"。
后者更新最终位置并在内部调用start()。
此外,updateListener和removeListener分别是侦听器,用于侦听更新并在不再需要时删除侦听器。
简单的Spring动画
在Java中,要在任何视图上设置Spring动画,我们可以:
SpringAnimation springAnim = new SpringAnimation(fab, SpringAnimation.TRANSLATION_Y); SpringForce springForce = new SpringForce(); springForce.setFinalPosition(-200f); springForce.setStiffness(SpringForce.STIFFNESS_LOW); springForce.setDampingRatio(SpringForce.DAMPING_RATIO_HIGH_BOUNCY); springAnim.setSpring(springForce); springAnim.start();
该头寸必须为浮动值。
Y方向的负数是向上的。
X方向的负数向左。
fab是动画发生的视图实例。
像运动一样拖动视图弹簧
我们还可以拖动某个视图,然后看到它像Spring一样反弹。
为此,我们需要在视图上设置触摸监听器。
private SpringAnimation xAnimation;
private SpringAnimation yAnimation;
ImageView imageView;
private void imageViewDragSpringAnimation() {
imageView.getViewTreeObserver().addOnGlobalLayoutListener(globalLayoutListener);
imageView.setOnTouchListener(touchListener);
}
private ViewTreeObserver.OnGlobalLayoutListener globalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
xAnimation = createSpringAnimation(imageView, SpringAnimation.X, imageView.getX(),
SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_HIGH_BOUNCY);
yAnimation = createSpringAnimation(imageView, SpringAnimation.Y, imageView.getY(),
SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_HIGH_BOUNCY);
}
};
private View.OnTouchListener touchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
dX = v.getX() - event.getRawX();
dY = v.getY() - event.getRawY();
//cancel animations
xAnimation.cancel();
yAnimation.cancel();
break;
case MotionEvent.ACTION_MOVE:
imageView.animate()
.x(event.getRawX() + dX)
.y(event.getRawY() + dY)
.setDuration(0)
.start();
break;
case MotionEvent.ACTION_UP:
xAnimation.start();
yAnimation.start();
break;
}
return true;
}
};
public SpringAnimation createSpringAnimation(View view,
DynamicAnimation.ViewProperty property,
float finalPosition,
float stiffness,
float dampingRatio) {
SpringAnimation animation = new SpringAnimation(view, property);
SpringForce springForce = new SpringForce(finalPosition);
springForce.setStiffness(stiffness);
springForce.setDampingRatio(dampingRatio);
animation.setSpring(springForce);
return animation;
}
当ImageView显示在屏幕上并确定其宽度和高度时,将触发" GlobalLayoutListener"。
完成此操作后,我们将SpringAnimation的最终位置设置为静止时ImageView的当前X和Y坐标。
" onTouchListener"用于在屏幕上拖动视图。
当拖动开始时,我们捕获到视图的左上角和触摸点之间的差异,并且在移动视图时,差异会添加到当前位置。
当拖动停止时,SpringAnimation被取消,视图返回其原始位置。
连锁Spring动画
在这种类型的动画中,我们将视图组合在一起以使其动画在一起。
每个动画的动画取决于其弹簧属性。
在以下部分中,我们将在Android Studio项目中实现上述每种类型的Spring动画。
项目结构
Android Spring动画代码
下面给出了activity_main.xml布局的代码:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_main"
</android.support.design.widget.CoordinatorLayout>
下面给出了content_main.xml布局的代码:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".MainActivity"
tools:showIn="@layout/activity_main">
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
android:layout_marginBottom="16dp"
android:layout_marginStart="16dp"
app:backgroundTint="@color/colorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp"
app:backgroundTint="@android:color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
app:backgroundTint="@android:color/holo_green_dark"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
app:backgroundTint="#1A1A1A"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.4"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fab4"
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
app:backgroundTint="@android:color/holo_red_dark"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fab5"
<ImageView
android:id="@+id/imageView"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="#FF3456"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
</android.support.constraint.ConstraintLayout>
我们创建了六个FloatingActionButton。
三个用于简单的Spring动画,三个用于链接的Spring动画。
唯一的ImageView将用于显示拖动视图如何导致Spring Animation。
MainActivity.java的代码如下:
package com.theitroad.androidspringanimations;
import android.os.Bundle;
import android.support.animation.DynamicAnimation;
import android.support.animation.SpringAnimation;
import android.support.animation.SpringForce;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.MotionEvent;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
public class MainActivity extends AppCompatActivity {
private SpringAnimation xAnimation;
private SpringAnimation yAnimation;
ImageView imageView;
private float dX;
private float dY;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
imageViewDragSpringAnimation();
chainedSpringAnimation();
final FloatingActionButton fab = findViewById(R.id.fab);
final FloatingActionButton fab2 = findViewById(R.id.fab2);
final FloatingActionButton fab3 = findViewById(R.id.fab3);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SpringAnimation springAnim = new SpringAnimation(fab, SpringAnimation.TRANSLATION_Y);
SpringForce springForce = new SpringForce();
springForce.setFinalPosition(-200f);
springForce.setStiffness(SpringForce.STIFFNESS_LOW);
springForce.setDampingRatio(SpringForce.DAMPING_RATIO_HIGH_BOUNCY);
springAnim.setSpring(springForce);
springAnim.start();
}
});
fab2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
final SpringAnimation springAnim = new SpringAnimation(fab2, SpringAnimation.TRANSLATION_Y);
SpringForce springForce = new SpringForce();
springForce.setFinalPosition(-200f);
springForce.setStiffness(SpringForce.STIFFNESS_HIGH);
springForce.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
springAnim.setSpring(springForce);
springAnim.start();
}
});
fab3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
final SpringAnimation springAnim = new SpringAnimation(fab3, SpringAnimation.TRANSLATION_X);
SpringForce springForce = new SpringForce();
springForce.setFinalPosition(-200f);
springForce.setStiffness(SpringForce.STIFFNESS_MEDIUM);
springForce.setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY);
springAnim.setSpring(springForce);
springAnim.start();
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
//Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
//Handle action bar item clicks here. The action bar will
//automatically handle clicks on the Home/Up button, so long
//as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
private void imageViewDragSpringAnimation() {
imageView = findViewById(R.id.imageView);
imageView.getViewTreeObserver().addOnGlobalLayoutListener(globalLayoutListener);
imageView.setOnTouchListener(touchListener);
}
private ViewTreeObserver.OnGlobalLayoutListener globalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
xAnimation = createSpringAnimation(imageView, SpringAnimation.X, imageView.getX(),
SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_HIGH_BOUNCY);
yAnimation = createSpringAnimation(imageView, SpringAnimation.Y, imageView.getY(),
SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_HIGH_BOUNCY);
}
};
private View.OnTouchListener touchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
//capture the difference between view's top left corner and touch point
dX = v.getX() - event.getRawX();
dY = v.getY() - event.getRawY();
//cancel animations
xAnimation.cancel();
yAnimation.cancel();
break;
case MotionEvent.ACTION_MOVE:
// a different approach would be to change the view's LayoutParams.
imageView.animate()
.x(event.getRawX() + dX)
.y(event.getRawY() + dY)
.setDuration(0)
.start();
break;
case MotionEvent.ACTION_UP:
xAnimation.start();
yAnimation.start();
break;
}
return true;
}
};
public SpringAnimation createSpringAnimation(View view,
DynamicAnimation.ViewProperty property,
float finalPosition,
float stiffness,
float dampingRatio) {
SpringAnimation animation = new SpringAnimation(view, property);
SpringForce springForce = new SpringForce(finalPosition);
springForce.setStiffness(stiffness);
springForce.setDampingRatio(dampingRatio);
animation.setSpring(springForce);
return animation;
}
public SpringAnimation createSpringAnimation(View view,
DynamicAnimation.ViewProperty property,
float stiffness,
float dampingRatio) {
SpringAnimation animation = new SpringAnimation(view, property);
SpringForce springForce = new SpringForce();
springForce.setStiffness(stiffness);
springForce.setDampingRatio(dampingRatio);
animation.setSpring(springForce);
return animation;
}
private void chainedSpringAnimation() {
final FloatingActionButton fab4 = findViewById(R.id.fab4);
final FloatingActionButton fab5 = findViewById(R.id.fab5);
final FloatingActionButton fab6 = findViewById(R.id.fab6);
final SpringAnimation firstXAnim = createSpringAnimation(fab5, DynamicAnimation.X, SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_HIGH_BOUNCY);
final SpringAnimation firstYAnim = createSpringAnimation(fab5, DynamicAnimation.Y, SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_HIGH_BOUNCY);
final SpringAnimation secondXAnim = createSpringAnimation(fab6, DynamicAnimation.X, SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_HIGH_BOUNCY);
final SpringAnimation secondYAnim = createSpringAnimation(fab6, DynamicAnimation.Y, SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_HIGH_BOUNCY);
final ViewGroup.MarginLayoutParams fab5Params = (ViewGroup.MarginLayoutParams) fab5.getLayoutParams();
final ViewGroup.MarginLayoutParams fab6Params = (ViewGroup.MarginLayoutParams) fab6.getLayoutParams();
firstXAnim.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {
@Override
public void onAnimationUpdate(DynamicAnimation dynamicAnimation, float v, float v1) {
secondXAnim.animateToFinalPosition(v + ((fab5.getWidth()
fab6.getWidth())/2));
}
});
firstYAnim.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {
@Override
public void onAnimationUpdate(DynamicAnimation dynamicAnimation, float v, float v1) {
secondYAnim.animateToFinalPosition(v + fab5.getHeight() +
fab6Params.topMargin);
}
});
fab4.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
dX = view.getX() - motionEvent.getRawX();
dY = view.getY() - motionEvent.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float newX = motionEvent.getRawX() + dX;
float newY = motionEvent.getRawY() + dY;
view.animate().x(newX).y(newY).setDuration(0).start();
firstXAnim.animateToFinalPosition(newX + ((fab4.getWidth()
fab5.getWidth())/2));
firstYAnim.animateToFinalPosition(newY + fab4.getHeight() +
fab5Params.topMargin);
break;
}
return true;
}
});
}
}
第一种类型的Simple Spring Animation是在fab,fab2和fab3上执行的。
其中之一可以水平平移。
正如我们之前所讨论的," imageViewDragSpringAnimation()"会触发第二种动画类型。
chainedSpringAnimation()方法触发第三个类型。
在chainedSpringAnimation中,我们拖动第一个fab,即fab4,它将触发X和Y Spring动画的动画更新侦听器。
在每个这些侦听器中,我们触发其他两个"浮动动作"按钮的"弹簧动画",从而将它们链接起来。

