最近開始學習自定義View,看到如今公司項目上的一個動畫效果,頓時想到其實能夠本身畫,因而就開始着手優(zhuang)化(bi)這個動畫。java
動畫以下: git
其實很簡單對不對,但初學者的我仍是要思考一下。github
動畫有兩部分,canvas
根據UI提供的詳細動畫細節,能夠知道:app
動效總共時間爲2S,以後反覆循環,每秒幀數爲24幀,其中圓形元素控件大小爲6px,拉伸都是以圓心爲拉伸中心點進行拉伸。 0-1s爲從左到右的拉伸動畫,1s-2s爲從右到左的拉伸動畫,以後爲循環。ide
每個點,都有5種不一樣的拉伸量,這裏咱們把對應的拉伸後的Y的高度命名爲:函數
public float maxHeight;
public float threeHeight;
public float halfHeight;
public float oneHeight;
複製代碼
其中還有一種拉伸量爲0。maxHeight是最大的高度,threeHeight爲次最高高度。post
第1幀: 初始化,每個的點均爲 6 px * 6 px性能
第2幀: 元素1 高7.3px 對應 oneHeight,其餘元素保持初始狀態。學習
第3幀: 元素1 高11px 對應 halfHeight,其餘元素保持初始狀態
第4幀: 元素1 高14.7px 對應 threeHeight,元素2 高8.3px 對應 oneHeight,其餘元素保持初始狀態。
第5幀: 元素1 高16px 對應 maxHeight,元素2 高15px 對應 halfHeight,其餘元素保持初始狀態。
第6幀: 元素1 高14.7px 對應 threeHeight,元素2 高21.7px 對應 threeHeight,元素3 高10.1px 對應 oneHeight,其餘元素保持初始狀態。
... 經過觀察,我...先定義一個類,來存儲這些點的四種拉伸量的高度(咱們也稱之爲狀態)和中心位置的座標:
public class VoiceAnimPoint {
public int centerX, centerY;
public float maxHeight;
public float threeHeight;
public float halfHeight;
public float oneHeight;
public VoiceAnimPoint(int centerX, int centerY, float maxHeight, float threeHeight, float halfHeight, float oneHeight) {
this.centerX = centerX;
this.centerY = centerY;
this.maxHeight = maxHeight;
this.threeHeight = threeHeight;
this.halfHeight = halfHeight;
this.oneHeight = oneHeight;
}
}
複製代碼
而後在 onSizeChanged
中初始化這五個點:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
points = new VoiceAnimPoint[5];
points[0] = new VoiceAnimPoint(getWidth()/2-24,getHeight()/2,16f,14.7f,11f,7.3f);
points[1] = new VoiceAnimPoint(getWidth()/2-12,getHeight()/2,24f,21.7f,15f, 8.3f);
points[2] = new VoiceAnimPoint(getWidth()/2,getHeight()/2,38f,33.9f,22f,10.1f);
points[3] = new VoiceAnimPoint(getWidth()/2+12,getHeight()/2,24f,21.7f,15f,8.3f);
points[4] = new VoiceAnimPoint(getWidth()/2+24,getHeight()/2,16f,14.7f,11f,7.3f);
}
複製代碼
對於元素1,咱們看到有這樣的一個變化過程:
那麼其餘的元素呢,咱們把他們的變化過程放在一塊兒:
那麼onDraw中能夠這樣來:pointIndex 表明着元素1繪製的第 N 幀,而後依次按照如上所分析的去獲得其餘元素的對應幀。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < points.length; i++) {
VoiceAnimPoint point = points[i];
int y = indexChangeFunc(pointIndex - i*2);
switch (y) {
case 0:
canvas.drawLine(point.centerX,point.centerY, point.centerX,point.centerY+0.01f,paint);
break;
case 2:
canvas.drawLine(point.centerX,point.centerY-point.halfHeight/2+pointWidth/2,
point.centerX,point.centerY+point.halfHeight/2-pointWidth/2,paint);
break;
case 4:
canvas.drawLine(point.centerX,point.centerY-point.maxHeight/2+pointWidth/2,
point.centerX,point.centerY+point.maxHeight/2-pointWidth/2,paint);
break;
case 1:
canvas.drawLine(point.centerX,point.centerY-point.oneHeight/2+pointWidth/2,
point.centerX,point.centerY+point.oneHeight/2-pointWidth/2,paint);
break;
case 3:
canvas.drawLine(point.centerX,point.centerY-point.threeHeight/2+pointWidth/2,
point.centerX,point.centerY+point.threeHeight/2-pointWidth/2,paint);
break;
}
}
}
複製代碼
其中的0-4狀態就是上面的函數的Y值,經過X獲得相應的Y,而Y則對應則元素的5中狀態,onDraw中就是根據5中狀態去繪製相應的高度:
/** * 動畫軌跡其實符合一個函數 * 這裏傳入對應的x,返回函數的y * @param x 位置 * @return y 4 : 最大, 3:threeHeight, 2: 一半, 1:oneHeight, 0 :0 。 */
private int indexChangeFunc(int x) {
if (x<0)
return 0;
else if (x<4)
return x;
else if (x<8)
return -x + 8;
else
return 0;
}
複製代碼
drawLine
畫,並且上下端是圓角的,因此咱們要設置Paint
的線帽爲圓形,paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(0xffC2E379);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(pointWidth);
paint.setStrokeCap(Paint.Cap.ROUND);
複製代碼
注意的一點,假如你的Paint
寬10px,而同時你又設置了線帽爲圓形,畫了20px的line
,那其實你畫的以下:
因此纔有上面的onDraw
中的,高度要減去線帽:
canvas.drawLine(point.centerX,point.centerY-point.maxHeight/2+pointWidth/2,point.centerX,point.centerY+point.maxHeight/2-pointWidth/2,paint);
複製代碼
pointIndex
自增表明正序播放,pointIndex
自減表明倒敘播放。@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(bgBitmap, getWidth() / 2 - bgBitmap.getWidth() / 2, getHeight() / 2 - bgBitmap.getHeight() / 2, paint);
for (int i = 0; i < points.length; i++) {
VoiceAnimPoint point = points[i];
int y = indexChangeFunc(pointIndex - i*2);
switch (y) {
case 0:
canvas.drawLine(point.centerX,point.centerY, point.centerX,point.centerY+0.01f,paint);
break;
case 2:
canvas.drawLine(point.centerX,point.centerY-point.halfHeight/2+pointWidth/2,
point.centerX,point.centerY+point.halfHeight/2-pointWidth/2,paint);
break;
case 4:
canvas.drawLine(point.centerX,point.centerY-point.maxHeight/2+pointWidth/2,
point.centerX,point.centerY+point.maxHeight/2-pointWidth/2,paint);
break;
case 1:
canvas.drawLine(point.centerX,point.centerY-point.oneHeight/2+pointWidth/2,
point.centerX,point.centerY+point.oneHeight/2-pointWidth/2,paint);
break;
case 3:
canvas.drawLine(point.centerX,point.centerY-point.threeHeight/2+pointWidth/2,
point.centerX,point.centerY+point.threeHeight/2-pointWidth/2,paint);
break;
}
}
if (!isRevert) {
pointIndex++;
}
else {
pointIndex--;
}
if (pointIndex == 23) {
isRevert = true;
pointIndex = 17;
}
else if (pointIndex == -6) {
pointIndex = 0;
isRevert = false;
}
}
複製代碼
invalidate()
方法。private Runnable r = new Runnable() {
@Override
public void run() {
VoiceAnimView.this.invalidate();
VoiceAnimView.this.postDelayed(r, 42);
}
};
public void startAnim() {
if (!isStart) {
isStart = true;
this.post(r);
}
}
public void stopAnim() {
if (isStart) {
isStart = false;
this.removeCallbacks(r);
}
}
複製代碼
lottie
直接去實現,可謂方便快捷:用了自定義動畫後:
這算是本身的第一個自定義View,可能實現思路上有問題,或者你們有更好的思路,歡迎一塊兒討論! 還有幾個問題:
invalidate()
的無參數方法,使用有參數的方法,但我查了一下官方文檔,在API21後,有參數的方法就無論用了。還有什麼別的方法優化本身的自定義動畫?