屬性動畫
屬性動畫是通過逐步改變像素值從而達到動畫的效果。例如把view從左移到右,通過transformX逐步修改橫向坐標值。其他如縮放,旋轉,漸變同理。
ViewPropertyAnimator
viewPropertyAnimator屬性動畫是對于view來說的。任何其他的view都是繼承view,包括viewGroup。view內有一個方法animate通過鏈式調用就可以達到屬性動畫的效果。它也是屬性動畫中比較簡單實用的方法,但在自定義屬性動畫中,這種方法就不適合用了,原因就在于該鏈式調用的api有局限性,也就是不能對自定義屬性進行動畫,也就不能夠滿足我們自定義屬性動畫開發的需求。從而在自定義view中使用到的屬性動畫就不是選擇ViewPropertyAnimator,而是選擇另外的屬性動畫,后面會講到。
view.animate()
.translationX(DisplayUtils.dpToPx(200))
.translationY(DisplayUtils.dpToPx(200))
.alpha(100)
.rotationX(100)
.rotationY(100)
.scaleX(100)
.scaleY(100)
.withStartAction(new Runnable() {
@Override
public void run() {
}
})
.withEndAction(new Runnable() {
@Override
public void run() {
}
})
.setStartDelay(1000)
.rotation(360)
.start();
ViewPropertyAnimator使用的都是些固定的動畫和對動畫進行監聽等等,都能夠看下源碼就知道如何使用,大家可以查看下,我這里就不贅述了。
ObjectAnimator
ObjectAnimator屬性動畫可以通過自定義屬性達到我們想要的屬性動畫效果,比如說我這里自定義一個圓,通過改變圓的半徑來對圓進行縮放,我們就以這樣的效果來舉個簡單的例子,讓大家能夠很清晰的對ObjectAnimator屬性動畫有一個清晰的認識。
第一步:自定義circleView繼承view
第二步:初始化畫筆和定義變量radius:
private float radius=DisplayUtils.dpToPx(50)
第三步:在ondraw方法中畫圓:canvas.drawCircle(getWidth()/2,getHeight()/2,radius,paint);
簡單的一個自定義view就算完了,接下來我們再來使用ObjectAnimator
這里大家可以看到我們定義的屬性為radius,然而通過上面的截圖,我們能夠看到是有問題的,就是提示錯誤,提示的錯誤就是我們沒有發現setRadius(float radius)這樣的方法。我們再回到自定義CircleView處設置該方法,這里的錯誤提示就沒有了。
public void setRadius(float radius){
this.radius = radius;
}
public float getRadius(){
return radius;
}
但是這里要注意,1.get和set方法都要寫,2.雖然這里解決了錯誤提示的問題,但是當我們運行代碼,這里是沒有效果的,這又是為什么呢?原因就是我們沒有刷新view,也就是在setRadius方法塊內在設置完值后要進行刷新view的操作。也就是調用invalidate(),再次運行程序,屬性動畫效果就有了。
public void setRadius(float radius){
this.radius = radius;
invalidate();
}
public float getRadius(){
return radius;
}
這里是單個屬性的使用案例,但在實際的開發過程中,一個view可能會涉及到多個屬性動畫。這里就要講到自定義屬性的另外一個針對多個屬性動畫的動畫集合AnimatorSet
animatorSet
animatorSet是動畫集合,比如一個view有多個動畫,比如說有旋轉動畫,位移動畫,縮放動畫,以及各類不同參數下對應的動畫的一個集合,我們這里可以選擇使用animatorSet,同樣為了更好的理解屬性動畫animatorset,我們可以舉一個上篇文章最后講到的camera幾何變換中的例子展開,更深入的了解android中動畫的使用。
定義三個屬性變量分別對應camera的旋轉角度,以及上下折疊的關于x軸方向的旋轉角度flipRotation ,topFlip ,bottomFlip,并替換掉對應固定的值。然后根據上面講解的,對這三個變量進行封裝成get和set方法并在set方法賦完值后調用invalidate()
我們先來看下效果,這里我們先以bottomFlip屬性為例,代碼如下:
ObjectAnimator bottomFlipAnimator = ObjectAnimator.ofFloat(view,"bottomFlip",135);
bottomFlipAnimator.setStartDelay(1000);
bottomFlipAnimator.setDuration(1000);
bottomFlipAnimator.start();
由于我這里是使用的markdown,貌似不能插入視頻,那么我們就以運行的結果來顯示下。順便提下,如果有讀者知道如何在markdown下插入視頻,看到這篇文章希望能夠提供下解決方案。
運行結果如下:
接下來我們在把剩下的屬性動畫設置下并把三種不同的屬性動畫通過animatorSet.playSequentially添加到動畫集合animatorSet中:
ObjectAnimator bottomFlipAnimator = ObjectAnimator.ofFloat(view,"bottomFlip",30);
bottomFlipAnimator.setDuration(1000);
ObjectAnimator TopFlipAnimator = ObjectAnimator.ofFloat(view,"topFlip",-30);
TopFlipAnimator.setDuration(1000);
ObjectAnimator flipRotationAnimator = ObjectAnimator.ofFloat(view,"flipRotation",270);
flipRotationAnimator.setDuration(1000);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playSequentially(bottomFlipAnimator,flipRotationAnimator,TopFlipAnimator);
animatorSet.setStartDelay(1000);
animatorSet.start();
propertyValuelueHolder
animatorSet動畫集合就是這樣完成的,當然了除了animatorSet之外,其實ObjectAnimator也同樣的提供了類似的api,比如說propertyValueHolder,接下來我們就以例子來看下propertyValuelueHolder的使用,其實也很好理解,第一個參數就是屬性名稱,第二個參數就是屬性值。
PropertyValuesHolder bottomFlipHolder = PropertyValuesHolder.ofFloat("bottomFlip",30);
PropertyValuesHolder topFlipHolder = PropertyValuesHolder.ofFloat("topFlip",-30);
PropertyValuesHolder flipRotationHolder = PropertyValuesHolder.ofFloat("flipRotation",270);
ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(view,bottomFlipHolder,flipRotationHolder,topFlipHolder);
objectAnimator.setStartDelay(1000);
objectAnimator.setDuration(1000);
objectAnimator.start();
Keyframe
keyframe叫做關鍵幀,keyframe想必大家有可能沒有接觸過,但keyframe是相當好用的,比如你可以對某一個屬性動畫的效果能夠更加符合你理想的效果,比如阻尼系數,平移動畫每個時間段不同的運動效果,比如說在100s實現加速過后100s實現勻速最后100s實現減速等等都可以使用keyframe。同樣舉個簡單例子,比如做一個平移動畫,先加速然后減速最后加速的效果
float distance = DisplayUtils.dpToPx(300);
Keyframe keyframe1 = Keyframe.ofFloat(0,0);
Keyframe keyframe2 = Keyframe.ofFloat(0.1f,0.4f*distance);
Keyframe keyframe3 = Keyframe.ofFloat(0.9f,0.6f*distance);
Keyframe keyframe4 = Keyframe.ofFloat(1,distance);
PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("translationX",
keyframe1,keyframe2,keyframe3,keyframe4);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(imageView,holder);
animator.setStartDelay(1000);
animator.setDuration(1000);
animator.start();
interpolator
interpolator插值器我之前有一篇文章是專門講解插值器的如果讀者有興趣的,可以轉到我的另外一篇文章android動畫(一)之插值器,這里我就不贅述了。
TypeEvaluator
上面講解到的int或者float類型的屬性動畫,它們變化過程的每一個值都要被計算出來的,而幫我們計算的就是這里要講到的TypeEvaluator。TypeEvaluator就是一個計算器,他會對指定類型的屬性,精確的計算動畫里面每一個屬性值,這里的每一個屬性值不是指的時間,而是指的動畫完成度。
接下來我們實現一個類似于美團點餐的貝塞爾曲線效果,點擊左上方的菜單,右下方就是購物車,由于沒有圖片這里用圓點和正方形表示下,布局文件如下:
<RelativeLayout 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=".TypeEvaluatorActivity">
<com.zhaofan.property_animator.view.PointView
android:id="@+id/view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onStartAnimationClick"/>
<View
android:id="@+id/targetView"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:background="#000000"/>
</RelativeLayout>
由于我這里是講解屬性動畫相關的知識點,對于貝塞爾曲線大家可以到百度搜索相關的文章。我這里是使用的二階貝塞爾曲線,公式如下:
p0,p1,p2分別表示的是起始點,控制點和結束點,這里的t表示的是完成度
public void onStartAnimationClick(View view) {
//終點
int[] endPointLocation = new int[2];
targetView.getLocationOnScreen(endPointLocation);
Log.d(TAG, "outLocationX:" + endPointLocation[0] + "-----outLocationY" + endPointLocation[1]);
PointF endPointF = new PointF(endPointLocation[0], endPointLocation[1]);
controlPoint.set(endPointLocation[0] / 2f + offsetControlX, endPointLocation[1] / 2f - offsetControlY);
//起始點
PointF startPointF = new PointF(0, 0);
ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointFEvaluator(), startPointF, endPointF);
valueAnimator.setStartDelay(1000);
valueAnimator.setDuration(3000);
valueAnimator.start();
}
上面的代碼主要就是定義起始點和終點以及控制點,接下來我們使用貝塞爾公式來計算view繪制的軌跡
private static class PointFEvaluator implements TypeEvaluator<PointF> {
@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
//二階貝塞爾曲線
Log.d(TAG, "startValueX:" + startValue.x + "---startValueY:" + startValue.y);
float x = (1 - fraction) * (1 - fraction) * startValue.x + 2 * fraction * (1 - fraction) * controlPoint.x + fraction * fraction * endValue.x;
float y = (1 - fraction) * (1 - fraction) * startValue.y + 2 * fraction * (1 - fraction) * controlPoint.y + fraction * fraction * endValue.y;
return new PointF(x, y);
}
}
然后通過實現addUpdateListener監聽實現真正的動畫操作。
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF pointF = (PointF) animation.getAnimatedValue();
pointView.setX(pointF.x);
pointView.setY(pointF.y);
}
});
硬件加速
軟件繪制:用cpu來使用繪制代碼,在軟件層面就已經把所有像素都繪制好了,然后所有的像素數據都在你和屏幕交互前都已經準備好了,這種繪制方法叫軟件繪制。
硬件繪制:使用cpu調用canvas相關的api,它們并不是繪制出來,而是把它們做初步的處理,轉換成GPU操作。屏幕顯示的時候,讓GPU來繪制轉換成實際的像素。
硬件加速也就是使用GPU繪制
GPU如何實現硬件加速
原因一:GPU分擔一部分cpu工作任務,同步完成一些繪制工作,從而實現了硬件加速
原因二:跟GPU設計有關,它對繪制簡單圖形有優勢。
原因三:跟軟件流程有關。
但是硬件加速也存在一些問題,比較常見的就是兼容性問題
在動畫中使用setLayerType如下可以提高性能,這個也是google推薦的
屬性動畫的知識點差不多都涉及到了,還有一個就是文本相關的動畫,這里就不寫出來,如果大家需要看的話,代碼demo已經上傳到github。目錄在property-animator下。