图片轮播控件Android版version0.1已在简书中发布,请先前往查看。本文详细介绍该控件的实现原理。
需求分析
为了实现一个功能丰富、适配各种设备的通用型图片轮播控件,0.1版本暂提出如下需求:
+ 基本的图片轮播展示功能,可左右翻页
+ 自动循环播放,由定时器控制每3秒向右翻动一页,最后一页继续翻页到第一页。可在程序中禁用自动播放。
+ 横竖屏自动适配,以该控件的高度宽度比为判断标准。当高度大于宽度时,作为竖屏模式处理,每次只显示一个图片;当宽度大于高度时,作为横屏模式处理,每次显示多个图片,每个图片具有相同的高度和宽度,都等于控件的高度。
实现原理
本控件使用一个ViewPager
作为图片轮播的主体,在底部添加一个LinearLayout
作为若干个圆点的容器,当前选中的图片对应的圆点为亮色,其它为暗色。控件的xml布局如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rtv_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false" >
<android.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="0dp"
android:layout_marginRight="0dp"
android:clipChildren="false"
/>
<LinearLayout
android:id="@+id/dot_container"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_alignParentBottom="true"
android:layout_centerInParent="true"
android:gravity="center" />
</RelativeLayout>
ViewPager
需要自定义一个适配器ViewPagerAdapter
,继承自PagerAdapter
:
class ViewPagerAdapter extends PagerAdapter {
private List<View> views;
public ViewPagerAdapter (List<View> views) {
this.views = views;
}
//销毁position位置的界面
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager)container).removeView(views.get(position));
}
//获得当前界面数
@Override
public int getCount() {
return views.size();
}
//初始化position位置的界面
@Override
public Object instantiateItem(ViewGroup container, final int position) {
View view = views.get(position);
container.addView(view);
return view;
}
//判断是否由对象生成界面
@Override
public boolean isViewFromObject(View view, Object object) {
return (view == object);
}
}
这段代码即大家公认的ViewPager
的适配器代码,未做任何改动,因此不必赘述。
至此,控件已经实现了基本的图片展示功能,可以手动滑动翻页。接下来需要实现循环翻页和定时翻页功能。
使用ViewPager
实现循环翻页并不是件容易的事情,网络上公认的两种解决方法都有或多或少的缺陷。我提出了一种新的解决方法,详细内容可以参考我的另一篇博文《真无限循环的ViewPager——解决两端滑动的平滑问题》,不过在这里我还是要简单介绍一下。在网络上流行的真无限循环的ViewPager
解决方案中,使用CABCA的图片序列代替实际的图片序列ABC。在向右从第2个C翻页到第2个A后,自动跳转到第1个A(不带过渡效果);在向左从第1个A翻页到第1个C后,自动跳转到第2个C(不带过渡效果)。以此方式实现循环翻页。但问题是如果在onPageSelected
中做如上所述的跳转,将得到不连续的翻页体验。因此我的实现方法是在onPageScrollStateChanged
中跳转。先看ViewPager
的监听器实现类MyPageChangeListener
的代码,实现了OnPageChangeListener
接口:
private final class MyPageChangeListener implements OnPageChangeListener {
@Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
if (currentPosition == views.size() - 1) {
viewPager.setCurrentItem(1, false);
}
else if (currentPosition == 0) {
viewPager.setCurrentItem(views.size() - 2, false);
}
}
}
@Override
public void onPageScrolled(int scrolledPosition, float percent, int pixels) {
//empty
}
@Override
public void onPageSelected(int position) {
currentPosition = position;
}
}
在onPageSelected
方法中只需要记录新到达的页面的下标,由于该方法调用的时候,翻页的过渡过程还没有结束,所以在onPageScrollStateChanged
方法中,等待到达SCROLL_STATE_IDLE
状态。一旦到达IDLE
状态,说明翻页的过渡过程完全结束,这时候再去判断当前页是哪一页,是否需要立即跳转。经过测试,这种方式达到了很好的效果。另外,上面的代码为了突出重点,略去了一些设置控件底部小圆点的程序段。
定时翻页功能的实现非常简单,只要使用一个定时器就能完成,此处不再赘述。
最后一个非常关键的技术手段就是横竖屏适配。需要在控件初始化的时候确定当前应该选择哪种模式,如果是竖屏模式,那么代码不需做任何改动,使用ViewPager
的默认设置就可以了;如果是横屏模式,就需要对ViewPager
做一些个性化的设置。根据我们的需求分析,横屏时每个图片占据一个正方形大小的空间,正方形的边长等于控件的高度。ViewPager
有个特殊的用法,当我们把它的clipChildren
属性设为false
,并将它的左右margin
扩大,margin
占据的空间就会显示出相邻的图片,而且margin
越大,显示出的图片就越多。请看如下代码:
ViewTreeObserver vto = viewPager.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@SuppressWarnings("deprecation")
@Override
public void onGlobalLayout() {
viewPager.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int height = viewPager.getHeight();
int width = viewPager.getWidth();
RelativeLayout.LayoutParams viewPagerLayoutParams = (RelativeLayout.LayoutParams) viewPager.getLayoutParams();
if(height < width) {
viewPagerLayoutParams.leftMargin = (width - height) / 2;
viewPagerLayoutParams.rightMargin = (width - height) / 2;
viewPager.setLayoutParams(viewPagerLayoutParams);
}
}
});
为了在控件加载完成后获取它的大小,使用了控件的ViewTreeObserver
对象。我们判断高度和宽度的关系,如果高度小于宽度,则调整ViewPager
的左右margin
,调整的结果是整个控件的宽度除去左右margin
之后正好等于控件的高度。
至此,整个控件的核心原理已经叙述完毕。
如有疑问,请直接回复或发送邮件到wjg172184@163.com。