先上圖
思考步驟還是和上一篇講的一樣芦圾,相同的套路:
自定義View(1)--QQ運動計步器
構(gòu)造器不說了憔恳,正常情況一般三個都會寫,這篇我沒從attr
里面寫東西婴洼,所以這一步跳過了,我們直接來要真正思考的地方:首先我們這里實現(xiàn)的話有很多種方式撼嗓,可以用圖片柬采,可以自己繪制欢唾,這里我們選擇自己繪制,但是繪制的話粉捻,我們又要考慮這個動畫是需要我們自己算坐標然后重繪礁遣?還是寫成單獨的一個view
然后通過動畫實現(xiàn),這里我選擇了后者肩刃,原因有三個:第一個是如果在 view
內(nèi)部寫相關(guān)的移動旋轉(zhuǎn)邏輯的話祟霍,計算量不用說增加了,這個是很費時間的盈包,而且很容易出錯沸呐;第二個是因為你不停的調(diào)用invalidate
,看了源碼的都知道呢燥,調(diào)用這個函數(shù)的會做很多工作崭添,造成不必要的gpu
耗費,這樣不好叛氨;第三個是實用性呼渣,如果以后我們需要這樣一個圖像,我們可以直接把這個形狀的view
復制過去就可以使用力试,所以我們理清思路徙邻。
首先,上面跳動的view
作為一個單獨的view
畸裳,下面的弧形陰影也做為一個單獨的view
,然后在外層通過一個viewGroup
把他們組裝起來并且控制相關(guān)的代碼和其性能的優(yōu)化。
我們先繪制圖形的 view
淳地,也就是上下跳動的哪個view
怖糊。我們 需要組裝,所以我們肯定要重寫onMeasure
設置其寬高颇象,這里我打算指定他的高度和寬度為圓的半徑的兩倍:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
}
/**
* Determines the width of this view
*
* @param measureSpec A measureSpec packed into an int
* @return The width of the view, honoring constraints from measureSpec
*/
private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = mRadio * 2;
}
return result;
}
/**
* Determines the height of this view
*
* @param measureSpec A measureSpec packed into an int
* @return The height of the view, honoring constraints from measureSpec
*/
private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 2 * mRadio;
}
return result;
}
接下來我們繪制圖形伍伤,考略到我們需要變換不同形狀的view
,所以我寫的時候分別寫了三個繪制不同圖案的函數(shù),然后添加一個flag
判斷繪誰即可
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
initCenterPoint();
if (mFlog == 0)
drawCircle(canvas);
else if (mFlog == 1)
drawRect(canvas);
else
drawTriangle(canvas);
}
private void initCenterPoint() {
if (mCenterX == -1 || mCenterY == -1) {
mCenterX = getMeasuredWidth() / 2;
mCenterY = getMeasuredHeight() / 2;
// setPivotX(mCenterX);
// setPivotY(mCenterY);
}
}
/**
* 畫圓
*
* @param canvas
*/
private void drawCircle(Canvas canvas) {
canvas.drawCircle(mCenterX, mCenterY, mRadio, mCirclePaint);
}
/**
* 畫正方形
*
* @param canvas
*/
private void drawRect(Canvas canvas) {
Rect rect = new Rect(mCenterX - mRadio,
mCenterY - mRadio,
mCenterX + mRadio,
mCenterY + mRadio);
canvas.drawRect(rect, mRectPaint);
}
public void setFlog(int mFlog) {
this.mFlog = mFlog;
invalidate();
}
/**
* 繪制三角形
*
* @param canvas
*/
private void drawTriangle(Canvas canvas) {
Path path = new Path();
path.moveTo(mCenterX, mCenterY - mRadio);
path.lineTo(mCenterX - mRadio, mCenterY + mRadio);
path.lineTo(mCenterX + mRadio, mCenterY + mRadio);
canvas.drawPath(path, mTrianglePaint);
}
測試一下這個圖案已經(jīng)繪制完成了遣钳,接下來我們繪制下面的陰影扰魂,同樣的我們需要先測量,我這里設置寬高為半徑值的2/3
,寬為兩倍的半徑:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
}
/**
* Determines the width of this view
*
* @param measureSpec A measureSpec packed into an int
* @return The width of the view, honoring constraints from measureSpec
*/
private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = mRadio * 2;
}
return result;
}
/**
* Determines the height of this view
*
* @param measureSpec A measureSpec packed into an int
* @return The height of the view, honoring constraints from measureSpec
*/
private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 2 * mRadio / 3;
}
return result;
}
接下來我們繪制圖案蕴茴,我在繪制這個圖案的時候是采用drawArc
的方式劝评,所以我們需要計算一下它的范圍,我這采用的方案如下:
因為我們要上面的那塊橫線挨著物塊view
掉下的位置,所以rect
的中心點就是這個,然后計算如下:
int left = getWidth() / 2 - mRadio;
int top = -getHeight() / 2;
int right = getWidth() / 2 + mRadio;
int bottom = getHeight() / 2;
mRectF = new RectF(left, top, right, bottom);
其實getHeight
就是2/3
的半徑
我們繪制的時候倦淀,繪制180°
即可蒋畜,:
@Override
protected void onDraw(Canvas canvas) {
if (mRectF == null) {
int left = getWidth() / 2 - mRadio;
int top = -getHeight() / 2;
int right = getWidth() / 2 + mRadio;
int bottom = getHeight() / 2;
mRectF = new RectF(left, top, right, bottom);
}
canvas.drawArc(mRectF, 0, 180, false, mArcPaint);
}
最后我們將這兩個圖案組裝起來,加上動畫即可:
public void startAnim() {
if (mAnimatorSet == null) {
mAnimatorSet = new AnimatorSet();
//up
ObjectAnimator rotationAnim = ObjectAnimator.ofFloat(mShapeView, "rotation", 0.0f, 360.0f);
rotationAnim.setDuration(JUMP_UP_TIME);
rotationAnim.setInterpolator(new AccelerateDecelerateInterpolator());//先快后慢
ObjectAnimator translationAnimUp = ObjectAnimator.ofFloat(mShapeView, "translationY", 0, -JUMP_MAX_HEIGHT);
translationAnimUp.setDuration(JUMP_UP_TIME);
translationAnimUp.setInterpolator(new AccelerateDecelerateInterpolator());//先快后慢
ObjectAnimator scaleXAnimUp = ObjectAnimator.ofFloat(mArcView, "scaleX", 1.0f, 0.2f);
scaleXAnimUp.setDuration(JUMP_UP_TIME);
scaleXAnimUp.setInterpolator(new AccelerateDecelerateInterpolator());//先快后慢
ObjectAnimator scaleYAnimUp = ObjectAnimator.ofFloat(mArcView, "scaleY", 1.0f, 0.2f);
scaleYAnimUp.setDuration(JUMP_UP_TIME);
scaleYAnimUp.setInterpolator(new AccelerateDecelerateInterpolator());//先快后慢
//down
ObjectAnimator translationAnimDown = ObjectAnimator.ofFloat(mShapeView, "translationY", -JUMP_MAX_HEIGHT, 0);
translationAnimDown.setDuration(JUMP_UP_TIME);
translationAnimDown.setInterpolator(new AccelerateInterpolator());//先慢后快
ObjectAnimator scaleXAnimXDown = ObjectAnimator.ofFloat(mArcView, "scaleX", 0.2f, 1.0f);
scaleXAnimXDown.setDuration(JUMP_UP_TIME);
scaleXAnimXDown.setInterpolator(new AccelerateInterpolator());//先慢后快
ObjectAnimator scaleYAnimDown = ObjectAnimator.ofFloat(mArcView, "scaleY", 0.2f, 1.0f);
scaleYAnimDown.setDuration(JUMP_UP_TIME);
scaleYAnimDown.setInterpolator(new AccelerateInterpolator());//先慢后快
mAnimatorSet.play(translationAnimUp)
.with(rotationAnim)
.with(scaleXAnimUp)
.with(scaleYAnimUp)
.before(translationAnimDown)
.before(scaleXAnimXDown)
.before(scaleYAnimDown);
mAnimatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mFlog++;//圓形 0-->1方形 -->2 三角形 -->3->0---->
if (mFlog > 2) {
mFlog = 0;
}
mShapeView.setFlog(mFlog);
startAnim();
}
});
}
mAnimatorSet.start();
}
這樣的話效果實現(xiàn)了撞叽,感覺沒什么問題了姻成。但是不要忘記了優(yōu)化
優(yōu)化
在這里我們主要優(yōu)化這個動畫對應Activity的生命周期 ,讓其可見的時候加載動畫插龄,不可見的時候停止動畫。主要使用Application.ActivityLifecycleCallbacks
這個接口實現(xiàn),具體實現(xiàn)的代碼如下:
@Override
protected void onAttachedToWindow() {
mActivity.getApplication().registerActivityLifecycleCallbacks(animLifecyleCallback);
super.onAttachedToWindow();
}
@Override
protected void onDetachedFromWindow() {
mActivity.getApplication().unregisterActivityLifecycleCallbacks(animLifecyleCallback);
super.onDetachedFromWindow();
}
private SimpleActivityLifecycleCallbacks animLifecyleCallback = new SimpleActivityLifecycleCallbacks() {
@Override
public void onActivityResumed(Activity activity) { // 頁面第一次啟動的時候不會執(zhí)行
if (activity == mActivity)
startAnim();
super.onActivityResumed(activity);
}
@Override
public void onActivityPaused(Activity activity) {
if (activity == mActivity)
stopAnim();
super.onActivityPaused(activity);
}
};
這樣就更加優(yōu)雅了科展。
源碼github下載地址:
https://github.com/ChinaZeng/CustomView