Android 自訂 ViewPager 打造千變萬化的圖片轉場效果

來源:互聯網
上載者:User

Android 自訂 ViewPager 打造千變萬化的圖片轉場效果

 

記得第一次見到ViewPager這個控制項,瞬間愛不釋手,做東西的主介面通通ViewPager,以及圖片切換也拋棄了ImageSwitch之類的,開始讓ViewPager來做。時間長了,ViewPager的轉場效果覺得枯燥,形成了審美疲勞~~我們需要改變,今天教大家如何改變ViewPager切換時的效果,實現個人化的圖片切換~~

看一下這樣效果的圖片切換:

是不是比傳統的效果個性很多,嘿嘿~~其實很簡單,學習完這篇部落格,保證你可以自訂切換開關效果,做出各種喪心病狂的切換~~

1、製作前的分析

觀察下,實際上改變的就是切換時的動畫,那麼簡單了,只需要使用者在切換時,拿到當前的View和下一個View,然後添加動畫是不是就可以了。好,第一步,擷取使用者切換時的當前View和切換至的目的View。

我們在來看一下,如果或者了當前View和目的View,對於動畫我們需要緩慢的變化,最好是根據使用者的手勢滑動。比如上述效果,使用者滑動時,目的圖片根據使用者滑動距離緩緩出現和慢慢變大。好,第二步,設計動畫的梯度變化。

經過分析,我們總結出兩個步驟,下面我們開始一步一步來打造~~~

2、擷取使用者切換時當前View和切換至的目的View。

ViewPager也需要監聽使用者的手勢,所以肯定提供了某個方法。於是縱觀ViewPager的方法,發現了一個叫做 onPageScrolled(int position, float positionOffset, int positionOffsetPixels)的方法~~

沒錯就是這個方法:在頁面滾動時調用~

下面仔細研究下這幾個參數:

直接說測試結果:

在非第一頁與最後一頁時,滑動到下一頁,position為當前頁位置;滑動到上一頁:position為當前頁-1

positionOffset 滑動到下一頁,[0,1)區間上變化;滑動到上一頁:(1,0]區間上變化

positionOffsetPixels這個和positionOffset很像:滑動到下一頁,[0,寬度)區間上變化;滑動到上一頁:(寬度,0]區間上變化

第一頁時:滑動到上一頁position=0 ,其他基本為0 ;最後一頁滑動到下一頁 position為當前頁位置,其他兩個參數為0

 

豁然發現,我們需要的步驟的第二步解決了,positionOffset很適合作為,漸層,縮放的控制參數;positionOffsetPixels則可以作為平移等的控制參數。

那麼如何獲得當前View和目的View呢:

分享幾個我的歧途:

1、【錯誤】我通過getChildAt(position),getChildAt(position+1),getChildAt(position-1)獲得滑動時,左右的兩個View;乍一看,還真覺得不錯~~~在代碼寫出來,再乍效果也出不來~~錯誤原因:我們忽略一個特別大的東西,ViewPager的機制,滑動時動態載入和刪除View,ViewPager其實只會維持2到3個View,而position的範圍基本屬於無限~~

2、【錯誤】我通過getCurrentItem獲得當前的位置,然後+1,-1獲得後一個或者前一個~~正在竊喜,趕快代碼改過來,效果怎麼也不對,亂七八糟的~~仔細觀察日誌,這個getCurrentItem當使用者手指離開的螢幕,Page還在動畫執行時,就改變了~~難怪~整個滑動過程並不是固定的~~唉,心都碎了~

3、【錯誤】position在整個滑動的過程中是不變化的,而且ViewPager會儲存2個或3個View;那麼我考慮,如果是第一頁、或者最後一頁那麼我取getChildAt(0)和getChildAt(1),如果在其他頁面則為getChildAt(0),getChildAt(2),然後經過一系列的變化~我想這會總該對了吧,於是我遇到第一問題,第一頁的時候,不管左右position都為0,尼瑪,這哪個為左View,哪個為右View~~

說了這麼多錯誤,大家可以繞過這些彎路,也能從這些彎路裡面看出點什麼~

