blob: a7bde91ae966540706d648dbcc4b0ee5c319b308 [file] [log] [blame] [view]
davemorrisseya0336982015-02-23 21:49:23 +00001Subsampling Scale Image View
David Morrisseya0bf8022014-05-30 14:08:31 +01002===========================
davemorrisseyb064b0b2013-08-26 09:45:26 -07003
David Morrissey36cc1252015-01-12 21:42:02 +00004Custom image views for Android, designed for photo galleries and displaying huge images (e.g. maps and building plans) without `OutOfMemoryError`s. Includes pinch to zoom, panning, rotation and animation support, and allows easy extension so you can add your own overlays and touch event detection.
David Morrisseyd4191862014-10-18 23:14:07 +01005
David Morrissey36cc1252015-01-12 21:42:02 +00006This library includes two view classes. `SubsamplingScaleImageView` uses subsampling and tiles to support large images - as you zoom in, the low resolution initial image is overlaid with smaller high resolution tiles to avoid holding too much data in memory. It's ideal for displaying large images while allowing you to zoom in to the high resolution details. `ScaleImageView` doesn't subsample images but can display `Bitmap` objects and supports all the same features. To decide which is best for you, see below.
David Morrissey365ccab2014-07-31 00:08:41 +01007
David Morrisseyef41d8a2014-06-10 01:00:09 +01008#### Download the sample app
9
10[![Get it on Google Play](https://developer.android.com/images/brand/en_generic_rgb_wo_60.png)](https://play.google.com/store/apps/details?id=com.davemorrissey.labs.subscaleview.sample)
11
David Morrisseyd4191862014-10-18 23:14:07 +010012#### Hall of fame
David Morrissey0e895c22013-08-26 20:07:35 +010013
David Morrisseyd4191862014-10-18 23:14:07 +010014**Are you using this library in your app? Let me know and I'll add it to this list.**
15
davemorrisseye2d8c462015-02-24 19:21:58 +000016| [![Fourth Mate](https://lh3.ggpht.com/2ALnL-05ILKLwP9U8Dfy7n4iI54OlXeZG-rHf31FP5l8Bup9wws9wnSlyX56ShgzlQ=w100)](https://play.google.com/store/apps/details?id=com.sleetworks.serenity.android) | [![Sync for reddit](https://lh5.ggpht.com/eOcmQUHHFCXM5uiajTkTsak5sIB5eTLKXaKSGGGWi8TJ3edYtqz8EtvjlOto5eFYvoLb=w100)](https://play.google.com/store/apps/details?id=com.laurencedawson.reddit_sync) | [![Journey](https://lh3.ggpht.com/Mz6YqxKsLfVbjYVHj_3nfUxLe5Yvl9W4KO2sKnwud6hZl5mnGitm55PnILT2jx4Hafv6=w100)](https://play.google.com/store/apps/details?id=com.journey.app) |
davemorrisseyc7e461c2015-02-24 15:33:01 +000017|---|---|---|
18| **Fourth Mate** | **Sync for reddit** | **Journal** |
19| [![Clover](https://lh5.ggpht.com/Q8vw6LLyj3AjRev4ID3uvFUxnMp4ca4eBEaPlkupcK7cNn2xtVg-wIxVsKSJ-IIFaUM=w100)](https://play.google.com/store/apps/details?id=org.floens.chan) | [![Tag Gallery](https://lh5.ggpht.com/mKch3_fgPYswBPmZ-qEvp91_fPKdbvN2UubCvUTDqy1sAaLJBzfFYETb-sJgPfCvDg=w100)](https://play.google.com/store/apps/details?id=me.snapdiary.us.taggallery) | [![nycTrans.it](https://lh5.ggpht.com/eDe_bnb2KVXd6fwjJDroWYfEs7Qy-ity93s4LnOwei3S8AGZIeJy8wwmjllt1TKciD4=w100)](https://play.google.com/store/apps/details?id=com.nyctrans.it) |
20| **Clover** | **Tag Gallery** | **nycTrans.it** |
David Morrisseyd4191862014-10-18 23:14:07 +010021
David Morrissey36cc1252015-01-12 21:42:02 +000022## Features
David Morrissey0e895c22013-08-26 20:07:35 +010023
David Morrisseya0bf8022014-05-30 14:08:31 +010024#### Image display
David Morrissey365ccab2014-07-31 00:08:41 +010025
David Morrissey36cc1252015-01-12 21:42:02 +000026* Display images from assets, resources or the file system
David Morrisseya0bf8022014-05-30 14:08:31 +010027* Automatically rotate images from the file system (e.g. the camera or gallery) according to EXIF
28* Manually rotate images in 90° increments
29* Swap images at runtime
David Morrissey36cc1252015-01-12 21:42:02 +000030* Use a custom bitmap decoder
David Morrissey0e895c22013-08-26 20:07:35 +010031
David Morrissey365ccab2014-07-31 00:08:41 +010032*`SubsamplingScaleImageView` only:*
33
34* Display huge images, larger than can be loaded into memory
35* Show high resolution detail on zooming in
36* Tested up to 20,000x13,000px, though larger images are slower
37
David Morrissey36cc1252015-01-12 21:42:02 +000038*These views don't extend `ImageView` and aren't intended as a general purpose replacement for it. They're specialised for the display of photos and other large images, not the display of 9-patches, shapes and the other types of drawable that ImageView supports.*
David Morrissey365ccab2014-07-31 00:08:41 +010039
David Morrisseya0bf8022014-05-30 14:08:31 +010040#### Gesture detection
41* One finger pan
42* Two finger pinch to zoom
davemorrisseya0336982015-02-23 21:49:23 +000043* Quick scale (one finger zoom)
David Morrisseya0bf8022014-05-30 14:08:31 +010044* Pan while zooming
45* Seamless switch between pan and zoom
46* Fling momentum after panning
David Morrissey77096ba2014-06-05 21:22:44 +010047* Double tap to zoom in and out
David Morrissey02ceb3d2014-05-30 20:48:51 +010048* Options to disable pan and/or zoom gestures
David Morrisseya0bf8022014-05-30 14:08:31 +010049
David Morrissey9f3fad12014-06-08 10:16:49 +010050#### Animation
51* Public methods for animating the scale and center
52* Customisable duration and easing
53* Optional uninterruptible animations
54
David Morrisseya0bf8022014-05-30 14:08:31 +010055#### Overridable event detection
56* Supports `OnClickListener` and `OnLongClickListener`
57* Supports interception of events using `GestureDetector` and `OnTouchListener`
58* Extend to add your own gestures
59
60#### Easy integration
61* Use within a `ViewPager` to create a photo gallery
62* Easily restore scale, center and orientation after screen rotation
63* Can be extended to add overlay graphics that move and scale with the image
64* Handles view resizing and `wrap_content` layout
65
David Morrisseya0bf8022014-05-30 14:08:31 +010066#### Limitations
David Morrissey365ccab2014-07-31 00:08:41 +010067* `SubsamplingScaleImageView` requires SDK 10 (Gingerbread).
David Morrissey36cc1252015-01-12 21:42:02 +000068* `SubsamplingScaleImageView` cannot display a `Bitmap` object - the image file needs to be in assets, resources or external storage.
69* `SubsamplingScaleImageView` cannot display grayscale PNGs on Android Lollipop, due to bugs in the skia library and/or BitmapRegionDecoder. Earlier versions of Android also have issues displaying some grayscale PNGs, but not all. I have reported these bugs to Google. For a workaround, see the section on custom bitmap decoders below.
David Morrissey365ccab2014-07-31 00:08:41 +010070* These views do not extend ImageView so attributes including android:tint, android:scaleType and android:src are not supported.
David Morrissey36cc1252015-01-12 21:42:02 +000071* Images stored in resources and assets cannot be rotated based on EXIF, you'll need to do it manually. You probably know the orientation of your own files :-)
David Morrisseya0bf8022014-05-30 14:08:31 +010072
David Morrissey365ccab2014-07-31 00:08:41 +010073## Which view is best?
74
75Use `SubsamplingScaleImageView` if:
76
77* You want to zoom into very large images without losing detail.
78* You need to display images of unknown size e.g. from the camera or gallery.
79* You don't know if the images may be too large to fit in memory on some devices.
80* You need to display images larger than 2048px.
81* You don't need to support devices older than SDK 10.
82
83Use `ScaleImageView` if:
84
85* You know the size of the images you're displaying.
86* You know the images are small enough to fit in memory on all your target devices.
87* Your images are no larger than 2048px, or you are able to scale them down.
88* You need to support devices older than SDK 10.
89
David Morrissey36cc1252015-01-12 21:42:02 +000090## Installation
David Morrisseya0bf8022014-05-30 14:08:31 +010091
David Morrissey2b6553c2015-01-09 00:05:43 +000092Add the library to your app using one of these methods:
David Morrissey64f2ef02014-11-15 15:39:35 +000093
davemorrisseya0336982015-02-23 21:49:23 +000094* Add `com.davemorrissey.labs:subsampling-scale-image-view:2.4.0` as a dependency in your build.gradle file
David Morrissey2b6553c2015-01-09 00:05:43 +000095* *or* download the library aar file from the releases page and add to your app manually
96* *or* clone the project and import the library subproject as a module in your app
David Morrissey64f2ef02014-11-15 15:39:35 +000097* *or* clone the project and copy the resources and classes from `com.davemorrissey.labs.subscaleview` into your project
David Morrisseya0bf8022014-05-30 14:08:31 +010098
99Add the view to your layout XML as shown below. Normally you should set width and height to `match_parent`.
100
101 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
102 android:layout_width="match_parent"
103 android:layout_height="match_parent" >
104
105 <com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
106 android:id="@+id/imageView"
David Morrisseye11ee3e2014-05-30 14:09:41 +0100107 android:layout_width="match_parent"
108 android:layout_height="match_parent"/>
David Morrisseya0bf8022014-05-30 14:08:31 +0100109
110 </RelativeLayout>
111
David Morrissey36cc1252015-01-12 21:42:02 +0000112Now, in your fragment or activity, set the image resource, asset name or file path.
David Morrisseya0bf8022014-05-30 14:08:31 +0100113
114 SubsamplingScaleImageView imageView = (SubsamplingScaleImageView)findViewById(id.imageView);
David Morrissey36cc1252015-01-12 21:42:02 +0000115 imageView.setImageResource(R.drawable.monkey);
116 // ... or ...
David Morrisseya0bf8022014-05-30 14:08:31 +0100117 imageView.setImageAsset("map.png");
118 // ... or ...
David Morrissey36cc1252015-01-12 21:42:02 +0000119 imageView.setImageUri("/sdcard/DCIM/DSCM00123.JPG");
David Morrisseya0bf8022014-05-30 14:08:31 +0100120
121That's it! Keep reading for some more options.
122
123## Define asset name in XML
124
125For a zero code approach to showing an image from your assets, you need to define the custom namespace in your layout.
126
127 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
128 xmlns:ssiv="http://schemas.android.com/apk/res-auto"
129 android:layout_width="match_parent"
130 android:layout_height="match_parent" >
131
132 <com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
133 ssiv:assetName="map.png"
David Morrisseye11ee3e2014-05-30 14:09:41 +0100134 android:layout_width="match_parent"
135 android:layout_height="match_parent"/>
David Morrisseya0bf8022014-05-30 14:08:31 +0100136
137 </RelativeLayout>
138
139**This method doesn't support restoring state after a screen orientation change.**
140
141## Handle screen orientation changes
142
143If you want the current scale, center and orientation to be preserved when the screen is rotated, you can request it from the view's `getState` method, and restore it after rotation, by passing it to the view along with the image asset name or file path. Here's a simple example of how you might do this in a fragment.
144
145 private static final String BUNDLE_STATE = "ImageViewState";
146
147 @Override
148 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
149 View rootView = inflater.inflate(R.layout.my_fragment, container, false);
150
151 ImageViewState imageViewState = null;
152 if (savedInstanceState != null && savedInstanceState.containsKey(BUNDLE_STATE)) {
153 imageViewState = (ImageViewState)savedInstanceState.getSerializable(BUNDLE_STATE);
154 }
155 SubsamplingScaleImageView imageView = (SubsamplingScaleImageView)rootView.findViewById(id.imageView);
156 imageView.setImageAsset("map.png", imageViewState);
157
158 return rootView;
159 }
160
161 @Override
162 public void onSaveInstanceState(Bundle outState) {
163 View rootView = getView();
164 if (rootView != null) {
165 SubsamplingScaleImageView imageView = (SubsamplingScaleImageView)rootView.findViewById(id.imageView);
166 ImageViewState state = imageView.getState();
167 if (state != null) {
168 outState.putSerializable(BUNDLE_STATE, imageView.getState());
169 }
170 }
171 }
172
David Morrissey36cc1252015-01-12 21:42:02 +0000173## Custom bitmap decoders
174
175Android's `BitmapRegionDecoder` class is based on the Skia library. Some users have reported problems with this library, particularly when displaying grayscale JPGs. Unfortunately it seems to be less reliable in recent versions of Android. If you don't control the format of the images displayed in your app (for example, they are user generated content) you may require a more reliable decoder.
176
177To use your own decoder based on a different library, implement the `ImageRegionDecoder` class (do not include a constructor) and enable it using this call:
178
179 imageView.setDecoderClass(MyImageRegionDecoder.class);
180
181As an example, see the [`RapidImageRegionDecoder`](https://github.com/davemorrissey/subsampling-scale-image-view/blob/master/sample/src/com/davemorrissey/labs/subscaleview/sample/imagedisplay/decoders/RapidImageRegionDecoder.java) class, which is based on [RapidDecoder](https://github.com/suckgamony/RapidDecoder). This library is better at decoding grayscale JPG images but does not handle large images as well as `BitmapRegionDecoder` - it is significantly slower and more likely to throw out of memory errors. It appears to be very fast and reliable for PNG images. If you can detect the size and type of an image before displaying it, you can use different decoders for different images to get the best results.
182
183Whenever possible, convert your images to a format Android's Skia library can support, and test with a variety of devices.
184
185## Quality notes
186
187Images are decoded as dithered RGB_565 bitmaps by default, because this requires half as much memory as ARGB_8888. For most
188JPGs you won't notice the difference in quality. If you are displaying large PNGs with alpha channels, Android will probably
189decode them as ARGB_8888, and this may cause `OutOfMemoryError`s. **If possible, remove the alpha channel from PNGs larger than about 2,000x2,000.**
190This allows them to be decoded as RGB_565.
191
David Morrisseya0bf8022014-05-30 14:08:31 +0100192## Extending functionality
193
David Morrissey365ccab2014-07-31 00:08:41 +0100194Take a look at the sample app for examples of classes that overlay graphics on top of the image so that they move and scale with it. `FreehandView` adds event detection, capturing only the touch events it needs so pan and zoom still work normally.
David Morrissey5833e302014-06-06 22:08:14 +0100195
196## About
197
davemorrissey5962f3f2015-01-30 21:55:05 +0000198Copyright 2014 David Morrissey, and licensed under the Apache License, Version 2.0. No attribution is necessary but it's very much appreciated. Star this project if you like it, and send a link to your project on GitHub or app in Google Play if you'd like me to add it to this page.