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动画的动画更新侦听器。
在每个这些侦听器中,我们触发其他两个"浮动动作"按钮的"弹簧动画",从而将它们链接起来。