1. 程式人生 > >Android為ViewPager增加切換動畫——使用屬性動畫

Android為ViewPager增加切換動畫——使用屬性動畫

 ViewPager作為Android最常用的的元件之一,相信大家在專案中會頻繁的使用到的,例如利用ViewPager製作引導頁、輪播圖,甚至做整個app的表現層的框架等等。

Android3.0以下不支援切換動畫

但是在Android 3.0(API 11)以下的ViewPager是比較死板的,不支援動畫特效的,這也就讓ViewPager在切換的時候達不到很好的使用者體驗,下面就是Android3.0以下不新增動畫的ViewPager的實現程式碼以及效果演示:

public class MainActivity extends Activity {

	private ViewPager mViewPager;
	private int[] imgRes = new int[] { R.drawable.guide_image1, R.drawable.guide_image2, R.drawable.guide_image3 };
	private List<ImageView> imgList = new ArrayList<ImageView>();

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.activity_main);
		mViewPager = (ViewPager) findViewById(R.id.viewpager);
		mViewPager.setAdapter(new PagerAdapter() {

			@Override
			public boolean isViewFromObject(View arg0, Object arg1) {
				return arg0 == arg1;
			}

			@Override
			public int getCount() {
				return imgRes.length;
			}

			@Override
			public Object instantiateItem(ViewGroup container, int position) {
				ImageView mImageView = new ImageView(MainActivity.this);
				mImageView.setBackgroundResource(imgRes[position]);
				mImageView.setScaleType(ScaleType.CENTER_CROP);
				imgList.add(mImageView);
				container.addView(mImageView);
				return mImageView;
			}

			@Override
			public void destroyItem(ViewGroup container, int position, Object object) {
				container.removeView(imgList.get(position));
			}
		});
	}
}
上面是最簡單的ViewPager使用的Demo,執行如下,看起來很普通很死板:

支援Android3.0以上的官方方法

值得慶幸的是,Google在Android3.0以上的版本中增加了給ViewPager設定切換動畫的API,允許開發者在Android3.0以上版本的應用中為ViewPager增加動畫切換效果,這樣就可以讓ViewPager的切換效果變的絢麗點了,為ViewPager新增動畫效果的API如下:
 public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer){...}
其中第一個引數boolean型別設定true就好,第二個引數PageTransformer就是我們自定義好的動畫效果:
mViewPager = (ViewPager) findViewById(R.id.viewpager);
mViewPager.setPageTransformer(true, new ZoomOutPageTransformer());
其中ZoomOutPageTransformer的程式碼來自於google的training文件中,英文好的朋友可以直接進入文件檢視,連結是 原始碼如下:
public class ZoomOutPageTransformer implements ViewPager.PageTransformer {
	private static final float MIN_SCALE = 0.85f;
	private static final float MIN_ALPHA = 0.5f;

	public void transformPage(View view, float position) {
		int pageWidth = view.getWidth();
		int pageHeight = view.getHeight();

		if (position < -1) { // [-Infinity,-1)
			// This page is way off-screen to the left.
			view.setAlpha(0);

		} else if (position <= 1) { // [-1,1]
			// Modify the default slide transition to shrink the page as well
			float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
			float vertMargin = pageHeight * (1 - scaleFactor) / 2;
			float horzMargin = pageWidth * (1 - scaleFactor) / 2;
			if (position < 0) {
				view.setTranslationX(horzMargin - vertMargin / 2);
			} else {
				view.setTranslationX(-horzMargin + vertMargin / 2);
			}

			// Scale the page down (between MIN_SCALE and 1)
			view.setScaleX(scaleFactor);
			view.setScaleY(scaleFactor);

			// Fade the page relative to its size.
			view.setAlpha(MIN_ALPHA + (scaleFactor - MIN_SCALE) / (1 - MIN_SCALE) * (1 - MIN_ALPHA));

		} else { // (1,+Infinity]
			// This page is way off-screen to the right.
			view.setAlpha(0);
		}
	}
}
另外Google文件中還提供了另外一個動畫的實現方式,我暫且把原始碼附在下面:
public class DepthPageTransformer implements ViewPager.PageTransformer {
	private static final float MIN_SCALE = 0.75f;

	public void transformPage(View view, float position) {
		int pageWidth = view.getWidth();

		if (position < -1) { // [-Infinity,-1)
			// This page is way off-screen to the left.
			view.setAlpha(0);

		} else if (position <= 0) { // [-1,0]
			// Use the default slide transition when moving to the left page
			view.setAlpha(1);
			view.setTranslationX(0);
			view.setScaleX(1);
			view.setScaleY(1);

		} else if (position <= 1) { // (0,1]
			// Fade the page out.
			view.setAlpha(1 - position);

			// Counteract the default slide transition
			view.setTranslationX(pageWidth * -position);

			// Scale the page down (between MIN_SCALE and 1)
			float scaleFactor = MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position));
			view.setScaleX(scaleFactor);
			view.setScaleY(scaleFactor);

		} else { // (1,+Infinity]
			// This page is way off-screen to the right.
			view.setAlpha(0);
		}
	}
}
兩種方式的所實現的效果如下所示,一是ZoomOutPageTransformer,二是DepthPageTransformer         

