知識
不管是網(wǎng)站,軟件還是小程序,都要直接或間接能為您產(chǎn)生價值,我們在追求其視覺表現(xiàn)的同時,更側(cè)重于功能的便捷,營銷的便利,運營的高效,讓網(wǎng)站成為營銷工具,讓軟件能切實提升企業(yè)內(nèi)部管理水平和效率。優(yōu)秀的程序為后期升級提供便捷的支持!
Android好奇寶寶_06_聊一聊Android里的動畫
發(fā)表時間:2020-10-19
發(fā)布人:葵宇科技
瀏覽次數(shù):66
這一篇我們來聊一聊高大年夜上的動畫效不雅。
起首說一個常識,一個對懂得動畫最重要的概念,亦是動畫的本質(zhì):
動畫的道理是利人眼的視覺暫留的特點,即如不雅一幀幀圖像切換的足夠快的話,人眼就察覺不到逗留,看起來就像持續(xù)的動畫了。
動畫的道理很簡單,就是讓圖像進(jìn)行快速的切換。動畫的可貴是計算出每兩幀之間的差別,比如一個位移動畫,對于每一幀你都必須計算出它的地位,如不雅是直線勻速的。很輕易計算,但如不雅曲直線的并且照樣有加快度(即移動的速度是會變更的)的,那么計算就會變的復(fù)雜了。
總結(jié)一下,動畫有兩個要素,一個是若干的幀圖像,一個是變更。
回到Android的動畫體系,有一道很廣泛的面試題:Android中動畫的種類?
在我以前面試時,謎底還只有兩種,不過如今3.0版本今后如今變成3種了。
下面我們一種一種講。
(1)Frame Animation(幀動畫)
這個是最簡單的,即我們供給第一幀到最后一幀的所有幀,然后體系幫我們快速的顯示出來罷了,沒啥好說的。
這種動畫在實際開辟中也比較罕用,因為須要大年夜量的圖片資本,浪費存儲空間。
(2)傳統(tǒng)View動畫
我先說下這種動畫的道理,然后我們再到源碼中去驗證。
前面說過,動畫有兩個要素:若干的幀和變更。
而傳統(tǒng)View動畫的道理就是:我們只供給一幀和變更,然后體系基于我們供給的┞封一幀和變更,去生成動畫須要的所有幀,然后一向的刷新界面輪播幀直到動畫停止。
來看一下一最簡單的動畫實現(xiàn):
Button btn=new Button(MainActivity.this); ScaleAnimation anim=new ScaleAnimation(0, 1, 0, 1); btn.startAnimation(anim);
這里的Button的初始狀況就是我們供給的一幀,構(gòu)造ScaleAnimation的參數(shù)列表就是我們供給的變更。
startAnimation是通知體系開端履行動畫的辦法:
public void startAnimation(Animation animation) { animation.setStartTime(Animation.START_ON_FIRST_FRAME); setAnimation(animation); invalidateParentCaches(); invalidate(true); }
startAnimation會將要履行的動畫保存(setAnimation),然后請求重繪。所以我們可以肯定在重繪過程中,必定會對這個保存動畫的變量進(jìn)行是否為空和動畫類型的斷定。
這里呢不計算具體講請求重繪的過程,我們只須要知道重繪的請求會一向向上向父View傳遞,然后到最頂層父View后再反向向下傳遞,我們這里只存眷傳遞到Button的父View時產(chǎn)生的事。
我們知道所有容器View都是ViewGroup或其子類,在重繪時子View是由父View來繪制出來的,這里我們大年夜下面的ViewGroup的dispatchDraw辦法開端追蹤:
protected void dispatchDraw(Canvas canvas) { //...省略非關(guān)鍵代碼... //看到回調(diào)辦法onAnimationStart在這里被調(diào)用,解釋動畫是大年夜這里之后就開端的 if (mAnimationListener != null) { mAnimationListener.onAnimationStart(controller.getAnimation()); } //...省略非關(guān)鍵代碼... boolean more = false; final long drawingTime = getDrawingTime(); //這個是斷定child是否有特定的繪制次序,跟我們的動畫實現(xiàn)無關(guān),我們只存眷一種情況 if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) { for (int i = 0; i < count; i++) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime); } } } else { for (int i = 0; i < count; i++) { final View child = children[getChildDrawingOrder(count, i)]; //如不雅這個child可見或者有動畫須要履行的話 //因為我們之前在 startAnimation辦法中調(diào)用了setAnimation(animation)辦法 //所以getAnimation()結(jié)不雅不為空 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { //more用來標(biāo)示動畫的狀況,more==true時表示動畫還沒停止,為false則表示動畫已經(jīng)停止了 more |= drawChild(canvas, child, drawingTime); } } } //...省略非關(guān)鍵代碼... }
跳到drawChild(canvas, child, drawingTime)辦法:
protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); }
跳到child的draw(Canvas canvas, ViewGroup parent, long drawingTime)辦法,這個辦法很長,我只解釋一些關(guān)鍵語句:
boolean more = false;同樣用來標(biāo)示動畫是否停止
Transformation transformToApply = null;之前說過動畫的所有幀都有體系生成,而Transformation是用來描述每一幀的狀況信息,Transformation中有3個成員變量:
//一個矩陣,經(jīng)由過程改變它可以改變畫布 //會影響畫布的大年夜小、地位和扭轉(zhuǎn)角度 //而畫布的改變就會影響到繪制在其膳綾擎的View //傳統(tǒng)View動畫就是經(jīng)由過程改變畫布(Canvas)的方法去產(chǎn)生所有的幀 protected Matrix mMatrix; //影響透明度 protected float mAlpha; //動畫須要改變的類型 //大年夜小,位移、扭轉(zhuǎn)須要改變mMatrix //透明度轉(zhuǎn)更改畫須要改變mAlpha protected int mTransformationType;
持續(xù):
final Animation a = getAnimation(); if (a != null) { more = drawAnimation(parent, drawingTime, a, scalingRequired); //...省略非關(guān)鍵代碼... //這一句后面會解釋 transformToApply = parent.getChildTransformation(); }
先看下drawAnimation(ViewGroup parent, long drawingTime,Animation a, boolean scalingRequired)辦法:
private boolean drawAnimation(ViewGroup parent, long drawingTime, Animation a, boolean scalingRequired) { Transformation invalidationTransform; final int flags = parent.mGroupFlags; //這里斷定動畫是否進(jìn)行過初始化,若不然進(jìn)行初始化 final boolean initialized = a.isInitialized(); if (!initialized) { a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight()); a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop); if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler); onAnimationStart(); } //這里留意幀信息是保存在parent的 final Transformation t = parent.getChildTransformation(); //這一句是關(guān)鍵,是計算變更的處所 //getTransformation辦法的感化就是要根據(jù)動畫已經(jīng)進(jìn)行的時光和一些其它信息 //來計算出當(dāng)前時將近顯示的┞封一幀的狀況信息,并保存到t中 //接下來后面就會大年夜t掏出信息來改變畫布 boolean more = a.getTransformation(drawingTime, t, 1f); //...省略非關(guān)鍵代碼... //如不雅動畫還沒到停止時光 if (more) { //會在這里調(diào)用 parent.invalidate()辦法請求重繪,然后呢又會去計算下一幀的信息,改變畫布,繪制幀,一向輪回直到動畫停止 } return more; }
回到draw(Canvas canvas, ViewGroup parent, long drawingTime)辦法,前面沒解釋的:
transformToApply = parent.getChildTransformation();
如今我們知道了drawAnimation辦法計算出來的繪制當(dāng)前幀的信息是保存在parent里的,這里就是把它掏出來。
接下來照樣在draw(Canvas canvas, ViewGroup parent, long drawingTime)辦法里,如不雅是動畫類型是須要改變Matrix的話,會調(diào)用:
canvas.concat(transformToApply.getMatrix());
要改變透明度的話會調(diào)用:
canvas.saveLayerAlpha();
最后調(diào)用:
draw(canvas);
把改變后的畫布傳給draw(canvas)辦法,開端繪制幀。
小結(jié):傳統(tǒng)View動畫經(jīng)由過程改變Canvas來生成動畫所須要的幀,每一幀Canvas的變更信息由Transformation類來保存,而該若何變更由Animation實現(xiàn)類來決定,具體是每個Animation的子類都重寫了辦法:
protected void applyTransformation(float interpolatedTime, Transformation t)
參數(shù)interpolatedTime是動畫已經(jīng)進(jìn)行的時光(注:這是在沒有設(shè)置Interpolator的情況下,關(guān)于Interpolator我會在后面再解釋),t用來保存計算出來的結(jié)不雅。
彌補:傳統(tǒng)View動畫有一個經(jīng)常會出現(xiàn)的問題就是,一個按鈕在進(jìn)行位移動畫之后,如不雅設(shè)置了setFillAfter(true),那么會逗留在最后一幀,然則點擊的觸發(fā)地位照樣在原地位。如不雅你細(xì)心瀏覽了膳綾擎得源碼分析,你就明白原因了:傳統(tǒng)View動畫只是改變畫布,對于進(jìn)行動畫的物體(比如我們例子中的Button)并不會進(jìn)行改變,而觸摸、點擊等事宜的地位斷定并不受畫布的影響。
一個不完美的解決辦法:不要設(shè)置setFillAfter(true),設(shè)置動畫監(jiān)聽,在動畫停止時調(diào)用layout()辦法進(jìn)行從新構(gòu)造。
例子:
對一個按鈕(btn)進(jìn)行了一個向右移100、向下移200的動畫:
anim = new TranslateAnimation(0, 100, 0, 200); btn.startAnimation(anim);設(shè)置監(jiān)聽:
anim.setAnimationListener(new AnimationListener() { public void onAnimationStart(Animation animation) { } public void onAnimationRepeat(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { // TODO Auto-generated method stub btn.layout(btn.getLeft() + 100, btn.getTop() + 200, btn.getRight() + 100, btn.getBottom() + 200); } });
這種辦法的缺點:
(1)layout辦法會導(dǎo)致Button閃一下。
(2)這里只是一個簡單的位移動畫,如不雅動畫復(fù)雜的話,想計算出layout辦法的4個參數(shù)也會變得很復(fù)雜。
(3)Property Animation(屬性動畫)
屬性動畫跟傳統(tǒng)View動畫是類似的,其實所有動畫都是類似的,不合的是我們是經(jīng)由過程改變什么來達(dá)到動畫的視覺效不雅的。
傳統(tǒng)View動畫經(jīng)由過程改變View地點的畫布,讓View跟著畫布的變更而變更,但直接改變要進(jìn)行動畫的物體本身可能更簡單。
屬性動畫就是經(jīng)由過程改變物體的屬性來達(dá)到動畫效不雅的,這里說物體而不是View是因為Android的屬性動畫并沒針砭定進(jìn)行動畫的必須是View(當(dāng)然大年夜多半、幾乎全部、差不多都是View),它只是根據(jù)我們供給的改變?nèi)ジ淖円粋€對象的屬性值,至于改變了這個屬性值之后會產(chǎn)生什么事,它是不管的。
說部屬性動畫最重要的兩個類:
(1)ValueAnimator
這個類就像它的名字一樣,值的動畫師,它只存眷值的變更,根據(jù)我們給出的變更來供給某個時刻的值應(yīng)當(dāng)為若干。
例子:
我們想讓一個值在1s的時光內(nèi)大年夜0勻速地變?yōu)?,那么ValueAnimator會在0ms時返回0,500ms時返回0.5,以詞攀類推。
(2)ObjectAnimator
ObjectAnimator是ValueAnimator的子類,它與ValueAnimator的差別是它不僅僅供給值,它還會在得出值后去改變屬性值。
ObjectAnimator的實現(xiàn)道理:
ObjectAnimator objectAnimator=ObjectAnimator.ofFloat(btn, "x", new float[]{0,100}); objectAnimator.setDuration(1000); objectAnimator.start();
第一個參數(shù)就是我們想改變其屬性的對象。
第二個參數(shù)是屬性的名稱,要留意的是這并不料味著這個對象就必須要擁有這個屬性變量。
因為ObjectAnimator是經(jīng)由過程反射屬性的getter和setter辦法去改變和獲取屬性值,所以你只要有對應(yīng)的getter和setter辦法(要相符駝峰定名規(guī)矩)。
第三個參數(shù)就是我們供給的變更,一個數(shù)組參數(shù),數(shù)組的大年夜小必須為2或者1。
2的情況:數(shù)組第一個元素做為初始值,第二個作為停止值,這種情況下對應(yīng)的getter辦法不是必須的。
1的情況:ObjectAnimator經(jīng)由過程反射調(diào)用getter辦法將獲得的結(jié)不雅作為初始值,將數(shù)組中獨一元素作為停止值,這種情況下getter辦法是必須的。
我來竽暌姑文字來描述一下膳綾擎幾條語句表達(dá)的意思:
請在1s內(nèi)勻速的將btn的"x"屬性值大年夜0增長到100,感謝!
注:像我膳綾擎說的,改變屬性值不是真的類似btn.x=value這種方法,比瘸瑯綾擎這個例子,ObjectAnimator每隔一段時光,當(dāng)ValueAnimator計算出新的值時,它就會經(jīng)由過程反射去履行語句btn.setX(value);,直到動畫時光停止。
當(dāng)然,對于View來說,在調(diào)用setX辦法時肯定會去請求重繪,而在重繪過程中,不管setX做了什么,最終肯定會影響到View繪制出來的程度地位。當(dāng)這些都不關(guān)ObjectAnimator事,ObjectAnimator只是改變屬性值,不關(guān)懷改變后會產(chǎn)生什么。
關(guān)于屬性動畫的源碼我就不分析了,有興趣的推薦一篇博客,講得很好:傳送門
附:Interpolator
前面的例子都是勻速地變更,而Interpolator就是可以改變改變速度的東東(是兩個改變,我沒打錯),可以實現(xiàn)類似物理中的加快度,但其實可以實現(xiàn)更多。
不管是傳統(tǒng)View動畫照樣屬性動畫,都得先計算出下一幀的信息再去請求刷新,而Interpolator就是供給一個對計算出來的值一次修改的機會。照樣膳綾擎的例子:
在沒有設(shè)置Interpolator的情況下,"x"的值在500ms時應(yīng)當(dāng)為50;
如不雅設(shè)置了一個越來越快的Interpolator,那么"x"的值在500ms時應(yīng)當(dāng)小于50,btn開端會移動地比較慢,然后越來越快。Interpolator就是在得出"x"為50的基本上再進(jìn)行一次修改,此次修改可所以隨便率性的,但一般不會誤差太大年夜??伤?0、60、100,這是比較正常的,也可所以200、99999999,這些也是可以的,不過我們不會這么做。
回到前面的:
protected void applyTransformation(float interpolatedTime, Transformation t)
參數(shù)名稱是interpolatedTime,注解Interpolator是經(jīng)由過程改更改畫已經(jīng)進(jìn)行的時光來改變最終值的。
我們可以自定義Interpolator,只要實現(xiàn)Interpolator接口重寫float getInterpolation(float input);辦法,這里的input并不是50,而是動畫已進(jìn)行時光的百分比。體系已經(jīng)實現(xiàn)了幾個常用的Interpolator,一般情況下是夠用了。如AccelerateInterpolator就是膳綾擎說的開端慢,然后越來越快,與之對應(yīng)的DecelerateInterpolator則相反,開端快然后越來越慢。
相關(guān)案例查看更多
相關(guān)閱讀
- 開發(fā)微信小程序
- 云南小程序公司
- 昆明軟件公司
- 網(wǎng)站排名
- 云南小程序開發(fā)制作
- 云南建設(shè)廳網(wǎng)站
- 用戶登錄
- 云南網(wǎng)站建設(shè)靠譜公司
- 安家微信小程序
- 云南網(wǎng)站建設(shè)方案 doc
- 昆明小程序公司
- 云南小程序哪家好
- 服務(wù)器
- 北京小程序開發(fā)
- 關(guān)鍵詞快速排名
- 云南小程序開發(fā)哪家好
- 汽車拆解系統(tǒng)
- 正規(guī)網(wǎng)站建設(shè)公司
- 區(qū)塊鏈
- 云南網(wǎng)站建設(shè)電話
- 網(wǎng)站建設(shè)選
- 出入小程序
- 云南旅游網(wǎng)站建設(shè)
- web學(xué)習(xí)路線
- 保山小程序開發(fā)
- 網(wǎng)站建設(shè)首選
- 汽車報廢拆解管理系統(tǒng)
- 網(wǎng)站收錄
- web開發(fā)
- 全國前十名小程序開發(fā)公司