├── .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 | 
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------