在安卓开发中我们可以通过动画添加视觉提示,向用户通知应用中的动态。当界面状态发生改变时(例如有新内容加载或有新操作可用时),动画尤其有用。动画还为应用增加了优美的外观,使其拥有更高品质的外观和风格。
首先来简单了解一下动画
Android 动画主要分为两大类:视图动画和属性动画,其中视图动画又包含补间动画和逐帧动画
补间动画
逐帧动画
拆分成帧
属性动画
因为上两个动画作用对象局限,仅改变视觉效果且效果单一,为了弥补视图动画的缺陷,于是 Android 在 3.0(API 11)开始提供了一种全新的动画模式:属性动画(Property Animation)
回到正题 —— 运用物理特性让动画更自然
对比上面两个图,非基于物理特性的动画是完全静态的,并且具有固定的持续时间。如果目标值发生变化,则需要在目标值更改时取消动画,使用新值作为新起始值重新配置动画,并添加新目标值。在视觉上,此过程会导致动画突然停止,之后会出现不连贯的运动,如上图所示。而使用基于物理特性的动画 API 构建的动画是依靠力来驱动的。目标值的变化会导致力的变化。新力作用于现有速度,从而实现以连续的方式过渡到新目标。此过程使动画看起来更加自然,如下图所示
运用物理特性运动实例有很多下面来学一个经典的 —— 运用弹簧物理学原理为图形运动添加动画
基于物理特性的动画是依靠力来驱动的。弹簧弹力就是这样一种引导相互作用和运动的力。弹簧弹力具有阻尼和刚度这两个属性。在基于弹簧特性的动画中,值和速度是根据施加到每一帧的弹簧弹力计算的。需要应用到三个关键属性(初始速度,弹簧属性-刚度、阻尼比)
控件初始速度
起始速度用于定义在动画开始时动画属性更改的速度。默认起始速度设置为 0 像素/秒。您可以将速度设置为触摸手势的速度,也可以将起始速度设置为固定值。如果您选择提供固定值,我们建议您以“dp/秒”为单位定义该值,然后将其转换为以“像素/秒”为单位。以“dp/秒”为单位定义该值可使速度与密度和外形规格无关。
要设置速度,请调用 setStartVelocity()方法并传递速度(以“像素/秒”为单位)。该方法会返回设置了速度的弹簧弹力对象。
弹簧的刚度
刚度定义了用于衡量弹簧强度的弹簧常量。不在静止位置的坚硬弹簧可对所连接的对象施加更大的力。要为弹簧增加刚度,请执行以下步骤:
1.调用 getSpring()方法来检索要增加刚度的弹簧。 2.调用 setStiffness() 方法并传递要增加到弹簧上的刚度值。该方法会返回设置了刚度的弹簧弹力对象。
刚度必须为正数
系统中提供了以下刚度常量:
STIFFNESS_HIGH
STIFFNESS_MEDIUM
STIFFNESS_LOW
STIFFNESS_VERY_LOW
弹簧的阻尼比
阻尼比用于描述弹簧振动逐渐衰减的状况。通过使用阻尼比,您可以定义振动从一次弹跳到下一次弹跳所衰减的速度有多快。以下列出了可使弹簧弹力衰减的四种不同方式:
-
当阻尼比大于 1 时,会出现过阻尼现象。它会使对象快速地返回到静止位置。
-
当阻尼比等于 1 时,会出现临界阻尼现象。这会使对象在最短时间内返回到静止位置。
-
当阻尼比小于 1 时,会出现欠阻尼现象。这会使对象多次经过并越过静止位置,然后逐渐到达静止位置。
-
当阻尼比等于零时,便会出现无阻尼现象。这会使对象永远振动下去。
要为弹簧增加阻尼比,请执行以下步骤: 1.调用 getSpring()方法来检索要增加阻尼比的弹簧。
2.调用 setDampingRatio()方法并传递要增加到弹簧上的阻尼比。该方法会返回设置了阻尼比的弹簧弹力对象。
阻尼比必须为非负数。如果将阻尼比设置为零,弹簧永远不会到达静止位置。换句话说,它会永远振动下去。
系统中提供以下阻尼比常量:
DAMPING_RATIO_HIGH_BOUNCY(高弹跳)
DAMPING_RATIO_MEDIUM_BOUNCY (中弹跳)
DAMPING_RATIO_LOW_BOUNCY (低弹跳)
DAMPING_RATIO_NO_BOUNCY(无弹跳)
除了上述的三个属性还需要设置监听器来监听运动状态比如用来获取初始速度;设置动画的边界值来规定控件的移动位置;通过调用 start()或 animateToFinalPosition()来启动你的弹簧动画;通过 cancel()方法可在动画当前所在的值处终止弹簧动画或 skipToEnd 方法可使动画跳至最终值,然后终止弹簧动画。
下面用一个实例来展示
添加支持库
打开应用模块的 build.gradle 文件。将支持库添加到 dependencies 部分。
dependencies {
def dynamicanimation_version = "1.0.0"
implementation 'androidx.dynamicanimation:dynamicanimation:$dynamicanimation_version'
}
创建布局文件
添加个滑块用于展示运动
<RelativeLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="300dp">
<View
android:id="@+id/box"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_centerInParent="true"
android:layout_margin="20dp"
android:background="@color/colorAccent" />
</RelativeLayout>
设置两个拖动条完成对弹簧两个属性:刚度和阻尼值的改变来观察不同属性下滑块的运动情况
<SeekBar
android:id="@+id/damping"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_margin="30dp"
android:progress="30" />
<SeekBar
android:id="@+id/stiffness"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_margin="30dp"
android:progress="20" />
创建 MainActivity
public class MainActivity extends Activity {
//XY坐标
private float downX, downY;
//调整参数的SeekBar
private SeekBar dampingSeekBar, stiffnessSeekBar;
//X/Y方向速度相关的帮助类
private VelocityTracker velocityTracker;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(android.R.id.content).setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE //不限制滑块的移动面积,可以移出手机面板
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
stiffnessSeekBar = (SeekBar) findViewById(R.id.stiffness);//通过ID找到刚度拖动条
dampingSeekBar = (SeekBar) findViewById(R.id.damping);//通过ID找到阻尼拖动条
velocityTracker = VelocityTracker.obtain();//速度追踪,用于追踪手指滑动过程中的瞬时速度
final View box = findViewById(R.id.box);//通过ID找到可拖动滑块
findViewById(R.id.content).setOnTouchListener(new View.OnTouchListener() {//找到滑块所在的控件设置监听器识别滑动手势
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN://当屏幕检测到第一个触点按下之后就会触发到这个事件
downX = event.getX();//记录第一次按下的X位置
downY = event.getY();//记录第一次按下的Y位置
velocityTracker.addMovement(event);
return true;
case MotionEvent.ACTION_MOVE://当触点在屏幕上移动时触发,触点在屏幕上停留也是会触发的
box.setTranslationX(event.getX() - downX);//算出改变的X大小
box.setTranslationY(event.getY() - downY);//算出改变的Y大小
velocityTracker.addMovement(event);
return true;
case MotionEvent.ACTION_UP://当触点松开时被触发
case MotionEvent.ACTION_CANCEL://不是由用户直接触发,由系统在需要的时候触发
velocityTracker.computeCurrentVelocity(1000);//计算出拖动滑块并释放时滑动速度
if (box.getTranslationX() != 0) {//X值有变化即滑块有移动
SpringAnimation animX = new SpringAnimation(box, SpringAnimation.TRANSLATION_X, 0);//使用SpringAnimation类完成弹簧动画
animX.getSpring().setStiffness(getStiffnessSeekBar());//获取拖拉条刚度值
animX.getSpring().setDampingRatio(getDampingSeekBar());//获取拖拉条阻尼值
animX.setStartVelocity(velocityTracker.getXVelocity());//获取初始速度
animX.start();//开始运动
}
if (box.getTranslationY() != 0) {//Y值有变化即滑块有移动
SpringAnimation animY = new SpringAnimation(box, SpringAnimation.TRANSLATION_Y, 0);//使用SpringAnimation类完成弹簧动画
animY.getSpring().setStiffness(getStiffnessSeekBar());//获取拖拉条刚度值
animY.getSpring().setDampingRatio(getDampingSeekBar());//获取拖拉条阻尼值
animY.setStartVelocity(velocityTracker.getYVelocity());//获取初始速度
animY.start();//开始运动
}
velocityTracker.clear();//滑块回到原点速度归零即停止
return true;
}
return false;
}
});
}
private float getStiffnessSeekBar() {
return Math.max(stiffnessSeekBar.getProgress(), 1f);
}//从SeekBar获取自定义刚度返回刚度
private float getDampingSeekBar() {
return dampingSeekBar.getProgress() / 100f;
}//从SeekBar获取自定义的阻尼返回阻尼值
}
来看运行效果
可以看出不同的刚度和阻尼下滑块的轨迹不一样,当二者为 0 时会无限震荡下去
作者:刘佳民
原文链接:https://blog.csdn.net/qq_63462634/article/details/127991674?spm=1001.2014.3001.5502