在项目中,我们经常用到轮播图,前几天项目中再次遇到了这个需求。需求比较简单,三张图片无限循环,每隔2秒自动滑到下一页。需求不难,很快就写出来了。分析思路:
一、既然是无限制的,那么就适配器的 getCount() 方法返回值,返回一个 int 范围允许的最大值;
二、适配器中创建对象的 instantiateItem() 方法,我们展示图片,加载 url 时,对当前页 position 对 3 取余,这样获取集合中的图片就会对应;
三、既然可以左右滑动,如果开始位置是0,则向左滑动不了,所以一开始,就调用 ViewPager 的 setCurrentItem() 方法,直接跳转到一些页数,比如 3 * 1000 ,页数是3的倍数;
四、每隔2秒自动滑动到下一页,则我们需要定期调用 ViewPager 的 getCurrentItem() 方法获取当前的位置 mCurItem,然后调用 setCurrentItem() 方法,参数在原先的基础上加1;
五、这个属于细节优化,轮播图自动滑到了新页面,过了1秒,手指按下,这时候需要取消自动滑动功能,手指松开时再执行自动滑动功能,否则手指按下时图片自动滑到下一页,或者刚手动滑到下一页,马上又自动滑动了一页,体验效果很差;
既然有思路了,就按照思路来写代码
Activity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
initView();
}
String[] srcs = {
"http://02.imgmini.eastday.com/mobile/20180528/20180528170057_5b2bd0949a9a84cde18b82c8dc649bb3_6_mwpm_03200403.jpg",
"http://00.imgmini.eastday.com/video/vdianying/20180525/20180525213221696817529_1_mwpm_03200403.jpeg",
"http://02.imgmini.eastday.com/video/vyule/20180525/20180525211750122871425_1_mwpm_03200403.jpg"};
private ViewPager mViewPagerView;
private void initView() {
mViewPagerView = (ViewPager) mRootView.findViewById(R.id.viewpager);
List<String> list = Arrays.asList(srcs);
GameAdPager adp = new GameAdPager(list);
mViewPagerView.setAdapter(adp);
mViewPagerView.setCurrentItem(1000 * list.size());
mViewPagerView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
stopCurrent();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
startCurrent();
break;
}
return false;
}
});
startCurrent();
}
Handler mHandler = new Handler();
int delayTime = 2;
Runnable runnable = new Runnable() {
@Override
public void run() {
mViewPagerView.setCurrentItem(mViewPagerView.getCurrentItem() + 1);
mHandler.postDelayed(this, delayTime);
}
};
private void startCurrent() {
stopCurrent();
mHandler.postDelayed(runnable, delayTime);
}
private void stopCurrent(){
mHandler.removeCallbacks(runnable);
}
@Override
public void onResume() {
super.onResume();
startCurrent();
}
@Override
public void onPause() {
super.onPause();
stopCurrent();
}
xml 布局 activity_test
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
适配器
public class GamePager extends PagerAdapter {
private List<String> mList;
int mCount;
private LinkedList<View> mRecycledViews = new LinkedList<>();
public GamePager(List<String> list) {
this.mList = list;
mCount = mList.size();
}
@Override
public int getCount() {
return Integer.MAX_VALUE;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
ImageView iv = new ImageView(container.getContext());
iv.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
ImageLoaderUtils.with(container.getContext(), iv, mList.get(position % mCount));
container.addView(iv);
return iv;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
}
代码写的比较随意,命名各方面都不规范,大家谅解。到此,基本功能实现,感觉好像万事了,但这个代码还停留在初级阶段,试想一下,如果有用户不停的滑动,或者说让它自己滑动半个小时,虽说 ViewPager 有自动回收机制,最多加载3个页面,但我们这个功能是接近无限循环,这样适配器的 instantiateItem() 和 destroyItem() 方法不停的创建和销毁,就导致其中代码ImageView 也不停的创建,ImageLoaderUtils 是加载图片的工具类,有缓存机制,这个还好。但是不停的创建 ImageView 就浪费了,我们能想办法把它优化吗?
所谓优化性能,在我等菜鸟的水平上,也就是缓存和复用了,或者预加载数据,就这三板斧,在这个地方既然是不停的创建对象,那么咱们就把回收的象给缓存起来,下次需要创建对象时,优先去缓存中去找,如果缓存没有对象再创建,这样就能避免不必要的对象创建。
public class GamePager extends PagerAdapter {
private List<String> mList;
int mCount;
private LinkedList<View> mRecycledViews = new LinkedList<>();
public GamePager(List<String> list) {
this.mList = list;
mCount = mList.size();
}
@Override
public int getCount() {
return Integer.MAX_VALUE;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
ImageView iv;
if (mRecycledViews != null && mRecycledViews.size() > 0) {
iv = (ImageView) mRecycledViews.getFirst();
mRecycledViews.removeFirst();
} else {
iv = new ImageView(container.getContext());
iv.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
ImageLoaderUtils.with(container.getContext(), iv, mList.get(position % mCount));
container.addView(iv);
return iv;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
if (object != null) {
mRecycledViews.addLast((View) object);
}
}
}
适配器中添加一个集合,destroyItem() 方法中,系统需要我们把view布局给移除掉,这时候我们把它装到集合中;instantiateItem() 方法中是创建view,我们先去集合中获取,如果没有再 new 布局,就这么简单的添加个集合,就避免了布局view的重复创建。
进一步优化,我们可以仿照 RecyclerView 的方式,抽取一个抽象的适配器基类,创建 ViewHolder 辅助类,在它里面保存布局view的各种控件,instantiateItem() 中创建 ViewHolder,老规矩,还是先从集合中获取,如果没有就 new 一个,对外暴露 onCreateViewHolder() 方法,创建 holder 后,把它通过view的 setTag() 方法保存到view中;对外暴露 onBindViewHolder() 方法绑定数据;在 destroyItem() 中,通过view的 getTag() 方法获取到 holder,添加到集合中。 这样再写适配器就像写RecyclerView的适配器一样了。