Reputation: 427
I am working on an app where there is a list of item and each item consist of a viewpager and below the viewpager you have other widgets (text, button....etc). I have to load url of images from the server and show the images inside my viewpager
Here is my Viewpager adapter:
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);
}
}
Here is a portion of my recycler view item:
<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>
Here is my (partial) viewholder class inside the recyclerview adapter
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);
}
}
In onBindViewHolder this is what i've got (as far as the viewpager is concerned):
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);
Right now my reclerview has 10 items that I load from the server. Thing is, I noticed that when I scroll down say to the 7th item of my list, everything seems fine but when I scroll up back to see say the 1st item of my recycler list, the images found in viewpager of the 1st item tend to disappear and are (temporarily) replaced by the placeholder image i.e the viewpager is completely emptied and I temporarily have the placeholder; if the item is still visible on my screen the images for the viewpager a loaded again from the server.. I picked Glide because it's well known for it's caching capabilities. Moreover, a viewpager can have upto 20 IMAGES !!...
Does anyone have an idea on how to solve this ?? Am I doing anything wrong ?? Any help will be appreciated.....
Upvotes: 4
Views: 10923
Reputation: 1615
The "problem" is that the items of the recyclerview will be recycled. You never save the loaded images. If you scroll down and up, onBindViewHolder
will be called and a new adapter instance will be created so the images will be loaded again and again on every call of onBindViewHolder
.
The solution is to save the adapter object (in a map -> position, adapter) and bind it inside of 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);
You also have to save the position of the viewpager.
Put this as a member:
// 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<>();
Also add this in your recyclerview adapter:
@Override
public void onViewRecycled(YourViewHolderClass holder) {
int position = holder.getAdapterPosition();
viewPageStates.put(position, holder.mViewPagerPhotoView.getCurrentItem());
super.onViewRecycled(holder);
}
Important:
My solution will only work if you don't add / remove / sort items because we save adapter positions. If you also need it you have to modify the maps on add / delete / sort etc
Upvotes: 3
Reputation: 30735
Try to keep ImageAdapter's instance in 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);
}
}
Create init(ArrayList<String> imagePaths)
method in your adapter:
void init(ArrayList<String> paths) {
this.imagePaths = paths;
notifyDataSetChanged();
}
It should help. If it didn't helped then something is wrong in your using of Glide.
Upvotes: 1
Reputation: 189
Well the problem is about image. You said "a viewpager can have upto 20 IMAGES", I don't know what do you mean by upto 20. ViewPager design to be unlimited item unless you load all item at one which doesn't make sense.
So as you describe, I guess the problem is your image is being recycle that why when you scroll up you won't see your image again until Glide load the image and render it again.
So the solution is not quite simple to deal but first let me explain about image and viewpager. Let said you have 10 items in recyclerview adapter and recyclerview loaded only 5 items as necessary. So now the minimum total image that need to be in the memory is 5 x 2 (Each viewpager need at least 2 images) equal 10 images in total. This is huge and it depend on your image, if your image is quite large you will run out of memory and that when Glide will destroy some image to make space for the new coming. Hope you can see the impact of image being loaded.
Now my suggestion:
You can however make use of RecyclerView.ViewCacheExtension to manage your own view recycler but I don't see it would help. Moreover you can also implement cache view for Pager adapter, ViewPager deos not recycler any view so when ViewPager need a View it simply call instantiateItem and View will be created.
Upvotes: 2