【问题标题】:Viewpager inside recyclerview adapterRecyclerview 适配器内的 Viewpager
【发布时间】:2017-09-23 19:22:29
【问题描述】:

我正在开发一个应用程序,其中有一个项目列表,每个项目都由一个 viewpager 组成,在 viewpager 下方还有其他小部件(文本、按钮...等)。我必须从服务器加载图像的 url 并在我的 viewpager 中显示图像

这是我的 Viewpager 适配器:

public class ImageAdapter extends PagerAdapter {

private Context mContext;
private ArrayList<String> imagePaths;
private static final String TAG = ImageAdapter.class.getName();
// constructor
public FullScreenImageAdapter(Context context, ArrayList<String> imagePaths){
    this.mContext = context;
    this.imagePaths = imagePaths;
}

@Override
public int getCount() {
    return null == imagePaths ? 0 : this.imagePaths.size();
}

@Override
public Object instantiateItem(final ViewGroup container, final int position) {

    final LayoutInflater inflater = LayoutInflater.from(mContext);
    final View viewLayout = inflater.inflate(R.layout.full_screen_image, container, false);

    final ImageView image = (ImageView) viewLayout.findViewById(R.id.image);
    final ProgressBar progressBar = (ProgressBar)viewLayout.findViewById(R.id.pBar);

    String url = BuildConfig.URL_API_IMAGES;
    final SparseArray<Object> mBitmapMap = new SparseArray<>();

    Glide.with(mContext)
         .load(url + imagePaths.get(position)).asBitmap()
         .placeholder(R.drawable.car)
         .dontAnimate()
         .into(new BitmapImageViewTarget(image){
             @Override
             public void onLoadStarted(Drawable placeholder){
                 super.onLoadStarted(placeholder);
                 image.setImageDrawable(placeholder);
             }

             @Override
             public void onResourceReady(Bitmap resource,
                     GlideAnimation<? super Bitmap> glideAnimation){
                 super.onResourceReady(resource, glideAnimation);
                 mBitmapMap.put(position, resource);
                 progressBar.setVisibility(View.INVISIBLE);
                 image.setImageBitmap(resource);
             }

             @Override
             public void onLoadFailed(Exception e, Drawable errorDrawable){
                 super.onLoadFailed(e, errorDrawable);
                 progressBar.setVisibility(View.INVISIBLE);
             }
         });

    container.addView(viewLayout);
    return viewLayout;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    try{
        container.removeView((RelativeLayout) object);
        Glide.clear(((View) object).findViewById(R.id.image));
        unbindDrawables((View) object);
        object = null;
    }catch (Exception e) {
        Log.w(TAG, "destroyItem: failed to destroy item and clear it's used resources", e);
    }
}

protected void unbindDrawables(View view) {
    if (view.getBackground() != null) {
        view.getBackground().setCallback(null);
    }
    if (view instanceof ViewGroup) {
        for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
            unbindDrawables(((ViewGroup) view).getChildAt(i));
        }
        ((ViewGroup) view).removeAllViews();
    }
}

@Override
public boolean isViewFromObject(View view, Object object){
    return view == ((RelativeLayout) object);
}

}

这是我的回收站视图项目的一部分:

