一、原理
轉自:http://www.2cto.com/kf/201401/275838.html
Android動畫學習Demo(3) 沿着貝塞爾曲線移動的Property Animation
Property Animation中最重要,最基礎的一個類就是ValueAnimator了。Property Animation利用ValueAnimator來跟蹤記錄對象屬性已經變化了多長時間及當前這個時間點的值。
而在ValueAnimator中,又封裝了兩個類:
1)TimeInterpolator,也稱插值器,是來計算當前動畫運動的一個跟時間有關系的比例因子。
2)TypeEvaluator,這個就是利用TimeInterpolator計算出來的因子來算出當前動畫運行到的位置。
這樣講太抽象了,我們還是先用自然語言來描述一下整個動畫的過程吧。
動畫的原理,其實就是一幀幀的畫面順着時間順序,在我們眼中形成視覺殘留的效果。所以在動畫中,時間的概念是很重要的,只有時間的變化,才能形成動畫效果。
0)動畫准備開始,我們在這里設置了一個動畫的時長(duration),如果不設置的話,動畫的時長就是300毫秒,每個畫面顯示的時間是10ms。同時也設置了某個屬性值在這個時間段中變化的起始值start和結束值end,意思就是說,在duration時間中,屬性值要從start 變化到 end。
1)動畫開始了,過了 t 時間,ValueAnimator會根據 t / duration 算出一個時間消逝的比例因子(elapsed fraction),意思就是說,現在時間到 t了,我們假設總的時間的duration就是3t吧,那就是現在已經過了1/3時間了,那這個屬性值也應該要變化到1/3了。
2)動畫繼續,現在到了2t了,那么現在動畫時間已經過了2/3了,那么這個屬性值是不是已經變化到2/3了呢。
3)現在到了3t了,動畫結束了,屬性值就已經從start變成end值了。
那么現在問題來了,如果都是這樣算的話,那動畫不就一直是很勻速的了嗎?是的,如果用的是LinearInterpolator的話。
TimeInterpolator
TimeInterpolator就是用來改變我們這個動畫速度的這樣一個類了。為什么叫插值器呢?我理解就是,本來動畫踩着時間點,一步一步走的挺好的,它硬生生在中間的插了些值進去,或者抽了一些值出去,讓整條路變得不好走了,前面變然變上坡了,走起來就慢了,本來過去 t 時間之后,動畫的畫面也應該在1/3的位置了,但是路不好走,它就走不到1/3,而可能只走了1/4了,而后面是下坡,一激動,步伐就快了許多,又趕上去了,但是不管中間的路怎么變化,時間點一到,一定是剛剛好落在最終的位置上的。 Android中提供的Interpolator主要有九個: 1)AccelerateDecelerateInterpolator:先加速再減速。
2)AccelerateInterpolator:一直加速。
3)AnticipateInterpolator:先往后一下,再嗖的一聲一往無前。
4)AnticipateOvershootInterpolator:先往后一下,再一直往前超過終點,再往回收一下。
5)BounceInterpolator:最后像個小球彈幾下。
6)CycleInterpolator:重復幾次,感覺就是環形進度條那種,具體我還沒試過。
7)DecelerateInterpolator:一直減速。
8)LinearInterpolator:線性,這個就是我們上面講到的很均勻的了。
9)OvershootInterpolator:到了終點之后,超過一點,再往回走。有個參數可以定義,超過的力度。
這些Interpolator都是實現了TimeInterpolator接口的類,它們只需要實現一個方法:getInterpolation (float input),將這個input根據自己的需求重新計算這個比例
第一步:當到了某時間t之后,ValueAnimator會算出某個比例 fraction = t / duration,而Interpolator會接收這個比例fraction,再調用其getInterpolation方法將這個比例因子重新計算一下,返回一個新的比例因子,比如LinearInterpolator實現的方法就是什么都不變,如下:
1
2
3
|
public
float
getInterpolation(
float
input) {
return
input;
}
|
而 AccelerateDecelerateInterpolator 則會利用余弦函數的對稱性變化計算這個比例因子,如下:
1
2
3
|
public
float
getInterpolation(
float
input) {
return
(
float
)(Math.cos((input +
1
) * Math.PI) /
2
.0f) +
0
.5f;
}
|
如上所述,通過第一步 Interpolator 的插值,我們會得到一個比例因子,接下來就是要用到我們的TypeEvaluator了。
TypeEvaluator
第二步:TypeEvaluator會接受第一步中算出來的比例因子,然后算出當前的屬性的值,將其返回給ValuaAnimator,由ValueAnimator去設置對應屬性的值。 比如,我自己寫了一個BezierTypeEvaluator,根據時間的變化來讓一個按鈕沿着貝塞爾曲線移動,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
class
BezierEvaluator
implements
TypeEvaluator<pointf>{
@Override
public
PointF evaluate(
float
fraction, PointF startValue,
PointF endValue) {
final
float
t = fraction;
float
oneMinusT =
1
.0f - t;
PointF point =
new
PointF();
PointF point0 = (PointF)startValue;
PointF point1 =
new
PointF();
point1.set(width,
0
);
PointF point2 =
new
PointF();
point2.set(
0
, height);
PointF point3 = (PointF)endValue;
point.x = oneMinusT * oneMinusT * oneMinusT * (point0.x)
+ 3 * oneMinusT * oneMinusT * t * (point1.x)
+ 3 * oneMinusT * t * t * (point2.x)
+ t * t * t * (point3.x);
point.y = oneMinusT * oneMinusT * oneMinusT * (point0.y)
+ 3 * oneMinusT * oneMinusT * t * (point1.y)
+ 3 * oneMinusT * t * t * (point2.y)
+ t * t * t * (point3.y);
return
point;
}
}</pointf>
|
自定義TypeEvaluator,我們必須實現其evaluate方法,目的就是計算出目前的對象對應屬性的值,而它會接收三個參數,一個是上文中通過interpolator算出的比例,還有我們在創建動畫時設置的起始值和結束值。
ValueAnimator.AnimatorUpdateListener
既然我們已經算出了在 t 時刻,對象的某個屬性的值,那么我們要把這個值重新設置到對象中,才能夠起作用啊。所以ValueAnimator也提供了一個內部的Listener接口,其只有一個方法,就是獲取TypeEvaluator計算出來的值,並設置給對應的屬性,比如我們Demo中的代碼:
1
2
3
4
5
6
7
8
|
valueAnimator.addUpdateListener(
new
AnimatorUpdateListener() {
@Override
public
void
onAnimationUpdate(ValueAnimator animation) {
PointF pointF = (PointF)animation.getAnimatedValue();
button.setX(pointF.x);
button.setY(pointF.y);
}
});
|
我們在這里改變Button的X坐標和Y坐標,從而改變其具體的位置。至於validate,然后引起重新繪制的過程,對於這些基本的屬性,ValueAnimator已經幫我們實現了。 下面,我們看看效果圖,然后再總結一下ValueAnimator的實現機制。 下載
嗯,這一篇文章大概就是這樣了,大家如果有興趣了解Property Animation的應用的話,可以看一下Android動畫學習Demo(2) 關於Property Animation的用法及總結
最后還是要提醒一下,Property Animation是3.0以后才支持的,如果大家想在3.0之前去應用這些屬性的話,可以去下載jake wharton的nineoldandroids包,基本上都可以直接將方法套上,不過據我實驗,還是有某些方法,比如 PropertyValuesHolder就會有些bug出現的。我把這個包也放在這里吧,
點擊NineoldAndroids下載
二、自定義貝塞爾曲線View
轉自:http://www.2cto.com/kf/201604/497130.html
Android 自定義View高級特效,神奇的貝塞爾曲線
效果圖
效果圖中我們實現了一個簡單的隨手指滑動的二階貝塞爾曲線,還有一個復雜點的,穿越所有已知點的貝塞爾曲線。學會使用貝塞爾曲線后可以實現例如QQ紅點滑動刪除啦,360動態球啦,bulabulabula~
什么是貝塞爾曲線?
貝賽爾曲線(Bézier曲線)是電腦圖形學中相當重要的參數曲線。更高維度的廣泛化貝塞爾曲線就稱作貝塞爾曲面,其中貝塞爾三角是一種特殊的實例。貝塞爾曲線於1962年,由法國工程師皮埃爾·貝塞爾(Pierre Bézier)所廣泛發表,他運用貝塞爾曲線來為汽車的主體進行設計。貝塞爾曲線最初由Paul de Casteljau於1959年運用de Casteljau算法開發,以穩定數值的方法求出貝塞爾曲線。
讀完上述貝塞爾曲線簡介我還是一頭霧水,來個示例唄。
示例
線性貝塞爾曲線
給定點P0、P1,線性貝塞爾曲線只是一條兩點之間的直線。這條線由下式給出:
二次方貝塞爾曲線
二次方貝塞爾曲線的路徑由給定點P0、P1、P2的函數B(t)追蹤:
三次方貝塞爾曲線
P0、P1、P2、P3四個點在平面或在三維空間中定義了三次方貝塞爾曲線。曲線起始於P0走向P1,並從P2的方向來到P3。一般不會經過P1或P2;公式如下:
N次方貝塞爾曲線
身為三維生物超出三維我很方,這里只給示例圖。想具體了解的同學請左轉度娘。
就當沒看過上面
Android在API=1的時候就提供了貝塞爾曲線的畫法,只是隱藏在Path#quadTo()和Path#cubicTo()方法中,一個是二階貝塞爾曲線,一個是三階貝塞爾曲線。當然,如果你想自己寫個方法,依照上面貝塞爾的表達式也是可以的。不過一般沒有必要,因為Android已經在native層為我們封裝好了二階和三階的函數。
從一個二階貝塞爾開始
自定義一個BezierView
初始化各個參數,花3s掃一下即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
<code
class
=
"hljs java"
>
private
Paint mPaint;
private
Path mPath;
private
Point startPoint;
private
Point endPoint;
// 輔助點
private
Point assistPoint;
public
BezierView(Context context) {
this
(context,
null
);
}
public
BezierView(Context context, AttributeSet attrs) {
this
(context, attrs,
0
);
}
public
BezierView(Context context, AttributeSet attrs,
int
defStyleAttr) {
super
(context, attrs, defStyleAttr);
init(context);
}
private
void
init(Context context) {
mPaint =
new
Paint();
mPath =
new
Path();
startPoint =
new
Point(
300
,
600
);
endPoint =
new
Point(
900
,
600
);
assistPoint =
new
Point(
600
,
900
);
// 抗鋸齒
mPaint.setAntiAlias(
true
);
// 防抖動
mPaint.setDither(
true
);
}</code>
|
在onDraw中畫二階貝塞爾
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<code
class
=
"hljs avrasm"
>
// 畫筆顏色
mPaint.setColor(Color.BLACK);
// 筆寬
mPaint.setStrokeWidth(POINTWIDTH);
// 空心
mPaint.setStyle(Paint.Style.STROKE);
// 重置路徑
mPath.reset();
// 起點
mPath.moveTo(startPoint.x, startPoint.y);
// 重要的就是這句
mPath.quadTo(assistPoint.x, assistPoint.y, endPoint.x, endPoint.y);
// 畫路徑
canvas.drawPath(mPath, mPaint);
// 畫輔助點
canvas.drawPoint(assistPoint.x, assistPoint.y, mPaint);</code>
|
上面注釋很清晰就不贅述了。示例中貝塞爾是可以跟着手指的滑動而變化,我一拍榴蓮,肯定是復寫了onTouchEvent()!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<code
class
=
"hljs cs"
>
@Override
public
boolean
onTouchEvent(MotionEvent event) {
switch
(event.getAction()) {
case
MotionEvent.ACTION_DOWN:
case
MotionEvent.ACTION_MOVE:
assistPoint.x = (
int
) event.getX();
assistPoint.y = (
int
) event.getY();
Log.i(TAG,
"assistPoint.x = "
+ assistPoint.x);
Log.i(TAG,
"assistPoint.Y = "
+ assistPoint.y);
invalidate();
break
;
}
return
true
;
}</code>
|
最后將我們自定義的BezierView添加到布局文件中。至此一個簡單的二階貝塞爾曲線就完成了。假設一下,在向下拉動的過程中,在曲線上增加一個“小超人”,360動態清理是不是就出來了呢?有興趣的可以自己拓展下。
以一個三階貝塞爾結束
天氣預報曲線圖示例
(圖一)
(圖二)
概述
要想得到上圖的效果,需要二階貝塞爾和三階貝塞爾配合。具體表現為,第一段和最后一段曲線為二階貝塞爾,中間N段都為三階貝塞爾曲線。
思路
先根據相鄰點(P1,P2, P3)計算出相鄰點的中點(P4, P5),然后再計算相鄰中點的中點(P6)。然后將(P4,P6, P5)組成的線段平移到經過P2的直線(P8,P2,P7)上。接着根據(P4,P6,P5,P2)的坐標計算出(P7,P8)的坐標。最后根據P7,P8等控制點畫出三階貝塞爾曲線。
點和線的解釋
黑色點:要經過的點,例如溫度 藍色點:兩個黑色點構成線段的中點 黃色點:兩個藍色點構成線段的中點 灰色點:貝塞爾曲線的控制點 紅色線:黑色點的折線圖 黑色線:黑色點的貝塞爾曲線,也是我們最終想要的效果
聲明
為了方便講解以及讀者的理解。本篇以圖一效果為例進行講解。BezierView坐標都是根據屏幕動態生成的,想要圖二的效果只需修改初始坐標,不用對代碼做很大的修改即可實現。
那么,開始吧!
初始化參數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
<code
class
=
"hljs java"
>
private
static
final
String TAG =
"BIZIER"
;
private
static
final
int
LINEWIDTH =
5
;
private
static
final
int
POINTWIDTH =
10
;
private
Context mContext;
/** 即將要穿越的點集合 */
private
List<point> mPoints =
new
ArrayList<>();
/** 中點集合 */
private
List<point> mMidPoints =
new
ArrayList<>();
/** 中點的中點集合 */
private
List<point> mMidMidPoints =
new
ArrayList<>();
/** 移動后的點集合(控制點) */
private
List<point> mControlPoints =
new
ArrayList<>();
private
int
mScreenWidth;
private
int
mScreenHeight;
private
void
init(Context context) {
mPaint =
new
Paint();
mPath =
new
Path();
// 抗鋸齒
mPaint.setAntiAlias(
true
);
// 防抖動
mPaint.setDither(
true
);
mContext = context;
getScreenParams();
initPoints();
initMidPoints(
this
.mPoints);
initMidMidPoints(
this
.mMidPoints);
initControlPoints(
this
.mPoints,
this
.mMidPoints ,
this
.mMidMidPoints);
}</point></point></point></point></code>
|
第一個函數獲取屏幕寬高就不說了。緊接着初始化了初始點、中點、中點的中點、控制點。我們一個個的跟進。首先是初始點。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<code
class
=
"hljs java"
>
/** 添加即將要穿越的點 */
private
void
initPoints() {
int
pointWidthSpace = mScreenWidth /
5
;
int
pointHeightSpace =
100
;
for
(
int
i =
0
; i <
5
; i++) {
Point point;
// 一高一低五個點
if
(i%
2
!=
0
) {
point =
new
Point((
int
) (pointWidthSpace*(i +
0.5
)), mScreenHeight/
2
- pointHeightSpace);
}
else
{
point =
new
Point((
int
) (pointWidthSpace*(i +
0.5
)), mScreenHeight/
2
);
}
mPoints.add(point);
}
}</code>
|
這里循環創建了一高一低五個點,並添加到List mPoints中。上文說道圖一到圖二只需修改這里的初始點即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
<code
class
=
"hljs java"
>
/** 初始化中點集合 */
private
void
initMidPoints(List<point> points) {
for
(
int
i =
0
; i < points.size(); i++) {
Point midPoint =
null
;
if
(i == points.size()-
1
){
return
;
}
else
{
midPoint =
new
Point((points.get(i).x + points.get(i +
1
).x)/
2
, (points.get(i).y + points.get(i +
1
).y)/
2
);
}
mMidPoints.add(midPoint);
}
}
/** 初始化中點的中點集合 */
private
void
initMidMidPoints(List<point> midPoints){
for
(
int
i =
0
; i < midPoints.size(); i++) {
Point midMidPoint =
null
;
if
(i == midPoints.size()-
1
){
return
;
}
else
{
midMidPoint =
new
Point((midPoints.get(i).x + midPoints.get(i +
1
).x)/
2
, (midPoints.get(i).y + midPoints.get(i +
1
).y)/
2
);
}
mMidMidPoints.add(midMidPoint);
}
}</point></point></code>
|
這里算出中點集合以及中點的中點集合,小學數學題沒什么好說的。唯一需要注意的是他們數量的差別。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<code
class
=
"hljs avrasm"
>
/** 初始化控制點集合 */
private
void
initControlPoints(List<point> points, List<point> midPoints, List<point> midMidPoints){
for
(
int
i =
0
; i < points.size(); i ++){
if
(i ==
0
|| i == points.size()-
1
){
continue
;
}
else
{
Point before =
new
Point();
Point after =
new
Point();
before.x = points.get(i).x - midMidPoints.get(i -
1
).x + midPoints.get(i -
1
).x;
before.y = points.get(i).y - midMidPoints.get(i -
1
).y + midPoints.get(i -
1
).y;
after.x = points.get(i).x - midMidPoints.get(i -
1
).x + midPoints.get(i).x;
after.y = points.get(i).y - midMidPoints.get(i -
1
).y + midPoints.get(i).y;
mControlPoints.add(before);
mControlPoints.add(after);
}
}
}</point></point></point></code>
|
大家需要注意下這個方法的計算過程。以圖一(P2,P4, P6,P8)為例。現在P2、P4、P6的坐標是已知的。根據由於(P8, P2)線段由(P4, P6)線段平移而來,所以可得如下結論:P2 - P6 = P8 - P4 。即P8 = P2 - P6 + P4。其余同理。
畫輔助點以及對比折線圖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<code
class
=
"hljs mel"
>
@Override
protected
void
onDraw(Canvas canvas) {
super
.onDraw(canvas);
// ***********************************************************
// ************* 貝塞爾進階--曲滑穿越已知點 **********************
// ***********************************************************
// 畫原始點
drawPoints(canvas);
// 畫穿越原始點的折線
drawCrossPointsBrokenLine(canvas);
// 畫中間點
drawMidPoints(canvas);
// 畫中間點的中間點
drawMidMidPoints(canvas);
// 畫控制點
drawControlPoints(canvas);
// 畫貝塞爾曲線
drawBezier(canvas);
}</code>
|
可以看到,在畫貝塞爾曲線之前我們畫了一系列的輔助點,還有和貝塞爾曲線作對比的折線圖。效果如圖一。輔助點的坐標全都得到了,基本的畫畫就比較簡單了。有能力的可跳過下面這段,直接進入drawBezier(canvas)
方法。基本的畫畫這里只貼代碼,如有疑問可評論或者私信。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
<code
class
=
"hljs java"
>
/** 畫原始點 */
private
void
drawPoints(Canvas canvas) {
mPaint.setStrokeWidth(POINTWIDTH);
for
(
int
i =
0
; i < mPoints.size(); i++) {
canvas.drawPoint(mPoints.get(i).x, mPoints.get(i).y, mPaint);
}
}
/** 畫穿越原始點的折線 */
private
void
drawCrossPointsBrokenLine(Canvas canvas) {
mPaint.setStrokeWidth(LINEWIDTH);
mPaint.setColor(Color.RED);
// 重置路徑
mPath.reset();
// 畫穿越原始點的折線
mPath.moveTo(mPoints.get(
0
).x, mPoints.get(
0
).y);
for
(
int
i =
0
; i < mPoints.size(); i++) {
mPath.lineTo(mPoints.get(i).x, mPoints.get(i).y);
}
canvas.drawPath(mPath, mPaint);
}
/** 畫中間點 */
private
void
drawMidPoints(Canvas canvas) {
mPaint.setStrokeWidth(POINTWIDTH);
mPaint.setColor(Color.BLUE);
for
(
int
i =
0
; i < mMidPoints.size(); i++) {
canvas.drawPoint(mMidPoints.get(i).x, mMidPoints.get(i).y, mPaint);
}
}
/** 畫中間點的中間點 */
private
void
drawMidMidPoints(Canvas canvas) {
mPaint.setColor(Color.YELLOW);
for
(
int
i =
0
; i < mMidMidPoints.size(); i++) {
canvas.drawPoint(mMidMidPoints.get(i).x, mMidMidPoints.get(i).y, mPaint);
}
}
/** 畫控制點 */
private
void
drawControlPoints(Canvas canvas) {
mPaint.setColor(Color.GRAY);
// 畫控制點
for
(
int
i =
0
; i < mControlPoints.size(); i++) {
canvas.drawPoint(mControlPoints.get(i).x, mControlPoints.get(i).y, mPaint);
}
}
</code>
|
畫貝塞爾曲線
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<code
class
=
"hljs avrasm"
>
/** 畫貝塞爾曲線 */
private
void
drawBezier(Canvas canvas) {
mPaint.setStrokeWidth(LINEWIDTH);
mPaint.setColor(Color.BLACK);
// 重置路徑
mPath.reset();
for
(
int
i =
0
; i < mPoints.size(); i++){
if
(i ==
0
){
// 第一條為二階貝塞爾
mPath.moveTo(mPoints.get(i).x, mPoints.get(i).y);
// 起點
mPath.quadTo(mControlPoints.get(i).x, mControlPoints.get(i).y,
// 控制點
mPoints.get(i +
1
).x,mPoints.get(i +
1
).y);
}
else
if
(i < mPoints.size() -
2
){
// 三階貝塞爾
mPath.cubicTo(mControlPoints.get(
2
*i-
1
).x,mControlPoints.get(
2
*i-
1
).y,
// 控制點
mControlPoints.get(
2
*i).x,mControlPoints.get(
2
*i).y,
// 控制點
mPoints.get(i+
1
).x,mPoints.get(i+
1
).y);
// 終點
}
else
if
(i == mPoints.size() -
2
){
// 最后一條為二階貝塞爾
mPath.moveTo(mPoints.get(i).x, mPoints.get(i).y);
// 起點
mPath.quadTo(mControlPoints.get(mControlPoints.size()-
1
).x,mControlPoints.get(mControlPoints.size()-
1
).y,
mPoints.get(i+
1
).x,mPoints.get(i+
1
).y);
// 終點
}
}
canvas.drawPath(mPath,mPaint);
}
</code>
|
注釋太詳細,都沒什么好寫的了。不過這里需要注意判斷里面的條件,對起點和終點的判斷一定要理解。要不然很可能會送你一個ArrayIndexOutOfBoundsException。
結束
貝塞爾曲線可以實現很多絢麗的效果,難的不是貝塞爾,而是good idea。
三、使用
轉自:
研究一下貝塞爾曲線.
- /**
- * 三階貝塞爾方程
- */
- private class BeizerEvaluator implements TypeEvaluator<PointF> {
- private PointF point1;
- private PointF point2;
- private PointF pointF;
- public BeizerEvaluator(PointF point1, PointF point2) {
- this.point1 = point1;
- this.point2 = point2;
- }
- @Override
- public PointF evaluate(float time, PointF start, PointF end) {
- float timeLeft = 1.0f - time;
- pointF = new PointF();//結果
- PointF point0 = start;//起點
- PointF point3 = end;//終點
- pointF.x = timeLeft * timeLeft * timeLeft * (point0.x)
- + 3 * timeLeft * timeLeft * time * (point1.x)
- + 3 * timeLeft * time * time * (point2.x)
- + time * time * time * (point3.x);
- pointF.y = timeLeft * timeLeft * timeLeft * (point0.y)
- + 3 * timeLeft * timeLeft * time * (point1.y)
- + 3 * timeLeft * time * time * (point2.y)
- + time * time * time * (point3.y);
- return pointF;
- }
- }
- //初始化一個BezierEvaluator
- BeizerEvaluator evaluator = new BeizerEvaluator(getPointF(1), getPointF(2));
- ValueAnimator animator = ValueAnimator.ofObject(evaluator, new PointF(rand.nextInt(getWidth()), 0), new PointF(rand.nextInt(getWidth()), mHeight - dHeight));//隨機
- animator.addUpdateListener(new BezierListenr(tag));
- animator.setInterpolator(interpolators[rand.nextInt(3)]);
- animator.setTarget(tag);
- animator.setDuration(3000);
然后在需要更新的時候去Update設置imageVIew的路徑:
- private class BezierListenr implements ValueAnimator.AnimatorUpdateListener {
- private View target;
- public BezierListenr(View target) {
- this.target = target;
- }
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- PointF pointF = (PointF) animation.getAnimatedValue();
- ViewHelper.setX(target, pointF.x);
- ViewHelper.setY(target, pointF.y);
- ViewHelper.setAlpha(target, 1 - animation.getAnimatedFraction());
- }
- }
GitHub:https://github.com/q422013/BezierFlower