首页 > 代码库 > Android好奇宝宝_07_ViewPager切换动画(兼容低版本)

Android好奇宝宝_07_ViewPager切换动画(兼容低版本)

闲着无聊,写写Demo

想着写一个图片轮播,百度了一下基本都是用ViewPager实现的,那就用ViewPager来练手。

写完了再自定义切换效果,发现3.0以下不兼容,只好想办法。

先上效果图:

技术分享

下面一步一步来:


(1)写布局:

<RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >

            <jjj.demo.viewpagerdemo.AnimViewPager
                android:id="@+id/test_viewPager"
                android:layout_width="match_parent"
                android:layout_height="300dp"
                android:background="#f7f7f7" >
            </jjj.demo.viewpagerdemo.AnimViewPager>

            <jjj.demo.viewpagerdemo.DianDian
                android:id="@+id/test_dianDian"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentTop="true"
                android:layout_centerHorizontal="true"
                android:layout_marginTop="270dp"
                app:count="5" >
            </jjj.demo.viewpagerdemo.DianDian>
        </RelativeLayout>

AnimViewPager是我重写的ViewPager,解决低版本Viewpager不支持自定义切换动画,这里先将其当成普通的ViewPager就行了。

DianDian是我很久以前写得一个自定义控件,就是底部那5个点。

简单说下原理:

DianDian继承了LinearLayout,通过GradientDrawable这个类来生成选中和没选中时的点的drawable,通过Margin控制间隔距离,然后添加到LinearLayout里面就行了。


(2)为ViewPager填充内容:

		viewPager = (AnimViewPager) findViewById(R.id.test_viewPager);
		initIamgeList();
		viewPager.setAdapter(mAdapter);
initImageList()初始化一个长度为5的ArrayList<ImageView>做测试用。

mAdapter跟网上其它demo一样,没什么特别的:

PagerAdapter mAdapter = new PagerAdapter() {
		public void destroyItem(android.view.ViewGroup container, int position, Object object) {
			((AnimViewPager) container).removeView(imageViewsList.get(position));
		};

		public Object instantiateItem(android.view.ViewGroup container, int position) {
			((AnimViewPager) container).addView(imageViewsList.get(position));
			return imageViewsList.get(position);
		};

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

		@Override
		public int getCount() {
			return 5;
		}
	};


(3)设置监听

设置Pager切换监听,以切换底下的5个点的显示。

	OnPageChangeListener mChangeListener = new OnPageChangeListener() {
		@Override
		public void onPageSelected(int arg0) {
			dianDian.setSelect(arg0);
		}

		@Override
		public void onPageScrolled(int arg0, float arg1, int arg2) {
		}

		@Override
		public void onPageScrollStateChanged(int arg0) {
		}
	};

好吧,我只写了一句dianDian.setSelect(arg0)。


(4)自定义切换动画

上面三步做完后ViewPager已经能正常使用了。

但是呢我们还想做点酷酷的事情,于是决定自定义ViewPager的切换动画效果。

所以百度了一下,发现Google提供了简单的方法来实现:

		viewPager.setPageTransformer(false, new PageTransformer() {

			@Override
			public void transformPage(View view, float position) {
			}
		});


其中第一个参数我们不管(好像是绘制顺序什么的),第二个参数是PageTransformer类型。


其实这里跟设置了监听一样的,就像我们经常写的:

		button.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				
			}
		});

viewPager对应button,PageTransformer对应OnClickListener。

所不同的是OnClickListener的OnClick方法会在button被点击的时候被调用,并且把button当做参数传过去。

PageTransformer的transformPage方法则是会在ViewPager被拖动的时候被调用。

下面来分析下transformPage方法的两个参数:


view:

当ViewPager被拖动时,会有两个Pager被影响,即当前显示的pager1和拖动后导致可见的pager2。而这个view参数就是这两个中的一个。这里不像Button的点击回调一样,点击一次就回调一次onClick,而是回调两次,两次的view参数分别为pager1和pager2,所传入的第二个参数position也是不同的。


position:一张图来解释,嫌我画的丑的你来打我啊

技术分享

所以呢PageTransformer只是告诉我们ViewPager当前拖动的状态,移动了百分之几而已。动画效果还需要我们自己实现,而我们需要view参数作为动画的主体,position作为动画的参数。