<RelativeLayout
    android:id="@+id/rl_pictureLayout"
    android:layout_width="match_parent"
    android:layout_marginLeft="@dimen/dimen_5"
    android:layout_marginRight="@dimen/dimen_5"
    android:layout_marginTop="@dimen/dimen_5"
    android:layout_height="@dimen/sliderLayoutHeight">

    <ViewPager
        android:id="@+id/vp_photos"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

    <me.relex.circleindicator.CircleIndicator
        android:id="@+id/indicator"
        android:layout_width="match_parent"
        android:layout_marginBottom="50dp"
        android:layout_height="40dp"
        android:layout_alignParentBottom="true"
        />

    <ImageButton
        android:id="@+id/ib_bookmark"
        android:background="@color/transparent"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"
        android:layout_marginTop="@dimen/dimen_16"
        android:layout_marginRight="@dimen/dimen_16"
        android:layout_marginEnd="@dimen/dimen_16"
        android:src="@drawable/ic_bookmark_border_white_24dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

    <TextView
        android:id="@+id/tv_picture_counter"
        android:textColor="@color/white"
        android:textAllCaps="true"
        android:fontFamily="sans-serif"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_marginLeft="@dimen/dimen_16"
        android:layout_marginStart="@dimen/dimen_16"
        android:layout_marginBottom="@dimen/dimen_24"
        />

    <TextView
        android:id="@+id/tv_damagedPhotos"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/white"
        android:fontFamily="sans-serif"
        android:layout_alignParentBottom="true"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:layout_marginEnd="@dimen/dimen_16"
        android:layout_marginRight="@dimen/dimen_16"
        android:layout_marginBottom="@dimen/dimen_24"
        android:background="@color/transparent"
        android:drawableRight="@drawable/ic_photo_camera_red_24dp"
        android:drawableEnd="@drawable/ic_photo_camera_red_24dp"
        android:drawablePadding="@dimen/dimen_5"
        />
</RelativeLayout>

<RelativeLayout
    android:id="@+id/rl_timerLayout"
    android:layout_below="@id/rl_pictureLayout"
    android:layout_marginTop="@dimen/dimen_5"
    android:layout_alignLeft="@id/rl_pictureLayout"
    android:layout_alignStart="@id/rl_pictureLayout"
    android:layout_alignRight="@id/rl_pictureLayout"
    android:layout_alignEnd="@id/rl_pictureLayout"
    android:layout_marginBottom="@dimen/dimen_10"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/tv_timer"
        style="@style/text_item"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_centerVertical="true"
        android:layout_alignParentLeft="true"
        android:textColor="@color/ColorBlack"
        android:textStyle="bold"
        />

    <TextView
        android:id="@+id/tv_highest_bid"
        style="@style/text_item"
        android:layout_centerInParent="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textStyle="bold"
        />

    <Button
        android:id="@+id/btn_bid"
        style="@style/button"
        android:layout_width="60dp"
        android:layout_height="30dp"
        android:textAllCaps="false"
        android:background="@drawable/curved_border_bgd_red"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"
        android:stateListAnimator="@null"
        android:text="@string/btn_text_bid"
        />
</RelativeLayout>

这是我在 recyclerview 适配器中的(部分)viewholder 类

static class ViewHolder extends RecyclerView.ViewHolder
{
    @BindView(R.id.indicator)
    CircularIndicator mCircleIndicator;
    @BindView(R.id.vp_photos)
    ViewPager mViewPagerPhotoView;

    public ViewHolder(View itemView)
    {
        super(itemView);
        ButterKnife.bind(this, itemView);
    }
}

在 onBindViewHolder 这就是我所拥有的(就 viewpager 而言):

Item item = mDataSet.get(position);
mImageAdapter = new ImageAdapter(mContext, item.getUrlPhotos());
holder.mViewPagerPhotoView.setAdapter(mImageAdapter);
holder.mViewPagerPhotoView.setOffscreenPageLimit(mImageAdapter.getCount() - 1);
holder.mCircleIndicator.setViewPager(holder.mViewPagerPhotoView);

现在我的 reclerview 有 10 个我从服务器加载的项目。事情是,我注意到当我向下滚动到列表的第 7 项时,一切似乎都很好,但是当我向上滚动查看说我的回收站列表的第 1 项时,在第 1 项的 viewpager 中找到的图像往往消失并(暂时)被占位符图像替换,即查看器完全清空,我暂时拥有占位符;如果该项目在我的屏幕上仍然可见,则从服务器再次加载 viewpager 的图像。我选择 Glide 是因为它以缓存功能而闻名。此外,一个浏览器最多可以有 20 个图像 !!...

有没有人知道如何解决这个问题? 我做错什么了吗?? 任何帮助将不胜感激.....

