今天在学习一个常用的ViewPager控件,遇到了一些小坑,在这里记录下来。
根本原因在于ViewPager的缓存机制。因为缓存,出现了一个状况:左滑时会闪退并且报错
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.。
其根本原因在于viewPager的缓存机制,只在三张图片的时候会出现这个问题,当把轮播图增加到四张时,问题就解决了😂😂。
先按照我的学习过程走一遍,首先是viewpager的使用。
<androidx.viewpager.widget.ViewPager
android:id="@+id/picture_viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"/>
ViewPager的定义比较简单,唯一需要注意的是在新版本的AndroidStudio中ViewPager已经不再从V4包导入,而是改为从androidx包里面导入。
然后是在MainActivity中定义一下。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
picture_text = findViewById(R.id.picture_text);
picture_viewPager = findViewById(R.id.picture_viewPager);
initData();
initView();
}
前置工作完成。下一步是初始化数据:
private void initData(){
image_ids = new int[] {R.drawable.pager_image1,R.drawable.pager_image2,R.drawable.pager_image3};
//添加图片到图片列表里
imageViewList = new ArrayList<>();
ImageView iv;
for (int i = 0; i < image_ids.length; i++) {
iv = new ImageView(this);
iv.setBackgroundResource(image_ids[i]);//设置图片
iv.setId(image_ids[i]);//顺便给图片设置id
imageViewList.add(iv);
}
}
private void initView(){
ViewPagerAdapter viewPagerAdapter = new ViewPagerAdapter(imageViewList, picture_viewPager);
picture_viewPager.setAdapter(viewPagerAdapter);
}
图片啥的,就在本地选了几个图片作为本地资源了。后面就设置适配器adapter了。
接下来新建一个ViewPagerAdapter继承自PagerAdapter,这个比较简单,直接copy
/**
* com.fenger.viewpager
* Created by fenger
* in 2020-01-06
*/
public class ViewPagerAdapter extends PagerAdapter {
private List<ImageView> imageViewList;
private ViewPager viewPager;
/**
* 构造方法,传入图片列表和ViewPager实例
* @param images
* @param viewPager
*/
public ViewPagerAdapter(List<ImageView> images, ViewPager viewPager){
this.imageViewList = images;
this.viewPager = viewPager;
}
@Override
public int getCount() {
// return imageViewList.size() * 2;
return Integer.MAX_VALUE;//保证能够无限循环
}
// 4.指定复用的判断逻辑(一般为固定写法)
@Override
public boolean isViewFromObject(View view, Object object) {
boolean a = view == object;
return a;
}
// 2.返回要显示的条目,并创建条目
@Override
public Object instantiateItem(ViewGroup container, int position) {
//container:容器,其实也就是ViewPager
//position:当前要显示的条目的位置
int newPosition = position % imageViewList.size();
ImageView imageView = imageViewList.get(newPosition);
Log.e("fenger", "创建条目:"+position );
//a.将View对象添加到container容器中
container.addView(imageView);
//b.把View对象返回给框架,适配器
return imageView;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
Log.e("fenger","删除条目:"+position);
}
}
这里注意下我在删除条目和创建条目两个地方打印了日志文件,这也是后面报bug的原因处。
viewPager的小知识点就这些,接下来软件跑起来。
基本的循环效果确实出来了,那么滑动一下。同时利用日志一步步调试。
1.应用启动加载viewPager的时候
打印了创建0和1。0是一开始的初始图片,1是预加载的一张图片。在这里我就以为viewPager是回一次性缓存一张,加载一张,一共两张图片。
2.在第一次向右滑的时候
创建了第2张图片,这是因为现在显示的是第1张图片,所以缓存第2张。
3.第一次向左滑
删除了第2张,也就是说只缓存下一张图片。
4,再次向右滑,第三次向右滑
这一次和第二次的结果一样,只是创建了第2张和第3张,删除了第0张。
5,向左滑动
只要到了2以后的所有图片,全部都只要左滑就闪退。
闪退的代码是
//a.将View对象添加到container容器中 container.addView(imageView);
具体原因也大概有解释:这个imageView有个父控件占有了它。
这里可以说明一下,首先一开始我以为只是加载的后一张缓存,但是经过之后的操作会发现,前一张不会被删除,但是回删除更前面的图片的缓存,所以应该是加载中间一张图片,同时缓存前后两张图片。然后就是,假设现在有3张图片(4,5,6,显示的是5,缓存的是4和6),由于
在这里是直接把imageView加载到container里面,但是我已经缓存了三张图片。在向右滑(显示的变为6)的时候,根据日志可以看出,是先删除后创建,所以4里面的图片被拿了出来,创建一个7,并且把图片放到了7里面。从6向左滑的时候,先创建后删除,所以先创建了4,结果发现要用的imageView正在7里面,所以就会出现报错。因此,在图片数量大于等于四的时候,这里应该不会出现这个问题。经过测试成功了。
但是万一必须要用三张轮播图呢?有几个方案:1是舍弃轮播图缓存机制,2是重写ViewPager,在左滑的时候先删除后创建(不确定会不会出问题,没看过源码),3把三张循环一次变成六张,伪装成三张使用(视觉效果相同,我觉得不错),4.不直接addView的时候添加View,而是新建一个view进行添加(这也是我发现原因的起因,同一个view不能被两个地方调用?这个问题之后研究下)