Skip to content


Repository files navigation

Basic structure


Lists all image media folders, using


It's important to note that the Media external/internal URI has nothing to do with storage location, but actually refer to internal = private to the app, and external = public folders



  • Has two instances of the layout/view_image_details.xml, along with a widget.Switch to en-/disable sync between the two
  • Manages a list of selected images per folder (see "Data and file storage overview") ** Image selection is NOT a cache, as it cannot be rebuilt from other information

The main actions are

  • Handle image swiping with a ViewPager, disallowing the index currently in use on the other ViewPager
  • Sync zoom/pan to other view
  • Manage image selection
  • Provide actions to share selected images

The "pan and zoom listener" on a PhotoView is toggled in two cases:

  • During loading of a new image ** Disabled when starting to load a new high-resolution image ** Re-enabled once in the onApplyZoomPanMatrix() implementation of the ImageDetailViewImpl
  • When applying a display matrix ** Disabled at the start of PhotoViewMediator.copyPanAndZoom() ** Re-enabled in the finally block of above method

Synchronizing pan and zoom

When a user changes pan/zoom on one of the images:

  1. The SubsamplingScaleImageView triggers the registered StateChangedListener; this will be a ImageViewListener
  2. The ImageViewListener notifies all registered ImageViewEventListener a. The CrossViewEventHandler sync's pan/zoom to the other image. There will be 2 instances, one sync'ing top to bottom, and one bottom to top. b. The ZoomPanRestoreHandler remembers pan/zoom so those settings can be applied to the next image when swiping
  3. The CrossViewEventHandler calls PhotoViewMediator.onPanOrZoomChanged()
  4. The PhotoViewMediator checks if synchronization is currently active or not. a. If not active, the PhotoViewMediator updates its internal offsets b. If active, the PhotoViewMediator maps the pan/zoom to the other ImageDetailView
  5. ImageDetailView receives a setPanAndZoomState() with the new settings

Relevant points to be aware of:

  • SubsamplingScaleImageView does not enfore maxScale when using setScaleAndCenter(), but does so on user interaction


The main actions are

  • Remove images from selection
  • Share images


The only non-Android library used are Glide and Subsampling Scale Image View.

Subsampling Scale Image View

Based on 06. State of the documentation, view state synchronization is based on OnImageEventListener (basic image readiness) and OnStateChangedListener (pan and zoom). The class

ImageViewListener implements SubsamplingScaleImageView.OnImageEventListener, SubsamplingScaleImageView.OnStateChangedListener 

wraps these two, and passes the events to our own ImageViewEventListener, so that multiple listeners can react to event.


SubsamplingScaleImageView sets a GestureDetector internally, which runs if panEnabled is true. The resulting pan has ORIGIN_FLING, thus it's important that ImageViewListener does not restrict event handling on "origin".

Glide / PhotoView / ViewPager and OOM

According to this github issue,

If you want to zoom an image you need to tell Glide to load a bigger image, otherwise it'll be blurry: .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL).

However the ViewPager will load 1 off-screen view per side, ie with a top/bottom view that 6 "original size" images in memory. An image from a EOS 77D is 7-9MB, ie for images alone a heap of around 50MB is required. When scrolling, garbage collection of destroyed views might be a bit delayed, thus increasing required heap size yet even more.

According to this stackoverflow, the standard heap size on an Android device is in the area of 16-24 MB, while android:largeHeap="true" increases that to 48-128MB.

Resources used for this app