David Morrissey | a0bf802 | 2014-05-30 14:08:31 +0100 | [diff] [blame] | 1 | Subsampling Zoom Image View |
| 2 | =========================== |
davemorrissey | b064b0b | 2013-08-26 09:45:26 -0700 | [diff] [blame] | 3 | |
David Morrissey | d419186 | 2014-10-18 23:14:07 +0100 | [diff] [blame^] | 4 | Custom image views for Android with pinch to zoom, panning, rotation and animation support, with easy extension so you can add your own overlays and touch event detection. |
| 5 | |
David Morrissey | 5fa74e7 | 2014-07-31 00:11:06 +0100 | [diff] [blame] | 6 | This library includes two classes, `ScaleImageView` and `SubsamplingScaleImageView`. `SubsamplingScaleImageView` is best for large images but doesn't support display of `Bitmap` objects or resources, and `ScaleImageView` supports `Bitmap` objects but not subsampling or large images. To decide which is best for you, see below. |
David Morrissey | 365ccab | 2014-07-31 00:08:41 +0100 | [diff] [blame] | 7 | |
David Morrissey | ef41d8a | 2014-06-10 01:00:09 +0100 | [diff] [blame] | 8 | #### Download the sample app |
| 9 | |
| 10 | [](https://play.google.com/store/apps/details?id=com.davemorrissey.labs.subscaleview.sample) |
| 11 | |
David Morrissey | d419186 | 2014-10-18 23:14:07 +0100 | [diff] [blame^] | 12 | #### Hall of fame |
David Morrissey | 0e895c2 | 2013-08-26 20:07:35 +0100 | [diff] [blame] | 13 | |
David Morrissey | d419186 | 2014-10-18 23:14:07 +0100 | [diff] [blame^] | 14 | **Are you using this library in your app? Let me know and I'll add it to this list.** |
| 15 | |
| 16 | | [](https://play.google.com/store/apps/details?id=com.sleetworks.serenity.android) | |
| 17 | | ------------- | |
| 18 | | **Fourth Mate** | |
| 19 | |
| 20 | #### About |
David Morrissey | 365ccab | 2014-07-31 00:08:41 +0100 | [diff] [blame] | 21 | |
| 22 | `SubsamplingScaleImageView` uses subsampling and tiles to support large images. While zooming in, the |
| 23 | low resolution, full size base layer is overlaid with smaller tiles at least as high resolution as the screen, and |
| 24 | tiles are loaded and discarded during panning to avoid holding too much bitmap data in memory. This is ideal for use in image gallery apps where the size of the images may be large enough to require subsampling, and where |
David Morrissey | 260aec2 | 2013-08-29 23:31:20 +0100 | [diff] [blame] | 25 | pinch to zoom is required to view the high resolution detail. |
| 26 | |
David Morrissey | 365ccab | 2014-07-31 00:08:41 +0100 | [diff] [blame] | 27 | *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 Morrissey | 0e895c2 | 2013-08-26 20:07:35 +0100 | [diff] [blame] | 28 | |
David Morrissey | a0bf802 | 2014-05-30 14:08:31 +0100 | [diff] [blame] | 29 | #### Image display |
David Morrissey | 365ccab | 2014-07-31 00:08:41 +0100 | [diff] [blame] | 30 | |
David Morrissey | a0bf802 | 2014-05-30 14:08:31 +0100 | [diff] [blame] | 31 | * Display images from assets or the file system |
| 32 | * Automatically rotate images from the file system (e.g. the camera or gallery) according to EXIF |
| 33 | * Manually rotate images in 90° increments |
| 34 | * Swap images at runtime |
David Morrissey | 0e895c2 | 2013-08-26 20:07:35 +0100 | [diff] [blame] | 35 | |
David Morrissey | 365ccab | 2014-07-31 00:08:41 +0100 | [diff] [blame] | 36 | *`SubsamplingScaleImageView` only:* |
| 37 | |
| 38 | * Display huge images, larger than can be loaded into memory |
| 39 | * Show high resolution detail on zooming in |
| 40 | * Tested up to 20,000x13,000px, though larger images are slower |
| 41 | |
| 42 | |
David Morrissey | a0bf802 | 2014-05-30 14:08:31 +0100 | [diff] [blame] | 43 | #### Gesture detection |
| 44 | * One finger pan |
| 45 | * Two finger pinch to zoom |
| 46 | * Pan while zooming |
| 47 | * Seamless switch between pan and zoom |
| 48 | * Fling momentum after panning |
David Morrissey | 77096ba | 2014-06-05 21:22:44 +0100 | [diff] [blame] | 49 | * Double tap to zoom in and out |
David Morrissey | 02ceb3d | 2014-05-30 20:48:51 +0100 | [diff] [blame] | 50 | * Options to disable pan and/or zoom gestures |
David Morrissey | a0bf802 | 2014-05-30 14:08:31 +0100 | [diff] [blame] | 51 | |
David Morrissey | 9f3fad1 | 2014-06-08 10:16:49 +0100 | [diff] [blame] | 52 | #### Animation |
| 53 | * Public methods for animating the scale and center |
| 54 | * Customisable duration and easing |
| 55 | * Optional uninterruptible animations |
| 56 | |
David Morrissey | a0bf802 | 2014-05-30 14:08:31 +0100 | [diff] [blame] | 57 | #### Overridable event detection |
| 58 | * Supports `OnClickListener` and `OnLongClickListener` |
| 59 | * Supports interception of events using `GestureDetector` and `OnTouchListener` |
| 60 | * Extend to add your own gestures |
| 61 | |
| 62 | #### Easy integration |
| 63 | * Use within a `ViewPager` to create a photo gallery |
| 64 | * Easily restore scale, center and orientation after screen rotation |
| 65 | * Can be extended to add overlay graphics that move and scale with the image |
| 66 | * Handles view resizing and `wrap_content` layout |
| 67 | |
David Morrissey | a0bf802 | 2014-05-30 14:08:31 +0100 | [diff] [blame] | 68 | #### Limitations |
David Morrissey | 365ccab | 2014-07-31 00:08:41 +0100 | [diff] [blame] | 69 | * `SubsamplingScaleImageView` requires SDK 10 (Gingerbread). |
| 70 | * `SubsamplingScaleImageView` cannot decode an image from resources or display a `Bitmap` object - the image file needs to be in assets or external storage. |
| 71 | * These views do not extend ImageView so attributes including android:tint, android:scaleType and android:src are not supported. |
David Morrissey | a0bf802 | 2014-05-30 14:08:31 +0100 | [diff] [blame] | 72 | * Images stored in assets cannot be rotated based on EXIF, you'll need to do it manually. You probably know the orientation of your own assets :-) |
| 73 | |
David Morrissey | 365ccab | 2014-07-31 00:08:41 +0100 | [diff] [blame] | 74 | ## Which view is best? |
| 75 | |
| 76 | Use `SubsamplingScaleImageView` if: |
| 77 | |
| 78 | * You want to zoom into very large images without losing detail. |
| 79 | * You need to display images of unknown size e.g. from the camera or gallery. |
| 80 | * You don't know if the images may be too large to fit in memory on some devices. |
| 81 | * You need to display images larger than 2048px. |
| 82 | * You don't need to support devices older than SDK 10. |
| 83 | |
| 84 | Use `ScaleImageView` if: |
| 85 | |
| 86 | * You know the size of the images you're displaying. |
| 87 | * You know the images are small enough to fit in memory on all your target devices. |
| 88 | * Your images are no larger than 2048px, or you are able to scale them down. |
| 89 | * You need to support devices older than SDK 10. |
| 90 | |
David Morrissey | 8e742a7 | 2014-06-06 20:23:03 +0100 | [diff] [blame] | 91 | ## Quality notes |
| 92 | |
| 93 | Images are decoded as dithered RGB_565 bitmaps by default, because this requires half as much memory as ARGB_8888. For most |
| 94 | JPGs you won't notice the difference in quality. If you are displaying large PNGs with alpha channels, Android will probably |
| 95 | decode them as ARGB_8888, and this may cause `OutOfMemoryError`s. **If possible, remove the alpha channel from PNGs larger than about 2,000x2,000.** |
| 96 | This allows them to be decoded as RGB_565. |
| 97 | |
David Morrissey | a0bf802 | 2014-05-30 14:08:31 +0100 | [diff] [blame] | 98 | ## Basic setup |
| 99 | |
| 100 | Checkout the project and import the library project as a module in your app. Alternatively you can just copy the classes in `com.davemorrissey.labs.subscaleview` to your project. |
| 101 | |
| 102 | Add the view to your layout XML as shown below. Normally you should set width and height to `match_parent`. |
| 103 | |
| 104 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| 105 | android:layout_width="match_parent" |
| 106 | android:layout_height="match_parent" > |
| 107 | |
| 108 | <com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView |
| 109 | android:id="@+id/imageView" |
David Morrissey | e11ee3e | 2014-05-30 14:09:41 +0100 | [diff] [blame] | 110 | android:layout_width="match_parent" |
| 111 | android:layout_height="match_parent"/> |
David Morrissey | a0bf802 | 2014-05-30 14:08:31 +0100 | [diff] [blame] | 112 | |
| 113 | </RelativeLayout> |
| 114 | |
| 115 | Now, in your fragment or activity, set the image asset name or file path. |
| 116 | |
| 117 | SubsamplingScaleImageView imageView = (SubsamplingScaleImageView)findViewById(id.imageView); |
| 118 | imageView.setImageAsset("map.png"); |
| 119 | // ... or ... |
| 120 | imageView.setImageFile("/sdcard/DCIM/DSCM00123.JPG"); |
| 121 | |
| 122 | That's it! Keep reading for some more options. |
| 123 | |
| 124 | ## Define asset name in XML |
| 125 | |
| 126 | For a zero code approach to showing an image from your assets, you need to define the custom namespace in your layout. |
| 127 | |
| 128 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| 129 | xmlns:ssiv="http://schemas.android.com/apk/res-auto" |
| 130 | android:layout_width="match_parent" |
| 131 | android:layout_height="match_parent" > |
| 132 | |
| 133 | <com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView |
| 134 | ssiv:assetName="map.png" |
David Morrissey | e11ee3e | 2014-05-30 14:09:41 +0100 | [diff] [blame] | 135 | android:layout_width="match_parent" |
| 136 | android:layout_height="match_parent"/> |
David Morrissey | a0bf802 | 2014-05-30 14:08:31 +0100 | [diff] [blame] | 137 | |
| 138 | </RelativeLayout> |
| 139 | |
| 140 | **This method doesn't support restoring state after a screen orientation change.** |
| 141 | |
| 142 | ## Handle screen orientation changes |
| 143 | |
| 144 | If 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. |
| 145 | |
| 146 | private static final String BUNDLE_STATE = "ImageViewState"; |
| 147 | |
| 148 | @Override |
| 149 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { |
| 150 | View rootView = inflater.inflate(R.layout.my_fragment, container, false); |
| 151 | |
| 152 | ImageViewState imageViewState = null; |
| 153 | if (savedInstanceState != null && savedInstanceState.containsKey(BUNDLE_STATE)) { |
| 154 | imageViewState = (ImageViewState)savedInstanceState.getSerializable(BUNDLE_STATE); |
| 155 | } |
| 156 | SubsamplingScaleImageView imageView = (SubsamplingScaleImageView)rootView.findViewById(id.imageView); |
| 157 | imageView.setImageAsset("map.png", imageViewState); |
| 158 | |
| 159 | return rootView; |
| 160 | } |
| 161 | |
| 162 | @Override |
| 163 | public void onSaveInstanceState(Bundle outState) { |
| 164 | View rootView = getView(); |
| 165 | if (rootView != null) { |
| 166 | SubsamplingScaleImageView imageView = (SubsamplingScaleImageView)rootView.findViewById(id.imageView); |
| 167 | ImageViewState state = imageView.getState(); |
| 168 | if (state != null) { |
| 169 | outState.putSerializable(BUNDLE_STATE, imageView.getState()); |
| 170 | } |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | ## Extending functionality |
| 175 | |
David Morrissey | 365ccab | 2014-07-31 00:08:41 +0100 | [diff] [blame] | 176 | Take 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 Morrissey | 5833e30 | 2014-06-06 22:08:14 +0100 | [diff] [blame] | 177 | |
| 178 | ## About |
| 179 | |
| 180 | Copyright 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 to show your gratitude. |
| 181 | |
| 182 | This project started life as a way of showing very large images (e.g. a large building floor plan) with gestures to pan and zoom, with support for extensions that showed overlays (location pins, annotations) aligned with the image. It's grown massively, but for the moment I am keeping everything in one class to prevent subclasses and extensions breaking the assumptions (or violating invariants) on which the class depends. |