相容Android3.0以下的版本

前面我們說過的,Android3.0以下版本是不支援ViewPager增加切換動畫的,原因很簡單,我們可以參考一下上面貼出的兩段程式碼,可以看到兩段程式碼中都是使用了Android的屬性動畫寫的切換效果,我們知道Android屬性動畫是在Android3.0才出來的特性,僅支援Android3.0及其以上版本,所以ViewPager的切換動畫在Android3.0以下的版本中就不會支援了。分析出了不能在Android3.0以下版本中新增動畫的原因後,我們就可以通過其它的方法來解決這個相容性的問題了。 還記得之前我們在部落格中就聊過老外的一個大牛——JakeWharton,對的,這個大牛在GitHub開源了自己為Android3.0以下系統新增屬性動畫的專案——NineOldAndroids,我們可以下載原始碼或者jar匯入到我們的工程中來,用它來相容我們的Android3.0以下的版本。 下面就是我在DepthPageTransformer類中將View換成NineOldAndroids下的ViewHelper後,修改可以相容Android3.0以下版本的屬性動畫:
public class DepthPageTransformer implements ViewPager.PageTransformer {
	private static final float MIN_SCALE = 0.75f;

	public void transformPage(View view, float position) {
		int pageWidth = view.getWidth();

		if (position < -1) {
			// view.setAlpha(0);
			ViewHelper.setAlpha(view, 0);
		} else if (position <= 0) {
			// view.setAlpha(1);
			ViewHelper.setAlpha(view, 1);
			// view.setTranslationX(0);
			ViewHelper.setTranslationX(view, 0);
			// view.setScaleX(1);
			ViewHelper.setScaleX(view, 1);
			// view.setScaleY(1);
			ViewHelper.setScaleY(view, 1);
		} else if (position <= 1) {
			// view.setAlpha(1 - position);
			ViewHelper.setAlpha(view, 1 - position);
			// view.setTranslationX(pageWidth * -position);
			ViewHelper.setTranslationX(view, pageWidth * -position);
			float scaleFactor = MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position));
			// view.setScaleX(scaleFactor);
			ViewHelper.setScaleX(view, scaleFactor);
			// view.setScaleY(scaleFactor);
			ViewHelper.setScaleY(view, scaleFactor);
		} else {
			// view.setAlpha(0);
			ViewHelper.setAlpha(view, 0);
		}
	}
}
為了好理解,我沒有將Google提供的原始碼刪除,而是註釋掉了,方便大家進行比較閱讀,然而即使這樣使用了NineOldAndroids對我們的動畫原始碼進行了改造,當我們開啟一個Android2.3的模擬器執行一下的時候發現,沒有起到效果,也就是這個動畫效果並沒有執行,在模擬器上執行的ViewPager滑動的效果還是原始預設的左右來回切換的動畫,這又是怎麼回事呢?為止,我們需要開啟ViewPager的原始碼進行閱讀一下了,既然是在mViewPager.setPageTransformer(true, new DepthPageTransformer());這句程式碼沒有起到效果,那麼我們就點進去檢視一下setPageTransformer這個方法的原始碼,原始碼如下:
public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer){
	if (Build.VERSION.SDK_INT >= 11) {
		boolean hasTransformer = transformer != null;
		boolean needsPopulate = hasTransformer != (this.mPageTransformer != null);
		this.mPageTransformer = transformer;
		setChildrenDrawingOrderEnabledCompat(hasTransformer);
		if (hasTransformer) {
			this.mDrawingOrder = (reverseDrawingOrder ? 2 : 1);
		 } else {
			this.mDrawingOrder = 0;
		}
		if (needsPopulate) 
		    populate();
		}
}
好了,原始碼一目瞭然,我們分析到在ViewPager中的setPageTransFormer這個方法中,首先判斷了一下當前裝置的Build.VERSION_SDK_INT>=11,也就是說當前裝置是Android3.0以上系統的話,這個方法體執行沒問題,但是若是3.0一下,那就抱歉,無法執行了。知道這個原因之後我們就需要解決這個問題了,我在這裡修改了一下ViewPager的原始碼,修改ViewPager的原始碼之前,讀者可以先去下載一份ViewPager的原始碼,然後拷貝到工程中,重新命名一下,將裡面的if判斷語句給刪除了。以下就是我修改後的部分原始碼,命名為ViewPagerCompat:
public void setPageTransformer(boolean reverseDrawingOrder, ViewPager.PageTransformer transformer) {
	// if (Build.VERSION.SDK_INT >= 11) {
	final boolean hasTransformer = transformer != null;
	final boolean needsPopulate = hasTransformer != (mPageTransformer != null);
	mPageTransformer = transformer;
	setChildrenDrawingOrderEnabledCompat(hasTransformer);
	if (hasTransformer) {
		mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD;
	} else {
		mDrawingOrder = DRAW_ORDER_DEFAULT;
	}
	if (needsPopulate)
		populate();
	// }
}
好,原始碼修改完畢,將您的原始碼中的ViewPager替換成這個修改後的原始碼ViewPagerCompat,那麼ViewPager切換動畫的效果就出現了。

