├── .classpath ├── .gitignore ├── .project ├── AndroidManifest.xml ├── LICENSE ├── README.md ├── ZoomImageViewGooglePlay.jpg ├── gen └── com │ └── tenthbit │ └── zoomimageview │ ├── BuildConfig.java │ └── R.java ├── libs └── android-support-v4.jar ├── lint.xml ├── project.properties ├── res ├── drawable-nodpi │ ├── image1.jpg │ ├── image2.jpg │ ├── image3.jpg │ ├── image4.jpg │ ├── image5.jpg │ ├── image6.jpg │ ├── image7.jpg │ ├── image8.jpg │ └── image9.jpg ├── drawable-xhdpi │ └── ic_launcher.png ├── layout │ ├── one_image.xml │ └── view_pager.xml ├── menu │ └── main_menu.xml └── values │ └── strings.xml └── src └── com └── tenthbit ├── view ├── ZoomImageView.java └── ZoomViewPager.java └── zoomimageview └── sample ├── MainActivity.java ├── OneImageSampleActivity.java └── ViewPagerSampleActivity.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | 15 | # Local configuration file (sdk path, etc) 16 | local.properties 17 | 18 | # Eclipse project files 19 | .classpath 20 | .project 21 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | ZoomImageView 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Google Play Image](/ZoomImageViewGooglePlay.jpg) 2 | 3 | ZoomImageView is an Android ImageView with zoom functionality, all in one class. 4 | 5 | Works great with ViewPager, example provided. 6 | 7 | Used in [Couple](https://play.google.com/store/apps/details?id=com.tenthbit.juliet "Couple") app. 8 | 9 | 10 | Features 11 | - 12 | 13 | * Multi-touch pinch zoom 14 | * Double-tap zoom 15 | * Smooth zooming and scrolling, using animation thread 16 | * All in one class 17 | * Easy to put in a scrolling parent, like the ViewPager (example provided) 18 | 19 | 20 | 21 | Examples 22 | - 23 | This project provides two examples: 24 | 25 | * Simple use of ImageViewZoom in [OneImageSampleActivity](https://github.com/tenthbitinc/ZoomImageView/blob/master/src/com/tenthbit/zoomimageview/sample/OneImageSampleActivity.java "OneImageSampleActivity") 26 | * Use of ImageViewZoom with ViewPager in [ViewPagerSampleActivity](https://github.com/tenthbitinc/ZoomImageView/blob/master/src/com/tenthbit/zoomimageview/sample/ViewPagerSampleActivity.java "ViewPagerSampleActivity") 27 | 28 | Support 29 | - 30 | Android 2.2 (API 8) and newer. 31 | 32 | Origin 33 | - 34 | This project is based on the excellent [PhotoView](https://github.com/chrisbanes/PhotoView "PhotoView") by Chris Banes. 35 | Many thanks to Chris for writing it. 36 | 37 | Improvements over PhotoView: 38 | - The library has been simplified from seven files to just one. 39 | - Fixed pinch zoom accuracy, also with multiple fingers. 40 | - Some parts of code have been removed, rewritten and overall simplified. 41 | 42 | License 43 | - 44 | 45 | Copyright 2013 TenthBit Inc. 46 | 47 | Licensed under the Apache License, Version 2.0 (the "License"); 48 | you may not use this file except in compliance with the License. 49 | You may obtain a copy of the License at 50 | 51 | http://www.apache.org/licenses/LICENSE-2.0 52 | 53 | Unless required by applicable law or agreed to in writing, software 54 | distributed under the License is distributed on an "AS IS" BASIS, 55 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 56 | See the License for the specific language governing permissions and 57 | limitations under the License. 58 | 59 | -------------------------------------------------------------------------------- /ZoomImageViewGooglePlay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenthbitinc/ZoomImageView/a4618f74ce7d14286fb22baae362f8745585e9bd/ZoomImageViewGooglePlay.jpg -------------------------------------------------------------------------------- /gen/com/tenthbit/zoomimageview/BuildConfig.java: -------------------------------------------------------------------------------- 1 | /** Automatically generated file. DO NOT MODIFY */ 2 | package com.tenthbit.zoomimageview; 3 | 4 | public final class BuildConfig { 5 | public final static boolean DEBUG = true; 6 | } -------------------------------------------------------------------------------- /gen/com/tenthbit/zoomimageview/R.java: -------------------------------------------------------------------------------- 1 | /* AUTO-GENERATED FILE. DO NOT MODIFY. 2 | * 3 | * This class was automatically generated by the 4 | * aapt tool from the resource data it found. It 5 | * should not be modified by hand. 6 | */ 7 | 8 | package com.tenthbit.zoomimageview; 9 | 10 | public final class R { 11 | public static final class attr { 12 | } 13 | public static final class drawable { 14 | public static final int ic_launcher=0x7f020000; 15 | public static final int image1=0x7f020001; 16 | public static final int image2=0x7f020002; 17 | public static final int image3=0x7f020003; 18 | public static final int image4=0x7f020004; 19 | public static final int image5=0x7f020005; 20 | public static final int image6=0x7f020006; 21 | public static final int image7=0x7f020007; 22 | public static final int image8=0x7f020008; 23 | public static final int image9=0x7f020009; 24 | } 25 | public static final class id { 26 | public static final int menu_scale_fit_center=0x7f060003; 27 | public static final int menu_scale_fit_end=0x7f060005; 28 | public static final int menu_scale_fit_start=0x7f060004; 29 | public static final int menu_scale_fit_xy=0x7f060006; 30 | public static final int menu_scale_scale_center=0x7f060007; 31 | public static final int menu_scale_scale_center_crop=0x7f060008; 32 | public static final int menu_scale_scale_center_inside=0x7f060009; 33 | public static final int menu_zoom_toggle=0x7f060002; 34 | public static final int zoomImageView=0x7f060000; 35 | public static final int zoomViewPager=0x7f060001; 36 | } 37 | public static final class layout { 38 | public static final int one_image=0x7f030000; 39 | public static final int view_pager=0x7f030001; 40 | } 41 | public static final class menu { 42 | public static final int main_menu=0x7f050000; 43 | } 44 | public static final class string { 45 | public static final int app_name=0x7f040000; 46 | public static final int menu_scale_center=0x7f040007; 47 | public static final int menu_scale_center_crop=0x7f040009; 48 | public static final int menu_scale_center_inside=0x7f040008; 49 | public static final int menu_scale_fit_center=0x7f040003; 50 | public static final int menu_scale_fit_end=0x7f040005; 51 | public static final int menu_scale_fit_start=0x7f040004; 52 | public static final int menu_scale_fit_xy=0x7f040006; 53 | public static final int menu_zoom_disable=0x7f040002; 54 | public static final int menu_zoom_enable=0x7f040001; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenthbitinc/ZoomImageView/a4618f74ce7d14286fb22baae362f8745585e9bd/libs/android-support-v4.jar -------------------------------------------------------------------------------- /lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-17 15 | -------------------------------------------------------------------------------- /res/drawable-nodpi/image1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenthbitinc/ZoomImageView/a4618f74ce7d14286fb22baae362f8745585e9bd/res/drawable-nodpi/image1.jpg -------------------------------------------------------------------------------- /res/drawable-nodpi/image2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenthbitinc/ZoomImageView/a4618f74ce7d14286fb22baae362f8745585e9bd/res/drawable-nodpi/image2.jpg -------------------------------------------------------------------------------- /res/drawable-nodpi/image3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenthbitinc/ZoomImageView/a4618f74ce7d14286fb22baae362f8745585e9bd/res/drawable-nodpi/image3.jpg -------------------------------------------------------------------------------- /res/drawable-nodpi/image4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenthbitinc/ZoomImageView/a4618f74ce7d14286fb22baae362f8745585e9bd/res/drawable-nodpi/image4.jpg -------------------------------------------------------------------------------- /res/drawable-nodpi/image5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenthbitinc/ZoomImageView/a4618f74ce7d14286fb22baae362f8745585e9bd/res/drawable-nodpi/image5.jpg -------------------------------------------------------------------------------- /res/drawable-nodpi/image6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenthbitinc/ZoomImageView/a4618f74ce7d14286fb22baae362f8745585e9bd/res/drawable-nodpi/image6.jpg -------------------------------------------------------------------------------- /res/drawable-nodpi/image7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenthbitinc/ZoomImageView/a4618f74ce7d14286fb22baae362f8745585e9bd/res/drawable-nodpi/image7.jpg -------------------------------------------------------------------------------- /res/drawable-nodpi/image8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenthbitinc/ZoomImageView/a4618f74ce7d14286fb22baae362f8745585e9bd/res/drawable-nodpi/image8.jpg -------------------------------------------------------------------------------- /res/drawable-nodpi/image9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenthbitinc/ZoomImageView/a4618f74ce7d14286fb22baae362f8745585e9bd/res/drawable-nodpi/image9.jpg -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tenthbitinc/ZoomImageView/a4618f74ce7d14286fb22baae362f8745585e9bd/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/layout/one_image.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /res/layout/view_pager.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /res/menu/main_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 13 | 17 | 21 | 25 | 29 | 33 | 34 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ZoomImageView 4 | Enable Zoom 5 | Disable Zoom 6 | Change to FIT_CENTER 7 | Change to FIT_START 8 | Change to FIT_END 9 | Change to FIT_XY 10 | Change to CENTER 11 | Change to CENTER_INSIDE 12 | Change to CENTER_CROP 13 | 14 | -------------------------------------------------------------------------------- /src/com/tenthbit/view/ZoomImageView.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2013 Tomasz Zawada 3 | * 4 | * Based on the excellent PhotoView by Chris Banes: 5 | * https://github.com/chrisbanes/PhotoView 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | *******************************************************************************/ 19 | package com.tenthbit.view; 20 | 21 | import android.annotation.TargetApi; 22 | import android.content.Context; 23 | import android.graphics.Bitmap; 24 | import android.graphics.Matrix; 25 | import android.graphics.Matrix.ScaleToFit; 26 | import android.graphics.RectF; 27 | import android.graphics.drawable.Drawable; 28 | import android.net.Uri; 29 | import android.os.Build; 30 | import android.os.Build.VERSION; 31 | import android.os.Build.VERSION_CODES; 32 | import android.util.AttributeSet; 33 | import android.view.GestureDetector; 34 | import android.view.MotionEvent; 35 | import android.view.ScaleGestureDetector; 36 | import android.view.ScaleGestureDetector.OnScaleGestureListener; 37 | import android.view.VelocityTracker; 38 | import android.view.View; 39 | import android.view.ViewConfiguration; 40 | import android.view.ViewTreeObserver; 41 | import android.widget.ImageView; 42 | import android.widget.OverScroller; 43 | import android.widget.Scroller; 44 | 45 | public class ZoomImageView extends ImageView implements View.OnTouchListener, 46 | ViewTreeObserver.OnGlobalLayoutListener { 47 | 48 | /** 49 | * Interface definition for a callback to be invoked when the Photo is 50 | * tapped with a single tap. 51 | * 52 | * @author tomasz.zawada@gmail.com 53 | */ 54 | public static interface OnPhotoTapListener { 55 | /** 56 | * A callback to receive where the user taps on a photo. You will only 57 | * receive a callback if the user taps on the actual photo, tapping on 58 | * 'whitespace' will be ignored. 59 | * 60 | * @param view 61 | * - View the user tapped. 62 | * @param x 63 | * - where the user tapped from the of the Drawable, as 64 | * percentage of the Drawable width. 65 | * @param y 66 | * - where the user tapped from the top of the Drawable, as 67 | * percentage of the Drawable height. 68 | */ 69 | public void onPhotoTap(View view, float x, float y); 70 | } 71 | 72 | /** 73 | * Interface definition for a callback to be invoked when the ImageView is 74 | * tapped with a single tap. 75 | * 76 | * @author tomasz.zawada@gmail.com 77 | */ 78 | public static interface OnViewTapListener { 79 | /** 80 | * A callback to receive where the user taps on a ImageView. You will 81 | * receive a callback if the user taps anywhere on the view, tapping on 82 | * 'whitespace' will not be ignored. 83 | * 84 | * @param view 85 | * - View the user tapped. 86 | * @param x 87 | * - where the user tapped from the left of the View. 88 | * @param y 89 | * - where the user tapped from the top of the View. 90 | */ 91 | public void onViewTap(View view, float x, float y); 92 | } 93 | 94 | /** 95 | * 96 | * The MultiGestureDetector manages the multi-finger pinch zoom, pan and tap 97 | * 98 | * @author tomasz.zawada@gmail.com 99 | * 100 | */ 101 | private class MultiGestureDetector extends GestureDetector.SimpleOnGestureListener implements 102 | OnScaleGestureListener { 103 | private final ScaleGestureDetector scaleGestureDetector; 104 | private final GestureDetector gestureDetector; 105 | 106 | private VelocityTracker velocityTracker; 107 | private boolean isDragging; 108 | 109 | private float lastTouchX; 110 | private float lastTouchY; 111 | private float lastPointerCount; 112 | private final float scaledTouchSlop; 113 | private final float scaledMinimumFlingVelocity; 114 | 115 | public MultiGestureDetector(Context context) { 116 | scaleGestureDetector = new ScaleGestureDetector(context, this); 117 | 118 | gestureDetector = new GestureDetector(context, this); 119 | gestureDetector.setOnDoubleTapListener(this); 120 | 121 | final ViewConfiguration configuration = ViewConfiguration.get(context); 122 | scaledMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity(); 123 | scaledTouchSlop = configuration.getScaledTouchSlop(); 124 | } 125 | 126 | public boolean isScaling() { 127 | return scaleGestureDetector.isInProgress(); 128 | } 129 | 130 | public boolean onTouchEvent(MotionEvent event) { 131 | if (gestureDetector.onTouchEvent(event)) { 132 | return true; 133 | } 134 | 135 | scaleGestureDetector.onTouchEvent(event); 136 | 137 | /* 138 | * Get the center x, y of all the pointers 139 | */ 140 | float x = 0, y = 0; 141 | final int pointerCount = event.getPointerCount(); 142 | for (int i = 0; i < pointerCount; i++) { 143 | x += event.getX(i); 144 | y += event.getY(i); 145 | } 146 | x = x / pointerCount; 147 | y = y / pointerCount; 148 | 149 | /* 150 | * If the pointer count has changed cancel the drag 151 | */ 152 | if (pointerCount != lastPointerCount) { 153 | isDragging = false; 154 | if (velocityTracker != null) { 155 | velocityTracker.clear(); 156 | } 157 | lastTouchX = x; 158 | lastTouchY = y; 159 | } 160 | lastPointerCount = pointerCount; 161 | 162 | switch (event.getAction()) { 163 | case MotionEvent.ACTION_DOWN: 164 | if (velocityTracker == null) { 165 | velocityTracker = VelocityTracker.obtain(); 166 | } else { 167 | velocityTracker.clear(); 168 | } 169 | velocityTracker.addMovement(event); 170 | 171 | lastTouchX = x; 172 | lastTouchY = y; 173 | isDragging = false; 174 | break; 175 | 176 | case MotionEvent.ACTION_MOVE: { 177 | final float dx = x - lastTouchX, dy = y - lastTouchY; 178 | 179 | if (isDragging == false) { 180 | // Use Pythagoras to see if drag length is larger than 181 | // touch slop 182 | isDragging = Math.sqrt((dx * dx) + (dy * dy)) >= scaledTouchSlop; 183 | } 184 | 185 | if (isDragging) { 186 | if (getDrawable() != null) { 187 | suppMatrix.postTranslate(dx, dy); 188 | checkAndDisplayMatrix(); 189 | 190 | /** 191 | * Here we decide whether to let the ImageView's 192 | * parent to start taking over the touch event. 193 | * 194 | * First we check whether this function is enabled. 195 | * We never want the parent to take over if we're 196 | * scaling. We then check the edge we're on, and the 197 | * direction of the scroll (i.e. if we're pulling 198 | * against the edge, aka 'overscrolling', let the 199 | * parent take over). 200 | */ 201 | if (allowParentInterceptOnEdge && !multiGestureDetector.isScaling()) { 202 | if ((scrollEdge == EDGE_BOTH) 203 | || ((scrollEdge == EDGE_LEFT) && (dx >= 1f)) 204 | || ((scrollEdge == EDGE_RIGHT) && (dx <= -1f))) { 205 | 206 | if (getParent() != null) { 207 | getParent().requestDisallowInterceptTouchEvent(false); 208 | } 209 | } 210 | } 211 | } 212 | 213 | lastTouchX = x; 214 | lastTouchY = y; 215 | 216 | if (velocityTracker != null) { 217 | velocityTracker.addMovement(event); 218 | } 219 | } 220 | break; 221 | } 222 | case MotionEvent.ACTION_UP: { 223 | if (isDragging) { 224 | lastTouchX = x; 225 | lastTouchY = y; 226 | 227 | // Compute velocity within the last 1000ms 228 | if (velocityTracker != null) { 229 | velocityTracker.addMovement(event); 230 | velocityTracker.computeCurrentVelocity(1000); 231 | 232 | final float vX = velocityTracker.getXVelocity(), vY = velocityTracker 233 | .getYVelocity(); 234 | 235 | // If the velocity is greater than minVelocity perform 236 | // a fling 237 | if ((Math.max(Math.abs(vX), Math.abs(vY)) >= scaledMinimumFlingVelocity) 238 | && (getDrawable() != null)) { 239 | currentFlingRunnable = new FlingRunnable(getContext()); 240 | currentFlingRunnable.fling(getWidth(), getHeight(), (int) -vX, 241 | (int) -vY); 242 | post(currentFlingRunnable); 243 | } 244 | } 245 | } 246 | break; 247 | } 248 | case MotionEvent.ACTION_CANCEL: 249 | lastPointerCount = 0; 250 | if (velocityTracker != null) { 251 | velocityTracker.recycle(); 252 | velocityTracker = null; 253 | } 254 | break; 255 | } 256 | 257 | return true; 258 | } 259 | 260 | @Override 261 | public boolean onScale(ScaleGestureDetector detector) { 262 | float scale = getScale(); 263 | float scaleFactor = detector.getScaleFactor(); 264 | 265 | if ((getDrawable() != null) 266 | && (!(((scale >= maxScale) && (scaleFactor > 1f)) || ((scale <= 0.75) && (scaleFactor < 1f))))) { 267 | suppMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), 268 | detector.getFocusY()); 269 | checkAndDisplayMatrix(); 270 | } 271 | 272 | return true; 273 | } 274 | 275 | @Override 276 | public boolean onScaleBegin(ScaleGestureDetector detector) { 277 | return true; 278 | } 279 | 280 | @Override 281 | public void onScaleEnd(ScaleGestureDetector detector) { 282 | } 283 | 284 | @Override 285 | public boolean onDoubleTap(MotionEvent event) { 286 | try { 287 | float scale = getScale(); 288 | float x = event.getX(); 289 | float y = event.getY(); 290 | 291 | if (scale < midScale) { 292 | post(new AnimatedZoomRunnable(scale, midScale, x, y)); 293 | } else if ((scale >= midScale) && (scale < maxScale)) { 294 | post(new AnimatedZoomRunnable(scale, maxScale, x, y)); 295 | } else { 296 | post(new AnimatedZoomRunnable(scale, minScale, x, y)); 297 | } 298 | } catch (Exception e) { 299 | // Can sometimes happen when getX() and getY() is called 300 | } 301 | 302 | return true; 303 | } 304 | 305 | @Override 306 | public boolean onDoubleTapEvent(MotionEvent event) { 307 | // Wait for the confirmed onDoubleTap() instead 308 | return false; 309 | } 310 | 311 | @Override 312 | public boolean onSingleTapConfirmed(MotionEvent event) { 313 | if (photoTapListener != null) { 314 | final RectF displayRect = getDisplayRect(); 315 | 316 | if (null != displayRect) { 317 | final float x = event.getX(), y = event.getY(); 318 | 319 | // Check to see if the user tapped on the photo 320 | if (displayRect.contains(x, y)) { 321 | 322 | float xResult = (x - displayRect.left) / displayRect.width(); 323 | float yResult = (y - displayRect.top) / displayRect.height(); 324 | 325 | photoTapListener.onPhotoTap(ZoomImageView.this, xResult, yResult); 326 | return true; 327 | } 328 | } 329 | } 330 | if (viewTapListener != null) { 331 | viewTapListener.onViewTap(ZoomImageView.this, event.getX(), event.getY()); 332 | } 333 | 334 | return false; 335 | } 336 | 337 | @Override 338 | public void onLongPress(MotionEvent e) { 339 | if (longClickListener != null) { 340 | longClickListener.onLongClick(ZoomImageView.this); 341 | } 342 | } 343 | } 344 | 345 | /** 346 | * 347 | * The ScrollerProxy encapsulates the Scroller and OverScroller classes. 348 | * OverScroller is available since API 9. 349 | * 350 | * @author tomasz.zawada@gmail.com 351 | * 352 | */ 353 | @TargetApi(Build.VERSION_CODES.GINGERBREAD) 354 | private class ScrollerProxy { 355 | 356 | private boolean isOld; 357 | private Object scroller; 358 | 359 | public ScrollerProxy(Context context) { 360 | if (VERSION.SDK_INT < VERSION_CODES.GINGERBREAD) { 361 | isOld = true; 362 | scroller = new Scroller(context); 363 | } else { 364 | isOld = false; 365 | scroller = new OverScroller(context); 366 | } 367 | } 368 | 369 | public boolean computeScrollOffset() { 370 | return isOld ? ((Scroller) scroller).computeScrollOffset() : ((OverScroller) scroller) 371 | .computeScrollOffset(); 372 | } 373 | 374 | public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, 375 | int minY, int maxY, int overX, int overY) { 376 | 377 | if (isOld) { 378 | ((Scroller) scroller).fling(startX, startY, velocityX, velocityY, minX, maxX, minY, 379 | maxY); 380 | } else { 381 | ((OverScroller) scroller).fling(startX, startY, velocityX, velocityY, minX, maxX, 382 | minY, maxY, overX, overY); 383 | } 384 | } 385 | 386 | public void forceFinished(boolean finished) { 387 | if (isOld) { 388 | ((Scroller) scroller).forceFinished(finished); 389 | } else { 390 | ((OverScroller) scroller).forceFinished(finished); 391 | } 392 | } 393 | 394 | public int getCurrX() { 395 | return isOld ? ((Scroller) scroller).getCurrX() : ((OverScroller) scroller).getCurrX(); 396 | } 397 | 398 | public int getCurrY() { 399 | return isOld ? ((Scroller) scroller).getCurrY() : ((OverScroller) scroller).getCurrY(); 400 | } 401 | } 402 | 403 | private static final int EDGE_NONE = -1; 404 | private static final int EDGE_LEFT = 0; 405 | private static final int EDGE_RIGHT = 1; 406 | private static final int EDGE_BOTH = 2; 407 | 408 | public static final float DEFAULT_MAX_SCALE = 3.0f; 409 | public static final float DEFAULT_MID_SCALE = 1.75f; 410 | public static final float DEFAULT_MIN_SCALE = 1f; 411 | 412 | private float minScale = DEFAULT_MIN_SCALE; 413 | private float midScale = DEFAULT_MID_SCALE; 414 | private float maxScale = DEFAULT_MAX_SCALE; 415 | 416 | private boolean allowParentInterceptOnEdge = true; 417 | 418 | private MultiGestureDetector multiGestureDetector; 419 | 420 | // These are set so we don't keep allocating them on the heap 421 | private final Matrix baseMatrix = new Matrix(); 422 | private final Matrix drawMatrix = new Matrix(); 423 | private final Matrix suppMatrix = new Matrix(); 424 | private final RectF displayRect = new RectF(); 425 | private final float[] matrixValues = new float[9]; 426 | 427 | // Listeners 428 | private OnPhotoTapListener photoTapListener; 429 | private OnViewTapListener viewTapListener; 430 | private OnLongClickListener longClickListener; 431 | 432 | private int top, right, bottom, left; 433 | private FlingRunnable currentFlingRunnable; 434 | private int scrollEdge = EDGE_BOTH; 435 | 436 | private boolean isZoomEnabled; 437 | private ScaleType scaleType = ScaleType.FIT_CENTER; 438 | 439 | public ZoomImageView(Context context) { 440 | this(context, null); 441 | } 442 | 443 | public ZoomImageView(Context context, AttributeSet attr) { 444 | this(context, attr, 0); 445 | } 446 | 447 | public ZoomImageView(Context context, AttributeSet attr, int defStyle) { 448 | super(context, attr, defStyle); 449 | 450 | super.setScaleType(ScaleType.MATRIX); 451 | 452 | setOnTouchListener(this); 453 | 454 | multiGestureDetector = new MultiGestureDetector(context); 455 | 456 | setIsZoomEnabled(true); 457 | } 458 | 459 | /** 460 | * Gets the Display Rectangle of the currently displayed Drawable. The 461 | * Rectangle is relative to this View and includes all scaling and 462 | * translations. 463 | * 464 | * @return - RectF of Displayed Drawable 465 | */ 466 | public final RectF getDisplayRect() { 467 | checkMatrixBounds(); 468 | return getDisplayRect(getDisplayMatrix()); 469 | } 470 | 471 | /** 472 | * @return The current minimum scale level. What this value represents 473 | * depends on the current {@link android.widget.ImageView.ScaleType} 474 | */ 475 | public float getMinScale() { 476 | return minScale; 477 | } 478 | 479 | /** 480 | * Sets the minimum scale level. What this value represents depends on the 481 | * current {@link android.widget.ImageView.ScaleType}. 482 | */ 483 | public void setMinScale(float minScale) { 484 | checkZoomLevels(minScale, midScale, maxScale); 485 | this.minScale = minScale; 486 | } 487 | 488 | /** 489 | * @return The current middle scale level. What this value represents 490 | * depends on the current {@link android.widget.ImageView.ScaleType} 491 | */ 492 | public float getMidScale() { 493 | return midScale; 494 | } 495 | 496 | /** 497 | * Sets the middle scale level. What this value represents depends on the 498 | * current {@link android.widget.ImageView.ScaleType}. 499 | */ 500 | public void setMidScale(float midScale) { 501 | checkZoomLevels(minScale, midScale, maxScale); 502 | this.midScale = midScale; 503 | } 504 | 505 | /** 506 | * @return The current maximum scale level. What this value represents 507 | * depends on the current {@link android.widget.ImageView.ScaleType} 508 | */ 509 | public float getMaxScale() { 510 | return maxScale; 511 | } 512 | 513 | /** 514 | * Sets the maximum scale level. What this value represents depends on the 515 | * current {@link android.widget.ImageView.ScaleType}. 516 | */ 517 | public void setMaxScale(float maxScale) { 518 | checkZoomLevels(minScale, midScale, maxScale); 519 | this.maxScale = maxScale; 520 | } 521 | 522 | /** 523 | * Returns the current scale value 524 | * 525 | * @return float - current scale value 526 | */ 527 | public final float getScale() { 528 | suppMatrix.getValues(matrixValues); 529 | return matrixValues[Matrix.MSCALE_X]; 530 | } 531 | 532 | /** 533 | * Return the current scale type in use by the ImageView. 534 | */ 535 | @Override 536 | public final ScaleType getScaleType() { 537 | return scaleType; 538 | } 539 | 540 | /** 541 | * Controls how the image should be resized or moved to match the size of 542 | * the ImageView. Any scaling or panning will happen within the confines of 543 | * this {@link android.widget.ImageView.ScaleType}. 544 | * 545 | * @param scaleType 546 | * - The desired scaling mode. 547 | */ 548 | @Override 549 | public final void setScaleType(ScaleType scaleType) { 550 | if (scaleType == ScaleType.MATRIX) { 551 | throw new IllegalArgumentException(scaleType.name() 552 | + " is not supported in ZoomImageView"); 553 | } 554 | 555 | if (scaleType != this.scaleType) { 556 | this.scaleType = scaleType; 557 | update(); 558 | } 559 | } 560 | 561 | /** 562 | * Returns true if the ZoomImageView is set to allow zooming of Photos. 563 | * 564 | * @return true if the ZoomImageView allows zooming. 565 | */ 566 | public final boolean isZoomEnabled() { 567 | return isZoomEnabled; 568 | } 569 | 570 | /** 571 | * Allows you to enable/disable the zoom functionality on the ImageView. 572 | * When disable the ImageView reverts to using the FIT_CENTER matrix. 573 | * 574 | * @param isZoomEnabled 575 | * - Whether the zoom functionality is enabled. 576 | */ 577 | public final void setIsZoomEnabled(boolean isZoomEnabled) { 578 | this.isZoomEnabled = isZoomEnabled; 579 | update(); 580 | } 581 | 582 | /** 583 | * Whether to allow the ImageView's parent to intercept the touch event when 584 | * the photo is scroll to it's horizontal edge. 585 | */ 586 | public void setAllowParentInterceptOnEdge(boolean allowParentInterceptOnEdge) { 587 | this.allowParentInterceptOnEdge = allowParentInterceptOnEdge; 588 | } 589 | 590 | @Override 591 | public void setImageBitmap(Bitmap bitmap) { 592 | super.setImageBitmap(bitmap); 593 | update(); 594 | } 595 | 596 | @Override 597 | public void setImageDrawable(Drawable drawable) { 598 | super.setImageDrawable(drawable); 599 | update(); 600 | } 601 | 602 | @Override 603 | public void setImageResource(int resId) { 604 | super.setImageResource(resId); 605 | update(); 606 | } 607 | 608 | @Override 609 | public void setImageURI(Uri uri) { 610 | super.setImageURI(uri); 611 | update(); 612 | } 613 | 614 | /** 615 | * Register a callback to be invoked when the Photo displayed by this view 616 | * is long-pressed. 617 | * 618 | * @param listener 619 | * - Listener to be registered. 620 | */ 621 | @Override 622 | public final void setOnLongClickListener(OnLongClickListener listener) { 623 | longClickListener = listener; 624 | } 625 | 626 | /** 627 | * Register a callback to be invoked when the Photo displayed by this View 628 | * is tapped with a single tap. 629 | * 630 | * @param listener 631 | * - Listener to be registered. 632 | */ 633 | public final void setOnPhotoTapListener(OnPhotoTapListener listener) { 634 | photoTapListener = listener; 635 | } 636 | 637 | /** 638 | * Register a callback to be invoked when the View is tapped with a single 639 | * tap. 640 | * 641 | * @param listener 642 | * - Listener to be registered. 643 | */ 644 | public final void setOnViewTapListener(OnViewTapListener listener) { 645 | viewTapListener = listener; 646 | } 647 | 648 | @Override 649 | public final void onGlobalLayout() { 650 | if (isZoomEnabled) { 651 | final int top = getTop(); 652 | final int right = getRight(); 653 | final int bottom = getBottom(); 654 | final int left = getLeft(); 655 | 656 | /** 657 | * We need to check whether the ImageView's bounds have changed. 658 | * This would be easier if we targeted API 11+ as we could just use 659 | * View.OnLayoutChangeListener. Instead we have to replicate the 660 | * work, keeping track of the ImageView's bounds and then checking 661 | * if the values change. 662 | */ 663 | if ((top != this.top) || (bottom != this.bottom) || (left != this.left) 664 | || (right != this.right)) { 665 | // Update our base matrix, as the bounds have changed 666 | updateBaseMatrix(getDrawable()); 667 | 668 | // Update values as something has changed 669 | this.top = top; 670 | this.right = right; 671 | this.bottom = bottom; 672 | this.left = left; 673 | } 674 | } 675 | } 676 | 677 | @Override 678 | public final boolean onTouch(View v, MotionEvent ev) { 679 | boolean handled = false; 680 | 681 | if (isZoomEnabled) { 682 | switch (ev.getAction()) { 683 | case MotionEvent.ACTION_DOWN: 684 | // First, disable the Parent from intercepting the touch 685 | // event 686 | if (v.getParent() != null) { 687 | v.getParent().requestDisallowInterceptTouchEvent(true); 688 | } 689 | 690 | // If we're flinging, and the user presses down, cancel 691 | // fling 692 | if (currentFlingRunnable != null) { 693 | currentFlingRunnable.cancelFling(); 694 | currentFlingRunnable = null; 695 | } 696 | break; 697 | 698 | case MotionEvent.ACTION_CANCEL: 699 | case MotionEvent.ACTION_UP: 700 | // If the user has zoomed less than min scale, zoom back 701 | // to min scale 702 | if (getScale() < minScale) { 703 | RectF rect = getDisplayRect(); 704 | if (null != rect) { 705 | v.post(new AnimatedZoomRunnable(getScale(), minScale, rect.centerX(), 706 | rect.centerY())); 707 | handled = true; 708 | } 709 | } 710 | break; 711 | } 712 | 713 | // Finally, try the scale/drag/tap detector 714 | if ((multiGestureDetector != null) && multiGestureDetector.onTouchEvent(ev)) { 715 | handled = true; 716 | } 717 | } 718 | 719 | return handled; 720 | } 721 | 722 | @Override 723 | protected void onAttachedToWindow() { 724 | super.onAttachedToWindow(); 725 | 726 | getViewTreeObserver().addOnGlobalLayoutListener(this); 727 | } 728 | 729 | @SuppressWarnings("deprecation") 730 | @Override 731 | protected void onDetachedFromWindow() { 732 | super.onDetachedFromWindow(); 733 | 734 | getViewTreeObserver().removeGlobalOnLayoutListener(this); 735 | } 736 | 737 | protected Matrix getDisplayMatrix() { 738 | drawMatrix.set(baseMatrix); 739 | drawMatrix.postConcat(suppMatrix); 740 | return drawMatrix; 741 | } 742 | 743 | private final void update() { 744 | if (isZoomEnabled) { 745 | super.setScaleType(ScaleType.MATRIX); 746 | updateBaseMatrix(getDrawable()); 747 | } else { 748 | resetMatrix(); 749 | } 750 | } 751 | 752 | /** 753 | * Helper method that simply checks the Matrix, and then displays the result 754 | */ 755 | private void checkAndDisplayMatrix() { 756 | checkMatrixBounds(); 757 | setImageMatrix(getDisplayMatrix()); 758 | } 759 | 760 | private void checkMatrixBounds() { 761 | final RectF rect = getDisplayRect(getDisplayMatrix()); 762 | if (null == rect) { 763 | return; 764 | } 765 | 766 | final float height = rect.height(), width = rect.width(); 767 | float deltaX = 0, deltaY = 0; 768 | 769 | final int viewHeight = getHeight(); 770 | if (height <= viewHeight) { 771 | switch (scaleType) { 772 | case FIT_START: 773 | deltaY = -rect.top; 774 | break; 775 | case FIT_END: 776 | deltaY = viewHeight - height - rect.top; 777 | break; 778 | default: 779 | deltaY = ((viewHeight - height) / 2) - rect.top; 780 | break; 781 | } 782 | } else if (rect.top > 0) { 783 | deltaY = -rect.top; 784 | } else if (rect.bottom < viewHeight) { 785 | deltaY = viewHeight - rect.bottom; 786 | } 787 | 788 | final int viewWidth = getWidth(); 789 | if (width <= viewWidth) { 790 | switch (scaleType) { 791 | case FIT_START: 792 | deltaX = -rect.left; 793 | break; 794 | case FIT_END: 795 | deltaX = viewWidth - width - rect.left; 796 | break; 797 | default: 798 | deltaX = ((viewWidth - width) / 2) - rect.left; 799 | break; 800 | } 801 | scrollEdge = EDGE_BOTH; 802 | } else if (rect.left > 0) { 803 | scrollEdge = EDGE_LEFT; 804 | deltaX = -rect.left; 805 | } else if (rect.right < viewWidth) { 806 | deltaX = viewWidth - rect.right; 807 | scrollEdge = EDGE_RIGHT; 808 | } else { 809 | scrollEdge = EDGE_NONE; 810 | } 811 | 812 | // Finally actually translate the matrix 813 | suppMatrix.postTranslate(deltaX, deltaY); 814 | } 815 | 816 | /** 817 | * Helper method that maps the supplied Matrix to the current Drawable 818 | * 819 | * @param matrix 820 | * - Matrix to map Drawable against 821 | * @return RectF - Displayed Rectangle 822 | */ 823 | private RectF getDisplayRect(Matrix matrix) { 824 | Drawable d = getDrawable(); 825 | if (null != d) { 826 | displayRect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); 827 | matrix.mapRect(displayRect); 828 | return displayRect; 829 | } 830 | 831 | return null; 832 | } 833 | 834 | /** 835 | * Resets the Matrix back to FIT_CENTER, and then displays it.s 836 | */ 837 | private void resetMatrix() { 838 | suppMatrix.reset(); 839 | setImageMatrix(getDisplayMatrix()); 840 | checkMatrixBounds(); 841 | } 842 | 843 | /** 844 | * Calculate Matrix for FIT_CENTER 845 | * 846 | * @param d 847 | * - Drawable being displayed 848 | */ 849 | private void updateBaseMatrix(Drawable d) { 850 | if (null == d) { 851 | return; 852 | } 853 | 854 | final float viewWidth = getWidth(); 855 | final float viewHeight = getHeight(); 856 | final int drawableWidth = d.getIntrinsicWidth(); 857 | final int drawableHeight = d.getIntrinsicHeight(); 858 | 859 | baseMatrix.reset(); 860 | 861 | final float widthScale = viewWidth / drawableWidth; 862 | final float heightScale = viewHeight / drawableHeight; 863 | 864 | if (scaleType == ScaleType.CENTER) { 865 | baseMatrix.postTranslate((viewWidth - drawableWidth) / 2F, 866 | (viewHeight - drawableHeight) / 2F); 867 | 868 | } else if (scaleType == ScaleType.CENTER_CROP) { 869 | float scale = Math.max(widthScale, heightScale); 870 | baseMatrix.postScale(scale, scale); 871 | baseMatrix.postTranslate((viewWidth - (drawableWidth * scale)) / 2F, 872 | (viewHeight - (drawableHeight * scale)) / 2F); 873 | 874 | } else if (scaleType == ScaleType.CENTER_INSIDE) { 875 | float scale = Math.min(1.0f, Math.min(widthScale, heightScale)); 876 | baseMatrix.postScale(scale, scale); 877 | baseMatrix.postTranslate((viewWidth - (drawableWidth * scale)) / 2F, 878 | (viewHeight - (drawableHeight * scale)) / 2F); 879 | 880 | } else { 881 | RectF mTempSrc = new RectF(0, 0, drawableWidth, drawableHeight); 882 | RectF mTempDst = new RectF(0, 0, viewWidth, viewHeight); 883 | 884 | switch (scaleType) { 885 | case FIT_CENTER: 886 | baseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.CENTER); 887 | break; 888 | 889 | case FIT_START: 890 | baseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.START); 891 | break; 892 | 893 | case FIT_END: 894 | baseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.END); 895 | break; 896 | 897 | case FIT_XY: 898 | baseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.FILL); 899 | break; 900 | 901 | default: 902 | break; 903 | } 904 | } 905 | 906 | resetMatrix(); 907 | } 908 | 909 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 910 | private void postOnAnimation(View view, Runnable runnable) { 911 | if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { 912 | view.postOnAnimation(runnable); 913 | } else { 914 | view.postDelayed(runnable, 16); 915 | } 916 | } 917 | 918 | private void checkZoomLevels(float minZoom, float midZoom, float maxZoom) { 919 | if (minZoom >= midZoom) { 920 | throw new IllegalArgumentException("MinZoom should be less than MidZoom"); 921 | } else if (midZoom >= maxZoom) { 922 | throw new IllegalArgumentException("MidZoom should be less than MaxZoom"); 923 | } 924 | } 925 | 926 | private class AnimatedZoomRunnable implements Runnable { 927 | // These are 'postScale' values, means they're compounded each iteration 928 | static final float ANIMATION_SCALE_PER_ITERATION_IN = 1.07f; 929 | static final float ANIMATION_SCALE_PER_ITERATION_OUT = 0.93f; 930 | 931 | private final float focalX, focalY; 932 | private final float targetZoom; 933 | private final float deltaScale; 934 | 935 | public AnimatedZoomRunnable(final float currentZoom, final float targetZoom, 936 | final float focalX, final float focalY) { 937 | this.targetZoom = targetZoom; 938 | this.focalX = focalX; 939 | this.focalY = focalY; 940 | 941 | if (currentZoom < targetZoom) { 942 | deltaScale = ANIMATION_SCALE_PER_ITERATION_IN; 943 | } else { 944 | deltaScale = ANIMATION_SCALE_PER_ITERATION_OUT; 945 | } 946 | } 947 | 948 | public void run() { 949 | suppMatrix.postScale(deltaScale, deltaScale, focalX, focalY); 950 | checkAndDisplayMatrix(); 951 | 952 | final float currentScale = getScale(); 953 | 954 | if (((deltaScale > 1f) && (currentScale < targetZoom)) 955 | || ((deltaScale < 1f) && (targetZoom < currentScale))) { 956 | // We haven't hit our target scale yet, so post ourselves 957 | // again 958 | postOnAnimation(ZoomImageView.this, this); 959 | 960 | } else { 961 | // We've scaled past our target zoom, so calculate the 962 | // necessary scale so we're back at target zoom 963 | final float delta = targetZoom / currentScale; 964 | suppMatrix.postScale(delta, delta, focalX, focalY); 965 | checkAndDisplayMatrix(); 966 | } 967 | } 968 | } 969 | 970 | private class FlingRunnable implements Runnable { 971 | private final ScrollerProxy scroller; 972 | private int currentX, currentY; 973 | 974 | public FlingRunnable(Context context) { 975 | scroller = new ScrollerProxy(context); 976 | } 977 | 978 | public void cancelFling() { 979 | scroller.forceFinished(true); 980 | } 981 | 982 | public void fling(int viewWidth, int viewHeight, int velocityX, int velocityY) { 983 | final RectF rect = getDisplayRect(); 984 | if (null == rect) { 985 | return; 986 | } 987 | 988 | final int startX = Math.round(-rect.left); 989 | final int minX, maxX, minY, maxY; 990 | 991 | if (viewWidth < rect.width()) { 992 | minX = 0; 993 | maxX = Math.round(rect.width() - viewWidth); 994 | } else { 995 | minX = maxX = startX; 996 | } 997 | 998 | final int startY = Math.round(-rect.top); 999 | if (viewHeight < rect.height()) { 1000 | minY = 0; 1001 | maxY = Math.round(rect.height() - viewHeight); 1002 | } else { 1003 | minY = maxY = startY; 1004 | } 1005 | 1006 | currentX = startX; 1007 | currentY = startY; 1008 | 1009 | // If we actually can move, fling the scroller 1010 | if ((startX != maxX) || (startY != maxY)) { 1011 | scroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0); 1012 | } 1013 | } 1014 | 1015 | @Override 1016 | public void run() { 1017 | if (scroller.computeScrollOffset()) { 1018 | final int newX = scroller.getCurrX(); 1019 | final int newY = scroller.getCurrY(); 1020 | 1021 | suppMatrix.postTranslate(currentX - newX, currentY - newY); 1022 | setImageMatrix(getDisplayMatrix()); 1023 | 1024 | currentX = newX; 1025 | currentY = newY; 1026 | 1027 | // Post On animation 1028 | postOnAnimation(ZoomImageView.this, this); 1029 | } 1030 | } 1031 | } 1032 | } 1033 | -------------------------------------------------------------------------------- /src/com/tenthbit/view/ZoomViewPager.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2013 Tomasz Zawada 3 | * 4 | * Based on the excellent PhotoView by Chris Banes: 5 | * https://github.com/chrisbanes/PhotoView 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | *******************************************************************************/ 19 | package com.tenthbit.view; 20 | 21 | import android.content.Context; 22 | import android.support.v4.view.ViewPager; 23 | import android.util.AttributeSet; 24 | import android.view.MotionEvent; 25 | 26 | /** 27 | * Hacky fix for Issue #4 and 28 | * http://code.google.com/p/android/issues/detail?id=18990 29 | * 30 | * ScaleGestureDetector seems to mess up the touch events, which means that 31 | * ViewGroups which make use of onInterceptTouchEvent throw a lot of 32 | * IllegalArgumentException: pointerIndex out of range. 33 | * 34 | * Also the android.support.v4.view.MotionEventCompatEclair.getX() throws some 35 | * java.lang.ArrayIndexOutOfBoundsException exceptions which seems like a bug. 36 | */ 37 | public class ZoomViewPager extends ViewPager { 38 | 39 | public ZoomViewPager(Context context) { 40 | super(context); 41 | } 42 | 43 | public ZoomViewPager(final Context context, final AttributeSet attrs) { 44 | super(context, attrs); 45 | } 46 | 47 | @Override 48 | public boolean onInterceptTouchEvent(MotionEvent event) { 49 | try { 50 | return super.onInterceptTouchEvent(event); 51 | } catch (Exception e) { 52 | return false; 53 | } 54 | } 55 | 56 | @Override 57 | public boolean onTouchEvent(MotionEvent event) { 58 | try { 59 | return super.onTouchEvent(event); 60 | } catch (Exception e) { 61 | return false; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/com/tenthbit/zoomimageview/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2013 Tomasz Zawada 3 | * 4 | * Based on the excellent PhotoView by Chris Banes: 5 | * https://github.com/chrisbanes/PhotoView 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | *******************************************************************************/ 19 | package com.tenthbit.zoomimageview.sample; 20 | 21 | import android.annotation.TargetApi; 22 | import android.app.ListActivity; 23 | import android.content.Intent; 24 | import android.graphics.Color; 25 | import android.graphics.drawable.ColorDrawable; 26 | import android.os.Build; 27 | import android.os.Build.VERSION; 28 | import android.os.Build.VERSION_CODES; 29 | import android.os.Bundle; 30 | import android.view.View; 31 | import android.widget.ArrayAdapter; 32 | import android.widget.ListView; 33 | 34 | public class MainActivity extends ListActivity { 35 | 36 | public static final String[] options = { 37 | "One Image Sample", "ViewPager Sample" 38 | }; 39 | 40 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 41 | @Override 42 | protected void onCreate(Bundle savedInstanceState) { 43 | super.onCreate(savedInstanceState); 44 | 45 | if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { 46 | getActionBar().setBackgroundDrawable(new ColorDrawable(Color.GRAY)); 47 | // Note: if you use ActionBarSherlock use here getSupportActionBar() 48 | } 49 | 50 | setListAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, options)); 51 | 52 | getListView().setBackgroundColor(0xFF404040); 53 | } 54 | 55 | @Override 56 | protected void onListItemClick(ListView listView, View view, int position, long id) { 57 | Intent intent; 58 | 59 | switch (position) { 60 | default: 61 | case 0: 62 | intent = new Intent(this, OneImageSampleActivity.class); 63 | break; 64 | case 1: 65 | intent = new Intent(this, ViewPagerSampleActivity.class); 66 | break; 67 | } 68 | 69 | startActivity(intent); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/com/tenthbit/zoomimageview/sample/OneImageSampleActivity.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2013 Tomasz Zawada 3 | * 4 | * Based on the excellent PhotoView by Chris Banes: 5 | * https://github.com/chrisbanes/PhotoView 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | *******************************************************************************/ 19 | package com.tenthbit.zoomimageview.sample; 20 | 21 | import android.annotation.TargetApi; 22 | import android.app.Activity; 23 | import android.graphics.drawable.ColorDrawable; 24 | import android.os.Build; 25 | import android.os.Build.VERSION; 26 | import android.os.Build.VERSION_CODES; 27 | import android.os.Bundle; 28 | import android.view.Menu; 29 | import android.view.MenuItem; 30 | import android.view.View; 31 | import android.view.Window; 32 | import android.view.WindowManager; 33 | import android.widget.ImageView.ScaleType; 34 | import android.widget.Toast; 35 | 36 | import com.tenthbit.view.ZoomImageView; 37 | import com.tenthbit.view.ZoomImageView.OnPhotoTapListener; 38 | import com.tenthbit.zoomimageview.R; 39 | 40 | public class OneImageSampleActivity extends Activity { 41 | 42 | static final String PHOTO_TAP_TOAST_STRING = "Photo Tap! X: %.2f %% Y:%.2f %%"; 43 | 44 | private ZoomImageView zoomImageView; 45 | 46 | private Toast toast; 47 | 48 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 49 | @Override 50 | public void onCreate(Bundle savedInstanceState) { 51 | super.onCreate(savedInstanceState); 52 | 53 | /* 54 | * Use full screen window and translucent action bar 55 | */ 56 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 57 | WindowManager.LayoutParams.FLAG_FULLSCREEN); 58 | getWindow().setBackgroundDrawable(new ColorDrawable(0xFF000000)); 59 | if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { 60 | getWindow().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY); 61 | getActionBar().setBackgroundDrawable(new ColorDrawable(0x88000000)); 62 | // Note: if you use ActionBarSherlock use here getSupportActionBar() 63 | } 64 | 65 | setContentView(R.layout.one_image); 66 | 67 | zoomImageView = (ZoomImageView) findViewById(R.id.zoomImageView); 68 | 69 | zoomImageView.setImageDrawable(getResources().getDrawable(R.drawable.image1)); 70 | 71 | // Lets attach some listeners (optional) 72 | zoomImageView.setOnPhotoTapListener(new PhotoTapListener()); 73 | } 74 | 75 | @Override 76 | public boolean onCreateOptionsMenu(Menu menu) { 77 | getMenuInflater().inflate(R.menu.main_menu, menu); 78 | return super.onCreateOptionsMenu(menu); 79 | } 80 | 81 | @Override 82 | public boolean onPrepareOptionsMenu(Menu menu) { 83 | MenuItem zoomToggle = menu.findItem(R.id.menu_zoom_toggle); 84 | zoomToggle.setTitle(zoomImageView.isZoomEnabled() ? R.string.menu_zoom_disable 85 | : R.string.menu_zoom_enable); 86 | 87 | return super.onPrepareOptionsMenu(menu); 88 | } 89 | 90 | @Override 91 | public boolean onOptionsItemSelected(MenuItem item) { 92 | switch (item.getItemId()) { 93 | case R.id.menu_zoom_toggle: 94 | zoomImageView.setIsZoomEnabled(!zoomImageView.isZoomEnabled()); 95 | return true; 96 | 97 | case R.id.menu_scale_fit_center: 98 | zoomImageView.setScaleType(ScaleType.FIT_CENTER); 99 | return true; 100 | 101 | case R.id.menu_scale_fit_start: 102 | zoomImageView.setScaleType(ScaleType.FIT_START); 103 | return true; 104 | 105 | case R.id.menu_scale_fit_end: 106 | zoomImageView.setScaleType(ScaleType.FIT_END); 107 | return true; 108 | 109 | case R.id.menu_scale_fit_xy: 110 | zoomImageView.setScaleType(ScaleType.FIT_XY); 111 | return true; 112 | 113 | case R.id.menu_scale_scale_center: 114 | zoomImageView.setScaleType(ScaleType.CENTER); 115 | return true; 116 | 117 | case R.id.menu_scale_scale_center_crop: 118 | zoomImageView.setScaleType(ScaleType.CENTER_CROP); 119 | return true; 120 | 121 | case R.id.menu_scale_scale_center_inside: 122 | zoomImageView.setScaleType(ScaleType.CENTER_INSIDE); 123 | return true; 124 | } 125 | 126 | return super.onOptionsItemSelected(item); 127 | } 128 | 129 | private class PhotoTapListener implements OnPhotoTapListener { 130 | @Override 131 | public void onPhotoTap(View view, float x, float y) { 132 | float xPercentage = x * 100f; 133 | float yPercentage = y * 100f; 134 | 135 | if (toast != null) { 136 | toast.cancel(); 137 | } 138 | 139 | toast = Toast.makeText(OneImageSampleActivity.this, 140 | String.format(PHOTO_TAP_TOAST_STRING, xPercentage, yPercentage), 141 | Toast.LENGTH_SHORT); 142 | toast.show(); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/com/tenthbit/zoomimageview/sample/ViewPagerSampleActivity.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2013 Tomasz Zawada 3 | * 4 | * Based on the excellent PhotoView by Chris Banes: 5 | * https://github.com/chrisbanes/PhotoView 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | *******************************************************************************/ 19 | package com.tenthbit.zoomimageview.sample; 20 | 21 | import android.annotation.TargetApi; 22 | import android.app.Activity; 23 | import android.graphics.Bitmap; 24 | import android.graphics.BitmapFactory; 25 | import android.graphics.drawable.BitmapDrawable; 26 | import android.graphics.drawable.ColorDrawable; 27 | import android.os.Build; 28 | import android.os.Build.VERSION; 29 | import android.os.Build.VERSION_CODES; 30 | import android.os.Bundle; 31 | import android.os.Handler; 32 | import android.os.HandlerThread; 33 | import android.support.v4.view.PagerAdapter; 34 | import android.support.v4.view.ViewPager; 35 | import android.view.View; 36 | import android.view.ViewGroup; 37 | import android.view.ViewGroup.LayoutParams; 38 | import android.view.Window; 39 | import android.view.WindowManager; 40 | import android.widget.ImageView; 41 | 42 | import com.tenthbit.view.ZoomImageView; 43 | import com.tenthbit.zoomimageview.R; 44 | 45 | public class ViewPagerSampleActivity extends Activity { 46 | 47 | private static class SamplePagerAdapter extends PagerAdapter { 48 | private Handler backgroundHandler; 49 | 50 | public SamplePagerAdapter() { 51 | // Create a background thread and a handler for it 52 | final HandlerThread backgroundThread = new HandlerThread("backgroundThread"); 53 | backgroundThread.start(); 54 | backgroundHandler = new Handler(backgroundThread.getLooper()); 55 | } 56 | 57 | private static int[] drawables = { 58 | R.drawable.image1, R.drawable.image2, R.drawable.image3, R.drawable.image4, 59 | R.drawable.image5, R.drawable.image6, R.drawable.image7, R.drawable.image8, 60 | R.drawable.image9 61 | }; 62 | 63 | @Override 64 | public int getCount() { 65 | return drawables.length; 66 | } 67 | 68 | @Override 69 | public View instantiateItem(ViewGroup container, int position) { 70 | final ZoomImageView zoomImageView = new ZoomImageView(container.getContext()); 71 | 72 | /* 73 | * Load the new bitmap in the background thread 74 | */ 75 | final int bitmapResource = drawables[position]; 76 | backgroundHandler.post(new Runnable() { 77 | @Override 78 | public void run() { 79 | // Load the bitmap here. You should control the bitmap size 80 | // using the BitmapFactory.Options. 81 | final Bitmap bitmap = BitmapFactory.decodeResource( 82 | zoomImageView.getResources(), bitmapResource); 83 | 84 | // Show the bitmap 85 | zoomImageView.post(new Runnable() { 86 | @Override 87 | public void run() { 88 | zoomImageView.setImageBitmap(bitmap); 89 | } 90 | }); 91 | } 92 | }); 93 | 94 | container.addView(zoomImageView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 95 | 96 | return zoomImageView; 97 | } 98 | 99 | @Override 100 | public void destroyItem(ViewGroup container, int position, Object object) { 101 | container.removeView((View) object); 102 | 103 | /* 104 | * Recycle the old bitmap to free up memory straight away 105 | */ 106 | try { 107 | final ImageView imageView = (ImageView) object; 108 | final Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap(); 109 | imageView.setImageBitmap(null); 110 | bitmap.recycle(); 111 | } catch (Exception e) {} 112 | } 113 | 114 | @Override 115 | public boolean isViewFromObject(View view, Object object) { 116 | return view == object; 117 | } 118 | } 119 | 120 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 121 | @Override 122 | public void onCreate(Bundle savedInstanceState) { 123 | super.onCreate(savedInstanceState); 124 | 125 | /* 126 | * Use full screen window and translucent action bar 127 | */ 128 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 129 | WindowManager.LayoutParams.FLAG_FULLSCREEN); 130 | getWindow().setBackgroundDrawable(new ColorDrawable(0xFF000000)); 131 | if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) { 132 | getWindow().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY); 133 | getActionBar().setBackgroundDrawable(new ColorDrawable(0x88000000)); 134 | // Note: if you use ActionBarSherlock use here getSupportActionBar() 135 | } 136 | 137 | setContentView(R.layout.view_pager); 138 | 139 | ViewPager viewPager = (ViewPager) findViewById(R.id.zoomViewPager); 140 | viewPager.setAdapter(new SamplePagerAdapter()); 141 | 142 | // Add margin between pages (optional) 143 | viewPager.setPageMargin((int) getResources().getDisplayMetrics().density * 10); 144 | } 145 | } 146 | --------------------------------------------------------------------------------