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 | 0e895c2 | 2013-08-26 20:07:35 +0100 | [diff] [blame] | 4 | A custom ImageView for Android with pinch to zoom and subsampled tiles to support large images. While zooming in, the |
David Morrissey | a0bf802 | 2014-05-30 14:08:31 +0100 | [diff] [blame] | 5 | low resolution, full size base layer is overlaid with smaller tiles at least as high resolution as the screen, and |
David Morrissey | 260aec2 | 2013-08-29 23:31:20 +0100 | [diff] [blame] | 6 | tiles are loaded and discarded during panning to avoid holding too much bitmap data in memory. |
David Morrissey | 0e895c2 | 2013-08-26 20:07:35 +0100 | [diff] [blame] | 7 | |
David Morrissey | 260aec2 | 2013-08-29 23:31:20 +0100 | [diff] [blame] | 8 | Ideal for use in image gallery apps where the size of the images may be large enough to require subsampling, and where |
| 9 | pinch to zoom is required to view the high resolution detail. |
| 10 | |
David Morrissey | a0bf802 | 2014-05-30 14:08:31 +0100 | [diff] [blame] | 11 | *This view doesn't extend `ImageView` and isn't intended as a general replacement for it. It's specialized for the display of much larger images than `ImageView` can display, so it's perfect for image galleries and displaying images from the camera.* |
David Morrissey | 0e895c2 | 2013-08-26 20:07:35 +0100 | [diff] [blame] | 12 | |
David Morrissey | a0bf802 | 2014-05-30 14:08:31 +0100 | [diff] [blame] | 13 | #### Image display |
| 14 | * Display huge images, larger than can be loaded into memory |
| 15 | * Show high resolution detail on zooming in |
| 16 | * Tested up to 20,000x13,000px, though larger images are slower |
| 17 | * Display images from assets or the file system |
| 18 | * Automatically rotate images from the file system (e.g. the camera or gallery) according to EXIF |
| 19 | * Manually rotate images in 90° increments |
| 20 | * Swap images at runtime |
David Morrissey | 0e895c2 | 2013-08-26 20:07:35 +0100 | [diff] [blame] | 21 | |
David Morrissey | a0bf802 | 2014-05-30 14:08:31 +0100 | [diff] [blame] | 22 | #### Gesture detection |
| 23 | * One finger pan |
| 24 | * Two finger pinch to zoom |
| 25 | * Pan while zooming |
| 26 | * Seamless switch between pan and zoom |
| 27 | * Fling momentum after panning |
| 28 | |
| 29 | #### Overridable event detection |
| 30 | * Supports `OnClickListener` and `OnLongClickListener` |
| 31 | * Supports interception of events using `GestureDetector` and `OnTouchListener` |
| 32 | * Extend to add your own gestures |
| 33 | |
| 34 | #### Easy integration |
| 35 | * Use within a `ViewPager` to create a photo gallery |
| 36 | * Easily restore scale, center and orientation after screen rotation |
| 37 | * Can be extended to add overlay graphics that move and scale with the image |
| 38 | * Handles view resizing and `wrap_content` layout |
| 39 | |
| 40 | #### Coming soon... |
| 41 | * Gradle library structure |
| 42 | * Better support for very tall or wide images |
| 43 | * Double tap to zoom |
| 44 | * Demonstration app |
| 45 | |
| 46 | #### Limitations |
David Morrissey | 0e895c2 | 2013-08-26 20:07:35 +0100 | [diff] [blame] | 47 | * Requires SDK 10 (Gingerbread). |
David Morrissey | a0bf802 | 2014-05-30 14:08:31 +0100 | [diff] [blame] | 48 | * No support for decoding an image from resources - the image file needs to be in assets or external storage. |
David Morrissey | 260aec2 | 2013-08-29 23:31:20 +0100 | [diff] [blame] | 49 | * This view does 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] | 50 | * 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 :-) |
| 51 | |
| 52 | ## Basic setup |
| 53 | |
| 54 | 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. |
| 55 | |
| 56 | Add the view to your layout XML as shown below. Normally you should set width and height to `match_parent`. |
| 57 | |
| 58 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| 59 | android:layout_width="match_parent" |
| 60 | android:layout_height="match_parent" > |
| 61 | |
| 62 | <com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView |
| 63 | android:id="@+id/imageView" |
David Morrissey | e11ee3e | 2014-05-30 14:09:41 +0100 | [diff] [blame^] | 64 | android:layout_width="match_parent" |
| 65 | android:layout_height="match_parent"/> |
David Morrissey | a0bf802 | 2014-05-30 14:08:31 +0100 | [diff] [blame] | 66 | |
| 67 | </RelativeLayout> |
| 68 | |
| 69 | Now, in your fragment or activity, set the image asset name or file path. |
| 70 | |
| 71 | SubsamplingScaleImageView imageView = (SubsamplingScaleImageView)findViewById(id.imageView); |
| 72 | imageView.setImageAsset("map.png"); |
| 73 | // ... or ... |
| 74 | imageView.setImageFile("/sdcard/DCIM/DSCM00123.JPG"); |
| 75 | |
| 76 | That's it! Keep reading for some more options. |
| 77 | |
| 78 | ## Define asset name in XML |
| 79 | |
| 80 | For a zero code approach to showing an image from your assets, you need to define the custom namespace in your layout. |
| 81 | |
| 82 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| 83 | xmlns:ssiv="http://schemas.android.com/apk/res-auto" |
| 84 | android:layout_width="match_parent" |
| 85 | android:layout_height="match_parent" > |
| 86 | |
| 87 | <com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView |
| 88 | ssiv:assetName="map.png" |
David Morrissey | e11ee3e | 2014-05-30 14:09:41 +0100 | [diff] [blame^] | 89 | android:layout_width="match_parent" |
| 90 | android:layout_height="match_parent"/> |
David Morrissey | a0bf802 | 2014-05-30 14:08:31 +0100 | [diff] [blame] | 91 | |
| 92 | </RelativeLayout> |
| 93 | |
| 94 | **This method doesn't support restoring state after a screen orientation change.** |
| 95 | |
| 96 | ## Handle screen orientation changes |
| 97 | |
| 98 | 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. |
| 99 | |
| 100 | private static final String BUNDLE_STATE = "ImageViewState"; |
| 101 | |
| 102 | @Override |
| 103 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { |
| 104 | View rootView = inflater.inflate(R.layout.my_fragment, container, false); |
| 105 | |
| 106 | ImageViewState imageViewState = null; |
| 107 | if (savedInstanceState != null && savedInstanceState.containsKey(BUNDLE_STATE)) { |
| 108 | imageViewState = (ImageViewState)savedInstanceState.getSerializable(BUNDLE_STATE); |
| 109 | } |
| 110 | SubsamplingScaleImageView imageView = (SubsamplingScaleImageView)rootView.findViewById(id.imageView); |
| 111 | imageView.setImageAsset("map.png", imageViewState); |
| 112 | |
| 113 | return rootView; |
| 114 | } |
| 115 | |
| 116 | @Override |
| 117 | public void onSaveInstanceState(Bundle outState) { |
| 118 | View rootView = getView(); |
| 119 | if (rootView != null) { |
| 120 | SubsamplingScaleImageView imageView = (SubsamplingScaleImageView)rootView.findViewById(id.imageView); |
| 121 | ImageViewState state = imageView.getState(); |
| 122 | if (state != null) { |
| 123 | outState.putSerializable(BUNDLE_STATE, imageView.getState()); |
| 124 | } |
| 125 | } |
| 126 | } |
| 127 | |
| 128 | ## Extending functionality |
| 129 | |
| 130 | Take a look at the sample app for a simple implementation showing the view in a `ViewPager` with an `OnClickListener` and an `OnLongClickListener`. I'll add more examples to this soon. |