闲着无聊,写写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突然抽风把变量名换了,那么我们做的就失效了。当然这种事情发生的概率很低,也不用太在意。
本篇结束