summaryrefslogtreecommitdiff
path: root/android
diff options
context:
space:
mode:
authorTor Lillqvist <tlillqvist@suse.com>2012-05-31 22:31:58 +0300
committerTor Lillqvist <tlillqvist@suse.com>2012-05-31 22:32:40 +0300
commit5dc2b43e46c9f2c77f3ca236511eaf615a62f672 (patch)
treeac2412e7114004f274d34252984e234b2889e656 /android
parent984239ef0d4563d30606f30e640b1e3b2d8f0c0e (diff)
Use Jason Polites's GestureImageView, and some cleanup
Change-Id: I916c36b3b55681cdf8f0d1ffd0236e54f3b67b86
Diffstat (limited to 'android')
-rw-r--r--android/experimental/DocumentLoader/src/com/polites/android/Animation.java32
-rw-r--r--android/experimental/DocumentLoader/src/com/polites/android/Animator.java96
-rw-r--r--android/experimental/DocumentLoader/src/com/polites/android/FlingAnimation.java74
-rw-r--r--android/experimental/DocumentLoader/src/com/polites/android/FlingAnimationListener.java29
-rw-r--r--android/experimental/DocumentLoader/src/com/polites/android/FlingListener.java45
-rw-r--r--android/experimental/DocumentLoader/src/com/polites/android/GestureImageView.java712
-rw-r--r--android/experimental/DocumentLoader/src/com/polites/android/GestureImageViewListener.java30
-rw-r--r--android/experimental/DocumentLoader/src/com/polites/android/GestureImageViewTouchListener.java540
-rw-r--r--android/experimental/DocumentLoader/src/com/polites/android/MathUtils.java76
-rw-r--r--android/experimental/DocumentLoader/src/com/polites/android/MoveAnimation.java107
-rw-r--r--android/experimental/DocumentLoader/src/com/polites/android/MoveAnimationListener.java27
-rw-r--r--android/experimental/DocumentLoader/src/com/polites/android/VectorF.java63
-rw-r--r--android/experimental/DocumentLoader/src/com/polites/android/ZoomAnimation.java167
-rw-r--r--android/experimental/DocumentLoader/src/com/polites/android/ZoomAnimationListener.java26
-rw-r--r--android/experimental/DocumentLoader/src/org/libreoffice/android/examples/DocumentLoader.java109
15 files changed, 2076 insertions, 57 deletions
diff --git a/android/experimental/DocumentLoader/src/com/polites/android/Animation.java b/android/experimental/DocumentLoader/src/com/polites/android/Animation.java
new file mode 100644
index 000000000000..993620893f91
--- /dev/null
+++ b/android/experimental/DocumentLoader/src/com/polites/android/Animation.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+/**
+ * @author Jason Polites
+ *
+ */
+public interface Animation {
+
+ /**
+ * Transforms the view.
+ * @param view
+ * @param diffTime
+ * @return true if this animation should remain active. False otherwise.
+ */
+ public boolean update(GestureImageView view, long time);
+
+}
diff --git a/android/experimental/DocumentLoader/src/com/polites/android/Animator.java b/android/experimental/DocumentLoader/src/com/polites/android/Animator.java
new file mode 100644
index 000000000000..fb0728b7bf13
--- /dev/null
+++ b/android/experimental/DocumentLoader/src/com/polites/android/Animator.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+
+/**
+ * @author Jason Polites
+ *
+ */
+public class Animator extends Thread {
+
+ private GestureImageView view;
+ private Animation animation;
+ private boolean running = false;
+ private boolean active = false;
+ private long lastTime = -1L;
+
+ public Animator(GestureImageView view, String threadName) {
+ super(threadName);
+ this.view = view;
+ }
+
+ @Override
+ public void run() {
+
+ running = true;
+
+ while(running) {
+
+ while(active && animation != null) {
+ long time = System.currentTimeMillis();
+ active = animation.update(view, time - lastTime);
+ view.redraw();
+ lastTime = time;
+
+ while(active) {
+ try {
+ if(view.waitForDraw(32)) { // 30Htz
+ break;
+ }
+ }
+ catch (InterruptedException ignore) {
+ active = false;
+ }
+ }
+ }
+
+ synchronized(this) {
+ if(running) {
+ try {
+ wait();
+ }
+ catch (InterruptedException ignore) {}
+ }
+ }
+ }
+ }
+
+ public synchronized void finish() {
+ running = false;
+ active = false;
+ notifyAll();
+ }
+
+ public void play(Animation transformer) {
+ if(active) {
+ cancel();
+ }
+ this.animation = transformer;
+
+ activate();
+ }
+
+ public synchronized void activate() {
+ lastTime = System.currentTimeMillis();
+ active = true;
+ notifyAll();
+ }
+
+ public void cancel() {
+ active = false;
+ }
+}
diff --git a/android/experimental/DocumentLoader/src/com/polites/android/FlingAnimation.java b/android/experimental/DocumentLoader/src/com/polites/android/FlingAnimation.java
new file mode 100644
index 000000000000..3124b6201464
--- /dev/null
+++ b/android/experimental/DocumentLoader/src/com/polites/android/FlingAnimation.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+/**
+ * @author Jason Polites
+ *
+ */
+public class FlingAnimation implements Animation {
+
+ private float velocityX;
+ private float velocityY;
+
+ private float factor = 0.85f;
+
+ private float threshold = 10;
+
+ private FlingAnimationListener listener;
+
+ /* (non-Javadoc)
+ * @see com.polites.android.Transformer#update(com.polites.android.GestureImageView, long)
+ */
+ @Override
+ public boolean update(GestureImageView view, long time) {
+ float seconds = (float) time / 1000.0f;
+
+ float dx = velocityX * seconds;
+ float dy = velocityY * seconds;
+
+ velocityX *= factor;
+ velocityY *= factor;
+
+ boolean active = (Math.abs(velocityX) > threshold && Math.abs(velocityY) > threshold);
+
+ if(listener != null) {
+ listener.onMove(dx, dy);
+
+ if(!active) {
+ listener.onComplete();
+ }
+ }
+
+ return active;
+ }
+
+ public void setVelocityX(float velocityX) {
+ this.velocityX = velocityX;
+ }
+
+ public void setVelocityY(float velocityY) {
+ this.velocityY = velocityY;
+ }
+
+ public void setFactor(float factor) {
+ this.factor = factor;
+ }
+
+ public void setListener(FlingAnimationListener listener) {
+ this.listener = listener;
+ }
+}
diff --git a/android/experimental/DocumentLoader/src/com/polites/android/FlingAnimationListener.java b/android/experimental/DocumentLoader/src/com/polites/android/FlingAnimationListener.java
new file mode 100644
index 000000000000..b9611d51c040
--- /dev/null
+++ b/android/experimental/DocumentLoader/src/com/polites/android/FlingAnimationListener.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+
+/**
+ * @author Jason Polites
+ *
+ */
+public interface FlingAnimationListener {
+
+ public void onMove(float x, float y);
+
+ public void onComplete();
+
+}
diff --git a/android/experimental/DocumentLoader/src/com/polites/android/FlingListener.java b/android/experimental/DocumentLoader/src/com/polites/android/FlingListener.java
new file mode 100644
index 000000000000..ab3007a14b00
--- /dev/null
+++ b/android/experimental/DocumentLoader/src/com/polites/android/FlingListener.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.MotionEvent;
+
+
+/**
+ * @author Jason Polites
+ *
+ */
+public class FlingListener extends SimpleOnGestureListener {
+
+ private float velocityX;
+ private float velocityY;
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+ this.velocityX = velocityX;
+ this.velocityY = velocityY;
+ return true;
+ }
+
+ public float getVelocityX() {
+ return velocityX;
+ }
+
+ public float getVelocityY() {
+ return velocityY;
+ }
+}
diff --git a/android/experimental/DocumentLoader/src/com/polites/android/GestureImageView.java b/android/experimental/DocumentLoader/src/com/polites/android/GestureImageView.java
new file mode 100644
index 000000000000..1cde6e4b4889
--- /dev/null
+++ b/android/experimental/DocumentLoader/src/com/polites/android/GestureImageView.java
@@ -0,0 +1,712 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+import java.io.InputStream;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.ImageView;
+
+public class GestureImageView extends ImageView {
+
+ public static final String GLOBAL_NS = "http://schemas.android.com/apk/res/android";
+ public static final String LOCAL_NS = "http://schemas.polites.com/android";
+
+ private final Semaphore drawLock = new Semaphore(0);
+ private Animator animator;
+
+ private Drawable drawable;
+
+ private float x = 0, y = 0;
+
+ private boolean layout = false;
+
+ private float scaleAdjust = 1.0f;
+ private float startingScale = -1.0f;
+
+ private float scale = 1.0f;
+ private float maxScale = 5.0f;
+ private float minScale = 0.75f;
+ private float fitScaleHorizontal = 1.0f;
+ private float fitScaleVertical = 1.0f;
+ private float rotation = 0.0f;
+
+ private float centerX;
+ private float centerY;
+
+ private Float startX, startY;
+
+ private int hWidth;
+ private int hHeight;
+
+ private int resId = -1;
+ private boolean recycle = false;
+ private boolean strict = false;
+
+ private int displayHeight;
+ private int displayWidth;
+
+ private int alpha = 255;
+ private ColorFilter colorFilter;
+
+ private int deviceOrientation = -1;
+ private int imageOrientation;
+
+ private GestureImageViewListener gestureImageViewListener;
+ private GestureImageViewTouchListener gestureImageViewTouchListener;
+
+ private OnTouchListener customOnTouchListener;
+ private OnClickListener onClickListener;
+
+ public GestureImageView(Context context, AttributeSet attrs, int defStyle) {
+ this(context, attrs);
+ }
+
+ public GestureImageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ String scaleType = attrs.getAttributeValue(GLOBAL_NS, "scaleType");
+
+ if(scaleType == null || scaleType.trim().length() == 0) {
+ setScaleType(ScaleType.CENTER_INSIDE);
+ }
+
+ String strStartX = attrs.getAttributeValue(LOCAL_NS, "start-x");
+ String strStartY = attrs.getAttributeValue(LOCAL_NS, "start-y");
+
+ if(strStartX != null && strStartX.trim().length() > 0) {
+ startX = Float.parseFloat(strStartX);
+ }
+
+ if(strStartY != null && strStartY.trim().length() > 0) {
+ startY = Float.parseFloat(strStartY);
+ }
+
+ setStartingScale(attrs.getAttributeFloatValue(LOCAL_NS, "start-scale", startingScale));
+ setMinScale(attrs.getAttributeFloatValue(LOCAL_NS, "min-scale", minScale));
+ setMaxScale(attrs.getAttributeFloatValue(LOCAL_NS, "max-scale", maxScale));
+ setStrict(attrs.getAttributeBooleanValue(LOCAL_NS, "strict", strict));
+ setRecycle(attrs.getAttributeBooleanValue(LOCAL_NS, "recycle", recycle));
+
+ initImage();
+ }
+
+ public GestureImageView(Context context) {
+ super(context);
+ setScaleType(ScaleType.CENTER_INSIDE);
+ initImage();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+
+ if(drawable != null) {
+ int orientation = getResources().getConfiguration().orientation;
+ if(orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ displayHeight = MeasureSpec.getSize(heightMeasureSpec);
+
+ if(getLayoutParams().width == LayoutParams.WRAP_CONTENT) {
+ float ratio = (float) getImageWidth() / (float) getImageHeight();
+ displayWidth = Math.round( (float) displayHeight * ratio) ;
+ }
+ else {
+ displayWidth = MeasureSpec.getSize(widthMeasureSpec);
+ }
+ }
+ else {
+ displayWidth = MeasureSpec.getSize(widthMeasureSpec);
+ if(getLayoutParams().height == LayoutParams.WRAP_CONTENT) {
+ float ratio = (float) getImageHeight() / (float) getImageWidth();
+ displayHeight = Math.round( (float) displayWidth * ratio) ;
+ }
+ else {
+ displayHeight = MeasureSpec.getSize(heightMeasureSpec);
+ }
+ }
+ }
+ else {
+ displayHeight = MeasureSpec.getSize(heightMeasureSpec);
+ displayWidth = MeasureSpec.getSize(widthMeasureSpec);
+ }
+
+ setMeasuredDimension(displayWidth, displayHeight);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if(changed || !layout) {
+ setupCanvas(displayWidth, displayHeight, getResources().getConfiguration().orientation);
+ }
+ }
+
+ protected void setupCanvas(int measuredWidth, int measuredHeight, int orientation) {
+
+ if(deviceOrientation != orientation) {
+ layout = false;
+ deviceOrientation = orientation;
+ }
+
+ if(drawable != null && !layout) {
+ int imageWidth = getImageWidth();
+ int imageHeight = getImageHeight();
+
+ hWidth = Math.round(((float)imageWidth / 2.0f));
+ hHeight = Math.round(((float)imageHeight / 2.0f));
+
+ measuredWidth -= (getPaddingLeft() + getPaddingRight());
+ measuredHeight -= (getPaddingTop() + getPaddingBottom());
+
+ computeCropScale(imageWidth, imageHeight, measuredWidth, measuredHeight);
+
+ if(startingScale <= 0.0f) {
+ computeStartingScale(imageWidth, imageHeight, measuredWidth, measuredHeight);
+ }
+
+ scaleAdjust = startingScale;
+
+ this.centerX = (float) measuredWidth / 2.0f;
+ this.centerY = (float) measuredHeight / 2.0f;
+
+ if(startX == null) {
+ x = centerX;
+ }
+ else {
+ x = startX;
+ }
+
+ if(startY == null) {
+ y = centerY;
+ }
+ else {
+ y = startY;
+ }
+
+ gestureImageViewTouchListener = new GestureImageViewTouchListener(this, measuredWidth, measuredHeight);
+
+ if(isLandscape()) {
+ gestureImageViewTouchListener.setMinScale(minScale * fitScaleHorizontal);
+ }
+ else {
+ gestureImageViewTouchListener.setMinScale(minScale * fitScaleVertical);
+ }
+
+
+ gestureImageViewTouchListener.setMaxScale(maxScale * startingScale);
+
+ gestureImageViewTouchListener.setFitScaleHorizontal(fitScaleHorizontal);
+ gestureImageViewTouchListener.setFitScaleVertical(fitScaleVertical);
+ gestureImageViewTouchListener.setCanvasWidth(measuredWidth);
+ gestureImageViewTouchListener.setCanvasHeight(measuredHeight);
+ gestureImageViewTouchListener.setOnClickListener(onClickListener);
+
+ drawable.setBounds(-hWidth,-hHeight,hWidth,hHeight);
+
+ super.setOnTouchListener(new OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if(customOnTouchListener != null) {
+ customOnTouchListener.onTouch(v, event);
+ }
+ return gestureImageViewTouchListener.onTouch(v, event);
+ }
+ });
+
+ layout = true;
+ }
+ }
+
+ protected void computeCropScale(int imageWidth, int imageHeight, int measuredWidth, int measuredHeight) {
+ fitScaleHorizontal = (float) measuredWidth / (float) imageWidth;
+ fitScaleVertical = (float) measuredHeight / (float) imageHeight;
+ }
+
+ protected void computeStartingScale(int imageWidth, int imageHeight, int measuredWidth, int measuredHeight) {
+ switch(getScaleType()) {
+ case CENTER:
+ // Center the image in the view, but perform no scaling.
+ startingScale = 1.0f;
+ break;
+
+ case CENTER_CROP:
+ startingScale = Math.max((float) measuredHeight / (float) imageHeight, (float) measuredWidth/ (float) imageWidth);
+ break;
+
+ case CENTER_INSIDE:
+ if(isLandscape()) {
+ startingScale = fitScaleHorizontal;
+ }
+ else {
+ startingScale = fitScaleVertical;
+ }
+ break;
+ }
+ }
+
+ protected boolean isRecycled() {
+ if(drawable != null && drawable instanceof BitmapDrawable) {
+ Bitmap bitmap = ((BitmapDrawable)drawable).getBitmap();
+ if(bitmap != null) {
+ return bitmap.isRecycled();
+ }
+ }
+ return false;
+ }
+
+ protected void recycle() {
+ if(recycle && drawable != null && drawable instanceof BitmapDrawable) {
+ Bitmap bitmap = ((BitmapDrawable)drawable).getBitmap();
+ if(bitmap != null) {
+ bitmap.recycle();
+ }
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if(layout) {
+ if(drawable != null && !isRecycled()) {
+ canvas.save();
+
+ float adjustedScale = scale * scaleAdjust;
+
+ canvas.translate(x, y);
+
+ if(rotation != 0.0f) {
+ canvas.rotate(rotation);
+ }
+
+ if(adjustedScale != 1.0f) {
+ canvas.scale(adjustedScale, adjustedScale);
+ }
+
+ drawable.draw(canvas);
+
+ canvas.restore();
+ }
+
+ if(drawLock.availablePermits() <= 0) {
+ drawLock.release();
+ }
+ }
+ }
+
+ /**
+ * Waits for a draw
+ * @param max time to wait for draw (ms)
+ * @throws InterruptedException
+ */
+ public boolean waitForDraw(long timeout) throws InterruptedException {
+ return drawLock.tryAcquire(timeout, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ animator = new Animator(this, "GestureImageViewAnimator");
+ animator.start();
+
+ if(resId >= 0 && drawable == null) {
+ setImageResource(resId);
+ }
+
+ super.onAttachedToWindow();
+ }
+
+ public void animationStart(Animation animation) {
+ if(animator != null) {
+ animator.play(animation);
+ }
+ }
+
+ public void animationStop() {
+ if(animator != null) {
+ animator.cancel();
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ if(animator != null) {
+ animator.finish();
+ }
+ if(recycle && drawable != null && !isRecycled()) {
+ recycle();
+ drawable = null;
+ }
+ super.onDetachedFromWindow();
+ }
+
+ protected void initImage() {
+ if(this.drawable != null) {
+ this.drawable.setAlpha(alpha);
+ this.drawable.setFilterBitmap(true);
+ if(colorFilter != null) {
+ this.drawable.setColorFilter(colorFilter);
+ }
+ }
+
+ if(!layout) {
+ requestLayout();
+ redraw();
+ }
+ }
+
+ public void setImageBitmap(Bitmap image) {
+ this.drawable = new BitmapDrawable(getResources(), image);
+ initImage();
+ }
+
+ @Override
+ public void setImageDrawable(Drawable drawable) {
+ this.drawable = drawable;
+ initImage();
+ }
+
+ public void setImageResource(int id) {
+ if(this.drawable != null) {
+ this.recycle();
+ }
+ if(id >= 0) {
+ this.resId = id;
+ setImageDrawable(getContext().getResources().getDrawable(id));
+ }
+ }
+
+ public int getScaledWidth() {
+ return Math.round(getImageWidth() * getScale());
+ }
+
+ public int getScaledHeight() {
+ return Math.round(getImageHeight() * getScale());
+ }
+
+ public int getImageWidth() {
+ if(drawable != null) {
+ return drawable.getIntrinsicWidth();
+ }
+ return 0;
+ }
+
+ public int getImageHeight() {
+ if(drawable != null) {
+ return drawable.getIntrinsicHeight();
+ }
+ return 0;
+ }
+
+ public void moveBy(float x, float y) {
+ this.x += x;
+ this.y += y;
+ }
+
+ public void setPosition(float x, float y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public void redraw() {
+ postInvalidate();
+ }
+
+ public void setMinScale(float min) {
+ this.minScale = min;
+ if(gestureImageViewTouchListener != null) {
+ gestureImageViewTouchListener.setMinScale(min * fitScaleHorizontal);
+ }
+ }
+
+ public void setMaxScale(float max) {
+ this.maxScale = max;
+ if(gestureImageViewTouchListener != null) {
+ gestureImageViewTouchListener.setMaxScale(max * startingScale);
+ }
+ }
+
+ public void setScale(float scale) {
+ scaleAdjust = scale;
+ }
+
+ public float getScale() {
+ return scaleAdjust;
+ }
+
+ public float getImageX() {
+ return x;
+ }
+
+ public float getImageY() {
+ return y;
+ }
+
+ public boolean isStrict() {
+ return strict;
+ }
+
+ public void setStrict(boolean strict) {
+ this.strict = strict;
+ }
+
+ public boolean isRecycle() {
+ return recycle;
+ }
+
+ public void setRecycle(boolean recycle) {
+ this.recycle = recycle;
+ }
+
+ public void reset() {
+ x = centerX;
+ y = centerY;
+ scaleAdjust = startingScale;
+ redraw();
+ }
+
+ public void setRotation(float rotation) {
+ this.rotation = rotation;
+ }
+
+ public void setGestureImageViewListener(GestureImageViewListener pinchImageViewListener) {
+ this.gestureImageViewListener = pinchImageViewListener;
+ }
+
+ public GestureImageViewListener getGestureImageViewListener() {
+ return gestureImageViewListener;
+ }
+
+ @Override
+ public Drawable getDrawable() {
+ return drawable;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ this.alpha = alpha;
+ if(drawable != null) {
+ drawable.setAlpha(alpha);
+ }
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ this.colorFilter = cf;
+ if(drawable != null) {
+ drawable.setColorFilter(cf);
+ }
+ }
+
+ @Override
+ public void setImageURI(Uri mUri) {
+ if ("content".equals(mUri.getScheme())) {
+ try {
+ String[] orientationColumn = {MediaStore.Images.Media.ORIENTATION};
+
+ Cursor cur = getContext().getContentResolver().query(mUri, orientationColumn, null, null, null);
+
+ if (cur != null && cur.moveToFirst()) {
+ imageOrientation = cur.getInt(cur.getColumnIndex(orientationColumn[0]));
+ }
+
+ InputStream in = null;
+
+ try {
+ in = getContext().getContentResolver().openInputStream(mUri);
+ Bitmap bmp = BitmapFactory.decodeStream(in);
+
+ if(imageOrientation != 0) {
+ Matrix m = new Matrix();
+ m.postRotate(imageOrientation);
+ Bitmap rotated = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), m, true);
+ bmp.recycle();
+ setImageDrawable(new BitmapDrawable(getResources(), rotated));
+ }
+ else {
+ setImageDrawable(new BitmapDrawable(getResources(), bmp));
+ }
+ }
+ finally {
+ if(in != null) {
+ in.close();
+ }
+
+ if(cur != null) {
+ cur.close();
+ }
+ }
+ }
+ catch (Exception e) {
+ Log.w("GestureImageView", "Unable to open content: " + mUri, e);
+ }
+ }
+ else {
+ setImageDrawable(Drawable.createFromPath(mUri.toString()));
+ }
+
+ if (drawable == null) {
+ Log.e("GestureImageView", "resolveUri failed on bad bitmap uri: " + mUri);
+ // Don't try again.
+ mUri = null;
+ }
+ }
+
+ @Override
+ public Matrix getImageMatrix() {
+ if(strict) {
+ throw new UnsupportedOperationException("Not supported");
+ }
+ return super.getImageMatrix();
+ }
+
+ @Override
+ public void setScaleType(ScaleType scaleType) {
+ if(scaleType == ScaleType.CENTER ||
+ scaleType == ScaleType.CENTER_CROP ||
+ scaleType == ScaleType.CENTER_INSIDE) {
+
+ super.setScaleType(scaleType);
+ }
+ else if(strict) {
+ throw new UnsupportedOperationException("Not supported");
+ }
+ }
+
+ @Override
+ public void invalidateDrawable(Drawable dr) {
+ if(strict) {
+ throw new UnsupportedOperationException("Not supported");
+ }
+ super.invalidateDrawable(dr);
+ }
+
+ @Override
+ public int[] onCreateDrawableState(int extraSpace) {
+ if(strict) {
+ throw new UnsupportedOperationException("Not supported");
+ }
+ return super.onCreateDrawableState(extraSpace);
+ }
+
+ @Override
+ public void setAdjustViewBounds(boolean adjustViewBounds) {
+ if(strict) {
+ throw new UnsupportedOperationException("Not supported");
+ }
+ super.setAdjustViewBounds(adjustViewBounds);
+ }
+
+ @Override
+ public void setImageLevel(int level) {
+ if(strict) {
+ throw new UnsupportedOperationException("Not supported");
+ }
+ super.setImageLevel(level);
+ }
+
+ @Override
+ public void setImageMatrix(Matrix matrix) {
+ if(strict) {
+ throw new UnsupportedOperationException("Not supported");
+ }
+ }
+
+ @Override
+ public void setImageState(int[] state, boolean merge) {
+ if(strict) {
+ throw new UnsupportedOperationException("Not supported");
+ }
+ }
+
+ @Override
+ public void setSelected(boolean selected) {
+ if(strict) {
+ throw new UnsupportedOperationException("Not supported");
+ }
+ super.setSelected(selected);
+ }
+
+ @Override
+ public void setOnTouchListener(OnTouchListener l) {
+ this.customOnTouchListener = l;
+ }
+
+ public float getCenterX() {
+ return centerX;
+ }
+
+ public float getCenterY() {
+ return centerY;
+ }
+
+ public boolean isLandscape() {
+ return getImageWidth() >= getImageHeight();
+ }
+
+ public boolean isPortrait() {
+ return getImageWidth() <= getImageHeight();
+ }
+
+ public void setStartingScale(float startingScale) {
+ this.startingScale = startingScale;
+ }
+
+ public void setStartingPosition(float x, float y) {
+ this.startX = x;
+ this.startY = y;
+ }
+
+ @Override
+ public void setOnClickListener(OnClickListener l) {
+ this.onClickListener = l;
+
+ if(gestureImageViewTouchListener != null) {
+ gestureImageViewTouchListener.setOnClickListener(l);
+ }
+ }
+
+ /**
+ * Returns true if the image dimensions are aligned with the orientation of the device.
+ * @return
+ */
+ public boolean isOrientationAligned() {
+ if(deviceOrientation == Configuration.ORIENTATION_LANDSCAPE) {
+ return isLandscape();
+ }
+ else if(deviceOrientation == Configuration.ORIENTATION_PORTRAIT) {
+ return isPortrait();
+ }
+ return true;
+ }
+
+ public int getDeviceOrientation() {
+ return deviceOrientation;
+ }
+}
diff --git a/android/experimental/DocumentLoader/src/com/polites/android/GestureImageViewListener.java b/android/experimental/DocumentLoader/src/com/polites/android/GestureImageViewListener.java
new file mode 100644
index 000000000000..4a52358216d5
--- /dev/null
+++ b/android/experimental/DocumentLoader/src/com/polites/android/GestureImageViewListener.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+/**
+ * @author jasonpolites
+ *
+ */
+public interface GestureImageViewListener {
+
+ public void onTouch(float x, float y);
+
+ public void onScale(float scale);
+
+ public void onPosition(float x, float y);
+
+}
diff --git a/android/experimental/DocumentLoader/src/com/polites/android/GestureImageViewTouchListener.java b/android/experimental/DocumentLoader/src/com/polites/android/GestureImageViewTouchListener.java
new file mode 100644
index 000000000000..76751d145b58
--- /dev/null
+++ b/android/experimental/DocumentLoader/src/com/polites/android/GestureImageViewTouchListener.java
@@ -0,0 +1,540 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+import android.content.res.Configuration;
+import android.graphics.PointF;
+import android.view.GestureDetector;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnTouchListener;
+
+public class GestureImageViewTouchListener implements OnTouchListener {
+
+ private GestureImageView image;
+ private OnClickListener onClickListener;
+
+ private final PointF current = new PointF();
+ private final PointF last = new PointF();
+ private final PointF next = new PointF();
+ private final PointF midpoint = new PointF();
+
+ private final VectorF scaleVector = new VectorF();
+ private final VectorF pinchVector = new VectorF();
+
+ private boolean touched = false;
+ private boolean inZoom = false;
+
+ private float initialDistance;
+ private float lastScale = 1.0f;
+ private float currentScale = 1.0f;
+
+ private float boundaryLeft = 0;
+ private float boundaryTop = 0;
+ private float boundaryRight = 0;
+ private float boundaryBottom = 0;
+
+ private float maxScale = 5.0f;
+ private float minScale = 0.25f;
+ private float fitScaleHorizontal = 1.0f;
+ private float fitScaleVertical = 1.0f;
+
+ private int canvasWidth = 0;
+ private int canvasHeight = 0;
+
+ private float centerX = 0;
+ private float centerY = 0;
+
+ private float startingScale = 0;
+
+ private boolean canDragX = false;
+ private boolean canDragY = false;
+
+ private boolean multiTouch = false;
+
+ private int displayWidth;
+ private int displayHeight;
+
+ private int imageWidth;
+ private int imageHeight;
+
+ private FlingListener flingListener;
+ private FlingAnimation flingAnimation;
+ private ZoomAnimation zoomAnimation;
+ private MoveAnimation moveAnimation;
+ private GestureDetector tapDetector;
+ private GestureDetector flingDetector;
+ private GestureImageViewListener imageListener;
+
+ public GestureImageViewTouchListener(final GestureImageView image, int displayWidth, int displayHeight) {
+ super();
+
+ this.image = image;
+
+ this.displayWidth = displayWidth;
+ this.displayHeight = displayHeight;
+
+ this.centerX = (float) displayWidth / 2.0f;
+ this.centerY = (float) displayHeight / 2.0f;
+
+ this.imageWidth = image.getImageWidth();
+ this.imageHeight = image.getImageHeight();
+
+ startingScale = image.getScale();
+
+ currentScale = startingScale;
+ lastScale = startingScale;
+
+ boundaryRight = displayWidth;
+ boundaryBottom = displayHeight;
+ boundaryLeft = 0;
+ boundaryTop = 0;
+
+ next.x = image.getImageX();
+ next.y = image.getImageY();
+
+ flingListener = new FlingListener();
+ flingAnimation = new FlingAnimation();
+ zoomAnimation = new ZoomAnimation();
+ moveAnimation = new MoveAnimation();
+
+ flingAnimation.setListener(new FlingAnimationListener() {
+ @Override
+ public void onMove(float x, float y) {
+ handleDrag(current.x + x, current.y + y);
+ }
+
+ @Override
+ public void onComplete() {}
+ });
+
+ zoomAnimation.setZoom(2.0f);
+ zoomAnimation.setZoomAnimationListener(new ZoomAnimationListener() {
+ @Override
+ public void onZoom(float scale, float x, float y) {
+ if(scale <= maxScale && scale >= minScale) {
+ handleScale(scale, x, y);
+ }
+ }
+
+ @Override
+ public void onComplete() {
+ inZoom = false;
+ handleUp();
+ }
+ });
+
+ moveAnimation.setMoveAnimationListener(new MoveAnimationListener() {
+
+ @Override
+ public void onMove(float x, float y) {
+ image.setPosition(x, y);
+ image.redraw();
+ }
+ });
+
+ tapDetector = new GestureDetector(image.getContext(), new SimpleOnGestureListener() {
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ startZoom(e);
+ return true;
+ }
+
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ if(!inZoom) {
+ if(onClickListener != null) {
+ onClickListener.onClick(image);
+ return true;
+ }
+ }
+
+ return false;
+ }
+ });
+
+ flingDetector = new GestureDetector(image.getContext(), flingListener);
+ imageListener = image.getGestureImageViewListener();
+
+ calculateBoundaries();
+ }
+
+ private void startFling() {
+ flingAnimation.setVelocityX(flingListener.getVelocityX());
+ flingAnimation.setVelocityY(flingListener.getVelocityY());
+ image.animationStart(flingAnimation);
+ }
+
+ private void startZoom(MotionEvent e) {
+ inZoom = true;
+ zoomAnimation.reset();
+
+ float zoomTo = 1.0f;
+
+ if(image.isLandscape()) {
+ if(image.getDeviceOrientation() == Configuration.ORIENTATION_PORTRAIT) {
+ int scaledHeight = image.getScaledHeight();
+
+ if(scaledHeight < canvasHeight) {
+ zoomTo = fitScaleVertical / currentScale;
+ zoomAnimation.setTouchX(e.getX());
+ zoomAnimation.setTouchY(image.getCenterY());
+ }
+ else {
+ zoomTo = fitScaleHorizontal / currentScale;
+ zoomAnimation.setTouchX(image.getCenterX());
+ zoomAnimation.setTouchY(image.getCenterY());
+ }
+ }
+ else {
+ int scaledWidth = image.getScaledWidth();
+
+ if(scaledWidth == canvasWidth) {
+ zoomTo = currentScale*4.0f;
+ zoomAnimation.setTouchX(e.getX());
+ zoomAnimation.setTouchY(e.getY());
+ }
+ else if(scaledWidth < canvasWidth) {
+ zoomTo = fitScaleHorizontal / currentScale;
+ zoomAnimation.setTouchX(image.getCenterX());
+ zoomAnimation.setTouchY(e.getY());
+ }
+ else {
+ zoomTo = fitScaleHorizontal / currentScale;
+ zoomAnimation.setTouchX(image.getCenterX());
+ zoomAnimation.setTouchY(image.getCenterY());
+ }
+ }
+ }
+ else {
+ if(image.getDeviceOrientation() == Configuration.ORIENTATION_PORTRAIT) {
+
+ int scaledHeight = image.getScaledHeight();
+
+ if(scaledHeight == canvasHeight) {
+ zoomTo = currentScale*4.0f;
+ zoomAnimation.setTouchX(e.getX());
+ zoomAnimation.setTouchY(e.getY());
+ }
+ else if(scaledHeight < canvasHeight) {
+ zoomTo = fitScaleVertical / currentScale;
+ zoomAnimation.setTouchX(e.getX());
+ zoomAnimation.setTouchY(image.getCenterY());
+ }
+ else {
+ zoomTo = fitScaleVertical / currentScale;
+ zoomAnimation.setTouchX(image.getCenterX());
+ zoomAnimation.setTouchY(image.getCenterY());
+ }
+ }
+ else {
+ int scaledWidth = image.getScaledWidth();
+
+ if(scaledWidth < canvasWidth) {
+ zoomTo = fitScaleHorizontal / currentScale;
+ zoomAnimation.setTouchX(image.getCenterX());
+ zoomAnimation.setTouchY(e.getY());
+ }
+ else {
+ zoomTo = fitScaleVertical / currentScale;
+ zoomAnimation.setTouchX(image.getCenterX());
+ zoomAnimation.setTouchY(image.getCenterY());
+ }
+ }
+ }
+
+ zoomAnimation.setZoom(zoomTo);
+ image.animationStart(zoomAnimation);
+ }
+
+
+ private void stopAnimations() {
+ image.animationStop();
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+
+ if(!inZoom) {
+
+ if(!tapDetector.onTouchEvent(event)) {
+ if(event.getPointerCount() == 1 && flingDetector.onTouchEvent(event)) {
+ startFling();
+ }
+
+ if(event.getAction() == MotionEvent.ACTION_UP) {
+ handleUp();
+ }
+ else if(event.getAction() == MotionEvent.ACTION_DOWN) {
+ stopAnimations();
+
+ last.x = event.getX();
+ last.y = event.getY();
+
+ if(imageListener != null) {
+ imageListener.onTouch(last.x, last.y);
+ }
+
+ touched = true;
+ }
+ else if(event.getAction() == MotionEvent.ACTION_MOVE) {
+ if(event.getPointerCount() > 1) {
+ multiTouch = true;
+ if(initialDistance > 0) {
+
+ pinchVector.set(event);
+ pinchVector.calculateLength();
+
+ float distance = pinchVector.length;
+
+ if(initialDistance != distance) {
+
+ float newScale = (distance / initialDistance) * lastScale;
+
+ if(newScale <= maxScale) {
+ scaleVector.length *= newScale;
+
+ scaleVector.calculateEndPoint();
+
+ scaleVector.length /= newScale;
+
+ float newX = scaleVector.end.x;
+ float newY = scaleVector.end.y;
+
+ handleScale(newScale, newX, newY);
+ }
+ }
+ }
+ else {
+ initialDistance = MathUtils.distance(event);
+
+ MathUtils.midpoint(event, midpoint);
+
+ scaleVector.setStart(midpoint);
+ scaleVector.setEnd(next);
+
+ scaleVector.calculateLength();
+ scaleVector.calculateAngle();
+
+ scaleVector.length /= lastScale;
+ }
+ }
+ else {
+ if(!touched) {
+ touched = true;
+ last.x = event.getX();
+ last.y = event.getY();
+ next.x = image.getImageX();
+ next.y = image.getImageY();
+ }
+ else if(!multiTouch) {
+ if(handleDrag(event.getX(), event.getY())) {
+ image.redraw();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ protected void handleUp() {
+
+ multiTouch = false;
+
+ initialDistance = 0;
+ lastScale = currentScale;
+
+ if(!canDragX) {
+ next.x = centerX;
+ }
+
+ if(!canDragY) {
+ next.y = centerY;
+ }
+
+ boundCoordinates();
+
+ if(!canDragX && !canDragY) {
+
+ if(image.isLandscape()) {
+ currentScale = fitScaleHorizontal;
+ lastScale = fitScaleHorizontal;
+ }
+ else {
+ currentScale = fitScaleVertical;
+ lastScale = fitScaleVertical;
+ }
+ }
+
+ image.setScale(currentScale);
+ image.setPosition(next.x, next.y);
+
+ if(imageListener != null) {
+ imageListener.onScale(currentScale);
+ imageListener.onPosition(next.x, next.y);
+ }
+
+ image.redraw();
+ }
+
+ protected void handleScale(float scale, float x, float y) {
+
+ currentScale = scale;
+
+ if(currentScale > maxScale) {
+ currentScale = maxScale;
+ }
+ else if (currentScale < minScale) {
+ currentScale = minScale;
+ }
+ else {
+ next.x = x;
+ next.y = y;
+ }
+
+ calculateBoundaries();
+
+ image.setScale(currentScale);
+ image.setPosition(next.x, next.y);
+
+ if(imageListener != null) {
+ imageListener.onScale(currentScale);
+ imageListener.onPosition(next.x, next.y);
+ }
+
+ image.redraw();
+ }
+
+ protected boolean handleDrag(float x, float y) {
+ current.x = x;
+ current.y = y;
+
+ float diffX = (current.x - last.x);
+ float diffY = (current.y - last.y);
+
+ if(diffX != 0 || diffY != 0) {
+
+ if(canDragX) next.x += diffX;
+ if(canDragY) next.y += diffY;
+
+ boundCoordinates();
+
+ last.x = current.x;
+ last.y = current.y;
+
+ if(canDragX || canDragY) {
+ image.setPosition(next.x, next.y);
+
+ if(imageListener != null) {
+ imageListener.onPosition(next.x, next.y);
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public void reset() {
+ currentScale = startingScale;
+ next.x = centerX;
+ next.y = centerY;
+ calculateBoundaries();
+ image.setScale(currentScale);
+ image.setPosition(next.x, next.y);
+ image.redraw();
+ }
+
+
+ public float getMaxScale() {
+ return maxScale;
+ }
+
+ public void setMaxScale(float maxScale) {
+ this.maxScale = maxScale;
+ }
+
+ public float getMinScale() {
+ return minScale;
+ }
+
+ public void setMinScale(float minScale) {
+ this.minScale = minScale;
+ }
+
+ public void setOnClickListener(OnClickListener onClickListener) {
+ this.onClickListener = onClickListener;
+ }
+
+ protected void setCanvasWidth(int canvasWidth) {
+ this.canvasWidth = canvasWidth;
+ }
+
+ protected void setCanvasHeight(int canvasHeight) {
+ this.canvasHeight = canvasHeight;
+ }
+
+ protected void setFitScaleHorizontal(float fitScale) {
+ this.fitScaleHorizontal = fitScale;
+ }
+
+ protected void setFitScaleVertical(float fitScaleVertical) {
+ this.fitScaleVertical = fitScaleVertical;
+ }
+
+ protected void boundCoordinates() {
+ if(next.x < boundaryLeft) {
+ next.x = boundaryLeft;
+ }
+ else if(next.x > boundaryRight) {
+ next.x = boundaryRight;
+ }
+
+ if(next.y < boundaryTop) {
+ next.y = boundaryTop;
+ }
+ else if(next.y > boundaryBottom) {
+ next.y = boundaryBottom;
+ }
+ }
+
+ protected void calculateBoundaries() {
+
+ int effectiveWidth = Math.round( (float) imageWidth * currentScale );
+ int effectiveHeight = Math.round( (float) imageHeight * currentScale );
+
+ canDragX = effectiveWidth > displayWidth;
+ canDragY = effectiveHeight > displayHeight;
+
+ if(canDragX) {
+ float diff = (float)(effectiveWidth - displayWidth) / 2.0f;
+ boundaryLeft = centerX - diff;
+ boundaryRight = centerX + diff;
+ }
+
+ if(canDragY) {
+ float diff = (float)(effectiveHeight - displayHeight) / 2.0f;
+ boundaryTop = centerY - diff;
+ boundaryBottom = centerY + diff;
+ }
+ }
+}
diff --git a/android/experimental/DocumentLoader/src/com/polites/android/MathUtils.java b/android/experimental/DocumentLoader/src/com/polites/android/MathUtils.java
new file mode 100644
index 000000000000..df7f30db54a7
--- /dev/null
+++ b/android/experimental/DocumentLoader/src/com/polites/android/MathUtils.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+import android.graphics.PointF;
+import android.util.FloatMath;
+import android.view.MotionEvent;
+
+public class MathUtils {
+
+ public static float distance(MotionEvent event) {
+ float x = event.getX(0) - event.getX(1);
+ float y = event.getY(0) - event.getY(1);
+ return FloatMath.sqrt(x * x + y * y);
+ }
+
+ public static float distance(PointF p1, PointF p2) {
+ float x = p1.x - p2.x;
+ float y = p1.y - p2.y;
+ return FloatMath.sqrt(x * x + y * y);
+ }
+
+ public static float distance(float x1, float y1, float x2, float y2) {
+ float x = x1 - x2;
+ float y = y1 - y2;
+ return FloatMath.sqrt(x * x + y * y);
+ }
+
+ public static void midpoint(MotionEvent event, PointF point) {
+ float x1 = event.getX(0);
+ float y1 = event.getY(0);
+ float x2 = event.getX(1);
+ float y2 = event.getY(1);
+ midpoint(x1, y1, x2, y2, point);
+ }
+
+ public static void midpoint(float x1, float y1, float x2, float y2, PointF point) {
+ point.x = (x1 + x2) / 2.0f;
+ point.y = (y1 + y2) / 2.0f;
+ }
+ /**
+ * Rotates p1 around p2 by angle degrees.
+ * @param p1
+ * @param p2
+ * @param angle
+ */
+ public void rotate(PointF p1, PointF p2, float angle) {
+ float px = p1.x;
+ float py = p1.y;
+ float ox = p2.x;
+ float oy = p2.y;
+ p1.x = (FloatMath.cos(angle) * (px-ox) - FloatMath.sin(angle) * (py-oy) + ox);
+ p1.y = (FloatMath.sin(angle) * (px-ox) + FloatMath.cos(angle) * (py-oy) + oy);
+ }
+
+ public static float angle(PointF p1, PointF p2) {
+ return angle(p1.x, p1.y, p2.x, p2.y);
+ }
+
+ public static float angle(float x1, float y1, float x2, float y2) {
+ return (float) Math.atan2(y2 - y1, x2 - x1);
+ }
+}
diff --git a/android/experimental/DocumentLoader/src/com/polites/android/MoveAnimation.java b/android/experimental/DocumentLoader/src/com/polites/android/MoveAnimation.java
new file mode 100644
index 000000000000..5303d646672b
--- /dev/null
+++ b/android/experimental/DocumentLoader/src/com/polites/android/MoveAnimation.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+
+/**
+ * @author Jason Polites
+ *
+ */
+public class MoveAnimation implements Animation {
+
+ private boolean firstFrame = true;
+
+ private float startX;
+ private float startY;
+
+ private float targetX;
+ private float targetY;
+ private long animationTimeMS = 100;
+ private long totalTime = 0;
+
+ private MoveAnimationListener moveAnimationListener;
+
+ /* (non-Javadoc)
+ * @see com.polites.android.Animation#update(com.polites.android.GestureImageView, long)
+ */
+ @Override
+ public boolean update(GestureImageView view, long time) {
+ totalTime += time;
+
+ if(firstFrame) {
+ firstFrame = false;
+ startX = view.getImageX();
+ startY = view.getImageY();
+ }
+
+ if(totalTime < animationTimeMS) {
+
+ float ratio = (float) totalTime / animationTimeMS;
+
+ float newX = ((targetX - startX) * ratio) + startX;
+ float newY = ((targetY - startY) * ratio) + startY;
+
+ if(moveAnimationListener != null) {
+ moveAnimationListener.onMove(newX, newY);
+ }
+
+ return true;
+ }
+ else {
+ if(moveAnimationListener != null) {
+ moveAnimationListener.onMove(targetX, targetY);
+ }
+ }
+
+ return false;
+ }
+
+ public void reset() {
+ firstFrame = true;
+ totalTime = 0;
+ }
+
+
+ public float getTargetX() {
+ return targetX;
+ }
+
+
+ public void setTargetX(float targetX) {
+ this.targetX = targetX;
+ }
+
+
+ public float getTargetY() {
+ return targetY;
+ }
+
+ public void setTargetY(float targetY) {
+ this.targetY = targetY;
+ }
+
+ public long getAnimationTimeMS() {
+ return animationTimeMS;
+ }
+
+ public void setAnimationTimeMS(long animationTimeMS) {
+ this.animationTimeMS = animationTimeMS;
+ }
+
+ public void setMoveAnimationListener(MoveAnimationListener moveAnimationListener) {
+ this.moveAnimationListener = moveAnimationListener;
+ }
+}
diff --git a/android/experimental/DocumentLoader/src/com/polites/android/MoveAnimationListener.java b/android/experimental/DocumentLoader/src/com/polites/android/MoveAnimationListener.java
new file mode 100644
index 000000000000..a19a265e5844
--- /dev/null
+++ b/android/experimental/DocumentLoader/src/com/polites/android/MoveAnimationListener.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+
+/**
+ * @author Jason Polites
+ *
+ */
+public interface MoveAnimationListener {
+
+ public void onMove(float x, float y);
+
+}
diff --git a/android/experimental/DocumentLoader/src/com/polites/android/VectorF.java b/android/experimental/DocumentLoader/src/com/polites/android/VectorF.java
new file mode 100644
index 000000000000..1ff4b19d7e4f
--- /dev/null
+++ b/android/experimental/DocumentLoader/src/com/polites/android/VectorF.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+import android.graphics.PointF;
+import android.util.FloatMath;
+import android.view.MotionEvent;
+
+public class VectorF {
+
+ public float angle;
+ public float length;
+
+ public final PointF start = new PointF();
+ public final PointF end = new PointF();
+
+ public void calculateEndPoint() {
+ end.x = FloatMath.cos(angle) * length + start.x;
+ end.y = FloatMath.sin(angle) * length + start.y;
+ }
+
+ public void setStart(PointF p) {
+ this.start.x = p.x;
+ this.start.y = p.y;
+ }
+
+ public void setEnd(PointF p) {
+ this.end.x = p.x;
+ this.end.y = p.y;
+ }
+
+ public void set(MotionEvent event) {
+ this.start.x = event.getX(0);
+ this.start.y = event.getY(0);
+ this.end.x = event.getX(1);
+ this.end.y = event.getY(1);
+ }
+
+ public float calculateLength() {
+ length = MathUtils.distance(start, end);
+ return length;
+ }
+
+ public float calculateAngle() {
+ angle = MathUtils.angle(start, end);
+ return angle;
+ }
+
+
+}
diff --git a/android/experimental/DocumentLoader/src/com/polites/android/ZoomAnimation.java b/android/experimental/DocumentLoader/src/com/polites/android/ZoomAnimation.java
new file mode 100644
index 000000000000..673b7f9cb148
--- /dev/null
+++ b/android/experimental/DocumentLoader/src/com/polites/android/ZoomAnimation.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+import android.graphics.PointF;
+
+
+/**
+ * @author Jason Polites
+ *
+ */
+public class ZoomAnimation implements Animation {
+
+ private boolean firstFrame = true;
+
+ private float touchX;
+ private float touchY;
+
+ private float zoom;
+
+ private float startX;
+ private float startY;
+ private float startScale;
+
+ private float xDiff;
+ private float yDiff;
+ private float scaleDiff;
+
+ private long animationLengthMS = 200;
+ private long totalTime = 0;
+
+ private ZoomAnimationListener zoomAnimationListener;
+
+ /* (non-Javadoc)
+ * @see com.polites.android.Animation#update(com.polites.android.GestureImageView, long)
+ */
+ @Override
+ public boolean update(GestureImageView view, long time) {
+ if(firstFrame) {
+ firstFrame = false;
+
+ startX = view.getImageX();
+ startY = view.getImageY();
+ startScale = view.getScale();
+ scaleDiff = (zoom * startScale) - startScale;
+
+ if(scaleDiff > 0) {
+ // Calculate destination for midpoint
+ VectorF vector = new VectorF();
+
+ // Set the touch point as start because we want to move the end
+ vector.setStart(new PointF(touchX, touchY));
+ vector.setEnd(new PointF(startX, startY));
+
+ vector.calculateAngle();
+
+ // Get the current length
+ float length = vector.calculateLength();
+
+ // Multiply length by zoom to get the new length
+ vector.length = length*zoom;
+
+ // Now deduce the new endpoint
+ vector.calculateEndPoint();
+
+ xDiff = vector.end.x - startX;
+ yDiff = vector.end.y - startY;
+ }
+ else {
+ // Zoom out to center
+ xDiff = view.getCenterX() - startX;
+ yDiff = view.getCenterY() - startY;
+ }
+ }
+
+ totalTime += time;
+
+ float ratio = (float) totalTime / (float) animationLengthMS;
+
+ if(ratio < 1) {
+
+ if(ratio > 0) {
+ // we still have time left
+ float newScale = (ratio * scaleDiff) + startScale;
+ float newX = (ratio * xDiff) + startX;
+ float newY = (ratio * yDiff) + startY;
+
+ if(zoomAnimationListener != null) {
+ zoomAnimationListener.onZoom(newScale, newX, newY);
+ }
+ }
+
+ return true;
+ }
+ else {
+
+ float newScale = scaleDiff + startScale;
+ float newX = xDiff + startX;
+ float newY = yDiff + startY;
+
+ if(zoomAnimationListener != null) {
+ zoomAnimationListener.onZoom(newScale, newX, newY);
+ zoomAnimationListener.onComplete();
+ }
+
+ return false;
+ }
+ }
+
+ public void reset() {
+ firstFrame = true;
+ totalTime = 0;
+ }
+
+ public float getZoom() {
+ return zoom;
+ }
+
+ public void setZoom(float zoom) {
+ this.zoom = zoom;
+ }
+
+ public float getTouchX() {
+ return touchX;
+ }
+
+ public void setTouchX(float touchX) {
+ this.touchX = touchX;
+ }
+
+ public float getTouchY() {
+ return touchY;
+ }
+
+ public void setTouchY(float touchY) {
+ this.touchY = touchY;
+ }
+
+ public long getAnimationLengthMS() {
+ return animationLengthMS;
+ }
+
+ public void setAnimationLengthMS(long animationLengthMS) {
+ this.animationLengthMS = animationLengthMS;
+ }
+
+ public ZoomAnimationListener getZoomAnimationListener() {
+ return zoomAnimationListener;
+ }
+
+ public void setZoomAnimationListener(ZoomAnimationListener zoomAnimationListener) {
+ this.zoomAnimationListener = zoomAnimationListener;
+ }
+}
diff --git a/android/experimental/DocumentLoader/src/com/polites/android/ZoomAnimationListener.java b/android/experimental/DocumentLoader/src/com/polites/android/ZoomAnimationListener.java
new file mode 100644
index 000000000000..8df4bf641952
--- /dev/null
+++ b/android/experimental/DocumentLoader/src/com/polites/android/ZoomAnimationListener.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2012 Jason Polites
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.polites.android;
+
+
+/**
+ * @author Jason Polites
+ *
+ */
+public interface ZoomAnimationListener {
+ public void onZoom(float scale, float x, float y);
+ public void onComplete();
+}
diff --git a/android/experimental/DocumentLoader/src/org/libreoffice/android/examples/DocumentLoader.java b/android/experimental/DocumentLoader/src/org/libreoffice/android/examples/DocumentLoader.java
index 0778ad95143e..8ead7a96240d 100644
--- a/android/experimental/DocumentLoader/src/org/libreoffice/android/examples/DocumentLoader.java
+++ b/android/experimental/DocumentLoader/src/org/libreoffice/android/examples/DocumentLoader.java
@@ -34,11 +34,28 @@ import android.os.Bundle;
import android.util.Log;
import android.widget.ImageView;
+import com.polites.android.GestureImageView;
+
+import com.sun.star.awt.XBitmap;
+import com.sun.star.awt.XControl;
+import com.sun.star.awt.XDevice;
+import com.sun.star.awt.XToolkit;
+import com.sun.star.beans.PropertyValue;
+import com.sun.star.frame.XComponentLoader;
+import com.sun.star.frame.XController;
+import com.sun.star.frame.XFrame;
+import com.sun.star.frame.XModel;
+import com.sun.star.lang.XEventListener;
+import com.sun.star.lang.XMultiComponentFactory;
+import com.sun.star.lang.XTypeProvider;
+import com.sun.star.uno.Type;
+import com.sun.star.uno.UnoRuntime;
+import com.sun.star.uno.XComponentContext;
+import com.sun.star.view.XRenderable;
+
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
-import com.sun.star.uno.UnoRuntime;
-
import org.libreoffice.android.Bootstrap;
public class DocumentLoader
@@ -47,18 +64,18 @@ public class DocumentLoader
private static String TAG = "DocumentLoader";
class MyXController
- implements com.sun.star.frame.XController {
+ implements XController {
- com.sun.star.frame.XModel model;
- com.sun.star.frame.XFrame frame;
+ XFrame frame;
+ XModel model;
- public void attachFrame(com.sun.star.frame.XFrame frame)
+ public void attachFrame(XFrame frame)
{
Log.i(TAG, "attachFrame");
this.frame = frame;
}
- public boolean attachModel(com.sun.star.frame.XModel model)
+ public boolean attachModel(XModel model)
{
Log.i(TAG, "attachModel");
this.model = model;
@@ -82,13 +99,13 @@ public class DocumentLoader
Log.i(TAG, "restoreViewData");
}
- public com.sun.star.frame.XModel getModel()
+ public XModel getModel()
{
Log.i(TAG, "getModel");
return model;
}
- public com.sun.star.frame.XFrame getFrame()
+ public XFrame getFrame()
{
Log.i(TAG, "getFrame");
return frame;
@@ -99,12 +116,12 @@ public class DocumentLoader
Log.i(TAG, "dispose");
}
- public void addEventListener(com.sun.star.lang.XEventListener listener)
+ public void addEventListener(XEventListener listener)
{
Log.i(TAG, "addEventListener");
}
- public void removeEventListener(com.sun.star.lang.XEventListener listener)
+ public void removeEventListener(XEventListener listener)
{
Log.i(TAG, "removeEventListener");
}
@@ -117,19 +134,19 @@ public class DocumentLoader
if (object == null)
return;
- com.sun.star.lang.XTypeProvider typeProvider = (com.sun.star.lang.XTypeProvider)
- UnoRuntime.queryInterface(com.sun.star.lang.XTypeProvider.class, object);
+ XTypeProvider typeProvider = (XTypeProvider)
+ UnoRuntime.queryInterface(XTypeProvider.class, object);
Log.i(TAG, "typeProvider is " + (typeProvider != null ? typeProvider.toString() : "null"));
if (typeProvider == null)
return;
- com.sun.star.uno.Type[] types = typeProvider.getTypes();
+ Type[] types = typeProvider.getTypes();
if (types == null)
return;
- for (com.sun.star.uno.Type t : types)
+ for (Type t : types)
Log.i(TAG, " " + t.getTypeName());
}
@@ -171,14 +188,13 @@ public class DocumentLoader
Log.i(TAG, "Sleeping NOW");
Thread.sleep(20000);
- com.sun.star.uno.XComponentContext xContext = null;
+ XComponentContext xContext = null;
xContext = com.sun.star.comp.helper.Bootstrap.defaultBootstrap_InitialComponentContext();
Log.i(TAG, "xContext is" + (xContext!=null ? " not" : "") + " null");
- com.sun.star.lang.XMultiComponentFactory xMCF =
- xContext.getServiceManager();
+ XMultiComponentFactory xMCF = xContext.getServiceManager();
Log.i(TAG, "xMCF is" + (xMCF!=null ? " not" : "") + " null");
@@ -204,23 +220,22 @@ public class DocumentLoader
Bootstrap.initUCBHelper();
- com.sun.star.frame.XComponentLoader xCompLoader = (com.sun.star.frame.XComponentLoader)
- UnoRuntime.queryInterface(com.sun.star.frame.XComponentLoader.class, oDesktop);
+ XComponentLoader xCompLoader = (XComponentLoader)
+ UnoRuntime.queryInterface(XComponentLoader.class, oDesktop);
Log.i(TAG, "xCompLoader is" + (xCompLoader!=null ? " not" : "") + " null");
// Load the wanted document(s)
String[] inputs = input.split(":");
for (int i = 0; i < inputs.length; i++) {
- com.sun.star.beans.PropertyValue loadProps[] =
- new com.sun.star.beans.PropertyValue[3];
- loadProps[0] = new com.sun.star.beans.PropertyValue();
+ PropertyValue loadProps[] = new PropertyValue[3];
+ loadProps[0] = new PropertyValue();
loadProps[0].Name = "Hidden";
loadProps[0].Value = new Boolean(true);
- loadProps[1] = new com.sun.star.beans.PropertyValue();
+ loadProps[1] = new PropertyValue();
loadProps[1].Name = "ReadOnly";
loadProps[1].Value = new Boolean(true);
- loadProps[2] = new com.sun.star.beans.PropertyValue();
+ loadProps[2] = new PropertyValue();
loadProps[2].Name = "Preview";
loadProps[2].Value = new Boolean(true);
@@ -234,53 +249,33 @@ public class DocumentLoader
dumpUNOObject("oDoc", oDoc);
- // Test stuff, try creating various services, see what types
- // they offer, stuff that is hard to find out by reading
- // nonexistent useful documentation.
-
- Log.i(TAG, "Attempting to load private:factory/swriter");
-
- Object swriter =
- xCompLoader.loadComponentFromURL
- ("private:factory/swriter", "_blank", 0, loadProps);
-
- dumpUNOObject("swriter", swriter);
-
- Object frameControl = xMCF.createInstanceWithContext
- ("com.sun.star.frame.FrameControl", xContext);
-
- dumpUNOObject("frameControl", frameControl);
-
- com.sun.star.awt.XControl control = (com.sun.star.awt.XControl)
- UnoRuntime.queryInterface(com.sun.star.awt.XControl.class, frameControl);
-
Object toolkit = xMCF.createInstanceWithContext
("com.sun.star.awt.Toolkit", xContext);
dumpUNOObject("toolkit", toolkit);
- com.sun.star.awt.XToolkit xToolkit = (com.sun.star.awt.XToolkit)
- UnoRuntime.queryInterface(com.sun.star.awt.XToolkit.class, toolkit);
+ XToolkit xToolkit = (XToolkit)
+ UnoRuntime.queryInterface(XToolkit.class, toolkit);
- com.sun.star.awt.XDevice device = xToolkit.createScreenCompatibleDevice(1024, 1024);
+ XDevice device = xToolkit.createScreenCompatibleDevice(1024, 1024);
dumpUNOObject("device", device);
// I guess the XRenderable thing might be what we want to use,
// having the code pretend it is printing?
- com.sun.star.view.XRenderable renderBabe = (com.sun.star.view.XRenderable)
- UnoRuntime.queryInterface(com.sun.star.view.XRenderable.class, oDoc);
+ XRenderable renderBabe = (XRenderable)
+ UnoRuntime.queryInterface(XRenderable.class, oDoc);
- com.sun.star.beans.PropertyValue renderProps[] =
- new com.sun.star.beans.PropertyValue[3];
- renderProps[0] = new com.sun.star.beans.PropertyValue();
+ PropertyValue renderProps[] =
+ new PropertyValue[3];
+ renderProps[0] = new PropertyValue();
renderProps[0].Name = "IsPrinter";
renderProps[0].Value = new Boolean(true);
- renderProps[1] = new com.sun.star.beans.PropertyValue();
+ renderProps[1] = new PropertyValue();
renderProps[1].Name = "RenderDevice";
renderProps[1].Value = device;
- renderProps[2] = new com.sun.star.beans.PropertyValue();
+ renderProps[2] = new PropertyValue();
renderProps[2].Name = "View";
renderProps[2].Value = new MyXController();
@@ -288,7 +283,7 @@ public class DocumentLoader
renderBabe.render(0, oDoc, renderProps);
- com.sun.star.awt.XBitmap bitmap = device.createBitmap(0, 0, 1024, 1024);
+ XBitmap bitmap = device.createBitmap(0, 0, 1024, 1024);
byte[] image = bitmap.getDIB();
@@ -328,7 +323,7 @@ public class DocumentLoader
Bootstrap.twiddle_BGR_to_RGBA(image, imagebb.getInt(0x0a), width, height, argb);
- ImageView imageView = new ImageView(this);
+ ImageView imageView = new GestureImageView(this);
Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bm.copyPixelsFromBuffer(argb);