闲着无聊,写写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下载