本篇學習筆記主要根據官方文檔中使用動畫移動視圖,使用Fling動畫移動視圖以及運用彈簧物理學原理爲圖形運動添加動畫這三部分,想要看原文檔的能夠直接點擊連接進入。java
經過本篇筆記將會學習到如下內容:android
ObjectAnimator
移動視圖PathInterpolator
實現曲線動畫Interpolator
,理解曲線動畫和曲線軌跡的區別Path
配合ObjectAnimator
和AnimatorSet
實現曲線軌跡。曲線動畫是官方文檔這樣稱呼的,曲線軌跡是我本身瞎叫的,主要是爲了區別曲線動畫和想要繪製出沿着曲線運動的動畫軌跡的區別。spring
屏幕上的對象有時候須要從新定位,好比一些懸浮的視圖可能會跟隨手指移動,在手指擡起後可能須要停靠在左邊或者右邊等,在這些狀況下,應該使用動畫去從新定位視圖的位置,而不該該當即更新視圖的位置,由於當即更新視圖的位置會使視圖從一個位置閃跳到另外一個位置。canvas
Android提供了一些能夠在屏幕上從新定位對象的方法,好比ObjectAnimator
,使用屬性動畫咱們能夠提供但願對象停留的結束位置,以及動畫的持續時間,同時能夠經過設置插值器來控制動畫的加速或者減速。markdown
ObjectAnimator
更改視圖的位置使用ObjectAnimator
配合對象內部的屬性,就能夠很容易的實如今指定時間段內修改對象的屬性。在下面的實例中,經過ObjectAnimator
配合視圖內部的translationX
和translationY
這兩個屬性,則能夠很容易的實如今指定時間段內修改視圖的位置。app
translationX
使視圖橫向移動://橫向移動View
private void moveInX() {
ObjectAnimator animator =
ObjectAnimator.ofFloat(mBinding.tvMovedView, "translationX", 200);
animator.setDuration(300);
animator.start();
}
複製代碼
translationY
使視圖縱向移動://縱向移動View
private void moveInY() {
ObjectAnimator animator =
ObjectAnimator.ofFloat(mBinding.tvMovedView, "translationY", 200);
animator.setDuration(300);
animator.start();
}
複製代碼
AnimatorSet
同時移動視圖://同時移動View
private void moveTogether() {
AnimatorSet set = new AnimatorSet();
//橫向移動
ObjectAnimator xAnimator = ObjectAnimator.ofFloat(mBinding.tvMovedView, "translationX", mBinding.tvMovedView.getTranslationX() + 200);
xAnimator.setDuration(300);
//縱向移動
ObjectAnimator yAnimator = ObjectAnimator.ofFloat(mBinding.tvMovedView, "translationY", mBinding.tvMovedView.getTranslationY() + 200);
yAnimator.setDuration(300);
set.play(xAnimator).with(yAnimator);
set.start();
}
複製代碼
運行效果以下:ide
從上面的例子能夠看出,ObjectAnimator
可以很方便地實現位移動畫,可是默認狀況下它會使用起點和終點之間地直線從新定位視圖。這種實現方式雖然簡單,可是並不美觀,而使用曲線動畫則有助於提高應用地真實感,讓動畫更有趣。函數
PathInterpolator
類是在Android5.0
中引入的新的插值器,它基於貝塞爾曲線或Path
對象。此插值器是在一個1×1
的正方形內指定一個曲線動做,定位點位於(0,0)
和(1,1)
,而控制點則使用構造函數參數指定。如需建立PathInterpolator
對象,能夠建立Path
對象並將其傳遞給PathInterpolator
:oop
下面的代碼經過向PathInterpolator
構造函數傳遞一個控制點來構造一個二階貝塞爾曲線,並以此爲基礎構建動畫的變化規律:佈局
//使用pathInterpolator移動視圖
private void moveViewWithPathInterpolator(){
float endX = mBinding.tvMovedView.getTranslationX() > 200 ? mBinding.tvMovedView.getTranslationX() - 200 : mBinding.tvMovedView.getTranslationX() + 200;
//設置動畫
ObjectAnimator animatorX = ObjectAnimator.ofFloat(mBinding.tvMovedView,"translationX",mBinding.tvMovedView.getTranslationX(),endX,mBinding.tvMovedView.getTranslationX());
//判斷版本
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
PathInterpolator pathInterpolator = new PathInterpolator(1f,0f);
animatorX.setInterpolator(pathInterpolator);
}
animatorX.setDuration(1000);
animatorX.start();
}
複製代碼
最終執行效果以下:
這樣,咱們就經過上面的代碼實現了一個曲線動畫。這裏須要說明的是:曲線動畫並非說看見的動畫軌跡是曲線,而是表示動畫的變化規律是曲線。就像上面的動畫,緩慢加速到最右邊以後又迅速回到原始位置。在這個過程當中,這段變化過程是曲線的,若是沒有設置Interpolator
屬性或者設置的Interpolator
屬性是勻速運動的,那動畫就是先滑動到最右邊再滑動到最左邊,中間不會有任何速率的變化。
同時應該注意到,上面是經過給PathInterpolator
的構造函數傳遞兩個參數來構建的Interpolator
,這兩個參數實際上是二階貝塞爾曲線的控制點。構造函數同時支持三階貝塞爾曲線的兩個控制點以及一個Path
對象。
上面說到,這裏的曲線變化實際上是速率的變化,若是隻在一個方向執行動畫,咱們始終只能看到一個直線運動的軌跡,那若是咱們指望看到軌跡是曲線,就像下面這樣呢?
在上面的動畫中,咱們能夠看到View
的運動軌跡是一個弧形,也就是一條曲線。其實分析這個動畫就會發現,就像上學時候分析物體的受力狀況同樣,這個動畫實際上是在兩個方向上都包含了動畫,在X軸從0開始運動到最右邊,而後又運動到最左邊,在Y軸則是從0開始運動到指定位置。兩個方向同時執行,根據運動的軌跡,在Y軸就是一條直線,能夠認爲只須要設置一個勻速運動的動畫便可,可是在X軸則是一條曲線,咱們能夠提早規劃出曲線,而後寫出這個曲線的方程,並將這個方程設置到Interpolator
中(上面的曲線動畫的方程是一個簡單的二次函數:y = -2x^2 + 2x),而後將這個Interpolator
設置給須要曲線運動的那個動畫,讓兩個動畫同時執行就能夠了。因此最終實現這個曲線動畫的代碼以下:
//同時移動View
private void moveTogether() {
AnimatorSet set = new AnimatorSet();
//橫向移動
ObjectAnimator xAnimator = ObjectAnimator.ofFloat(mBinding.tvMovedView, "translationX", 200f);
xAnimator.setInterpolator(input -> input * input * -2f + 2 * input);
xAnimator.setDuration(1000);
//縱向移動
ObjectAnimator yAnimator = ObjectAnimator.ofFloat(mBinding.tvMovedView, "translationY", 200f);
yAnimator.setDuration(1000);
set.play(xAnimator).with(yAnimator);
set.start();
}
複製代碼
能夠看到,就是使用了以前就已經看到過的同時移動View動畫那部分代碼,只是對橫向運動的動畫設置了一個新的Interpolator
,而這個Interpolator
正是咱們上面提到過的那個方程。
可是其實上面的方程是有問題的,固然也不算是一個錯誤,只是視覺上咱們會發現橫向運動並無到達咱們設置的200f
這個距離,這是由於咱們設置的200f
是結束值而不是最大值,根據方程來看,咱們將在x = 1/2
的時候取到最大值爲1/2
,乘上200
則計算在這個動畫中,橫向運動的最大值爲100
。若是咱們仍然指望橫向運動的最大值爲200
,則一種方式是能夠經過修改方程,另外一種方式則能夠經過ObjectAnimator xAnimator = ObjectAnimator.ofFloat(mBinding.tvMovedView, "translationX", 0f,200f,0f);
來指定。
在上面經過兩個例子主要演示了PathInterpolator
的簡單使用,另外就是比較了曲線動畫和咱們視覺上看到的曲線軌跡的區別。通俗來說就是曲線動畫表示的是在一段時間內動畫的速率是變化的,而視覺上的曲線軌跡則首先須要至少兩個方向上的動畫,同時須要知足某一個方向上的動畫的變化速率是曲線的才能實現曲線動畫。
經過上面的例子咱們已經可以實現視覺上的曲線動畫了,那麼爲何還要PathInterpolator
呢?我我的認爲主要在於對Interpolator
的理解上,其實無論對於哪一種Interpolator
,咱們均可以認爲是在座標軸上的(0,0)
和(1,1)
構成的矩形範圍內畫線,起點是(0,0)
,終點則爲(1,1)
,這樣來看,只要咱們可以用一個函數來表示咱們畫出的這條線,咱們就能夠實現這樣的動畫。而有時候咱們很難直接用一個函數去表示畫出的一條線,因此有了Interpolator
,咱們沒必要用函數去表示這條線,而是直接用這條線做爲Interpolator
設置給動畫。
最重要的是:咱們能夠看到TimeInterpolator
須要咱們實現float getInterpolation(float input)
這個方法,在這個方法中,這個input
就是咱們函數中的x值,返回值就是咱們函數中的y值。由此,只要咱們能寫出方程,咱們就能獲得咱們想要的動畫。在有了PathInterpolator
後,只要咱們能畫出這條線,咱們就能獲得動畫。
上面咱們經過組合兩個動畫實現了視覺上的曲線運動軌跡,不過在ObjectAnimator
中也爲咱們提供了新的函數來實現這樣的效果:
//使用ObjectAnimator實現動畫路徑
private void useObjectAnimatorWithPath() {
//判斷版本
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Path path = new Path();
path.arcTo(0, mBinding.tvMovedView.getTop(), 500f, mBinding.tvMovedView.getTop() + 500f, -90, 180, true);
ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.tvMovedView, View.X, View.Y, path);
animator.setDuration(2000);
animator.start();
}
}
複製代碼
PathInterpolator
前面只是簡單的演示了PathInterpolator
的使用,咱們提到過還能夠經過傳遞一個Path
對象來設置一個PathInterpolator
,這樣咱們對於動畫則有了很大的改善空間,由於相比於寫一個函數,很明顯直接用路徑更容易設置。下面的代碼演示了建立一個簡單的小球墜落的動畫:
private void createPathInterpolatorWithPath() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Path path = new Path();
//移動到(0,0)點,必須從這裏開始
path.moveTo(0f, 0f);
//繪製第一個墜落的路徑,是一個1/4的橢圓,注意橢圓所在的矩形範圍
path.arcTo(new RectF(-0.5f, 0f, 0.5f, 2f), -90, 90);
//繪製第一個彈起的路徑,是一個1/2的橢圓
path.arcTo(new RectF(0.5f, 0.5f, 0.8f, 1.5f), -180, 180);
//再也不彈起,直接連線到結束
path.lineTo(1f, 1f);
//設置PathInterpolator
PathInterpolator pathInterpolator = new PathInterpolator(path);
//橫軸作普通動畫
ObjectAnimator animatorX = ObjectAnimator.ofFloat(mBinding.circleView, "translationX", 0f, 500f);
animatorX.setDuration(2000);
//縱軸根據指定的path作動畫
ObjectAnimator animatorY = ObjectAnimator.ofFloat(mBinding.circleView, "translationY", 0f, 500f);
//將pathInterpolator設置給縱軸方向上的運動
animatorY.setInterpolator(pathInterpolator);
animatorY.setDuration(2000);
//動畫集合
AnimatorSet set = new AnimatorSet();
set.play(animatorX).with(animatorY);
set.start();
}
}
複製代碼
最終實現的效果以下:
能夠看到,使用PathInterpolator
,咱們就能夠很輕易地定義出咱們想要的動畫路徑,若是咱們僅僅經過函數去設置,那麼這個函數可能比較難寫,用路徑則比較容易。固然,對於這個動畫,咱們仍然能夠經過函數去寫,一個比較簡單的方式就是將這一個動畫分解爲多個動畫,好比Y軸方向上,首先是一個從高處下落的曲線軌跡,而後是一個彈起落下的軌跡,這兩個軌跡均可以使用二次函數表示,這樣咱們將Y軸方向上的動畫分解爲3個動畫去顯示,仍然可以達到一樣的。
這裏須要注意的是,繪製弧線的方式,首先肯定弧線所在的橢圓,而後肯定橢圓所在的矩形,最後肯定矩形的座標,要確保x軸的連線始終是連續的,同時確保起始點和結束點分別是(0,0)
,(1,1)
投擲動畫利用與對象速度成正比的摩擦力,咱們可使用該動畫爲某個對象的屬性添加動畫效果,還可使用該動畫逐漸結束動畫。該動畫具備一個初始動量,主要從手勢速度得到,而後逐漸變慢。當動畫速度足夠低,在設備屏幕上沒有任何可見變化時,動畫便會結束。
須要注意的是:經過官方文檔的例子的學習,這個動畫仍是用於相似於ScrollView
這樣的須要滑動的地方比較有用,咱們能夠經過這個動畫,記錄用戶每次滑動屏幕的速度,而後根據這個速度去設置用戶手指擡起以後的動做,向用戶提供比較友好的反饋。從官方文檔來看,這個動畫仍是適用於那些須要有條件的緩慢結束的動畫(能夠把這個條件轉換爲速度或者摩擦力)。雖然也可以用在逐漸結束一個動畫上,可是我我的測試效果不明顯,並且這個需求徹底能夠經過Interpolator
去實現。
在文檔《運用彈簧物理學原理爲圖形運動添加動畫》那一節也提到,若是但願動畫啊只在一個方向上放慢速度,則可使用基於摩擦力的投擲動畫。
下面的例子實現了在一個加速動畫結束後經過投擲動畫將這個動畫緩慢中止:
private void createFlingAnimation(){
//首先經過一個動畫建立View移動的動畫
ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.circleView,"translationX",100f);
animator.setInterpolator(new AccelerateInterpolator());
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator anim) {
super.onAnimationEnd(anim);
//結束時建立投擲動畫緩慢減速
//建立投擲動畫實例
FlingAnimation animation = new FlingAnimation(mBinding.circleView, DynamicAnimation.TRANSLATION_X);
//設置速度
animation.setStartVelocity(200.0f);
//設置動畫的最小值
animation.setMinValue(50f);
//設置動畫的最大值
animation.setMaxValue(200f);
//設置摩擦力
animation.setFriction(2.0f);
animation.addUpdateListener((animation1, value, velocity) -> Logs.e("動畫執行中:"+ value));
//開始執行
animation.start();
}
});
animator.start();
}
複製代碼
須要注意的是,要使用投擲動畫,須要先導入依賴包,若是使用androidx
,使用下面的方式導入:
//基於物理特性的動畫API
implementation androidx.dynamicanimation:dynamicanimation-ktx:1.0.0-alpha03
複製代碼
若是使用支持庫,則使用下面的方式導入:
implementation 'com.android.support:support-dynamic-animation:28.0.0'
複製代碼
上面的代碼運行後的動畫效果以下:
在上面的代碼中,首先建立了一個使用ObjectAnimator
執行的動畫,這個動畫會將View
向右移動100個像素的距離,在這個動畫結束以後,咱們會建立一個投擲動畫來緩慢結束以前的動畫。
須要注意的是,咱們設置的最小值須要在當前值的範圍中,換句話說,最小值應該小於或者等於當前值(起始值),這是由於咱們沒有設置起始值,便會將當前值做爲起始值。好比在上面的代碼中,設置的投擲動畫的最小的translationX
的值應該小於等於100,若是設置爲大於100則會出現異常。異常信息爲"java.lang.IllegalArgumentException: Starting value need to be in between min value and max value"
基於物理特性的動畫
是依靠力來進行驅動,彈簧彈力就是這樣一種引導相互做用和運動的力。彈簧彈力具備阻尼和剛度這兩個屬性,在基於彈簧特性的動畫中,值和速度是根據施加到某一幀的彈簧彈力計算的。
彈簧動畫仍然須要添加物理特性的支持庫,和上面的支持庫是同樣的。
藉助SpringAnimation
類,能夠爲對象製做彈簧動畫。要製做彈簧動畫,須要建立SpringAnimation
類的一個實例,並提供一個對象,想要設置動畫的這個對象的屬性,以及但願動畫停留的最終彈簧位置(可選)。以下:
//建立彈性動畫
private void createSimpleSpringAnimation(){
SpringAnimation springAnimation = new SpringAnimation(mBinding.circleView,DynamicAnimation.TRANSLATION_X);
springAnimation.start();
}
複製代碼
使用上面的代碼就已經建立了一個彈性動畫實例了,可是直接運行會報錯:Incomplete SpringAnimation: Either final position or a spring force needs to be set.
,咱們須要指定finalPosition
或者指定一個SpringForce
,其實最終咱們仍是須要指定finalPosition
,直接建立SpringForce
不設置其中的finalPosition
仍然會致使運行失敗。這是由於在SpringAnimation
有一個默認的最大值mMaxValue = Float.MAX_VALUE
,在SpringForce
中finalPosition
的默認值爲Double.MAX_VALUE
,而在執行start()
方法時會判斷mMaxValue
是否小於或者等於finalPosition
,若是不是小於等於則會報錯。
//建立彈性動畫
private void createSimpleSpringAnimation(){
//直接經過構造函數指定finalPosition
SpringAnimation springAnimation = new SpringAnimation(mBinding.circleView,DynamicAnimation.TRANSLATION_X,500f);
//經過SpringForce指定finalPosition
// SpringForce force = new SpringForce();
// force.setFinalPosition(500f);
// springAnimation.setSpring(force);
springAnimation.start();
}
複製代碼
指定finalPosition
以後就能夠執行動畫了,執行效果以下:
能夠看到有一個回彈的效果,這個效果其實使用Interpolator
也是能夠實現的。以下:
ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.circleView,"translationX",0f,500f);
animator.setInterpolator(new OvershootInterpolator());
animator.start();
複製代碼
經過指定ObjectAnimator
的Interpolator
爲OvershootInterpolator
就實現了一個和默認彈性動畫差很少的動畫。
系統爲彈性動畫和投擲動畫提供瞭如下經常使用參數來設置動畫:
ALPHA
表示視圖的透明度,該值默認爲1,表示不透明,爲0則表示徹底透明,經過DynamicAnimation.ALPHA
指定TRANSLATION_X
,TRANSLATION_Y
,TRANSLATION_Z
這些屬性用於控制視圖的所在位置,值爲視圖的佈局容器所設置的左側座標,頂部座標和高度的增量ROTATION
,ROTATION_X
,ROTATION_Y
這些屬性用於控制視圖圍繞軸心點進行的2D(rotation
)屬性和3D旋轉。SCROLL_X
,SCROLL_Y
這些屬性表示視圖距離左邊和頂部邊緣的滾動偏移量,它還以頁面滾動的距離來表示位置SCALE_X
,SCALE_Y
這些屬性用於控制視圖圍繞其軸心點進行的2D縮放。X
,Y
,Z
這些是基本的實用屬性,用於描述視圖在容器中的最終位置。DynamicAnimation
類提供了兩個監聽器OnAnimationUpdateListener
和OnAnimationEndListener
,這些監聽器會監聽動畫中的更新,例如當動畫值發生改變或結束時則會回調這些監聽器。
當須要監聽動畫值發生改變的時候,咱們能夠經過註冊這個監聽器來得到動畫執行過程當中的當前值。例如當一個動畫參數依賴於另外一個動畫執行過程當中的數據時,這個監聽器就很是有用,以下所示:
//建立彈性動畫並註冊監聽器
private void createSpringAnimationWithListener(){
//對第一個View設置橫向和縱向移動的動畫
SpringAnimation animation1X = new SpringAnimation(mBinding.circleView,DynamicAnimation.TRANSLATION_X,500);
SpringAnimation animation1Y = new SpringAnimation(mBinding.circleView,DynamicAnimation.TRANSLATION_Y,300);
//對第二個View設置橫向和縱向移動的動畫
SpringAnimation animation2X = new SpringAnimation(mBinding.circleView2,DynamicAnimation.TRANSLATION_X);
SpringAnimation animation2Y = new SpringAnimation(mBinding.circleView2,DynamicAnimation.TRANSLATION_Y);
//添加動畫監聽器,當動畫1中的值改變後修改動畫2中的值
animation1X.addUpdateListener((animation, value, velocity) -> animation2X.animateToFinalPosition(value));
animation1Y.addUpdateListener((animation,value,velocity) -> animation2Y.animateToFinalPosition(value));
animation1X.addEndListener((animation,canceled,value,velocity) -> Logs.e("動畫1執行結束"));
animation2X.addEndListener((animation,canceled,value,velocity) -> Logs.e("動畫2執行結束"));
animation1X.start();
animation1Y.start();
}
複製代碼
在上面的代碼中,咱們對兩個View
設置了四個動畫,其中第二個View
的動畫跟隨第一個View
的動畫,經過對第一個View
的兩個動畫分別註冊更新監聽器,咱們可以拿到每次動畫更新後的值,而後將這這個值設置給第二個View
的動畫上,經過animateToFinalPosition()
方法更新第二個View
動畫的值,就達到了第二個View
跟隨第一個View
移動的效果。能夠看到,使用這種方式建立的動畫更加生動並符合客觀規律。
執行效果以下:
當不須要對動畫的狀態進行監聽的時候,能夠經過removeUpdateListener()
和removeEndListener()
方法移除相應的監聽器。
能夠經過調用setStartValue()
方法設置動畫的起始值,若是沒有設置動畫的起始值,則動畫將以對象屬性的當前值做爲起始值。
若是要將屬性值限制在特定的範圍內,則能夠設置最小動畫值和最大動畫值,若是爲具備內在範圍的屬性(如Alpha
透明度的範圍應該限制在0
到1
之間)添加動畫效果,這樣作還有助於控制範圍。
setMinValue()
設置最小值setMaxValue()
設置最大值須要注意的是:若是同時設置了動畫的範圍和動畫的起始值,則應該確保動畫的起始值在最小值和最大值之間。
經過SpringForce
類能夠爲每一個彈性動畫設置彈簧屬性,好比阻尼比和剛度。
阻尼比用於描述彈簧震動逐漸衰減的情況。經過使用阻尼比,能夠定義震動從一次彈跳到下一次彈跳所衰減的速度有多快,下面列出了可以使彈簧彈力衰減的四種不一樣的方式:
經過下面的方法爲彈簧設置阻尼比:
//爲彈性動畫設置阻尼比
private void createSpringAnimationWithSpringForce(){
SpringAnimation springAnimation = new SpringAnimation(mBinding.circleView2,DynamicAnimation.TRANSLATION_X);
//建立SpringForce對象
SpringForce force = new SpringForce(500f);
//設置阻尼比
force.setDampingRatio(0.2f);
//爲動畫設置SpringForce
springAnimation.setSpring(force);
//設置動畫的起始位置
springAnimation.setStartValue(0f);
//開始播放動畫
springAnimation.start();
}
複製代碼
經過上面的代碼咱們就爲一個彈性動畫設置了阻尼比,經過改變阻尼比的參數,就能夠看到不一樣的回彈效果,當阻尼比爲0時,動畫便會一直運動下去。當阻尼比爲1時,動畫會盡快到達指定的位置,當阻尼比在0到1之間時,便會看到幾回回彈效果,當阻尼比大於1時,動畫會緩慢到達指定位置。
系統中爲咱們提供瞭如下阻尼比常量:
DAMPING_RATIO_HIGH_BOUNCY
DAMPING_RATIO_MEDIUM_BOUNCY
DAMPING_RATIO_LOW_BOUNCY
DAMPING_RATIO_NO_BOUNCY
其中默認的阻尼比爲DAMPING_RATIO_MEDIUM_BOUNCY
.
剛度定義了用於衡量彈簧強度的彈簧常量。不在靜止位置的堅硬彈簧能夠對所鏈接的對象施加更大的力,經過如下步驟能夠爲彈簧增長剛度:
getSpring()
方法來檢索要增長剛度的彈簧setStiffness()
方法並傳遞//爲彈性動畫設置阻尼比
private void createSpringAnimationWithSpringForce(){
SpringAnimation springAnimation = new SpringAnimation(mBinding.circleView2,DynamicAnimation.TRANSLATION_X);
//建立SpringForce對象
SpringForce force = new SpringForce(500f);
//設置阻尼比
force.setDampingRatio(0.2f);
//設置剛度
force.setStiffness(10f);
//爲動畫設置SpringForce
springAnimation.setSpring(force);
//設置動畫的起始位置
springAnimation.setStartValue(0f);
//開始播放動畫
springAnimation.start();
}
複製代碼
設置剛度的意義在於設置回彈的速度,設置阻尼比在於設置回彈的次數,剛度越大,回彈的速度越快,阻尼比越大,回彈的次數越少。
系統已經爲咱們提供了下面四種剛度類型,分別是:
STIFFNESS_HIGH
高剛度 (10000)STIFFNESS_MEDIUM
中剛度 (1500)STIFFNESS_LOW
低剛度 (200)STIFFNESS_VERY_LOW
很是低的剛度 (50)想要查看這四種不一樣剛度和不一樣阻尼比的演示效果,能夠點擊此處跳轉到文檔部分。
下面是添加了阻尼比和剛度以後的運行效果:
須要注意的是:圖片不能和上面的代碼對應,這是由於我是在寫完全部代碼後再在模擬器運行而後截圖,使用上面的代碼仍然能夠正常運行。
在上面的代碼中已經演示瞭如何建立彈簧彈力的過程,分別是:
SpringForce
對象SpringForce
對象的setDampingRatio()
和setStiffness()
來設置彈簧的阻尼比和剛度SpringForce
對象設置給SpringAnimation
。能夠經過start()
或者animateToFinalPosition()
來啓動動畫,這兩個方法都須要在主線程中調用,其中animateToFinalPosition
會執行兩項任務:
animateToFinalPosition
會更新彈簧的最終位置並根據須要啓動動畫,所以咱們能夠隨時經過調用此方法來更改動畫過程。例如,在連接動畫中,一個視圖依賴於另外一個視圖,對於此類動畫,使用animateToFinalPosition
方法將會更加便捷。以下面的例子中演示了一個連接動畫。
//建立動畫用於修改位置
SpringAnimation animation1 = new SpringAnimation(mBinding.circleView2,DynamicAnimation.TRANSLATION_X);
SpringForce force1 = new SpringForce();
force1.setDampingRatio(0.5f);
force1.setStiffness(500);
animation1.setSpring(force1);
SpringAnimation animation2 = new SpringAnimation(mBinding.circleView2, DynamicAnimation.TRANSLATION_Y);
SpringForce force2 = new SpringForce();
force2.setDampingRatio(0.5f);
force2.setStiffness(500);
animation2.setSpring(force2);
SpringAnimation animation3 = new SpringAnimation(mBinding.circleView3,DynamicAnimation.TRANSLATION_X);
SpringForce force3 = new SpringForce();
force3.setDampingRatio(0.5f);
force3.setStiffness(500);
animation3.setSpring(force3);
SpringAnimation animation4 = new SpringAnimation(mBinding.circleView3,DynamicAnimation.TRANSLATION_Y);
SpringForce force4 = new SpringForce();
force4.setDampingRatio(0.5f);
force4.setStiffness(500);
animation4.setSpring(force4);
mBinding.circleView.setMoveXListener(aFloat -> {
animation1.animateToFinalPosition(aFloat);
return null;
});
mBinding.circleView.setMoveYListener(translationY ->{
animation2.animateToFinalPosition(translationY);
return null;
});
mBinding.circleView2.setMoveXListener(translationX -> {
animation3.animateToFinalPosition(translationX);
return null;
});
mBinding.circleView2.setMoveYListener(translationY -> {
animation4.animateToFinalPosition(translationY);
return null;
});
複製代碼
上面的動畫依賴的三個View是一個簡單的自定義的View
,代碼以下:
class CircleView: View {
private val mPaint by lazy {
Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.RED
}
}
//定義一個接口,當前的View移動時將位置傳遞出去
var moveXListener: ((translationX: Float) -> Unit)? = null
var moveYListener: ((translationY: Float) -> Unit)? = null
private val mRect by lazy {
RectF()
}
constructor(context: Context): this(context, null){
}
constructor(context: Context, attributes: AttributeSet?):this(context, attributes, 0){
}
constructor(context: Context, attributes: AttributeSet?, defStyleAttr: Int): super(
context,
attributes,
defStyleAttr
){
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
//super.onMeasure(50, 50)
setMeasuredDimension(
100,
100
)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.drawCircle(measuredWidth / 2f, measuredHeight / 2f, measuredWidth / 2f, mPaint)
}
//用戶手指按下的位置
private var mTouchX: Float = 0f
private var mTouchY: Float = 0f
override fun onTouchEvent(event: MotionEvent?): Boolean {
when(event?.action){
MotionEvent.ACTION_DOWN -> {
mTouchX = event.x
mTouchY = event.y
return true
}
MotionEvent.ACTION_MOVE -> {
this.translationX += event.x - mTouchX
this.translationY += event.y - mTouchY
return true
}
MotionEvent.ACTION_UP -> {
}
}
return super.onTouchEvent(event)
}
override fun setTranslationX(translationX: Float) {
super.setTranslationX(translationX)
moveXListener?.invoke(translationX)
}
override fun setTranslationY(translationY: Float) {
super.setTranslationY(translationY)
moveYListener?.invoke(translationY)
}
}
複製代碼
每次View
的位置改變以後都會經過接口將位置信息傳遞出去,而後再Activity
中獲取到第一個View的位置信息後經過動畫修改第二個CircleView
的位置信息,同理,第二個CircleView
的位置信息修改以後也會經過接口修改第三個CircleView
的位置信息。
上面的代碼運行效果以下:
若是用戶退出應用或者頁面,或者視圖變得不可見時,咱們可能須要取消動畫或者將動畫直接設置到結尾處。經過cancel()
方法能夠在動畫的當前值處終止動畫,skipToEnd()
方法會跳轉到動畫結束值處而後終止動畫。
在終止動畫以前,首先須要判斷動畫是否可以終止,若是動畫處於無阻尼狀態,則動畫永遠不會終止,經過調用canSkipEnd()
能夠判斷動畫可否終止,若是動畫處於無阻尼狀態則會返回false
,不然將會返回true
。注意:cancel()
方法不受影響,skipToEnd()
受到阻尼狀態的影響。
private void createSpringAnimationWithSpringForce(){
SpringAnimation springAnimation = new SpringAnimation(mBinding.circleView2,DynamicAnimation.TRANSLATION_X);
//建立SpringForce對象
SpringForce force = new SpringForce(500f);
//設置阻尼比
force.setDampingRatio(0f);
//設置剛度
force.setStiffness(20);
//爲動畫設置SpringForce
springAnimation.setSpring(force);
//設置動畫的起始位置
springAnimation.setStartValue(0f);
//開始播放動畫
springAnimation.start();
//通過1秒後終止動畫
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
springAnimation.cancel();
}
},1000);
}
複製代碼
在上面的代碼中,將動畫的阻尼比設置爲0
,而後執行動畫,1秒後經過cancel()
方法取消動畫,能夠看到,動畫中止在了當前位置。效果以下:
修改代碼,使用skipToEnd()
方法終止動畫,則會出現異常: java.lang.UnsupportedOperationException: Spring animations can only come to an end when there is damping
使用下面的方法動畫不會結束,可是也不會出錯:
//通過1秒後終止動畫
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if(springAnimation.canSkipToEnd()){
springAnimation.skipToEnd();
}
//springAnimation.cancel();
}
},1000);
複製代碼