歡迎轉載,但請務必
註明出處
!http://ryanhoo.github.io/blog/2014/07/16/step-by-step-implement-parallax-animation-for-splash-screen-of-zhihu/html
Parallax Scrolling(視差滾動)
,是一種常見的動畫效果。視差一詞來源於天文學,但在平常生活中也有它的身影。在疾馳的動車上看風景時,會發現越是離得近的,相對運動速度越快,而遠處的山川河流只是緩慢的移動着,這就是最多見的視差效果。視差動畫獨有的層次感能帶來極爲逼真的視覺體驗,iOS、Android Launcher、Website都將視差動畫做爲提高用戶視覺愉悅度的不二選擇。java
客戶端應用第一次打開出現引導頁也不是什麼新鮮的事兒,ViewPager
配上幾張設計師精心繪製的圖片,分分鐘便可了事。可是總有人把平凡的事情作到不平凡,如本文的知乎客戶端,亦或是新浪微博賀歲版,百度貼吧某版等衆多應用裏都出現了視差動畫的身影,隨着用戶手指的滑動,反饋以靈動、貼近真實的視覺以及操做體驗,對應用的初始印象登時被提高到一個極高的點。android
給我印象最深的是去年新浪微博的賀歲版,引導頁是一系列的年畫,裏面有紅色剪紙的小孩兒,滑動界面的時候感受這些元素在『動』,是真正的靈動,能勾起人童年的回憶,年味兒十足。不過話說我年怎麼過跟新浪微博一毛錢關係都沒有,可是這個啓動頁倒是深得我意。只是這個版本的微博找不到了,正好前兩天看到知乎的啓動頁作的也不錯,就正好拿來練練手吧。git
本文就知乎Android客戶端啓動頁面爲例,教你如何實現視差滾動效果。github
細心把玩下知乎的啓動頁,不難分析出來,視差動畫主要體如今背景層漸變、內容層元素差別滾動上,動畫內容分別是:ide
鑑於其它幾項比較簡單,本文主要講視差動畫以及背景漸變的實現,其它幾項請自行參閱代碼,見後文。學習
這裏的視差滾動效果,主要表現爲內容元素滾動速率的差別上。好比在ViewPager
中滑動了1px
,而A元素移動2px
,B元素移動1.5px
,這種移動差距的比率,我稱之爲parallaxCofficient
,即視差係數或者視差速率,正是同一個界面中的元素,因爲層級不一樣,賦予的視差係數不一樣,在移動速度上的差別造成了視差的錯覺,這就是咱們要追求的效果。動畫
那知道原理就好辦了,使用ViewPager.OnPageChangeListener
,動態計算不就得了。no no no! 後面完成背景漸變效果確實須要計算這個,可是ViewPager
已經爲咱們準備好了變形元素transformium: ViewPager.PageTransformer
,它有一個抽象方法transformPage(View page, float position)
,正是爲咱們完成視差動畫量身定製的。ui
PageTransformer在ViewPager
滑動時被觸發,它爲咱們自定義頁面中進行視圖變換打開了一扇大門。this
public abstract void transformPage (View page, float position)
在ViewPager
源碼中,咱們能夠很直觀的看到它的調用過程:
// ViewPager#onPageScrolled if (mPageTransformer != null) { final int scrollX = getScrollX(); final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (lp.isDecor) continue; final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth(); mPageTransformer.transformPage(child, transformPos); } }
從上面的代碼中,不難看出,page
就是當前被滑動的頁面,調試得知,每個child view被NoSaveStateFrameLayout
包裝,也就是說page.getChildAt(0)
便是每一個page
實際的child view。
position
這個參數不看代碼或者文檔,總會誤覺得就是咱們熟知的integer position
,不過它其實是滑動頁面的一個相對比例,本質跟 一、二、三、4 這種position
是同樣的。
好比知乎啓動頁共有6個頁面,分別是A,B,C,D,E,F初始狀態也就是A頁面靜止時,A頁面的position
正好是0,B頁面是1。然後滑動頁面(B -> A),在這個過程當中A的position
是間於[-1, 0]
,B頁面則是間於[0, 1]
。
不過這個參數的文檔倒是簡單不夠直觀,對照上面的例子,如今應該很清晰了。
Position of page relative to the current front-and-center position of the pager. 0 is front and center. 1 is one full page position to the right, and -1 is one page position to the left.
根據上面的分析,咱們能夠得出一個相對簡單的自定義transformer,對page view
進行遍歷,遞增或者遞減其parallaxCofficient
,以獲得咱們預期的效果,具體的係數設置請參考代碼。
class ParallaxTransformer implements ViewPager.PageTransformer { float parallaxCoefficient; float distanceCoefficient; public ParallaxTransformer(float parallaxCoefficient, float distanceCoefficient) { this.parallaxCoefficient = parallaxCoefficient; this.distanceCoefficient = distanceCoefficient; } @Override public void transformPage(View page, float position) { float scrollXOffset = page.getWidth() * parallaxCoefficient; // ... // layer is the id collection of views in this page for (int id : layer) { View view = page.findViewById(id); if (view != null) { view.setTranslationX(scrollXOffset * position); } scrollXOffset *= distanceCoefficient; } } }
留心纔會發現,從第一頁滑動到最後一頁,背景色會平滑的從深藍色過分到淺藍色,這種效果又該怎麼實現呢?
用過Property Animation
的同窗應該知道,之前的Animation
只能用在View上,而Property Animation
卻能夠用在任意類型屬性值上,這歸功於TypeEvaluator
。
正好咱們有ArgbEvaluator
,它能夠估算兩個顏色值之間,任意部分的色值。所以,只須要指定起始色值以及最終的色值,傳入滑動所對應的fraction
即當前位置相對總距離的比例值,便可得到相應的色值。
public class ArgbEvaluator implements TypeEvaluator { public Object evaluate(float fraction, Object startValue, Object endValue) { // ... } }
固然,前面說到須要使用ViewPager.OnPageChangeListener
的:
class GuidePageChangeListener implements ViewPager.OnPageChangeListener { ArgbEvaluator mColorEvaluator; int mPageWidth, mTotalScrollWidth; int mGuideStartBackgroundColor, mGuideEndBackgroundColor; public GuidePageChangeListener() { mColorEvaluator = new ArgbEvaluator(); mPageWidth = getWindowManager().getDefaultDisplay().getWidth(); mTotalScrollWidth = mPageWidth * mAdapter.getCount(); mGuideStartBackgroundColor = getResources().getColor(R.color.guide_start_background); mGuideEndBackgroundColor = getResources().getColor(R.color.guide_end_background); } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { float ratio = (mPageWidth * position + positionOffsetPixels) / (float) mTotalScrollWidth; Integer color = (Integer) mColorEvaluator.evaluate(ratio, mGuideStartBackgroundColor, mGuideEndBackgroundColor); mPager.setBackgroundColor(color); } @Override public void onPageSelected(int position) {} @Override public void onPageScrollStateChanged(int state) {} }
代碼已經push到Github了,諸位自取。不過請注意,其素材均取自於知乎Android客戶端(你懂的),學習交流便可,請勿用做商業用途。
還求更優雅的實現方式,歡迎發起pull request
。