下面說正確的,其實ViewPager在添加一個View或者銷毀一個View時,是我們自己的PageAdapter中控制的,於是我們可以在ViewPager裡面維繫一個HashMap,然後滑動的時候,通過get(position)取出,比如上述效果,始終是右邊的View變化,要麼從小到大,要麼從大到小

 

那麼滑倒下一頁:左邊的View:map.get(position) ,右邊的View : map.get(position+1) .

那麼滑倒上一頁:左邊的View : map.get(position) , 右邊的View : map.get(position+1) , 一樣的,因為滑到上一頁,position為當前頁-1

好了,至此,我們分析了且解決了所有步驟。

3、代碼

MainActivity

 

package com.example.zhy_jazzyviewpager;import android.app.Activity;import android.os.Bundle;import android.support.v4.view.PagerAdapter;import android.view.Menu;import android.view.View;import android.view.ViewGroup;import android.widget.ImageView;import android.widget.ImageView.ScaleType;public class MainActivity extends Activity{protected static final String TAG = MainActivity;private int[] mImgIds;private MyJazzyViewPager mViewPager;@Overrideprotected void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mImgIds = new int[] { R.drawable.a, R.drawable.b, R.drawable.c,R.drawable.d };mViewPager = (MyJazzyViewPager) findViewById(R.id.id_viewPager);mViewPager.setAdapter(new PagerAdapter(){@Overridepublic boolean isViewFromObject(View arg0, Object arg1){return arg0 == arg1;}@Overridepublic void destroyItem(ViewGroup container, int position,Object object){container.removeView((View) object);}@Overridepublic Object instantiateItem(ViewGroup container, int position){ImageView imageView = new ImageView(MainActivity.this);imageView.setImageResource(mImgIds[position]);imageView.setScaleType(ScaleType.CENTER_CROP);container.addView(imageView);mViewPager.setObjectForPosition(imageView, position);return imageView;}@Overridepublic int getCount(){return mImgIds.length;}});}}

這個很常見的代碼,就是初始化ViewPager~~就沒啥可說的了~~有一點需要注意:在instantiateItem方法,我們多調用了一個mViewPager.setObjectForPosition(imageView, position);其實就是為了給我們的Map存值

 

主要看自訂的ViewPager

 