分析動畫原始碼,實現自己的動畫效果

上面的動畫原始碼是我在Google的官方文件中找到的,裡面完整的實現了一個系列動畫的功能。那麼我們是不是可以也能通過分析一下Google文件中原始碼的寫法,找到規律後,我們自己動手實現一個自己的動畫效果呢?先來看一下DepthPageTransformer.java這段原始碼,這個類實現了一個介面ViewPager.PageTransformer,重寫了其中的一個方法
public void transformPage(View view, float position) {
	...
	Log.i("TAG", "view = " + view + ",position = " + position);	
        ...
}
如上所示,我先在這個方法中打印出方法中的引數view物件和position的值,然後我們來看一下LOG的輸出: 好,我們看LOG輸出日誌,可以看到有2個view物件在不停交錯的輸出,其中我選中標註的view的雜湊值是4054c260,未選中標註的view的雜湊值是4054c548,然後再來看一下position的值,可以注意的是,4054c260的view的position值從0.0一直不斷減小到-1.0,4054c548的view的position的值從1.0一直不斷減小到0.0,我們為了方便,記雜湊值為4054c260的view為A頁,記雜湊值為4054c548的view為B頁。 情景分析一下,也請一邊看一下DepthPageTransformer.java的原始碼; 當position < -1,此時position均不代表A頁和B頁的位置,所以這裡我們不做任何的動畫出來。 當position <= 0,此時position的範圍是在0.0 ~-1.0之間,可以認為這是代表A頁的運動軌跡,也就是A頁移出螢幕外。 當position <= 1,此時position的範圍是在1.0 ~ 0.0之間,可以認為這是代表B頁的運動軌跡,也就是B頁移到螢幕上。 當position > 1,此時position均不代表A頁和B頁的位置,所以這裡我們不做任何的動畫出來。 好了,通過我們的分析可知,我們實現動畫效果是根據position的變化來設定的。那麼,我們現在也要根據position來實現一個自己的ViewPager的動畫效果了,我們做一個ViewPager的旋轉移出螢幕和旋轉移到螢幕上的效果吧。草圖如下所示:
看著草圖,我們想象一下,當手指向左滑動的時候,A頁會旋轉一定的角度移出螢幕,B頁也會旋轉一定的角度移到螢幕上,那麼我們就先給移動的角度設定一個常量吧,這裡我假設旋轉的最大角度是20度了,以下是我的原始碼:
/**
 * ViewPager自定義旋轉動畫
 * 
 * @author Vincent
 * 
 */
public class RotateDownTransformer implements PageTransformer {

	// 旋轉的最大角度為20度
	private static final float MAX_ROTATE = 20.0f;
	// 旋轉過程中的角度
	private float currentRotate;

	@Override
	public void transformPage(View view, float position) {
		int pageWidth = view.getWidth();
		Log.i("TAG", "view = " + view + ",position = " + position);
		if (position < -1) {
			ViewHelper.setRotation(view, 0);
		} else if (position <= 0) {
			// position範圍[-1.0,0.0],此時A頁動畫移出螢幕
			currentRotate = position * MAX_ROTATE;
			// 設定當前頁的旋轉中心點,橫座標是螢幕寬度的1/2,縱座標為螢幕的高度
			ViewHelper.setPivotX(view, pageWidth / 2);
			ViewHelper.setPivotY(view, view.getHeight());
			ViewHelper.setRotation(view, currentRotate);
		} else if (position <= 1) {
			// position範圍(0.0,1.0],此時B頁動畫移到螢幕
			currentRotate = position * MAX_ROTATE;
			// 設定當前頁的旋轉中心點,橫座標是螢幕寬度的1/2,縱座標為螢幕的高度
			ViewHelper.setPivotX(view, pageWidth / 2);
			ViewHelper.setPivotY(view, view.getHeight());
			ViewHelper.setRotation(view, currentRotate);
		} else {
			ViewHelper.setRotation(view, 0);
		}
	}
}
效果圖就在上面哦,看起來還湊合吧,根據上面的描述,只要你知道屬性動畫的相關api,也可以自定義初各種各樣的動畫效果出來,可以將多種單一的動畫效果混合在一起使用,讓ViewPager的滑動效果看起來更加的“複雜”。
感謝CSDN部落格專家鴻洋無私奉獻的教程,教程視訊地址:http://www.imooc.com/learn/226