一个官方例子,也是效果图的实现:

		viewPager.setPageTransformer(false, new PageTransformer() {

			@Override
			public void transformPage(View view, float position) {
				int pageWidth = view.getWidth();
				if (position < -1) {
					view.setAlpha( 0);
				} else if (position <= 0) {
					view.setAlpha( 1 - position);
					view.setTranslationX( 0);
					view.setScaleX( 1);
					view.setScaleY( 1);
				} else if (position <= 1) {
					view.setAlpha(1 - position);
					view.setTranslationX(pageWidth * -position);
					float scaleFactor = MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position));
					view.setScaleX(scaleFactor);
					view.setScaleY(scaleFactor);
				} else {
					view.setAlpha(0);
				}
			}
		});

注意小于等于0的是对左边pager(即pager1)的处理,大于0的是对右边pager(即pager2)的处理。


通过拖动的百分比设置不同的透明度、位移、缩放等就可以实现我们的自定义动画了,读者可以自己试着实现几个。


就在这时候,问题出现了,你的IDE像个受惊的小婊砸向你呼喊,于是一条条的红线像血一样染红了夕阳。


跑偏了。。。


属性动画是在API 3.0之后才有的,所以setScaleX这些方法在11版本以下api是没有的。


你可以加一条NewApi注解忽略报错,这样的话在api11及以上版本能显示自定义的动画效果,在以下版本只能显示默认效果,貌似这样就不错了。


但是呢我却还不满足,我无法抑制我的饥渴。


于是呢我想到了大名鼎鼎的动画兼容库Nine Old Androids。

Ok,导入该库,修改调用语句为下面这种样式:

ViewHelper.setAlpha(view, 1 - position);


啊,一切都是那么美好。打开2.3的模拟器,接下来就是见证奇迹的时刻了。

开启中... ...开启中... ...开启中... ...开启中... ...

忍着把这破电脑砸掉的冲动,开始运行,然后开始拖动,然后就没有然后了。


坑爹啊这是,没起作用!!!还是默认的拖动动画!!!


不要惊慌,深呼吸。


出现问题,先查官方api说明,setPageTransformer方法的说明:

Note: Prior to Android 3.0 the property animation APIs did not exist. As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no effect.

坑爹呢这是,你又知道我调用这个就一定是为了实现属性动画,我没事调着玩不行啊。


看下它是怎么忽略低版本这个被歧视的种族的:

	public void setPageTransformer(boolean reverseDrawingOrder, 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();
		}
	}

我去,够狠,直接一个if判断,小于11直接啥事不干。

这里我们最关心的语句就是:

mPageTransformer = transformer;

将我们设置的transformer保存了下来,可以猜想,在拖动时一定会有类似代码被执行:

		if(mPageTransformer!=null){
			mPageTransformer.transformPage(view,position);
		}


所以现在我们的关键就是把我们的transformer赋值给mPageTransformer。


好,开始尝试。

首先呢要自定义一个Viewpager,并且重写setPageTransformer方法。


然后在setPageTransformer方法里加个判断,如果api低于11,我们自己处理,高于11,交给父类处理。


我们的处理就是要让语句mPageTransformer = transformer得以执行,而mPageTransformer已经有了个set方法,按照编码规范它应该是被声明为私有的了,我们无法直接获得其引用,一试果然如此。


我们只好另辟蹊径,用反射来实现了。

高清源码:

	@Override
	public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) {
		if (Build.VERSION.SDK_INT < 11) {
			try {
				//找到mPageTransformer变量,注意要在父类ViewPager找
				Field field = this.getClass().getSuperclass().getDeclaredField("mPageTransformer");
				//设置修改权限
				field.setAccessible(true);
				//这里相当于执行了语句mPageTransformer = transformer;
				field.set(this, transformer);
			} catch (Exception e) {
				e.printStackTrace();
			}
		} else {
			super.setPageTransformer(reverseDrawingOrder, transformer);
		}
	}

把布局的ViewPager改成我们自定义的AnimViewPager,怀着万分期待的心情,再次运行。bingo,成功了。实际上上面的效果图就是在2.3的模拟器上截的。

因为是通过反射实现的,要是那天Google突然抽风把变量名换了,那么我们做的就失效了。当然这种事情发生的概率很低,也不用太在意。

本篇结束


Demo下载

Android好奇宝宝_07_ViewPager切换动画(兼容低版本)