0

I want to use Viewpager to show images like Whatsapp shows, when we click on any photo in any person chat then it would open the full screen imageview and we can scroll left or right to see more media items from that chat.

I know they might be using pagination to load more images on demand on both ends of the Viewpager, but thinking of it if we update the list dynamically when user is watching a photo in the Viewpager then won't he notice the updation of the Image Arraylist.

Whatsapp handles it very clearly.

What could be done to add more data to the arraylist dynamically without letting the user know, I am asking for pagination on both left and right ends not only one side.

Ankit Kaswan
  • 35
  • 1
  • 8

1 Answers1

0

I don't know how WhatsApp works, but if you want to have an "infinite" sliding ViewPager you can return a large value in getCount() and use modulo to get the correct image.

Something like:

public class ImageAdapter extends PagerAdapter {
    List<Image> images;
    
    public ImageAdapter(List<Image> images) {
        this.images = images;
    }

    @Override
    public int getCount() {
        return images.size() * 100;
    }

    @Override
    public Object instantiateItem(ViewGroup container, final int position) {
        Image image = images.get(position % images.size());

        // Instantiate your view
    }
}

And when you set your ViewPager set the current item to a large value as well so you can actually scroll both ways

viewPager.setCurrentItem(images.size() * 50)

This will probably have some impact on performance as more views will have to be created.

Option 2

Another option is to add a ViewPager.OnPageChangeListener that changes which item is the active one. You'll still have to return a larger value in getCount(), but it's only three times the size of the list, so it won't be as drastic.

The idea is to scroll to the "same" item which takes you to the middle of the list so it feels like you're infinitely scrolling

In the adapter:

// For a list with 2 items, the list will look like:
// 0 1    0 1    0 1

@Override
public int getCount() {
    return images.size() * 3;
}
// Set the first active item to be in the middle
//      Here
//        |
// 0 1    0 1    0 1
viewPager.setCurrentItem(images.size());

viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
    int currentPos;

    @Override
    public void onPageSelected(int position) {
        currentPos = position;
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        if (state == ViewPager.SCROLL_STATE_IDLE) {
            // Here       Go here    Or here
            //   |          |          |
            // 0 1        0 1        0 1
            if (currentPos == images.size() - 1 || currentPos == adapter.getCount() - 1) {
                // Scrolling with "false" removes the animation and instantly switches 
                // item, so you won't notice the scroll
                viewPager.setCurrentItem(images.size() * 2 - 1, false);
            } else if (currentPos == images.size() * 2 || currentPos == 0) {
                // Or here  Go here    Here
                // |          |          |
                // 0 1        0 1        0 1
                viewPager.setCurrentItem(images.size(), false);
            }
        }
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        // Not implemented
    }
});

The problem with this solution is that it's not guaranteed that onPageScrollStateChanged will be called with SCROLL_STATE_IDLE since you might scroll to a new page before it reaches the idle state, which causes you to scroll past the item where it should switch. If you reach the end/start it will change items, but you will notice you can't scroll for a short time if you're scrolling fast enough.

Neither of these options are perfect, but hopefully it will give you an idea of what is possible.

Håkon Schia
  • 931
  • 1
  • 7
  • 12
  • both of your answers seem fine and first of all I also thought for the option 1, loading the viewpager with a large data and then replacing those positions with actual images on demand, but that's a performance overkill, What about Viewpager 2 and using it with notifyItemInsertedRange()?! – Ankit Kaswan Nov 18 '20 at 09:21
  • I can't straight away see how `notifyItemInsertedRange()` would be used, but I'll think about it and come back to you if I figure something out. Although with ViewPager2 you're using a RecyclerView adapter, so the performance hit for solution 1 might not be as bad as with ViewPager, you could potentially test it out. You could also combine the two solutions with a relatively low amount returned in `getCount()` (say `images.size() * 10`) and make it switch items to the middle everytime it's possible, so it's very likely you will hit switch to the middle (although not guaranteed still) – Håkon Schia Nov 18 '20 at 10:06
  • yup, waiting for your reply after you figure out the facts. – Ankit Kaswan Nov 18 '20 at 11:10
  • but I am suggesting viewpager 2 just because it can use Recyclerview.Adapter under the hood and that would sort out problems I think, without maybe adding extra data beforehand to the data list as you mentioned in option 1, because lazy loading would be quick enough to load data without even letting the user notice it, and then after data is lazy loaded just add it to starting or end of the data list and call Itemrangeinserted() – Ankit Kaswan Nov 18 '20 at 11:14