其实在Android刚推出属性动画的时候,就想利用它来设计一个Banner控件,一直没什么时间尝试。在当时看我们应用中的Banner,使用计时器来控制自动播放,设置一个很大的数,利用余数原理来实现ViewPager循环,在进行内存分析的时候,看似不起眼的Banner却是一个消耗内存的大户,相信很多App的Banner都是计时器加ViewPager的方式实现的。
最近时间比较宽裕,可以做一些研究性的工作,因此打算尝试使用属性动画实现Banner。目前仅仅是尝试了一下如何实现,暂还未将其做成一个控件,最后的示例代码也是如此,仅仅是实现原理的一个演示,待后续测一下性能,如果还可以的话再做成自定义控件。先做一些声明:
1.这里仅仅演示一个示例,说明这种Banner的实现原理,暂未封装成控件
2.示例中手势滑动仅用按钮点击来模拟
3.由于仅演示原理,示例中未添加Indicator的联动效果,也没有Banner的点击响应
4.Banner的移动速度由动画时间控制,可自由设置,示例中设置的时间较短,故移动比较快
5.Banner尺寸暂时写死了,仅适配Nexus4手机,其他手机测试请自行获取Banner宽度
6.请分别测试自动播放和左滑、右滑
7.上述问题皆可在封装成正式控件时解决,同时还有图片缓存等
8.另外Banner播放移动由属性动画控制,因此3.0以下需额外动画库nineoldandroids
先来看下效果图(动画录制的很差,上两张静态图好了,大概可以看到Banner的移动过程):
接下来说一下实现原理,目前仅测试了自动播放广告和左右滑动广告,暂时未处理自动播放过程中的左右滑动行为。没啥美术功底,草纸上大概画了个示意图:
下面具体来解释一下:
1.首先说明支持任意张广告,个人测试用了4张,以下直接按4张解释原理了
2.先看Banner布局。最外层放一个FrameLayout作为容器,之所以用FrameLayout,是因为后续需要在广告条上覆盖Indicator,即小圆点指示器(目前示例里没有)。FrameLayout放一个水平的LinearLayout,该LinearLayout中依次放4个ImageView,每个代表一幅广告。需要注意的是,初始时,手机屏幕宽度内仅显示第一幅广告,其他3个ImageView可以理解为在手机屏幕右侧看不见的位置。
3.再来说如何实现广告位的移动。大家都知道属性动画与之前的动画最大的区别就是属性动画真实地改变了其属性,比如颜色、位置等。不错,正是利用这一点,对ImageView做动画,改编其水平位置属性,达到移动广告位的目的。具体的动画实现请参考下文中的代码。
4.接下来看如何实现Banner自动播放。开始时,同时对4个ImageView做属性动画,将它们都向左移动一个广告位的宽度。第一次动画完成后,它们的位置是这样的:第1个广告可以理解为移动到了手机屏幕左边的位置,手机屏幕上显示的是第2个广告,第3个,第4个广告依然在手机屏幕右侧看不到的位置。然后再次对它们4个做上述移动的动画,就这样,做完一次动画再做一次,如果没有任何处理的话,等第4张也移动到手机屏幕左侧,再做动画就什么都看不到了,因为它们已经远远偏离手机屏幕了。因此需要做一个特别处理,就是在4个ImageView每次动画完成之后,检查自己的位置,是否已经处于手机屏幕左侧了,如果是,则重新设置一下自己的位置,这个位置也不是随便设置的,需要将它设置到右侧最后,紧跟前一个广告。这样做的效果就是每当Banner移动一次,移到左边的广告条会自动被重新放到右侧末尾,而不影响视觉效果,从而可以达到循环播放的效果。个人觉得这样做的好处是永远只有4个ImageView对象,摒弃了取余循环的方式,效率应该会有提升。
5.最后说手动左滑右滑的实现原理。由于只是演示原理,本例并未真正处理手势滑动,仅用按钮点击来模拟。如果一直向左滑动或向右滑动比较好处理,但手势滑动不像自动轮播,手势滑动可左一下,右一下,任意次数的左右滑动,因此也要做特殊处理。如果向左滑动,原理同自动播放,利用动画将4个ImageView左移一个广告位,随后将移到左侧的重新追加到右侧最后边,任何一次左滑也是同样的处理,因此任一次左滑结束后,一个广告当前显示,其余3个都可以认为在手机屏幕右侧。对于右滑,不同于左滑,它需要在右滑之前做处理,我们需要在右滑之前先提供可供滑动的广告位,因此需要事先把最右边的广告位拿到手机屏幕左侧,然后再执行右滑处理。其实可以这样认为,每次右滑之前,手机屏幕左侧是没有广告位的,需要预先从最右侧拿1幅广告到手机屏幕左侧。
下面附一些相关代码,以下是示例的布局代码:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <FrameLayout android:id="@+id/banner_container" android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <ImageView android:id="@+id/banner_1" android:layout_width="704px" android:layout_height="wrap_content" android:src="@drawable/banner1" /> <ImageView android:id="@+id/banner_2" android:layout_width="704px" android:layout_height="wrap_content" android:src="@drawable/banner2" /> <ImageView android:id="@+id/banner_3" android:layout_width="704px" android:layout_height="wrap_content" android:src="@drawable/banner3" /> <ImageView android:id="@+id/banner_4" android:layout_width="704px" android:layout_height="wrap_content" android:src="@drawable/banner4" /> </LinearLayout> </FrameLayout> <Button android:id="@+id/btn_start" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/banner_container" android:text="自动轮播" /> <Button android:id="@+id/btn_stop" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/btn_start" android:enabled="false" android:text="自动播放与滑动请分别测试" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/btn_stop"> <Button android:id="@+id/btn_left" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="模拟左滑" /> <Button android:id="@+id/btn_right" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="模拟右滑"/> </LinearLayout> </RelativeLayout>
自动播放的处理代码:
/** * 自动播放广告条,播放速度可由动画时间控制 */ public void doAutoPlay() { for (int i = 0; i < COUNT; i++) { doAutoAnimation(imgArray[i]); } } /** * 仅适用于自动播放 * * @param v */ public void doAutoAnimation(final View v) { v.animate().x(v.getX() - v.getWidth()).setDuration(1500).setInterpolator(new LinearInterpolator()).setListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) { } @Override public void onAnimationEnd(Animator animator) { if (v.getX() < 0) { v.setX((COUNT - 1) * v.getWidth()); } doAutoAnimation(v); } @Override public void onAnimationCancel(Animator animator) { } @Override public void onAnimationRepeat(Animator animator) { } }); }
向左滑动处理代码:
/** * 模拟向左滑动Banner */ public void doLeftAnimation() { for (int i = 0; i < COUNT; i++) { doLeftAnimation(imgArray[i]); } } /** * 左滑之后,将滑到左边看不到的广告位移动到右边末尾 * * @param v */ public void doLeftAnimation(final View v) { v.animate().x(v.getX() - v.getWidth()).setDuration(1500).setInterpolator(new LinearInterpolator()).setListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) { } @Override public void onAnimationEnd(Animator animator) { if (v.getX() < 0) { v.setX((COUNT - 1) * v.getWidth()); } } @Override public void onAnimationCancel(Animator animator) { } @Override public void onAnimationRepeat(Animator animator) { } }); }
向右滑动处理代码:
/** * 模拟向右滑动Banner, * 由于没有采用常规的设置一个很大数,然后取余实现循环的原理; * 本例默认N个Banner广告均在手机屏幕可见区域及右侧(屏幕可见区域仅显示当前广告条) * 因此向右滑动之前,需要预先将最右侧的广告条移到手机屏幕左侧不可见区域 */ public void doRightAnimation() { for (int i = 0; i < COUNT; i++) { if (imgArray[i].getX() == imgArray[i].getWidth() * (COUNT - 1)) { imgArray[i].setX(-imgArray[i].getWidth()); break; } } for (int i = 0; i < COUNT; i++) { doRightAnimation(imgArray[i]); } } /** * 在右滑之前需要先提供一个可向右滑动的广告位 */ public void doRightAnimation(final View v) { v.animate().x(v.getX() + v.getWidth()).setDuration(1500).setInterpolator(new LinearInterpolator()).setListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) { } @Override public void onAnimationEnd(Animator animator) { } @Override public void onAnimationCancel(Animator animator) { } @Override public void onAnimationRepeat(Animator animator) { } }); }
完整示例代码下载 当某天需要下载资料时,发现自己竟然没有下载积分,因此设了1个积分,有需要下载的可以支持下,多谢。
原文地址:http://blog.csdn.net/ahence/article/details/46646559