Lists all image media folders, using
getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI);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 theImageDetailViewImpl
- When applying a display matrix
- Disabled at the start of
PhotoViewMediator.copyPanAndZoom() - Re-enabled in the finally block of above method
- Disabled at the start of
When a user changes pan/zoom on one of the images:
- The
SubsamplingScaleImageViewtriggers the registeredStateChangedListener; this will be aImageViewListener - The
ImageViewListenernotifies all registeredImageViewEventListener- The
CrossViewEventHandlersync's pan/zoom to the other image. There will be 2 instances, one sync'ing top to bottom, and one bottom to top. - The
PanAndZoomStateHolderremembers pan/zoom so those settings can be applied to the next image on load
- The
- The
CrossViewEventHandlercallsPhotoViewMediator.onPanOrZoomChanged() - The
PhotoViewMediatorchecks if synchronization is currently active or not.- If not active, the
PhotoViewMediatorupdates its internal offsets - If active, the
PhotoViewMediatormaps the pan/zoom to the otherImageDetailView
- If not active, the
Relevant points to be aware of:
- SubsamplingScaleImageView does not enforce 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.
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".
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.