繁体   English   中英

Recyclerview 适配器内的 Viewpager

[英]Viewpager inside recyclerview adapter

我正在开发一个应用程序,其中有一个项目列表,每个项目由一个 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 项时,一切似乎都很好,但是当我向上滚动查看回收者列表的第一项时,在第一项的 viewpager 中找到的图像倾向于消失并(暂时)被占位符图像替换,即 viewpager 完全清空,我暂时拥有占位符; 如果该项目在我的屏幕上仍然可见,则从服务器再次加载 viewpager 的图像。我选择了 Glide,因为它以其缓存功能而闻名。 此外,一个viewpager最多可以有20张图片!!...

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

“问题”是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);
}

重要的:

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

那么问题出在图像上。 你说“一个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 deos 不会回收任何视图,因此当 ViewPager 需要一个 View 时,它只需调用 instantiateItem 并创建 View。

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

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<String> imagePaths)方法:

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM