轉載請標明出處:
http://blog.csdn.net/lmj623565791/article/details/51339751;
本文出自:【張鴻洋的博客】
一、概述
如果大家關注了我的微信公衆號的話,一定知道我在5月6號的時候推送了一篇文章,文章名爲Android超高仿QQ附近的人搜索展示(一),通過該文可以利用ViewPager實現單頁顯示多個Item且能夠添加一些炫酷的動畫效果。我當時閱讀這篇文章的時候,簡單做了下記錄,然後想了想,可以按照該思路做一個比較特殊輪播效果,如圖:
其實看到這個大家肯定不陌生,對於ViewPager切換有個很出名的庫叫JazzViewPager,沒錯,我又跑了下JazzyViewPager的例子,看看有什麼動畫效果可以借鑑的,ok,最終呢,產生以下幾個效果圖。
貼效果圖前,簡單說下我的公衆號,恩,我是在上週決定正式開始好好打理的,目前很多東西都在嘗試階段,當然支持大家的投稿,目前存在一些文章過長,或者代碼過長的排版問題,不過都在嘗試改善與解決,以及對推送文章的選材都在考慮,所以多謝大家的支持,也歡迎大家的關注(二維碼在側欄),相信我一定會做的更好。
此外,針對不好閱讀的問題,大家可以通過該倉庫,看到所有推送文章的一個列表,https://github.com/hongyangAndroid/hongyangWeixinArticles該倉庫會和公衆號推送的文章同步更新。
下面進入正題,本文主要是利用ViewPager做類似上圖風格的Banner,這種Banner在app上不是很常見,不過在web端還有tv的app上還是很常見的。
不過原理很簡單,說到核心,就兩個地方:
android:clipChildren="false"
viewPager.setPageTransformer
很久之前也寫過類似的文章,可以參考
二、效果圖
- Rotate Down
- Rotate Up
- ScaleIn
貼三個意思下,恩,更多效果見https://github.com/hongyangAndroid/MagicViewPager.
三、ViewPager一屏顯示多個頁面
ok,首先說明下控件,上述效果採用的控件是ViewPager
,大家都清楚哇,使用ViewPager
一般我們都是一屏幕顯示一個頁面,那麼如何做到一屏顯示多個頁面呢?
ViewPager如何做到一屏顯示多個頁面呢?
原理就一個屬性Android:clipChildren="false"
,該屬性的意思就是在子View進行繪製時不要去裁切它們的顯示範圍。ok,知道要使用這個屬性之後,剩下的事情就不麻煩了:
我們的佈局文件這麼寫:
<code class="language-xml hljs has-numbering"><span class="hljs-tag"><<span class="hljs-title">FrameLayout </span> <span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"match_parent"</span> <span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"160dp"</span> <span class="hljs-attribute">android:clipChildren</span>=<span class="hljs-value">"false"</span> <span class="hljs-attribute">android:layout_centerInParent</span>=<span class="hljs-value">"true"</span> <span class="hljs-attribute">android:background</span>=<span class="hljs-value">"#aadc71ff"</span> ></span> <span class="hljs-tag"><<span class="hljs-title">android.support.v4.view.ViewPager </span> <span class="hljs-attribute">android:id</span>=<span class="hljs-value">"@+id/id_viewpager"</span> <span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"match_parent"</span> <span class="hljs-attribute">android:layout_marginLeft</span>=<span class="hljs-value">"60dp"</span> <span class="hljs-attribute">android:layout_marginRight</span>=<span class="hljs-value">"60dp"</span> <span class="hljs-attribute">android:clipChildren</span>=<span class="hljs-value">"false"</span> <span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"120dp"</span> <span class="hljs-attribute">android:layout_gravity</span>=<span class="hljs-value">"center"</span> ></span> <span class="hljs-tag"></<span class="hljs-title">android.support.v4.view.ViewPager</span>></span> <span class="hljs-tag"></<span class="hljs-title">FrameLayout</span>></span></code>
我們設置了ViewPager
外層控件以及ViewPager
都設置了android:clipChildren="false"
。
我們的ViewPager的寬度是match_parent
,左後個設置了60dp
的邊距,就是爲了顯示出左右部分的Page.
接下來可以對ViewPager
設置Adapter等相關屬性。
<code class="language-java hljs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AppCompatActivity</span> {</span> <span class="hljs-keyword">private</span> ViewPager mViewPager; <span class="hljs-keyword">private</span> PagerAdapter mAdapter; <span class="hljs-keyword">int</span>[] imgRes = {R.drawable.a, R.drawable.b, R.drawable.c...}; <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span>(Bundle savedInstanceState) { <span class="hljs-keyword">super</span>.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mViewPager = (ViewPager) findViewById(R.id.id_viewpager); <span class="hljs-comment">//設置Page間間距</span> mViewPager.setPageMargin(<span class="hljs-number">20</span>); <span class="hljs-comment">//設置緩存的頁面數量</span> mViewPager.setOffscreenPageLimit(<span class="hljs-number">3</span>); mViewPager.setAdapter(mAdapter = <span class="hljs-keyword">new</span> PagerAdapter() { <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> Object <span class="hljs-title">instantiateItem</span>(ViewGroup container, <span class="hljs-keyword">int</span> position) { ImageView view = <span class="hljs-keyword">new</span> ImageView(MainActivity.<span class="hljs-keyword">this</span>); view.setImageResource(imgRes[position]); container.addView(view); <span class="hljs-keyword">return</span> view; } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">destroyItem</span>(ViewGroup container, <span class="hljs-keyword">int</span> position, Object object) { container.removeView((View) object); } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getCount</span>() { <span class="hljs-keyword">return</span> imgRes.length; } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">isViewFromObject</span>(View view, Object o) { <span class="hljs-keyword">return</span> view == o; } }); }</code>
ok,沒有任何複雜的地方,注意
<code class="hljs cs has-numbering"> <span class="hljs-comment">//設置Page間間距</span> mViewPager.setPageMargin(<span class="hljs-number">20</span>);</code>
以及
<code class="hljs cs has-numbering"><span class="hljs-comment">//設置緩存的頁面數量</span> mViewPager.setOffscreenPageLimit(<span class="hljs-number">3</span>);</code>
我們這裏最多可見就是3頁。
此時運行:
可以看到,我們已經實現了單屏幕顯示出多個page,而且是ViewPager所以肯定可以左右滑動。
這麼看,是不是非常簡單,接下來就是加特效了,大家都清楚對於ViewPager可以通過設置PageTransformer
來利用屬性動畫來設置特效,注意目前該方法添加的動畫在3.0即以上的手機中有效,因爲3.0以下並不存在屬性動畫,所以setPageTransformer
內部加了個判斷,不過現在已經幾乎沒有3.0以下的手機了,但是如果你非要較真,參考文章開始時給出的兩篇文章,裏面有解決方案。
四、給ViewPager加特效
這裏我們簡單抽取兩個動畫效果來講,其實以前的文章裏面也有詳細的描述,所以不準備花費太多的時間描述。
(1) AlphaPageTransformer
首先講個最簡單的動畫,叫AlphaPageTransformer
,顧名思義就是一個漸變的變化,那麼我們的步驟是這樣的:
- 實現
AlphaPageTransformer implements ViewPager.PageTransformer
- 調用
viewPager.setPageTransformer(new AlphaPageTransformer())
對於ViewPager.PageTransformer
就一個方法需要實現
<code class="language-ajva hljs cs has-numbering"><span class="hljs-preprocessor">#AlphaPageTransformer</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> final <span class="hljs-keyword">float</span> DEFAULT_MIN_ALPHA = <span class="hljs-number">0.5</span>f; <span class="hljs-keyword">private</span> <span class="hljs-keyword">float</span> mMinAlpha = DEFAULT_MIN_ALPHA; <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">pageTransform</span>(View view, <span class="hljs-keyword">float</span> position) { <span class="hljs-keyword">if</span> (position < -<span class="hljs-number">1</span>) { view.setAlpha(mMinAlpha); } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (position <= <span class="hljs-number">1</span>) { <span class="hljs-comment">// [-1,1]</span> <span class="hljs-keyword">if</span> (position < <span class="hljs-number">0</span>) <span class="hljs-comment">//[0,-1]</span> { <span class="hljs-keyword">float</span> factor = mMinAlpha + (<span class="hljs-number">1</span> - mMinAlpha) * (<span class="hljs-number">1</span> + position); view.setAlpha(factor); } <span class="hljs-keyword">else</span><span class="hljs-comment">//[1,0]</span> { <span class="hljs-keyword">float</span> factor = mMinAlpha + (<span class="hljs-number">1</span> - mMinAlpha) * (<span class="hljs-number">1</span> - position); view.setAlpha(factor); } } <span class="hljs-keyword">else</span> { <span class="hljs-comment">// (1,+Infinity]</span> view.setAlpha(mMinAlpha); } } </code>
代碼非常簡短,簡單的介紹下,可以看到postion主要分爲
- [-Infinity,-1)
- (1,+Infinity]
- [-1,1]
這三個區間,對於前兩個,拿我們的頁面上目前顯示的3個Page來說,前兩個分別對應左右兩個露出一點的Page,那麼對於alpha值,只需要設置爲最小值即可。
對於[-1,1]
,這個就需要詳細分析了,我們這裏拿:第一頁->第二頁這個過程來說,主要看position的變化
第1頁->第2頁
- 頁1的postion變化爲:從0到-1
- 頁2的postion變化爲:從1到0
第一頁到第二頁,實際上就是左滑,第一頁到左邊,第二頁成爲currentItem到達中間,那麼對應alpha的變化應該是:
- 頁1到左邊,對應alpha應該是:1到minAlpha
- 頁2到中間,成爲currentItem,對應alpha應該是:minAlpha到1
分析到這就是寫代碼了:
對於頁1
<code class="language-java hljs has-numbering"><span class="hljs-comment">//注意該代碼判斷在(position <= 1)的條件內</span> <span class="hljs-keyword">if</span> (position < <span class="hljs-number">0</span>) <span class="hljs-comment">//[0,-1]</span> { <span class="hljs-keyword">float</span> factor = mMinAlpha + (<span class="hljs-number">1</span> - mMinAlpha) * (<span class="hljs-number">1</span> + position); view.setAlpha(factor); } </code>
position是0到-1的變化
那麼1+position
就是從1到0的變化
(1 - mMinAlpha) * (1 + position)
就是1 - mMinAlpha
到0的變化
再加上一個mMinAlpha,就變爲1到mMinAlpha的變化。
其實繞來繞去就是爲了實現factor是1到minAlpha的變化,具體這樣的算式,每個人的思路可能不同,但是達到相同的效果即可。
同理,頁2是minAlpha到1的變化。
對應算式(postion爲1到0變化)
<code class="language-java hljs has-numbering"><span class="hljs-keyword">float</span> factor = mMinAlpha + (<span class="hljs-number">1</span> - mMinAlpha) * (<span class="hljs-number">1</span> - position);</code>
這個留給大家自己算,或者自己去總結出一個相同結果的算式。
ok,當我們完成AlphaPageTransformer
的編碼,然後ViewPager設置後,效果就是這樣的:
(2) RotateDownPageTransformer
再介紹個RotateDownPageTransformer
,因爲這個涉及到旋轉中心的變化,即:
<code class="language-java hljs has-numbering">view.setPivotX(); view.setPivotY();</code>
直接看代碼:
<code class="language-java hljs has-numbering"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">float</span> DEFAULT_MAX_ROTATE = <span class="hljs-number">15.0</span>f; <span class="hljs-keyword">private</span> <span class="hljs-keyword">float</span> mMaxRotate = DEFAULT_MAX_ROTATE; <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">pageTransform</span>(View view, <span class="hljs-keyword">float</span> position) { <span class="hljs-keyword">if</span> (position < -<span class="hljs-number">1</span>) { <span class="hljs-comment">// [-Infinity,-1)</span> <span class="hljs-comment">// This page is way off-screen to the left. </span> view.setRotation(mMaxRotate * -<span class="hljs-number">1</span>); view.setPivotX(view.getWidth()); view.setPivotY(view.getHeight()); } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (position <= <span class="hljs-number">1</span>) { <span class="hljs-comment">// [-1,1] </span> <span class="hljs-keyword">if</span> (position < <span class="hljs-number">0</span>)<span class="hljs-comment">//[0,-1]</span> { view.setPivotX(view.getWidth() * (<span class="hljs-number">0.5</span>f + <span class="hljs-number">0.5</span>f * (-position))); view.setPivotY(view.getHeight()); view.setRotation(mMaxRotate * position); } <span class="hljs-keyword">else</span><span class="hljs-comment">//[1,0]</span> { view.setPivotX(view.getWidth() * <span class="hljs-number">0.5</span>f * (<span class="hljs-number">1</span> - position)); view.setPivotY(view.getHeight()); view.setRotation(mMaxRotate * position); } } <span class="hljs-keyword">else</span> { <span class="hljs-comment">// (1,+Infinity] </span> <span class="hljs-comment">// This page is way off-screen to the right. </span> view.setRotation(mMaxRotate); view.setPivotX(view.getWidth() * <span class="hljs-number">0</span>); view.setPivotY(view.getHeight()); } }</code>
經過上面的分析,我們直接鎖定到第一頁到第二頁時,第一頁的相關變化的代碼:
<code class="language-java hljs has-numbering"><span class="hljs-keyword">if</span> (position < <span class="hljs-number">0</span>)<span class="hljs-comment">//[0,-1]</span> { <span class="hljs-keyword">float</span> factor = view.getWidth() * (<span class="hljs-number">0.5</span>f + <span class="hljs-number">0.5</span>f * (-position)); view.setPivotX(factor); view.setPivotY(view.getHeight()); view.setRotation(mMaxRotate * position); } </code>
第一頁開始時滑動時,旋轉中心上圖原點,即(width/2 , height).
第一頁滑動結束時,旋轉中心在左邊頁面的右下角,即(width,height).
恩,這個旋轉中心的位置是我自己定義的,不一定是最好的效果,如果有必要大家可以自己選擇,保證良好的顯示效果。
可以看到旋轉中心的縱座標沒有發生變化,主要看橫座標
<code class="hljs cpp has-numbering"> <span class="hljs-keyword">float</span> factor = view.getWidth() * (<span class="hljs-number">0.5f</span> + <span class="hljs-number">0.5f</span> * (-position));</code>
position爲0到-1,乘以-0.5之後,變爲0到0.5
在加上0.5,變爲0.5到1的變化
再乘以width,即變爲width/2到width的變化。
對應我們的旋轉中心x是需要從width/2到width,是不是剛好匹配。
旋轉中心的變化說明白了;再簡單說下,角度的變化,第一頁到達左邊頁面的狀態,角度是-15度,開始狀態是0度,那麼變化就是0到-15度之間,因爲position是0到-1之間變化,所以直接乘以15即可
<code class="hljs cpp has-numbering"><span class="hljs-keyword">float</span> rotation = position * <span class="hljs-number">15f</span> </code>
好了,經過上面的分析,本文就基本結束了,有興趣可以下載源碼多分析幾個,或者創造幾個動畫效果,千萬不要忘了告訴我,我可以加入到這個動畫庫中。
五、總結
本文的內容其實涉及到的API實際上比較多,再多的動畫其實質性的原理都是一樣的,關鍵在於找規律,所以帶大家梳理一下步驟:
- 確定View需要變化的屬性
- 確定該屬性的初始值,終值
- 確定該View對應的position變化的梯度
- 根據position的變化梯度,計算出需要變化的屬性的變化梯度
- 剩下的就是調用屬性動畫的API了
ok,相信通過該步驟大家一定能夠自己去定義出形態各異的動畫,此外,切記如果學習,一定要嘗試編寫,看一看就認爲了解的,可能有些坑是發現不了的。