一年栓、簡(jiǎn)介
最近開(kāi)始寫一些文章記錄一下以前的一些自己寫的小項(xiàng)目或者是定義View積累拆挥,積灰的東西還是要多翻出來(lái)整理整理看看的,在我的csdn上也有某抓。這個(gè)只完成了一部分燃起熄滅的動(dòng)畫纸兔,沒(méi)有為何燃起火焰的動(dòng)畫,希望有興趣的同學(xué)也可以接著完成并分享否副,話不多說(shuō)汉矿,我們來(lái)看這兩根萌萌的小蠟燭。
小蠟燭憋足氣把火焰燃起备禀,一下被旁邊的哥們吹滅了 0^0 ,看起來(lái)還是萌氣十足的啊洲拇。看著圖大家應(yīng)該能想到應(yīng)該怎么實(shí)現(xiàn)了吧曲尸,自定義View赋续!對(duì)了,但是具體要怎么把這個(gè)過(guò)程做好呢另患,跟著腳步一起來(lái)看一看吧纽乱。代碼稍微有點(diǎn)多,大家耐心觀看昆箕,有興趣的同學(xué)可以從我的GITHUB上clone下來(lái)鸦列,對(duì)著代碼看。
二鹏倘、過(guò)程實(shí)現(xiàn)
蠟燭的繪制和動(dòng)畫
-
本著面向?qū)ο蟮乃枷胧磬停苊黠@這里就是兩個(gè)蠟燭嘛!既然是這樣那我們就定義一個(gè)蠟燭類具有蠟燭的基本屬性纤泵。
public abstract class ICandle {
//蠟燭底部左下坐標(biāo)
protected int mCurX;
protected int mCurY;
//蠟燭寬高
protected int mCandleWidth;
protected int mCandleHeight;
//蠟燭左眼坐標(biāo)
protected Point mEyeLPoint;
//蠟燭右眼坐標(biāo)
protected Point mEyeRPoint;
//蠟燭眼睛半徑
protected int mEyeRadius;
//眼睛間隔距離
protected int mEyeDevide;
//身體顏色
protected int mCandleColor;
//是否停止動(dòng)畫中
protected boolean mIsAnimStoping = false;
//蠟燭芯坐標(biāo)
protected Point mCandlewickPoint;
public void initAnim(){
}
public void stopAnim(){
}
public void drawSelf(Canvas canvas){
}
}```
對(duì)應(yīng)著這蠟燭還有代碼骆姐,那就一目了然了。可能需要解釋的應(yīng)該就是下面的幾個(gè)方法了` public void initAnim()`诲锹, `stopAnim()`初始化開(kāi)始和結(jié)束動(dòng)畫需要的數(shù)據(jù)繁仁,小蠟燭將會(huì)實(shí)現(xiàn)這個(gè)方法,`drawSelf(Canvas canvas)`把畫布傳進(jìn)來(lái)然后蠟燭自己繪制自己归园。
現(xiàn)在就是讓我們來(lái)看一看小蠟燭身體內(nèi)部構(gòu)造的時(shí)候了黄虱,**hiahiahiahia!**
不對(duì)庸诱,和蠟燭生死相隨的還有火焰呢捻浦!先來(lái)看看火焰吧,等下小蠟燭還要燃燒自己呢桥爽。**+10086s**
+ **Flame**
一樣先來(lái)一睹我們的富勒姆真容
![flame.png](http://upload-images.jianshu.io/upload_images/2934422-44fd38d11b58617d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![flamex.png](http://upload-images.jianshu.io/upload_images/2934422-381f4978524815e7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
好像也沒(méi)什么毛病朱灿,首先是里面的區(qū)域,就是Flame啦钠四,外面的呢盗扒,就是Flame先生燃燒自己散發(fā)的人性之光和飄散的骨灰(手動(dòng)抹眼淚)。
來(lái)看一下Flame的實(shí)現(xiàn)吧缀去。我們一步步分析侣灶。
private static float CHANGE_FACTOR = 20;
private Paint mPaint;
private Path mPath;
//左下點(diǎn)坐標(biāo)
private int mCurX;
private int mCurY;
//火焰寬度
private int mWidth;
//火焰高度
private int mHeight;
//記錄初始高度
private int mPreHeight;
//記錄初始寬度
private int mPreWidth;
//火焰頂部貝塞爾曲線控制點(diǎn)變化參數(shù)
private int mTopXFactor;
private int mTopYFactor;
//用于實(shí)現(xiàn)火焰的抖動(dòng)
private Random mRandom;
//光環(huán)半徑
private int mHaloRadius;
//正在燃燒
private boolean mIsFiring;
//是否啟動(dòng)停止動(dòng)畫
private boolean mIsStopAnim = false;
private boolean mFlagStop = false;
private LinearGradient mLinearGradient;
private RadialGradient mRadialGradient;
private ValueAnimator mFlameAnimator;
private ValueAnimator mHaloAnimator;```
參數(shù)就是這些了,主要是我們的動(dòng)畫實(shí)現(xiàn)過(guò)程缕碎,也就是我們的屬性動(dòng)畫ValueAnimator
這里還有兩個(gè)渲染類不知道大家用過(guò)沒(méi)有褥影,LinearGradient
和RadialGradient
不了解的同學(xué)可以直接點(diǎn)鏈接我的csdn上我的博文了解一下。LinearGradient
繪制出了火焰咏雌,RadialGradient
繪制除了發(fā)散的光芒凡怎。
初始化的過(guò)程我就不寫了,大家對(duì)這代碼看吧赊抖。那主要的就是小火焰的是怎么繪制出來(lái)的呢 show the code
mPaint.setStyle(Paint.Style.FILL);
mPaint.setShader(mLinearGradient);
mPath.reset();
mPath.moveTo(mCurX, mCurY);
mPath.quadTo(mCurX + mWidth / 2,
mCurY + mHeight / 3,
mCurX + mWidth, mCurY);
mPath.quadTo(mCurX + mWidth / 2 + ((1 - mRandom.nextFloat()) * CHANGE_FACTOR) + mTopXFactor,
mCurY - 2 * mHeight + mTopYFactor,
mCurX, mCurY);
canvas.drawPath(mPath, mPaint);```
這就是火焰flame的繪制统倒,可以看到這里用到了二次貝塞爾曲線的繪制,不太清楚貝塞爾曲線的同學(xué)也可以點(diǎn)這[波浪Loading動(dòng)畫(貝塞爾曲線)](http://blog.csdn.net/xiong_1203/article/details/53453408)有簡(jiǎn)單的介紹熏迹,當(dāng)時(shí)是用在一個(gè)水波的view里面檐薯。這里的繪制是以前面那個(gè)圖里面的矩形為參照,我們?cè)賮?lái)看一下這個(gè)圖(當(dāng)然是加強(qiáng)版hiahia)注暗。
![flame.png](http://upload-images.jianshu.io/upload_images/2934422-daa719c1eda69309.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
那為什么上面的x坐標(biāo)還加了`mRandom.nextFloat()) * CHANGE_FACTOR`呢?你想啊墓猎,火焰不是會(huì)左右晃動(dòng)嗎捆昏,利用一個(gè)隨機(jī)來(lái)控制左右擺動(dòng)咯。
mFlameAnimator = ValueAnimator.ofFloat(0, 4).setDuration(4000);
mFlameAnimator.setRepeatCount(ValueAnimator.INFINITE);
mFlameAnimator.addUpdateListener(
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float zeroToOne = (float) animation.getAnimatedValue();
if (zeroToOne >= 1.0f && zeroToOne <= 1.2f) {
//火焰燃起
zeroToOne = 1.0f - 5 * (zeroToOne - 1.0f);//1-0
mHeight = (int) (mPreHeight * (1 - zeroToOne));
mIsFiring = true;
} else if (zeroToOne >= 3.5f) {
if (mFlagStop) {
mFlameAnimator.cancel();
return;
}
//火焰被吹滅
zeroToOne = 2 * (zeroToOne - 3.5f);//0-2
mTopXFactor = (int) (-20 * zeroToOne);
mTopYFactor = (int) (160 * zeroToOne);
// mWidth = (int) (mPreWidth * (1 -zeroToOne));
mIsFiring = false;
}
}
});```
在4秒的時(shí)間內(nèi)毙沾,火焰進(jìn)行了一系列活動(dòng)骗卜,從下面隨著燈芯移上來(lái),不斷的改變火焰的位置,分為了兩部分寇仓,火焰燃起和火焰熄滅举户,從代碼中可以看到,火焰燃起時(shí)mHeight
慢慢變大遍烦,然后就是有了升起的過(guò)程辣俭嘁,另外一個(gè)就是火焰被吹滅的時(shí)候,因?yàn)榇禍绲臅r(shí)候火焰的高度肯定是保持之前的值服猪,所以不需要改變供填,而是用了mTopXFactor
和mTopYFactor
這個(gè)兩個(gè)因子來(lái)控制火焰的位置。好了罢猪,既然火焰有了近她,蠟炬成灰淚始干啊,生命之光也該出場(chǎng)了膳帕。
光圈的繪制和動(dòng)畫就相對(duì)簡(jiǎn)單了
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5);
mPaint.setShader(mRadialGradient);
canvas.drawCircle(mCurX + mWidth / 2,
mCurY - mHeight / 2, mHaloRadius, mPaint);
canvas.drawCircle(mCurX + mWidth / 2,
mCurY - mHeight / 2, mHaloRadius + 5, mPaint);
canvas.drawCircle(mCurX + mWidth / 2,
mCurY - mHeight / 2, mHaloRadius - 5, mPaint);```
mHaloAnimator = ValueAnimator.ofFloat(0, 1).setDuration(500);
mHaloAnimator.setRepeatCount(ValueAnimator.INFINITE);
mHaloAnimator.addUpdateListener(
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float zeroToOne = (float) animation.getAnimatedValue();
if (mIsFiring) {
mHaloRadius = (int) (70 + zeroToOne % 1.0f * 20);
}
}
});```
這里改變的只有一個(gè)參數(shù)粘捎,mHaloRadius
也就是光圈的半徑。但是不要忘了危彩,其他參數(shù)同時(shí)也是在改變的呢攒磨,只不過(guò)是放在了mFlameAnimator
里面。
好了介紹到這Flame的介紹完了恬砂,任重而道遠(yuǎn)啊咧纠,寫了這么多卻還沒(méi)完結(jié),讓我想到一某位古人說(shuō)過(guò)泻骤,不是我漆羔。
還未老死,就先累死
-
FireCandle
這名字有點(diǎn)奇怪狱掂,火燭演痒,厲害了Word哥。前面已經(jīng)介紹過(guò)ICandle了趋惨,現(xiàn)在來(lái)看一下他的實(shí)現(xiàn)類鸟顺,蠟燭兩兄弟之FireCandle。
初始化照例也就不說(shuō)了器虾,來(lái)看該有的變量讯嫂。
private Paint mPaint;
//中心X坐標(biāo)
private int mCenterX;
//記錄初始寬
private int mPreWidth;
//記錄初始高
private int mPreHeight;
//蠟燭芯旋轉(zhuǎn)角
private int mCandlewickDegrees = 0;
private Flame mFlame;
private boolean mIsFire = false;
private boolean mIsStateOnStart = false;
private boolean mIsStateOnEnd = false;
private boolean mFlagStop = false;
private ValueAnimator mCandlesAnimator;```
命名還是挺規(guī)范的,應(yīng)該一看就知道是干嘛的兆沙。
我們還是來(lái)主要看繪制和屬性動(dòng)畫的配合欧芽,繪制就不看了(*光速打臉*)。來(lái)看動(dòng)畫葛圃。
mCandlesAnimator = ValueAnimator.ofFloat(0, 4).setDuration(4000);
mCandlesAnimator.addUpdateListener(
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float zeroToOne = (float) animation.getAnimatedValue();
if (zeroToOne <= 1.0f) {
//蠟燭芯蓄力下拉
mIsFire = true;
mCandleWidth = mPreWidth + (int) (zeroToOne * 40);
mCandleHeight = mPreHeight - (int) (zeroToOne * 30);
mCandlewickDegrees = (int) (-60 + (180 + 60) * zeroToOne);
refreshEyePosition();
} else if (zeroToOne <= 2.0f) {
zeroToOne = zeroToOne - 1.0f;
//蠟燭芯上擺
if (zeroToOne <= 0.2f) {
zeroToOne = 1.0f - 5 * zeroToOne;
mIsFire = false;
mCandleWidth = mPreWidth + (int) (zeroToOne * 40);
mCandleHeight = mPreHeight - (int) (zeroToOne * 30);
mCandlewickDegrees = (int) (180 * zeroToOne);
} else {
if (mFlameStateListener != null && !mIsStateOnStart) {
mFlameStateListener.flameStart();
mIsStateOnStart = true;
}
mCandleWidth = mPreWidth;
mCandleHeight = mPreHeight;
mCandlewickDegrees = 0;
if (mFlagStop) {
mCandlesAnimator.cancel();
}
}
refreshEyePosition();
} else if (zeroToOne >= 3.5f) {
//蠟燭芯被吹歪
zeroToOne = 2 * (zeroToOne - 3.5f);//0-1
mCandlewickDegrees = (int) (-60 * zeroToOne);
if (mFlameStateListener != null && !mIsStateOnEnd) {
mFlameStateListener.flameEnd();
mIsStateOnEnd = true;
}
}
}
});```
這個(gè)就過(guò)程就有點(diǎn)多了千扔,但是其實(shí)一點(diǎn)都不復(fù)雜憎妙,,首先我們看動(dòng)畫里面的小<small><small>蠟燭,<big><big>一開(kāi)始曲楚,他來(lái)了一個(gè)變胖紅臉深蹲厘唾,所以呢mCandleWidth
是變大的,mCandleHeight
是變小的龙誊,后面那個(gè)燈芯隨著深蹲來(lái)了一個(gè)大角度旋轉(zhuǎn)抚垃,燈芯的如何旋轉(zhuǎn)大家也看到了,改變坐標(biāo)系然后就可以了载迄。用到了
canvas.rotate(mCandlewickDegrees, mCenterX, mCurY - mCandleHeight);
這個(gè)方法讯柔。上擺過(guò)程也是一樣的,就不多說(shuō)了护昧。refreshEyePosition();
這個(gè)方法是改變眼睛位置的魂迄,兩個(gè)地方都用到了所以稍微獨(dú)立出來(lái)了。注意mIsFire
這個(gè)變量惋耙,沒(méi)有火焰的時(shí)候就做其他繪制捣炬,比如說(shuō)紅眼睛等等。好了好了绽榛,介紹到這湿酸,小蠟燭的部分就結(jié)束了。
-
SecCandle
大<big><big>蠟燭灭美,<small><small>帥蠟燭鎮(zhèn)樓推溃,實(shí)際的繪制和小蠟燭的就差不多了,這里就不解釋了届腐。
共同繪制View和控制器
- AnimControler
這個(gè)類的功能很簡(jiǎn)單铁坎,繪制地板部分還有就是把計(jì)算后傳過(guò)來(lái)的高度寬度賦給兩支蠟燭,然后控制兩支蠟燭各自開(kāi)始動(dòng)畫犁苏。
mFirCandle = new FirCandle(mRelativeX + mWidth / 6, mRelativeY + mHeight);
mFirCandle.initCandle(mFirCandleWidth, mFirCandleHeight);
mFirCandle.initAnim();
mSecCandle = new SecCandle(mRelativeX + mWidth / 2, mRelativeY + mHeight);
mSecCandle.initCandle(mSecCandleWidth, mSecCandleHeight - 80);
mSecCandle.initAnim();
最后的最后硬萍,就是我們的View了 - CandlesAnimView
//16ms刷新Canvas
mInvalidateAnimator = ValueAnimator.ofInt(0, 1).setDuration(16);
mInvalidateAnimator.setRepeatCount(ValueAnimator.INFINITE);
mInvalidateAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationRepeat(Animator animation) {
invalidate();
}
});
mInvalidateAnimator.start();```
這個(gè)屬性動(dòng)畫履行的任務(wù)就是快速的刷新界面,是Candle的動(dòng)畫能夠及時(shí)顯示围详。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = measureDimension(WIDTH_DEFAULT * mDensity,
widthMeasureSpec);
int height = measureDimension(HEIGHT_DEFAULT *mDensity,
heightMeasureSpec);
setMeasuredDimension(width, height);
}
public int measureDimension(int defaultSize, int measureSpec) {
int result;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = defaultSize;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
@Override
protected void onDraw(Canvas canvas) {
if (!mIsInit) {
initConfig();
mIsInit = true;
}
mAnimControler.drawMyView(canvas);
}```
可以看到最后在view里面調(diào)用了我們的控制器朴乖,把cavas
傳過(guò)去了。
最后的tip:大家有沒(méi)有發(fā)現(xiàn)每個(gè)動(dòng)畫的duration都是一樣的助赞。
三买羞、最后
<big>好了至此,本來(lái)一個(gè)簡(jiǎn)單的view自定義被我說(shuō)了這么多雹食。初次在簡(jiǎn)書上寫哩都,望大家多支持支持。
希望大家有什么建議和意見(jiàn)都可以提出婉徘。望斧正漠嵌。
期待你們的關(guān)注,一起交流android
GITHUB源碼下載
歡迎大家來(lái)我的博客逛逛,之前也沒(méi)什么時(shí)間寫博客文章的盖呼,最近開(kāi)始儒鹿,大家多多支持!<肝睢约炎!