package com.example.zhy_jazzyviewpager;import java.util.HashMap;import java.util.LinkedHashMap;import android.content.Context;import android.support.v4.view.ViewPager;import android.util.AttributeSet;import android.util.Log;import android.view.View;import com.nineoldandroids.view.ViewHelper;public class MyJazzyViewPager extends ViewPager{private float mTrans;private float mScale;/** * 最大的縮小比例 */private static final float SCALE_MAX = 0.5f;private static final String TAG = MyJazzyViewPager;/** * 儲存position與對於的View */private HashMap mChildrenViews = new LinkedHashMap();/** * 滑動時左邊的元素 */private View mLeft;/** * 滑動時右邊的元素 */private View mRight;public MyJazzyViewPager(Context context, AttributeSet attrs){super(context, attrs);}@Overridepublic void onPageScrolled(int position, float positionOffset,int positionOffsetPixels){//Log.e(TAG, position= + position+, positionOffset = +positionOffset+ ,positionOffsetPixels =   + positionOffsetPixels+ , currentPos =  + getCurrentItem());//滑動特別小的距離時,我們認為沒有動,可有可無的判斷float effectOffset = isSmall(positionOffset) ? 0 : positionOffset;//擷取左邊的ViewmLeft = findViewFromObject(position);//擷取右邊的ViewmRight = findViewFromObject(position + 1);// 添加切換動畫效果animateStack(mLeft, mRight, effectOffset, positionOffsetPixels);super.onPageScrolled(position, positionOffset, positionOffsetPixels);}public void setObjectForPosition(View view, int position){mChildrenViews.put(position, view);}/** * 通過過位置獲得對應的View *  * @param position * @return */public View findViewFromObject(int position){return mChildrenViews.get(position);}private boolean isSmall(float positionOffset){return Math.abs(positionOffset) < 0.0001;}protected void animateStack(View left, View right, float effectOffset,int positionOffsetPixels){if (right != null){/** * 縮小比例 如果手指從右至左的滑動(切換到後一個):0.0~1.0,即從一半到最大 * 如果手指從左至右的滑動(切換到前一個):1.0~0,即從最大到一半 */mScale = (1 - SCALE_MAX) * effectOffset + SCALE_MAX;/** * x位移量: 如果手指從右至左的滑動(切換到後一個):0-720 如果手指從左至右的滑動(切換到前一個):720-0 */mTrans = -getWidth() - getPageMargin() + positionOffsetPixels;ViewHelper.setScaleX(right, mScale);ViewHelper.setScaleY(right, mScale);ViewHelper.setTranslationX(right, mTrans);}if (left != null){left.bringToFront();}}}

可以看到,核心代碼都是onPageScrolled,我們通過findViewFromObject(position); findViewFromObject(position + 1);分別擷取了左右兩邊的View,然後添加動畫效果;當前這個例子添加了兩個動畫,一個是從0.5放大到1.0或者1.0縮小到0.5,沒錯由我們的positionOffset提供梯度的變化~~還有個平移的動畫:下一頁直接移動到當前螢幕(預設是在右邊,可以注釋這個效果,怎麼運行看看),然後不斷的通過positionOffsetPixels抵消原來預設移動時的位移,讓使用者感覺它就在原地放大縮小~~

 

好了,這樣就實現了~~你可以隨便寫自己喜歡的動畫效果,比如在預設上面加個淡入淡出或者神馬,隨便~~是不是很隨意~~

我們的布局檔案:

 

    


 

4、JazzyViewPager的使用

其實上面的實現就是github上JazzyViewPager的源碼,用法不用說了,就是我們的MainActivity,它內建了大概10來種效果,我們可以通過代碼或者布局上面設定動畫效果~~我們上面的例子效果,它叫做Stack;

使用JazzViewPager的代碼:其實基本一樣~~最後也會貼上JazzyViewPager的源碼的下載

MainActivity

 

package com.jfeinstein.jazzyviewpager;import com.jfeinstein.jazzyviewpager.JazzyViewPager.TransitionEffect;import android.app.Activity;import android.os.Bundle;import android.support.v4.view.PagerAdapter;import android.view.View;import android.view.ViewGroup;import android.widget.ImageView;import android.widget.ImageView.ScaleType;public class MainActivity extends Activity{protected static final String TAG = MainActivity;private int[] mImgIds;private JazzyViewPager mViewPager;@Overrideprotected void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mImgIds = new int[] { R.drawable.a, R.drawable.b, R.drawable.c,R.drawable.d };mViewPager = (JazzyViewPager) findViewById(R.id.id_viewPager);//設定轉場效果mViewPager.setTransitionEffect(TransitionEffect.Stack);mViewPager.setAdapter(new PagerAdapter(){@Overridepublic boolean isViewFromObject(View arg0, Object arg1){return arg0 == arg1;}@Overridepublic void destroyItem(ViewGroup container, int position,Object object){container.removeView((View) object);}@Overridepublic Object instantiateItem(ViewGroup container, int position){ImageView imageView = new ImageView(MainActivity.this);imageView.setImageResource(mImgIds[position]);imageView.setScaleType(ScaleType.CENTER_CROP);container.addView(imageView);mViewPager.setObjectForPosition(imageView, position);return imageView;}@Overridepublic int getCount(){return mImgIds.length;}});}}

與我們的代碼唯一區別就是:

 

//設定轉場效果
mViewPager.setTransitionEffect(TransitionEffect.Stack);

它有12中可選的轉場效果,其實就是寫了12個切換的動畫~~~

好了,最後附上一個我比較喜歡的效果:Tablet

最後,喜歡藉此部落格拋磚引玉~~大家對感興趣的github上的代碼,可以進行分析與自己嘗試去實現,有時候會發現不是很難~你也可以做到~!

 

源碼點擊下載

 



 

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.