【问题讨论】:

    标签: java android android-recyclerview android-viewpager android-adapter


    【解决方案1】:

    “问题”是recyclerview的项目将被回收。您永远不会保存加载的图像。如果您向下和向上滚动,onBindViewHolder 将被调用并创建一个新的适配器实例,因此图像将在每次调用 onBindViewHolder 时一次又一次地加载。 解决方案是保存适配器对象(在地图 -> 位置,适配器中)并将其绑定到 onBindViewHolder 内。

    ImageAdapter adapter = adaptermap.get(postion);
    int adapterPosition = 0;
    if(adapter == null) {
      adapter = new ImageAdapter(mContext, item.getUrlPhotos());
      adaptermap.put(position, adapter);
      adapterPosition = viewPageStates.get(position);
    }
    
    holder.mViewPagerPhotoView.setAdapter(adapter);
    holder.mViewPagerPhotoView.setCurrentItem(adapterPosition);
    

    您还必须保存 viewpager 的位置。

    将此作为会员:

    // you also can use a SparseIntArray for better performance
    // if the itemcount is less than a few hundret
    public HashMap<Integer, Integer> viewPageStates = new HashMap<>();
    

    在你的 recyclerview 适配器中添加这个:

    @Override
    public void onViewRecycled(YourViewHolderClass holder) {
      int position = holder.getAdapterPosition();
      viewPageStates.put(position, holder.mViewPagerPhotoView.getCurrentItem());
      super.onViewRecycled(holder);
    }
    

    重要:

    我的解决方案只有在您不添加/删除/排序项目时才有效,因为我们保存了适配器位置。如果您还需要它,您必须修改添加/删除/排序等地图

    【讨论】:

    • 根据我的阅读,您的意思是说在 onBindViewHolder 我将不得不实例化两次 ImageAdapter: 从这个语句: ImageAdapter adapter = adaptermap.get(postion);我想adaptermap必须具有ImageAdapter的值......即adapter.put(位置,myImageAdapterInstance)......那么我必须在什么时候初始化myImageAdapterInstance?
    • 上面的代码应该在onBindViewHolder,所以你是对的。首先,您检查适配器是否已经在地图内。如果不是,您必须初始化新适配器。我忘了您必须将新适配器放入地图中。
    • 让我明白这一点:我必须在 onBindViewHolder 中初始化 ImageAdapter 两次。第一次将使用一个空的构造函数:我将 myImageAdapterInstance 放在适配器映射中,如下所示:adaptermap.put(position, new ImageAdapter())...之后(即第二次)我将不得不插入 sn-p 你上面写的??....这就是你的意思吗??
    • 不,您在onBindViewHolder 中为每个位置初始化图像适配器一次。要理解我的代码 sn-p:使用adaptermap.get(postion),您要求适配器映射获取给定位置的图像适配器。但是,如果此适配器尚未初始化,它会返回 null。然后你必须初始化适配器并将其添加到适配器映射中。因此,当您滚动并onBindViewHolder 再次询问适配器映射时,它将返回您之前已经初始化的正确适配器。
    • 实际上,@beeb,我尝试了您的方法,并且每当我在回收站视图中垂直滚动时,图像都不再加载......但是我无法滑动浏览器图像......不管我的recyclerview中的项目位置..在我的viewpager中,每当我尝试滑动时,我都会不断回到vp中的第一张图片......即Iswipe到vp中的第二个项目;图像在几秒钟内出现,然后通过一些“魔法”,vp 当前位置始终设置为 0,vp 反弹回第一个项目
    【解决方案2】:

    尝试将 ImageAdapter 的实例保留在 ViewHolder 中:

    static class ViewHolder extends RecyclerView.ViewHolder
    {
        @BindView(R.id.indicator)
        CircularIndicator mCircleIndicator;
        @BindView(R.id.vp_photos)
        ViewPager mViewPagerPhotoView;
        ImageAdapter mAdapter;
    
        public ViewHolder(View itemView)
        {
            super(itemView);
            ButterKnife.bind(this, itemView);
    
            mAdapter = new ImageAdapter();
            mViewPagerPhotoView.setAdapter(mAdapter);
        }
    
        //Add bind() method which will be called in onBindViewHolder()
        public void bind(ArrayList<String> imagePaths) {
            //... maybe some other bindings
    
            mAdapter.init(imagePaths);
            mViewPagerPhotoView.setOffscreenPageLimit(mAdapter.getCount() - 1);
            mCircleIndicator.setViewPager(mViewPagerPhotoView);
        }
    }
    

    在您的适配器中创建init(ArrayList&lt;String&gt; imagePaths) 方法:

    void init(ArrayList<String> paths) {
          this.imagePaths = paths;
          notifyDataSetChanged();
    }
    

    应该有帮助。如果它没有帮助,那么您在使用 Glide 时出现了问题。

    【讨论】:

    • 我试过这个,但是一旦加载项目,CircleIndicator 就不可见了......我的意思是指示器实际上是不可见的......而且我在尝试在图片上滑动时崩溃了item.. 这是跟踪:java.lang.NullPointerException:尝试在 me.relex.circleindicator.CircleIndicator.onPageSelected(CircleIndicator. java:157)
    • 我删除了 CircularIndicator(不是因为它导致崩溃......而是因为我们最后不需要它)..但是,viewpager 滑动不起作用.. .每当我滑动到第二个项目时,viewpager 就会回到第一个项目
    • 最后我让它工作了!!!....事实证明我不需要在我的视图上设置任何键盘映射..我按照下面的 @ vsatkh 配置了一个缓存模块并结合您将 ImageAdapter 的实例保留在 ViewHolder 中的想法解决了我的问题.....谢谢队友
    • 嗨@ClaudeHangui 我知道这已经很长时间了,但我希望你能记住你的解决方案。那么你能分享你的代码吗?我有同样的问题,我几天都无法解决。
    【解决方案3】:

    问题在于图像。你说“一个viewpager最多可以有20张图片”,我不知道你说的最多20张是什么意思。ViewPager设计为无限项,除非你在一个没有意义的情况下加载所有项。

    正如您所描述的,我想问题是您的图像正在被回收,为什么当您向上滚动时您将不会再次看到您的图像,直到 Glide 加载图像并再次渲染它。

    所以解决方案不是很简单,但首先让我解释一下图像和浏览器。假设您在 recyclerview 适配器中有 10 个项目,而 recyclerview 仅根据需要加载了 5 个项目。所以现在需要在内存中的最小总图像是 5 x 2(每个 viewpager 至少需要 2 个图像)等于总共 10 个图像。这是巨大的,它取决于你的图像,如果你的图像非常大,你将耗尽内存,当 Glide 会破坏一些图像为新的图像腾出空间。希望您能看到加载图像的影响。

    现在我的建议:

    1. 确保您的 Glide 具有磁盘缓存配置,这样只要磁盘缓存仍有可用空间,您的图像就不会从它加载的服务器加载。
    2. 缩小图像以适合您的视图。示例原始图像是 1440x320,而您的视图只有 1080x240,那么最好将 1080x240 加载到内存中。或者,您甚至可以将其设置为比 1080x240 小一点。
    3. 让 Glide 用 builder.setMemoryCache(new LruResourceCache(yourSizeInBytes)) 控制缓存

    但是,您可以使用 RecyclerView.ViewCacheExtension 来管理您自己的视图回收器,但我认为它没有帮助。此外,您还可以为 Pager 适配器实现缓存视图,ViewPager 不会回收任何视图,因此当 ViewPager 需要视图时,只需调用 instantiateItem 即可创建视图。

    【讨论】:

    • 我确实为 Glide 设置了磁盘缓存配置,现在一切正常...加载后的图像在滚动时不再消失..Thx
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-29
    • 2019-12-13
    相关资源
    最近更新 更多