Introduction to SpringAnimation with examples
SpringAnimation详解与例子(Android StudioJava 实现)
目录
翻译部分(这部分是翻译,是翻译
说明
本文翻译自 原文链接
原文使用Kotlin实现的,源代码见SpringAnimation源代码Kotlin实现
本文改为了Java。
你曾经想过在Android上做一个像上面这样的弹性动画吗?如果你有,那你一定会很开心!
(开不开心不知道,反正觉得还挺困难的,哈哈哈)
Dynamic-animation(动态动画)是Android支持库25.3.0版本中引入的一个新模块。它提供了几个类来制作基于物理的真实视图动画。
你可能会说“不管怎样,我只需要在我的动画上添加一个BounceInterpolator或OvershootInterpolator就可以了”。事实上,这两个效果看起来并不是很好。当然,你也可以编写自己的插值器或实现一个完整的自定义动画——但现在有一个更简单的方法。
Classes(它的类,也是文字说明)
在写这篇文章的时候,这个模块只包含4个类。让我们看看他们的文档描述.
1.DynamicAnimation<T extends DynamicAnimation<T>>
原文
实际上现在都使用AndroidX,这个库也不再维护了。
这个类是基于物理的动画的基础类。它管理动画的生命周期,比如start()和cancel()。这个基类还处理所有子类动画的公共设置。例如,DynamicAnimation库支持添加DynamicAnimation.OnAnimationEndListener 和DynamicAnimation.OnAnimationUpdateListener,以便重要的动画事件可以通过回调来监听。DynamicAnimation的任何子类的启动条件都可以使用setStartValue(float)和setStartVelocity(float)来设置。
2.DynamicAnimation.ViewProperty
原文
还是使用AndroidX,反正似乎android都不再维护这几个库了。且看看它是用来干啥的。
ViewProperty持有View属性的访问权限。也就是说当你使用DynamicAnimation创建动画时,视图的相应属性值将通过这个ViewProperty实例更新。
我们来看看经典动画的参数:
ALPHA(透明度), ROTATION(旋转度,一般是弧度),ROTATION_X, ROTATION_Y,
SCALE_X(缩放x的倍数), SCALE_Y(缩放y的倍数),
SCROLL_X, SCROLL_Y, (滑动,表示其子控件与左、上之间的差值,换句话说就比如你屏幕内容过多,使用这个可以让你滑动着看,比如说现在这行字已经很多了,你可以拖动黑色背景下的滑块来查看后面的文字)
TRANSLATION_X(平移x个单位), TRANSLATION_Y, TRANSLATION_Z,
X, Y, Z(坐标)
3.SpringAnimation
什么叫SpringAnimation呢?春天动画吗?当然不是,看了很多资料,它被翻译为弹簧动画,说到弹簧,你又想起了胡克定律(力学弹性理论)。没错,SpringAnimation就是一个由SpringForce(弹力)驱动的动画。
弹簧力定义了弹簧的刚度、阻尼比以及静止位置。一旦启动了SpringAnimation,在每一帧上,弹簧力将更新动画的值和速度。动画将继续运行,直到弹簧力达到平衡。如果在动画中使用的弹簧是无阻尼的,动画将永远不会达到平衡。相反,它将永远振荡。
4 .SpringForce
弹簧力定义了动画中使用的弹簧的特性.
通过配置刚度和阻尼比,调用者可以创建一个外观和感觉适合他们的用例的弹簧。刚度对应于弹簧常数。弹簧越硬,拉伸就越困难,它经受的阻尼也就越快。
弹簧阻尼比描述了系统在受到扰动后振动是如何衰减的。当阻尼比> 1 (即过阻尼)时,物体将迅速返回到静止位置而不会超调(overshooting:或者翻译为过冲量
)。如果阻尼比等于1(即临界阻尼),物体将在最短的时间内恢复平衡。当阻尼比小于1(即欠阻尼)时,质量倾向于超调,并返回,再次超调。没有任何阻尼(即阻尼比= 0),质量将永远振荡。
这个类有3个参数:
finalPosition:弹簧的静止位置(或角度/刻度)。
stiffness:刚度
刚度对应于弹簧常数。弹簧越硬,拉伸就越困难,它经受的阻尼也就越快。刚度越高,物体沉降越快。
dampingRatio:阻尼比。
弹簧阻尼比描述了系统在受到扰动后振动是如何衰减的。
SpringForce有4个预定义的浮动常量用于刚度和阻尼比,但也可以设置自定义值,自己实现。
根据值的不同,我们的对象将:
- 围绕静止位置永远振荡。
- 围绕其静止位置振荡,直到静止。
- 轻停,很快(肉眼不可见的快)。
- 快速停止,没有过冲。
正如你所看到的,这个包目前非常小。如果你在寻找一些更复杂的弹簧动态,看看 Facebook’s Rebound library,可能需要科学上网。
注意
DynamicAnimation不扩展Animation,所以你不能只是替换一个或在AnimationSet中使用它。不过不要担心,整个过程还是很简单的。
来张沙雕柴柴图,哈哈哈,开始代码过程了。以下代码均为自己实际操作了,Java版本的,要看Kotlin的自己去原文看,本文开头附上链接地址了哈。
样例以及详解
这部分为本人自己操作过程记录。源码会放在文章末尾。本来想赚点积分的(但是每次看到下载要积分就很崩溃,但是附上下载链接了,大家不要下载啊(反话)),想想还是传到GitHub上吧,免费开源最香,不给花积分的机会。
1.新建一个空项目
file->new project,选择空项目(empty),够简单的吧。
2.添加依赖项
依赖项,打开你的buil.gradle,注意,是app目录下的,不是gradle目录下的。将项目调成project模式,它长这样:
注意,添加完代码后需要同步一下,大概我写出来的sync红色位置,点击,如果build成功,它就成功了。
implementation 'androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03'
如果添加依赖项没成功怎么办,可能是版本不对。你这样添加:点击file,选择Project Structure
在搜索框里输入库名,搜索一下,添加进来即可(一般搜索到的就是和你android studio版本适配的库版本了。)
3.创建弹簧动画
//创建弹性动画类SpringAnimation
SpringAnimation animation = new SpringAnimation(view, property);
//SpringForce类,定义弹性特质
SpringForce spring = new SpringForce(finalPosition);
spring.setStiffness(stiffness);//刚度
spring.setDampingRatio(dampingRatio);//阻尼比
//关联弹性特质
animation.setSpring(spring);
animation.start();//启动动画
将其移动到函数中去,当要创建动画时调用它即可。
@SuppressLint("Range")
SpringAnimation createSpringAnimation(View view,
DynamicAnimation.ViewProperty property,
Float finalPosition,
@FloatRange(from = 0.0) Float stiffness,
@FloatRange(from = 0.0) Float dampingRatio) {
//创建弹性动画类SpringAnimation
SpringAnimation animation = new SpringAnimation(view, property);
//SpringForce类,定义弹性特质
SpringForce spring = new SpringForce(finalPosition);
spring.setStiffness(stiffness);//
spring.setDampingRatio(dampingRatio);
//关联弹性特质
animation.setSpring(spring);
return animation;
}
4.Position,第一幅动画演示。
PositionActivity.java
package com.example.mydef20;
import androidx.annotation.FloatRange;
import androidx.appcompat.app.AppCompatActivity;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
public class PositionActivity extends AppCompatActivity {
float stiffNess= SpringForce.STIFFNESS_MEDIUM;//硬度,可设
float damp=SpringForce.DAMPING_RATIO_HIGH_BOUNCY;//阻尼比
SpringAnimation xAnimation;
SpringAnimation yAnimation;//坐标
View mView;
float dX=0f;
float dY=0f;
//创建一个动画对象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_position);
mView=findViewById(R.id.PView);
mView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
xAnimation=createSpringAnimation(mView,SpringAnimation.X,mView.getX(),stiffNess,damp);
yAnimation=createSpringAnimation(mView,SpringAnimation.Y,mView.getY(),stiffNess,damp);
mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});//确定初始位置,确定后移除监听器
mView.setOnTouchListener(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();
xAnimation.cancel();
yAnimation.cancel();//按住图片
break;
case MotionEvent.ACTION_MOVE:
mView.animate()
.x(event.getRawX()+dX)
.y(event.getRawY()+dY)
.setDuration(0)
.start();
break;
case MotionEvent.ACTION_UP:
xAnimation.start();
yAnimation.start();
break;
default:
break;
}
return true;
}
});
}
@SuppressLint("Range")
SpringAnimation createSpringAnimation(View view,
DynamicAnimation.ViewProperty property,
Float finalPosition,
@FloatRange(from = 0.0) Float stiffness,
@FloatRange(from = 0.0) Float dampingRatio) {
//创建弹性动画类SpringAnimation
SpringAnimation animation = new SpringAnimation(view, property);
//SpringForce类,定义弹性特质
SpringForce spring = new SpringForce(finalPosition);
spring.setStiffness(stiffness);//
spring.setDampingRatio(dampingRatio);
//关联弹性特质
animation.setSpring(spring);
return animation;
}
}
activity_position.xml
src那里是引入图片资源,img0是我自己放置的一张图片,你也可以放你自己的图片。
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".PositionActivity">
<ImageView
android:id="@+id/PView"
android:layout_width="128dp"
android:layout_height="128dp"
android:layout_gravity="center"
android:src="@drawable/img0"
tools:ignore="ContentDescription"/>
</FrameLayout>
效果演示:
csdn不对劲,传了好久都失败额,图片不知道动不动得了,要不去GitHub上看吧。
附上这张阔爱的猫咪老师图片:这就是drawable下的img0了
5.Rotation,第二幅动画演示。
RotationActivity.java
package com.example.mydef20;
import androidx.annotation.FloatRange;
import androidx.appcompat.app.AppCompatActivity;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import android.annotation.SuppressLint;
import android.icu.lang.UProperty;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
public class RotationActivity extends AppCompatActivity {
float stiffNess= SpringForce.STIFFNESS_MEDIUM;//硬度,可设
float damp=SpringForce.DAMPING_RATIO_HIGH_BOUNCY;//阻尼比
float spine=0f;//旋转角度,负表示逆时针,正代表顺时针
TextView rTextView;
ImageView rView;
SpringAnimation rotationAnimation;
float curRotation=0f,preRotation=0f;
float x,y;
@SuppressLint("ClickableViewAccessibility")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_rotation);
rTextView= findViewById(R.id.rTextView);
rView=findViewById(R.id.RView);
updateRotationText();
rotationAnimation=createSpringAnimation(rView,SpringAnimation.ROTATION,spine,stiffNess,damp);
rotationAnimation.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {
@Override
public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) {
updateRotationText();
}
});
final float cx=rView.getWidth()/2f;
final float cy=rView.getHeight()/2f;
rView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getActionMasked()){
case MotionEvent.ACTION_DOWN:
x= event.getX();
y= event.getY();
//rotationAnimation.cancel();//
UCRotation(v,x,y,cx,cy);
rotationAnimation.cancel();
break;
case MotionEvent.ACTION_MOVE:
x=event.getX();
y= event.getY();//检测鼠标移动,注意,会相对当前image的位置进行旋转
preRotation=curRotation;
UCRotation(v,x,y,cx,cy);
float angle=curRotation-preRotation;
float tmp=angle+v.getRotation();
v.setRotation(tmp);
updateRotationText();
break;
case MotionEvent.ACTION_UP:
rotationAnimation.start();
break;
default:
break;
}
return true;
}
});
}
public void UCRotation(View v,float tx,float ty,float ex,float ey){
curRotation=v.getRotation()+(float) (Math.toDegrees(Math.atan2(tx - ex, ey - ty)));
}
private void updateRotationText(){
@SuppressLint("DefaultLocale")
String context=String.format("%.3f", rView.getRotation());
rTextView.setText(context);
}
@SuppressLint("Range")
SpringAnimation createSpringAnimation(View view,
DynamicAnimation.ViewProperty property,
Float rangle,
@FloatRange(from = 0.0) Float stiffness,
@FloatRange(from = 0.0) Float dampingRatio) {
//创建弹性动画类SpringAnimation
SpringAnimation animation = new SpringAnimation(view, property);
//SpringForce类,定义弹性特质
SpringForce spring = new SpringForce(rangle);
spring.setStiffness(stiffness);//
spring.setDampingRatio(dampingRatio);
//关联弹性特质
animation.setSpring(spring);
return animation;
}
}
activity_rotation.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".RotationActivity">
<ImageView
android:id="@+id/RView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_rotation"
tools:ignore="ContentDescription"/>
<TextView
android:id="@+id/rTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="192dp"/>
</FrameLayout>
演示:
6.Scale,第三幅动画演示。
ScaleActivity.java
package com.example.mydef20;
import androidx.annotation.FloatRange;
import androidx.appcompat.app.AppCompatActivity;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.SeekBar;
public class ScaleActivity extends AppCompatActivity {
private SeekBar damping;
private SeekBar stiffness;//阻尼、硬度
View sView;
SpringAnimation xAnimation;
SpringAnimation yAnimation;//坐标
float dX=0f;
float dY=0f;
float sX;
float sY;
float vxDis;
float vyDis;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scale);
//调整seekbar,设置硬度范围
stiffness=(SeekBar) findViewById(R.id.stiffness);
stiffness.setMax((int) SpringForce.STIFFNESS_HIGH);
stiffness.setProgress((int) SpringForce.STIFFNESS_VERY_LOW);
//设置阻尼范围,因为阻尼系数范围是0.0-1.0,所以乘以1000可视化为seekbar,转化为整数
damping=(SeekBar) findViewById(R.id.damping);
//damping.setMax((int) (SpringForce.DAMPING_RATIO_NO_BOUNCY*1000));
damping.setProgress((int) (SpringForce.DAMPING_RATIO_HIGH_BOUNCY*1000));
sView=findViewById(R.id.SView);
sView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
vxDis=sView.getRight()-sView.getLeft();
vyDis=sView.getBottom()-sView.getTop();
sView.setPivotX(vxDis/2);
sView.setPivotY(vyDis/2);
xAnimation=createSpringAnimation(sView,SpringAnimation.SCALE_X,sView.getScaleX(),getStiffness(),getDamp());
yAnimation=createSpringAnimation(sView,SpringAnimation.SCALE_Y,sView.getScaleY(),getStiffness(),getDamp());
sView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});//确定初始位置,确定后移除监听器
sView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getActionMasked()){
case MotionEvent.ACTION_DOWN:
sX=vxDis/2-event.getRawX();//计算鼠标到当前视图中心的距离
sY=vyDis/2-event.getRawY();
xAnimation.getSpring().setStiffness(getStiffness());
yAnimation.getSpring().setStiffness(getStiffness());
xAnimation.getSpring().setDampingRatio(getDamp());
yAnimation.getSpring().setDampingRatio(getDamp());
// xAnimation.cancel();
// yAnimation.cancel();
break;
case MotionEvent.ACTION_MOVE:
if((vxDis/2-event.getRawX())/sX >= 0 && (vyDis/2-event.getRawY())/sY >= 0){
sView.animate()
.scaleX((vxDis/2-event.getRawX())/sX)
.scaleY((vyDis/2-event.getRawY())/sY)
.setDuration(0)
.start();
}
break;
case MotionEvent.ACTION_UP:
xAnimation.getSpring().setFinalPosition(1);
yAnimation.getSpring().setFinalPosition(1);
xAnimation.start();
yAnimation.start();
break;
default:
break;
}
return true;
}
});
}
@SuppressLint("Range")
SpringAnimation createSpringAnimation(View view,
DynamicAnimation.ViewProperty property,
Float scaleChange,
@FloatRange(from = 0.0) Float stiffness,
@FloatRange(from = 0.0) Float dampingRatio) {
//创建弹性动画类SpringAnimation
SpringAnimation animation = new SpringAnimation(view, property);
//SpringForce类,定义弹性特质
SpringForce spring = new SpringForce(scaleChange);
spring.setStiffness(stiffness);//
spring.setDampingRatio(dampingRatio);
//关联弹性特质
animation.setSpring(spring);
return animation;
}
private float getStiffness(){
return Math.max(stiffness.getProgress(),1f);
}
private float getDamp(){
return damping.getProgress()/1000f;
}
}
activity_scale.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ScaleActivity"
android:orientation="vertical">
<ImageView
android:id="@+id/SView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:layout_marginTop="20dp"
android:src="@drawable/img0"
tools:ignore="ContentDescription"/>
<!-- android:layout_gravity="center"-->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="20dp"
android:text="stiffness"
android:textSize="9sp"/>
<SeekBar
android:id="@+id/stiffness"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxHeight="20dp"
android:minHeight="20dp"
android:thumb="@drawable/ic_tag"
android:progressTint="#CE3BF4" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="damping"
android:textSize="9sp"/>
<SeekBar
android:id="@+id/damping"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:maxHeight="20dp"
android:minHeight="20dp"
android:thumb="@drawable/ic_tag"
android:progressTint="#F4811C"
/>
</LinearLayout>
演示:
7.MainActivity
package com.example.mydef20;
import androidx.annotation.FloatRange;
import androidx.appcompat.app.AppCompatActivity;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private Button poButton;
private Button roButton;
private Button scButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
poButton=(Button) findViewById(R.id.positionButton);
roButton=(Button) findViewById(R.id.rotationButton);
scButton=(Button) findViewById(R.id.scaleButton);
poButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent();
intent.setClass(MainActivity.this,PositionActivity.class);
startActivity(intent);
}
});
roButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent();
intent.setClass(MainActivity.this,RotationActivity.class);
startActivity(intent);
}
});
scButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent();
intent.setClass(MainActivity.this,ScaleActivity.class);
startActivity(intent);
}
});
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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=".MainActivity"
android:orientation="vertical">
<Button
android:id="@+id/positionButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Position"
android:textAllCaps="false"/>
<Button
android:id="@+id/rotationButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Rotation"
android:textAllCaps="false"/>
<Button
android:id="@+id/scaleButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Scale"
android:textAllCaps="false"/>
</LinearLayout>
我的源码
csdn下载链接:CSDN下载链接
GitHub:github链接
有什么问题欢迎指出。初学Android,有什么书籍大家也可以在评论区推荐一下,谢谢啦!