├── .classpath
├── .gitignore
├── .project
├── AndroidManifest.xml
├── README.md
├── build.gradle
├── build.xml
├── gradle.properties
├── licence.txt
├── proguard.cfg
├── project.properties
└── src
└── pl
└── polidea
└── view
└── ZoomView.java
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ^.gradle$
2 | ^log$
3 | ^build$
4 | ^bin$
5 | ^gen$
6 | ^local.properties$
7 | ^ota$
8 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | android-zoom-view
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | __This repository is no longer maintained. Issue reports and pull requests will not be attended.__
4 |
5 | ---
6 |
7 | Free and efficient widget for creating zoomable applications.
8 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | configurations.all {
3 | resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
4 | }
5 | repositories {
6 | maven {
7 | url "https://oss.sonatype.org/content/repositories/snapshots/"
8 | }
9 | mavenLocal()
10 | mavenCentral()
11 | }
12 | dependencies {
13 | classpath ('com.apphance.ameba:Ameba:0.99.6')
14 | }
15 | }
16 |
17 | apply plugin: 'ameba-project-configuration'
18 | apply plugin: 'ameba-android-build'
19 | apply plugin: 'ameba-project-release'
20 | apply plugin: 'ameba-android-release'
21 | //apply plugin: 'ameba-android-apphance'
22 | apply plugin: 'ameba-android-analysis'
23 | //apply plugin: 'ameba-android-test'
24 | //apply plugin: 'ameba-android-jarlibrary'
25 | apply from: 'http://dev.polidea.pl/ext/Ameba/polidea_conventions.gradle'
--------------------------------------------------------------------------------
/build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
29 |
30 |
31 |
40 |
41 |
42 |
43 |
47 |
48 |
49 |
51 |
63 |
64 |
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | ###########################################################
2 | # Generated by Ameba system by running
3 | # gradle prepareSetup
4 | # You can modify the file manually.
5 | # Or you can re-run the prepareSetup command
6 | # for guided re-configuration
7 | ###########################################################
8 | # Android properties
9 | ###########################################################
10 | android.mainVariant=Debug
11 | android.excluded.builds=
12 | android.minSdk.target=android-8
13 | ###########################################################
14 | # Release properties
15 | ###########################################################
16 | release.project.icon.file=
17 | release.project.url=http://dev.polidea.pl/ext/Zoom_View/
18 | release.project.language=en
19 | release.project.country=US
20 | release.mail.from=Jenkins
21 | release.mail.to=lab@polidea.pl
22 | release.mail.flags=qrCode,imageMontage
23 |
--------------------------------------------------------------------------------
/licence.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2012-2016 Polidea
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/proguard.cfg:
--------------------------------------------------------------------------------
1 | -optimizationpasses 5
2 | -dontusemixedcaseclassnames
3 | -dontskipnonpubliclibraryclasses
4 | -dontpreverify
5 | -verbose
6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
7 |
8 | -keep public class * extends android.app.Activity
9 | -keep public class * extends android.app.Application
10 | -keep public class * extends android.app.Service
11 | -keep public class * extends android.content.BroadcastReceiver
12 | -keep public class * extends android.content.ContentProvider
13 | -keep public class * extends android.app.backup.BackupAgentHelper
14 | -keep public class * extends android.preference.Preference
15 | -keep public class com.android.vending.licensing.ILicensingService
16 |
17 | -keepclasseswithmembernames class * {
18 | native ;
19 | }
20 |
21 | -keepclasseswithmembers class * {
22 | public (android.content.Context, android.util.AttributeSet);
23 | }
24 |
25 | -keepclasseswithmembers class * {
26 | public (android.content.Context, android.util.AttributeSet, int);
27 | }
28 |
29 | -keepclassmembers class * extends android.app.Activity {
30 | public void *(android.view.View);
31 | }
32 |
33 | -keepclassmembers enum * {
34 | public static **[] values();
35 | public static ** valueOf(java.lang.String);
36 | }
37 |
38 | -keep class * implements android.os.Parcelable {
39 | public static final android.os.Parcelable$Creator *;
40 | }
41 |
--------------------------------------------------------------------------------
/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 use,
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 |
10 | # Project target.
11 | target=android-7
12 | android.library=true
13 |
--------------------------------------------------------------------------------
/src/pl/polidea/view/ZoomView.java:
--------------------------------------------------------------------------------
1 | package pl.polidea.view;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Matrix;
8 | import android.graphics.Paint;
9 | import android.view.MotionEvent;
10 | import android.view.View;
11 | import android.widget.FrameLayout;
12 |
13 | /**
14 | * Zooming view.
15 | */
16 | public class ZoomView extends FrameLayout {
17 |
18 | /**
19 | * Zooming view listener interface.
20 | *
21 | * @author karooolek
22 | *
23 | */
24 | public interface ZoomViewListener {
25 |
26 | void onZoomStarted(float zoom, float zoomx, float zoomy);
27 |
28 | void onZooming(float zoom, float zoomx, float zoomy);
29 |
30 | void onZoomEnded(float zoom, float zoomx, float zoomy);
31 | }
32 |
33 | // zooming
34 | float zoom = 1.0f;
35 | float maxZoom = 2.0f;
36 | float smoothZoom = 1.0f;
37 | float zoomX, zoomY;
38 | float smoothZoomX, smoothZoomY;
39 | private boolean scrolling; // NOPMD by karooolek on 29.06.11 11:45
40 |
41 | // minimap variables
42 | private boolean showMinimap = false;
43 | private int miniMapColor = Color.BLACK;
44 | private int miniMapHeight = -1;
45 | private String miniMapCaption;
46 | private float miniMapCaptionSize = 10.0f;
47 | private int miniMapCaptionColor = Color.WHITE;
48 |
49 | // touching variables
50 | private long lastTapTime;
51 | private float touchStartX, touchStartY;
52 | private float touchLastX, touchLastY;
53 | private float startd;
54 | private boolean pinching;
55 | private float lastd;
56 | private float lastdx1, lastdy1;
57 | private float lastdx2, lastdy2;
58 |
59 | // drawing
60 | private final Matrix m = new Matrix();
61 | private final Paint p = new Paint();
62 |
63 | // listener
64 | ZoomViewListener listener;
65 |
66 | private Bitmap ch;
67 |
68 | public ZoomView(final Context context) {
69 | super(context);
70 | }
71 |
72 | public float getZoom() {
73 | return zoom;
74 | }
75 |
76 | public float getMaxZoom() {
77 | return maxZoom;
78 | }
79 |
80 | public void setMaxZoom(final float maxZoom) {
81 | if (maxZoom < 1.0f) {
82 | return;
83 | }
84 |
85 | this.maxZoom = maxZoom;
86 | }
87 |
88 | public void setMiniMapEnabled(final boolean showMiniMap) {
89 | this.showMinimap = showMiniMap;
90 | }
91 |
92 | public boolean isMiniMapEnabled() {
93 | return showMinimap;
94 | }
95 |
96 | public void setMiniMapHeight(final int miniMapHeight) {
97 | if (miniMapHeight < 0) {
98 | return;
99 | }
100 | this.miniMapHeight = miniMapHeight;
101 | }
102 |
103 | public int getMiniMapHeight() {
104 | return miniMapHeight;
105 | }
106 |
107 | public void setMiniMapColor(final int color) {
108 | miniMapColor = color;
109 | }
110 |
111 | public int getMiniMapColor() {
112 | return miniMapColor;
113 | }
114 |
115 | public String getMiniMapCaption() {
116 | return miniMapCaption;
117 | }
118 |
119 | public void setMiniMapCaption(final String miniMapCaption) {
120 | this.miniMapCaption = miniMapCaption;
121 | }
122 |
123 | public float getMiniMapCaptionSize() {
124 | return miniMapCaptionSize;
125 | }
126 |
127 | public void setMiniMapCaptionSize(final float size) {
128 | miniMapCaptionSize = size;
129 | }
130 |
131 | public int getMiniMapCaptionColor() {
132 | return miniMapCaptionColor;
133 | }
134 |
135 | public void setMiniMapCaptionColor(final int color) {
136 | miniMapCaptionColor = color;
137 | }
138 |
139 | public void zoomTo(final float zoom, final float x, final float y) {
140 | this.zoom = Math.min(zoom, maxZoom);
141 | zoomX = x;
142 | zoomY = y;
143 | smoothZoomTo(this.zoom, x, y);
144 | }
145 |
146 | public void smoothZoomTo(final float zoom, final float x, final float y) {
147 | smoothZoom = clamp(1.0f, zoom, maxZoom);
148 | smoothZoomX = x;
149 | smoothZoomY = y;
150 | if (listener != null) {
151 | listener.onZoomStarted(smoothZoom, x, y);
152 | }
153 | }
154 |
155 | public ZoomViewListener getListener() {
156 | return listener;
157 | }
158 |
159 | public void setListner(final ZoomViewListener listener) {
160 | this.listener = listener;
161 | }
162 |
163 | public float getZoomFocusX() {
164 | return zoomX * zoom;
165 | }
166 |
167 | public float getZoomFocusY() {
168 | return zoomY * zoom;
169 | }
170 |
171 | @Override
172 | public boolean dispatchTouchEvent(final MotionEvent ev) {
173 | // single touch
174 | if (ev.getPointerCount() == 1) {
175 | processSingleTouchEvent(ev);
176 | }
177 |
178 | // // double touch
179 | if (ev.getPointerCount() == 2) {
180 | processDoubleTouchEvent(ev);
181 | }
182 |
183 | // redraw
184 | getRootView().invalidate();
185 | invalidate();
186 |
187 | return true;
188 | }
189 |
190 | private void processSingleTouchEvent(final MotionEvent ev) {
191 |
192 | final float x = ev.getX();
193 | final float y = ev.getY();
194 |
195 | final float w = miniMapHeight * (float) getWidth() / getHeight();
196 | final float h = miniMapHeight;
197 | final boolean touchingMiniMap = x >= 10.0f && x <= 10.0f + w && y >= 10.0f && y <= 10.0f + h;
198 |
199 | if (showMinimap && smoothZoom > 1.0f && touchingMiniMap) {
200 | processSingleTouchOnMinimap(ev);
201 | } else {
202 | processSingleTouchOutsideMinimap(ev);
203 | }
204 | }
205 |
206 | private void processSingleTouchOnMinimap(final MotionEvent ev) {
207 | final float x = ev.getX();
208 | final float y = ev.getY();
209 |
210 | final float w = miniMapHeight * (float) getWidth() / getHeight();
211 | final float h = miniMapHeight;
212 | final float zx = (x - 10.0f) / w * getWidth();
213 | final float zy = (y - 10.0f) / h * getHeight();
214 | smoothZoomTo(smoothZoom, zx, zy);
215 | }
216 |
217 | private void processSingleTouchOutsideMinimap(final MotionEvent ev) {
218 | final float x = ev.getX();
219 | final float y = ev.getY();
220 | float lx = x - touchStartX;
221 | float ly = y - touchStartY;
222 | final float l = (float) Math.hypot(lx, ly);
223 | float dx = x - touchLastX;
224 | float dy = y - touchLastY;
225 | touchLastX = x;
226 | touchLastY = y;
227 |
228 | switch (ev.getAction()) {
229 | case MotionEvent.ACTION_DOWN:
230 | touchStartX = x;
231 | touchStartY = y;
232 | touchLastX = x;
233 | touchLastY = y;
234 | dx = 0;
235 | dy = 0;
236 | lx = 0;
237 | ly = 0;
238 | scrolling = false;
239 | break;
240 |
241 | case MotionEvent.ACTION_MOVE:
242 | if (scrolling || (smoothZoom > 1.0f && l > 30.0f)) {
243 | if (!scrolling) {
244 | scrolling = true;
245 | ev.setAction(MotionEvent.ACTION_CANCEL);
246 | super.dispatchTouchEvent(ev);
247 | }
248 | smoothZoomX -= dx / zoom;
249 | smoothZoomY -= dy / zoom;
250 | return;
251 | }
252 | break;
253 |
254 | case MotionEvent.ACTION_OUTSIDE:
255 | case MotionEvent.ACTION_UP:
256 |
257 | // tap
258 | if (l < 30.0f) {
259 | // check double tap
260 | if (System.currentTimeMillis() - lastTapTime < 500) {
261 | if (smoothZoom == 1.0f) {
262 | smoothZoomTo(maxZoom, x, y);
263 | } else {
264 | smoothZoomTo(1.0f, getWidth() / 2.0f, getHeight() / 2.0f);
265 | }
266 | lastTapTime = 0;
267 | ev.setAction(MotionEvent.ACTION_CANCEL);
268 | super.dispatchTouchEvent(ev);
269 | return;
270 | }
271 |
272 | lastTapTime = System.currentTimeMillis();
273 |
274 | performClick();
275 | }
276 | break;
277 |
278 | default:
279 | break;
280 | }
281 |
282 | ev.setLocation(zoomX + (x - 0.5f * getWidth()) / zoom, zoomY + (y - 0.5f * getHeight()) / zoom);
283 |
284 | ev.getX();
285 | ev.getY();
286 |
287 | super.dispatchTouchEvent(ev);
288 | }
289 |
290 | private void processDoubleTouchEvent(final MotionEvent ev) {
291 | final float x1 = ev.getX(0);
292 | final float dx1 = x1 - lastdx1;
293 | lastdx1 = x1;
294 | final float y1 = ev.getY(0);
295 | final float dy1 = y1 - lastdy1;
296 | lastdy1 = y1;
297 | final float x2 = ev.getX(1);
298 | final float dx2 = x2 - lastdx2;
299 | lastdx2 = x2;
300 | final float y2 = ev.getY(1);
301 | final float dy2 = y2 - lastdy2;
302 | lastdy2 = y2;
303 |
304 | // pointers distance
305 | final float d = (float) Math.hypot(x2 - x1, y2 - y1);
306 | final float dd = d - lastd;
307 | lastd = d;
308 | final float ld = Math.abs(d - startd);
309 |
310 | Math.atan2(y2 - y1, x2 - x1);
311 | switch (ev.getAction()) {
312 | case MotionEvent.ACTION_DOWN:
313 | startd = d;
314 | pinching = false;
315 | break;
316 |
317 | case MotionEvent.ACTION_MOVE:
318 | if (pinching || ld > 30.0f) {
319 | pinching = true;
320 | final float dxk = 0.5f * (dx1 + dx2);
321 | final float dyk = 0.5f * (dy1 + dy2);
322 | smoothZoomTo(Math.max(1.0f, zoom * d / (d - dd)), zoomX - dxk / zoom, zoomY - dyk / zoom);
323 | }
324 |
325 | break;
326 |
327 | case MotionEvent.ACTION_UP:
328 | default:
329 | pinching = false;
330 | break;
331 | }
332 |
333 | ev.setAction(MotionEvent.ACTION_CANCEL);
334 | super.dispatchTouchEvent(ev);
335 | }
336 |
337 | private float clamp(final float min, final float value, final float max) {
338 | return Math.max(min, Math.min(value, max));
339 | }
340 |
341 | private float lerp(final float a, final float b, final float k) {
342 | return a + (b - a) * k;
343 | }
344 |
345 | private float bias(final float a, final float b, final float k) {
346 | return Math.abs(b - a) >= k ? a + k * Math.signum(b - a) : b;
347 | }
348 |
349 | @Override
350 | protected void dispatchDraw(final Canvas canvas) {
351 | // do zoom
352 | zoom = lerp(bias(zoom, smoothZoom, 0.05f), smoothZoom, 0.2f);
353 | smoothZoomX = clamp(0.5f * getWidth() / smoothZoom, smoothZoomX, getWidth() - 0.5f * getWidth() / smoothZoom);
354 | smoothZoomY = clamp(0.5f * getHeight() / smoothZoom, smoothZoomY, getHeight() - 0.5f * getHeight() / smoothZoom);
355 |
356 | zoomX = lerp(bias(zoomX, smoothZoomX, 0.1f), smoothZoomX, 0.35f);
357 | zoomY = lerp(bias(zoomY, smoothZoomY, 0.1f), smoothZoomY, 0.35f);
358 | if (zoom != smoothZoom && listener != null) {
359 | listener.onZooming(zoom, zoomX, zoomY);
360 | }
361 |
362 | final boolean animating = Math.abs(zoom - smoothZoom) > 0.0000001f
363 | || Math.abs(zoomX - smoothZoomX) > 0.0000001f || Math.abs(zoomY - smoothZoomY) > 0.0000001f;
364 |
365 | // nothing to draw
366 | if (getChildCount() == 0) {
367 | return;
368 | }
369 |
370 | // prepare matrix
371 | m.setTranslate(0.5f * getWidth(), 0.5f * getHeight());
372 | m.preScale(zoom, zoom);
373 | m.preTranslate(-clamp(0.5f * getWidth() / zoom, zoomX, getWidth() - 0.5f * getWidth() / zoom),
374 | -clamp(0.5f * getHeight() / zoom, zoomY, getHeight() - 0.5f * getHeight() / zoom));
375 |
376 | // get view
377 | final View v = getChildAt(0);
378 | m.preTranslate(v.getLeft(), v.getTop());
379 |
380 | // get drawing cache if available
381 | if (animating && ch == null && isAnimationCacheEnabled()) {
382 | v.setDrawingCacheEnabled(true);
383 | ch = v.getDrawingCache();
384 | }
385 |
386 | // draw using cache while animating
387 | if (animating && isAnimationCacheEnabled() && ch != null) {
388 | p.setColor(0xffffffff);
389 | canvas.drawBitmap(ch, m, p);
390 | } else { // zoomed or cache unavailable
391 | ch = null;
392 | canvas.save();
393 | canvas.concat(m);
394 | v.draw(canvas);
395 | canvas.restore();
396 | }
397 |
398 | // draw minimap
399 | if (showMinimap) {
400 | if (miniMapHeight < 0) {
401 | miniMapHeight = getHeight() / 4;
402 | }
403 |
404 | canvas.translate(10.0f, 10.0f);
405 |
406 | p.setColor(0x80000000 | 0x00ffffff & miniMapColor);
407 | final float w = miniMapHeight * (float) getWidth() / getHeight();
408 | final float h = miniMapHeight;
409 | canvas.drawRect(0.0f, 0.0f, w, h, p);
410 |
411 | if (miniMapCaption != null && miniMapCaption.length() > 0) {
412 | p.setTextSize(miniMapCaptionSize);
413 | p.setColor(miniMapCaptionColor);
414 | p.setAntiAlias(true);
415 | canvas.drawText(miniMapCaption, 10.0f, 10.0f + miniMapCaptionSize, p);
416 | p.setAntiAlias(false);
417 | }
418 |
419 | p.setColor(0x80000000 | 0x00ffffff & miniMapColor);
420 | final float dx = w * zoomX / getWidth();
421 | final float dy = h * zoomY / getHeight();
422 | canvas.drawRect(dx - 0.5f * w / zoom, dy - 0.5f * h / zoom, dx + 0.5f * w / zoom, dy + 0.5f * h / zoom, p);
423 |
424 | canvas.translate(-10.0f, -10.0f);
425 | }
426 |
427 | // redraw
428 | // if (animating) {
429 | getRootView().invalidate();
430 | invalidate();
431 | // }
432 | }
433 | }
434 |
--------------------------------------------------------------------------------