com.codename1.components.ImageViewer Maven / Gradle / Ivy
/*
* Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codename1.components;
import com.codename1.ui.Component;
import static com.codename1.ui.ComponentSelector.$;
import com.codename1.ui.Display;
import com.codename1.ui.Form;
import com.codename1.ui.Graphics;
import com.codename1.ui.Image;
import com.codename1.ui.ImageFactory;
import com.codename1.ui.animations.Animation;
import com.codename1.ui.animations.Motion;
import com.codename1.ui.events.DataChangedListener;
import com.codename1.ui.events.SelectionListener;
import com.codename1.ui.geom.Dimension;
import com.codename1.ui.geom.Rectangle;
import com.codename1.ui.list.DefaultListModel;
import com.codename1.ui.list.ListModel;
import com.codename1.ui.plaf.Style;
/**
* ImageViewer allows zooming/panning an image and potentially flicking between multiple images
* within a list of images.
* E.g. the trivial usage works like this:
*
*
*
*
* You can simulate pinch to zoom on the simulator by dragging the right button away from the top left corner to
* zoom in and towards the top left corner to zoom out. On Mac touchpads you can drag two fingers to achieve that.
*
*
* A more elaborate usage includes flicking between multiple images e.g.:
*
*
*
*
*
* You can even download image URL's dynamically into the {@code ImageViewer} thanks to the usage of the
* {@link com.codename1.ui.list.ListModel}. E.g. in this model book cover images are downloaded dynamically:
*
*
*
*
* @author Shai Almog
*/
public class ImageViewer extends Component {
private float zoom = 1;
private float currentZoom = 1;
private static final int MIN_ZOOM = 1;
private static final int MAX_ZOOM = 10;
private Image image;
private int imageX, imageY, imageDrawWidth, imageDrawHeight;
private float panPositionX = 0.5f;
private float panPositionY = 0.5f;
private int pressX, pressY;
private ListModel swipeableImages;
private DataChangedListener listListener;
private Image swipePlaceholder;
private float swipeThreshold = 0.4f;
private int imageInitialPosition = IMAGE_FIT;
private Motion motion;
private boolean zooming = false;
private boolean animateZoom = true;
/**
* Allows the image to scale down when image initial position is set to fit
* this is off by default since the UX isn't great
*/
private boolean allowScaleDown;
/**
* Indicates the initial position of the image in the viewer to FIT to the
* component size
*/
public final static int IMAGE_FIT = 0;
/**
* Indicates the initial position of the image in the viewer to FILL the
* component size.
* Notice this type might drop edges of the images in order to stretch the image
* to the full size of the Component.
*/
public final static int IMAGE_FILL = 1;
// return values from image aspect calc
private int prefX, prefY, prefW, prefH;
private boolean eagerLock = true;
private boolean selectLock;
private boolean cycleLeft = true;
private boolean cycleRight = true;
/**
* Default constructor
*/
public ImageViewer() {
setFocusable(true);
setUIID("ImageViewer");
$(this).selectAllStyles().setBgTransparency(0x0);
}
/**
* {@inheritDoc}
*/
protected void resetFocusable() {
setFocusable(true);
}
/**
* {@inheritDoc}
*/
public String[] getPropertyNames() {
return new String[] {"eagerLock", "image", "imageList", "swipePlaceholder"};
}
/**
* {@inheritDoc}
*/
protected boolean shouldBlockSideSwipe() {
return true;
}
/**
* {@inheritDoc}
*/
public Class[] getPropertyTypes() {
return new Class[] {Boolean.class, Image.class,
com.codename1.impl.CodenameOneImplementation.getImageArrayClass(), Image.class};
}
/**
* {@inheritDoc}
*/
public String[] getPropertyTypeNames() {
return new String[] {"Boolean", "Image", "Image[]", "Image"};
}
/**
* {@inheritDoc}
*/
public Object getPropertyValue(String name) {
if(name.equals("eagerLock")) {
if(isEagerLock()) {
return Boolean.TRUE;
}
return Boolean.FALSE;
}
if(name.equals("image")) {
return getImage();
}
if(name.equals("imageList")) {
if(getImageList() == null) {
return null;
}
Image[] a = new Image[getImageList().getSize()];
int alen = a.length;
for(int iter = 0 ; iter < alen ; iter++) {
a[iter] = getImageList().getItemAt(iter);
}
return a;
}
if(name.equals("swipePlaceholder")) {
return getSwipePlaceholder();
}
return null;
}
/**
* {@inheritDoc}
*/
public String setPropertyValue(String name, Object value) {
if(name.equals("eagerLock")) {
setEagerLock(value != null && ((Boolean)value).booleanValue());
return null;
}
if(name.equals("image")) {
setImage((Image)value);
return null;
}
if(name.equals("imageList")) {
if(value == null) {
setImageList(null);
} else {
setImageList(new DefaultListModel((Image[])value));
}
return null;
}
if(name.equals("swipePlaceholder")) {
setSwipePlaceholder((Image)value);
return null;
}
return super.setPropertyValue(name, value);
}
/**
* {@inheritDoc}
*/
@Override
public void initComponent() {
super.initComponent();
if(image == null) {
// gui builder?
image = ImageFactory.createImage(this, 50, 50, 0);
} else {
image.lock();
}
if(image.isAnimation()) {
getComponentForm().registerAnimated(this);
}
eagerLock();
}
private void eagerLock() {
if(eagerLock) {
if(swipeableImages != null && swipeableImages.getSize() > 1) {
Image left = getImageLeft();
if(swipePlaceholder != null) {
left.asyncLock(swipePlaceholder);
} else {
left.lock();
}
if(swipeableImages.getSize() > 2) {
Image right = getImageRight();
if(swipePlaceholder != null) {
right.asyncLock(swipePlaceholder);
} else {
right.lock();
}
}
}
}
}
private void eagerUnlock() {
if(eagerLock) {
if(swipeableImages != null && swipeableImages.getSize() > 1) {
getImageLeft().unlock();
getImageRight().unlock();
}
}
}
/**
* Returns the x position of the image viewport which can be useful when it is being panned by the user
* @return x position within the image for the top left corner
*/
public int getImageX() {
return imageX;
}
/**
* Returns the y position of the image viewport which can be useful when it is being panned by the user
* @return y position within the image for the top left corner
*/
public int getImageY() {
return imageY;
}
/**
* {@inheritDoc}
*/
@Override
public void deinitialize() {
super.deinitialize();
image.unlock();
eagerUnlock();
}
/**
* Initializes the component with an image
* @param i image to show
*/
public ImageViewer(Image i) {
this();
setImage(i);
}
/**
* {@inheritDoc}
*/
@Override
public void keyReleased(int key) {
if(swipeableImages != null) {
int gk = Display.getInstance().getGameAction(key);
if((gk == Display.GAME_LEFT || gk == Display.GAME_UP) && (cycleLeft || swipeableImages.getSelectedIndex() > getImageLeftPos())) {
new AnimatePanX(-1, getImageLeft(), getImageLeftPos());
return;
}
if(gk == Display.GAME_RIGHT || gk == Display.GAME_RIGHT && (cycleRight || swipeableImages.getSelectedIndex() < getImageRightPos())) {
new AnimatePanX(2, getImageRight(), getImageRightPos());
return;
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void pointerPressed(int x, int y) {
pressX = x;
pressY = y;
currentZoom = zoom;
getComponentForm().addComponentAwaitingRelease(this);
}
@Override
protected void dragFinished(int x, int y) {
super.dragFinished(x, y);
}
private Image getImageRight() {
return swipeableImages.getItemAt(getImageRightPos());
}
private int getImageRightPos() {
return (swipeableImages.getSelectedIndex() + 1) % swipeableImages.getSize();
}
private int getImageLeftPos() {
int pos = swipeableImages.getSelectedIndex() - 1;
if(pos < 0) {
return swipeableImages.getSize() - 1;
}
return pos;
}
private Image getImageLeft() {
return swipeableImages.getItemAt(getImageLeftPos());
}
/**
* {@inheritDoc}
*/
@Override
public void pointerReleased(int x, int y) {
super.pointerReleased(x, y);
isPinchZooming = false;
if(panPositionX > 1) {
if(panPositionX >= 1 + swipeThreshold && (cycleRight || swipeableImages.getSelectedIndex() < getImageRightPos())) {
new AnimatePanX(2, getImageRight(), getImageRightPos());
} else {
// animate back
new AnimatePanX(1, null, 0);
}
return;
}
if(panPositionX < 0) {
if(panPositionX <= swipeThreshold * -1 && (cycleLeft || swipeableImages.getSelectedIndex() > getImageLeftPos())) {
new AnimatePanX(-1, getImageLeft(), getImageLeftPos());
} else {
// animate back
new AnimatePanX(0, null, 0);
}
return;
}
}
@Override
protected void pinchReleased(int x, int y) {
pressX = x;
pressY = y;
currentZoom = zoom;
isPinchZooming = false;
}
/**
* {@inheritDoc}
*/
@Override
public void pointerDragged(int x, int y) {
// could be a pan
float distanceX = ((float)pressX - x) / getZoom();
float distanceY = ((float)pressY - y) / getZoom();
// convert to a number between 0 - 1
distanceX /= ((float)getWidth());
distanceY /= ((float)getHeight());
// panning or swiping
if(getZoom() > 1) {
if(swipeableImages != null && swipeableImages.getSize() > 1) {
// this has the potential of being a pan operation...
if(panPositionX < 0 || panPositionX == 0 && distanceX < 0) {
panPositionX = ((float)pressX - x) / ((float)getWidth());
repaint();
return;
} else {
if(panPositionX > 1 || panPositionX == 1 && distanceX > 0) {
panPositionX = 1 + ((float)pressX - x) / ((float)getWidth());
repaint();
return;
}
}
}
pressX = x;
pressY = y;
panPositionX = panPositionX + distanceX * getZoom();
panPositionX = Math.min(1, Math.max(0, panPositionX));
panPositionY = Math.min(1, Math.max(0, panPositionY + distanceY * getZoom()));
updatePositions();
repaint();
} else {
if(swipeableImages != null && swipeableImages.getSize() > 1) {
panPositionX = distanceX;
// this has the potential of being a pan operation...
if(panPositionX < 0) {
repaint();
return;
} else {
if(panPositionX > 0) {
panPositionX += 1;
repaint();
return;
}
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
protected void laidOut() {
super.laidOut();
updatePositions();
}
private boolean isPinchZooming;
/**
* {@inheritDoc}
*/
@Override
protected boolean pinch(float scale) {
isPinchZooming = true;
zoom = currentZoom * scale;
if(zoom < MIN_ZOOM) {
zoom = MIN_ZOOM;
} else {
if(zoom > MAX_ZOOM) {
zoom = MAX_ZOOM;
}
}
updatePositions();
repaint();
return true;
}
private void imageAspectCalc(Image img) {
if(img == null) {
return;
}
int iW = img.getWidth();
int iH = img.getHeight();
Style s = getStyle();
int width = getWidth() - s.getHorizontalPadding();
int height = getHeight() - s.getVerticalPadding();
float r2;
if(imageInitialPosition == IMAGE_FIT){
r2 = Math.min(((float)width) / ((float)iW), ((float)height) / ((float)iH));
}else{
r2 = Math.max(((float)width) / ((float)iW), ((float)height) / ((float)iH));
}
// calculate the image position to fit
prefW = (int)(((float)iW) * r2);
prefH = (int)(((float)iH) * r2);
prefX = s.getPaddingLeftNoRTL() + (width - prefW) / 2;
prefY = s.getPaddingTop() + (height - prefH) / 2;
}
private void updatePositions() {
if(zoom == 1) {
imageAspectCalc(image);
imageDrawWidth = prefW;
imageDrawHeight = prefH;
imageX = prefX;
imageY = prefY;
cropBox.set(-imageY/(double)imageDrawHeight, (imageX + imageDrawWidth - getWidth())/(double)imageDrawWidth, (imageY + imageDrawHeight - getHeight())/(double)imageDrawHeight, -imageX/(double)imageDrawWidth);
return;
}
int iW = image.getWidth();
int iH = image.getHeight();
Style s = getStyle();
int width = getWidth() - s.getHorizontalPadding();
int height = getHeight() - s.getVerticalPadding();
float r2;
if(allowScaleDown || imageInitialPosition == IMAGE_FIT){
r2 = Math.min(((float)width) / ((float)iW), ((float)height) / ((float)iH));
}else{
r2 = Math.max(((float)width) / ((float)iW), ((float)height) / ((float)iH));
}
imageDrawWidth = (int)(((float)iW) * r2 * zoom);
imageDrawHeight = (int)(((float)iH) * r2 * zoom);
imageX = (int)(s.getPaddingLeftNoRTL()+ imageDrawWidth * (0.5-panPositionX));
if (imageDrawWidth < getInnerWidth()) {
imageX += (getInnerWidth() - imageDrawWidth)/2;
}
imageY = (int)(s.getPaddingTop() + imageDrawHeight * (0.5 - panPositionY));
if (imageDrawHeight < getInnerHeight()) {
imageY += (getInnerHeight() - imageDrawHeight)/2;
}
cropBox.set(-imageY/(double)imageDrawHeight, (imageX + imageDrawWidth - getWidth())/(double)imageDrawWidth, (imageY + imageDrawHeight - getHeight())/(double)imageDrawHeight, -imageX/(double)imageDrawWidth);
}
/**
* Gets the current image cropped using the current pan and zoom state. The cropped image
* dimensions will be the result of cropping the full-sized image with the current pan/zoom state. The aspect
* ratio will match the aspect ratio of the ImageViewer - not the source image itself.
* @param backgroundColor The background color, visible for letterboxing.
* @return The cropped image.
* @since 7.0
*/
public Image getCroppedImage(int backgroundColor) {
if (image == null) {
return null;
}
updatePositions();
int width = (int)Math.round(-image.getWidth() * (cropBox.left + cropBox.right - 1));
int height = (int)Math.round(-image.getHeight() * (cropBox.top + cropBox.bottom - 1));
return getCroppedImage(width, height, backgroundColor);
}
/**
* Gets the current image cropped using the current pan and zoom state.
* @param width The width of the cropped image. Use -1 to match aspect ratio of the ImageViewer component. Either height or width must be positive.
* @param height The height of the cropped image. Use -1 to match aspect ratio of the ImageViewer component. Either height or width must be positive.
* @param backgroundColor Background color to use for letterboxing.
* @return Cropped image in specified dimensions.
* @since 7.0
*/
public Image getCroppedImage(int width, int height, int backgroundColor) {
if (image == null) {
return null;
}
updatePositions();
if (width < 0) {
width = (int)Math.round(height * getWidth() / (double)getHeight());
}
if (height < 0) {
height = (int)Math.round(width * getHeight() / (double)getWidth());
}
Image out = ImageFactory.createImage(this, width, height, backgroundColor);
Graphics g = out.getGraphics();
g.setColor(backgroundColor);
g.fillRect(0, 0, width, height);
cropBox.paint(g, width, height);
return out;
}
/**
* {@inheritDoc}
*/
@Override
protected Dimension calcPreferredSize() {
if(image != null) {
return new Dimension(image.getWidth(), image.getHeight());
}
return new Dimension(Display.getInstance().getDisplayWidth(), Display.getInstance().getDisplayHeight());
}
/**
* {@inheritDoc}
*/
@Override
public boolean animate() {
boolean result = false;
if(image != null && image.isAnimation()) {
result = image.animate();
if (result) {
updatePositions();
}
}
if (zooming) {
float v = motion.getValue();
v /= 10000.0f;
zoom = v;
if ( ! result ) {
updatePositions();
}
if(motion.isFinished()) {
zooming = false;
if( ! result ) {
getComponentForm().deregisterAnimated(this);
}
}
repaint();
}
return super.animate() || result;
}
/**
* The crop box. This is automatically updated whenever pan/zoom is changed.
*/
private final CropBox cropBox = new CropBox();
/**
* Allows the image to scale down when image initial position is set to fit
* this is off by default since the UX isn't great
* @return the allowScaleDown
*/
public boolean isAllowScaleDown() {
return allowScaleDown;
}
/**
* Allows the image to scale down when image initial position is set to fit
* this is off by default since the UX isn't great
* @param allowScaleDown the allowScaleDown to set
*/
public void setAllowScaleDown(boolean allowScaleDown) {
this.allowScaleDown = allowScaleDown;
}
private class CropBox {
/**
* The top, left, right, bottom crop positions expressed as
* ratios of corresponding axis length. E.g. top/bottom are
* ratio of crop position to the height. Left/right are ratio of crop
* position to width. Positive values move inward toward image center
* Negative values move box out.
*/
private double top, left, right, bottom;
CropBox() {
}
CropBox(double top, double right, double bottom, double left) {
this.top = top;
this.left = left;
this.right = right;
this.bottom = bottom;
}
void set(double top, double right, double bottom, double left) {
this.top = top;
this.left = left;
this.right = right;
this.bottom = bottom;
}
@Override
public String toString() {
return "CropBox{"+top+", "+right+", "+bottom+", "+left+"}";
}
/**
* Given a cropped image width, this returns the source image width necessary
* to produce the cropped image.
* @param croppedWidth
* @return
*/
private int imageWidthForCroppedWidth(int croppedWidth) {
return (int)Math.round(-croppedWidth/(left + right - 1));
}
/**
* Given a cropped image height, this returns the source image height necessary to
* produce the cropped image.
* @param croppedHeight
* @return
*/
private int imageHeightForCroppedHeight(int croppedHeight) {
return (int)(Math.round(-croppedHeight/(top+bottom-1)));
}
/**
* Paints the source image onto a Graphics context for a cropped image.
* @param g The graphics context
* @param imageWidth The cropped image width.
* @param imageHeight The cropped image height.
*/
private void paint(Graphics g, int imageWidth, int imageHeight) {
int ih = imageHeightForCroppedHeight(imageHeight);
int iw = imageWidthForCroppedWidth(imageWidth);
g.drawImage(image, (int)Math.round(-left * iw), (int)Math.round(-top * ih), iw, ih);
}
}
/**
* {@inheritDoc}
*/
@Override
public void paint(Graphics g) {
if(panPositionX < 0) {
Style s = getStyle();
int width = getWidth() - s.getHorizontalPadding();
float ratio = ((float)width) * (panPositionX * -1);
if (isPinchZooming) {
g.setRenderingHints(Graphics.RENDERING_HINT_FAST);
}
g.drawImage(image, ((int)ratio) + getX() + imageX, getY() + imageY, imageDrawWidth, imageDrawHeight);
g.setRenderingHints(0);
if (cycleLeft || swipeableImages.getSelectedIndex() > getImageLeftPos()) {
Image left = getImageLeft();
if(swipePlaceholder != null) {
left.asyncLock(swipePlaceholder);
} else {
left.lock();
}
ratio = ratio - width;
imageAspectCalc(left);
if (isPinchZooming) {
g.setRenderingHints(Graphics.RENDERING_HINT_FAST);
}
g.drawImage(left, ((int)ratio) + getX() + prefX, getY() + prefY, prefW, prefH);
g.setRenderingHints(0);
}
return;
}
if(panPositionX > 1) {
Style s = getStyle();
int width = getWidth() - s.getHorizontalPadding();
float ratio = ((float)width) * (1 - panPositionX);
if (isPinchZooming) {
g.setRenderingHints(Graphics.RENDERING_HINT_FAST);
}
g.drawImage(image, ((int)ratio) + getX() + imageX, getY() + imageY, imageDrawWidth, imageDrawHeight);
g.setRenderingHints(0);
if (cycleRight || swipeableImages.getSelectedIndex() < getImageRightPos()) {
Image right = getImageRight();
if(swipePlaceholder != null) {
right.asyncLock(swipePlaceholder);
} else {
right.lock();
}
ratio = ratio + width;
imageAspectCalc(right);
if (isPinchZooming) {
g.setRenderingHints(Graphics.RENDERING_HINT_FAST);
}
g.drawImage(right, ((int)ratio) + getX() + prefX, getY() + prefY, prefW, prefH);
g.setRenderingHints(0);
}
return;
}
// can happen in the GUI builder
if(image != null) {
if (isPinchZooming) {
g.setRenderingHints(Graphics.RENDERING_HINT_FAST);
}
g.drawImage(image,
(imageDrawWidth <= getInnerWidth()) ? getX() + imageX : Math.max(
Math.min( //
getX(),
getX() + imageX
),
getX() - imageDrawWidth + getInnerWidth()),
(imageDrawHeight <= getInnerHeight()) ? getY() + imageY : Math.max(
Math.min(
getY(), getY() + imageY
), getY() - imageDrawHeight + getInnerHeight()
),
imageDrawWidth, imageDrawHeight);
g.setRenderingHints(0);
}
}
/**
* {@inheritDoc}
*/
@Override
protected void paintBackground(Graphics g) {
// disable background painting for performance when zooming
if(imageDrawWidth < getWidth() || imageDrawHeight < getHeight()) {
super.paintBackground(g);
}
}
/**
* Returns the currently showing image
* @return the image
*/
public Image getImage() {
return image;
}
/**
* Sets the currently showing image
* @param image the image to set
*/
public void setImage(Image image) {
if(this.image != image) {
panPositionX = 0.5f;
panPositionY = 0.5f;
zoom = MIN_ZOOM;
this.image = image;
updatePositions();
repaint();
if(image.isAnimation()) {
Form f = getComponentForm();
if(f != null) {
f.registerAnimated(this);
}
}
}
}
/**
* Sets the current image without any changes to the panning/scaling
* @param image new image instance
*/
public void setImageNoReposition(Image image) {
this.image = image;
repaint();
}
/**
* By providing this optional list of images you can allows swiping between multiple images
*
* @param model a list of images
*/
public void setImageList(ListModel model) {
if(model == null || model.getSize() == 0) {
return;
}
if(image == null) {
image = model.getItemAt(0);
}
if(swipeableImages != null) {
swipeableImages.removeDataChangedListener(listListener);
swipeableImages.removeSelectionListener((SelectionListener)listListener);
model.addDataChangedListener(listListener);
model.addSelectionListener((SelectionListener)listListener);
} else {
class Listener implements SelectionListener, DataChangedListener {
public void selectionChanged(int oldSelected, int newSelected) {
if(selectLock) {
return;
}
if(swipeableImages.getSize() > 0 && newSelected > -1 && newSelected < swipeableImages.getSize()) {
setImage(swipeableImages.getItemAt(newSelected));
}
}
public void dataChanged(int type, int index) {
if(swipeableImages.getSize() > 0 && swipeableImages.getSelectedIndex() > -1 && swipeableImages.getSelectedIndex() < swipeableImages.getSize()) {
setImage(swipeableImages.getItemAt(swipeableImages.getSelectedIndex()));
}
}
}
listListener = new Listener();
model.addDataChangedListener(listListener);
model.addSelectionListener((SelectionListener)listListener);
}
this.swipeableImages = model;
}
/**
* Returns the list model containing the images in the we can swipe through
* @return the list model
*/
public ListModel getImageList() {
return swipeableImages;
}
/**
* Indicates if the zoom should bee animated. It's true by default
* @param animateZoom true if zoom is animated
*/
public void setAnimateZoom(boolean animateZoom) {
this.animateZoom = animateZoom;
}
/**
* Indicates if the zoom should bee animated. It's true by default
* @return true if zoom is animated
*/
public boolean isAnimatedZoom() {
return animateZoom;
}
/**
* Manipulate the zoom level of the application
* @return the zoom
*/
public float getZoom() {
return zoom;
}
/**
* Manipulate the zoom level of the application
* @param zoom the zoom to set
*/
public void setZoom(float zoom) {
if (animateZoom) {
zooming = true;
float initZoom = this.zoom;
motion = Motion.createEaseInOutMotion((int) (initZoom * 10000), (int)(zoom * 10000), 200);
motion.start();
getComponentForm().registerAnimated(this);
} else {
this.zoom = zoom;
updatePositions();
repaint();
}
}
/**
* Manipulate the zoom level of the application
* @param zoom the zoom to set
* @param panPositionX A float value between 0 and 1 to set the image x position
* @param panPositionY A float value between 0 and 1 to set the image y position
*/
public void setZoom(float zoom, float panPositionX, float panPositionY) {
if (panPositionX > 1)
panPositionX = 1;
if (panPositionX < 0)
panPositionX = 0;
if (panPositionY > 1)
panPositionY = 1;
if (panPositionY < 0)
panPositionY = 0;
this.panPositionX = panPositionX;
this.panPositionY = panPositionY;
if (animateZoom) {
zooming = true;
float initZoom = this.zoom;
motion = Motion.createEaseInOutMotion((int) (initZoom * 10000), (int)(zoom * 10000), 200);
motion.start();
getComponentForm().registerAnimated(this);
} else {
this.zoom = zoom;
updatePositions();
repaint();
}
}
/**
* This image is shown briefly during swiping while the full size image is loaded
* @return the swipePlaceholder
*/
public Image getSwipePlaceholder() {
return swipePlaceholder;
}
/**
* This image is shown briefly during swiping while the full size image is loaded
* @param swipePlaceholder the swipePlaceholder to set
*/
public void setSwipePlaceholder(Image swipePlaceholder) {
this.swipePlaceholder = swipePlaceholder;
}
/**
* Eager locking effectively locks the right/left images as well as the main image, as a result
* more heap is taken
* @return the eagerLock
*/
public boolean isEagerLock() {
return eagerLock;
}
/**
* Eager locking effectively locks the right/left images as well as the main image, as a result
* more heap is taken
* @param eagerLock the eagerLock to set
*/
public void setEagerLock(boolean eagerLock) {
this.eagerLock = eagerLock;
}
/**
* By default the ImageViewer cycles from the beginning to the end of the list
* when going to the left, setting this to false prevents this behaviour
* @return true if it should cycle left from beginning
*/
public boolean isCycleLeft() {
return cycleLeft;
}
/**
* By default the ImageViewer cycles from the beginning to the end of the list
* when going to the left, setting this to false prevents this behaviour
* @param cycleLeft the cycle left to set
*/
public void setCycleLeft(boolean cycleLeft) {
this.cycleLeft = cycleLeft;
}
/**
* By default the ImageViewer cycles from the end to the beginning of the list
* when going to the right, setting this to false prevents this behaviour
* @return true if it should cycle right from the end
*/
public boolean isCycleRight() {
return cycleRight;
}
/**
* By default the ImageViewer cycles from the end to the beginning of the list
* when going to the right, setting this to false prevents this behaviour
* @param cycleRight the cycle right to set
*/
public void setCycleRight(boolean cycleRight) {
this.cycleRight = cycleRight;
}
/**
* The swipe threshold is a number between 0 and 1 that indicates the threshold
* after which the swiped image moves to the next image. Below that number the image
* will bounce back
* @return the threshold
*/
public float getSwipeThreshold() {
return swipeThreshold;
}
/**
* The swipe threshold is a number between 0 and 1 that indicates the threshold
* after which the swiped image moves to the next image. Below that number the image
* will bounce back
* @param swipeThreshold the swipeThreshold to set
*/
public void setSwipeThreshold(float swipeThreshold) {
this.swipeThreshold = swipeThreshold;
}
class AnimatePanX implements Animation {
private Motion motion;
private Image replaceImage;
private int updatePos;
public AnimatePanX(float destPan, Image replaceImage, int updatePos) {
motion = Motion.createEaseInOutMotion((int)(panPositionX * 10000), (int)(destPan * 10000), 200);
motion.start();
this.replaceImage = replaceImage;
this.updatePos = updatePos;
Display.getInstance().getCurrent().registerAnimated(this);
}
@Override
public boolean animate() {
float v = motion.getValue();
v /= 10000.0f;
panPositionX = v;
if(motion.isFinished()) {
if(replaceImage != null) {
if(!eagerLock) {
getImage().unlock();
setImage(replaceImage);
} else {
setImage(replaceImage);
Image left = getImageLeft();
Image right = getImageRight();
if(left != replaceImage) {
left.unlock();
}
if(right != replaceImage) {
right.unlock();
}
selectLock = true;
swipeableImages.setSelectedIndex(updatePos);
selectLock = false;
replaceImage.lock();
eagerLock();
}
selectLock = true;
swipeableImages.setSelectedIndex(updatePos);
selectLock = false;
panPositionX = 0.5f;
panPositionY = 0.5f;
zoom = MIN_ZOOM;
} else {
// free cached memory
if(swipeableImages != null && swipeableImages.getSize() > 1) {
getImageLeft().unlock();
getImageRight().unlock();
}
}
Display.getInstance().getCurrent().deregisterAnimated(this);
}
repaint();
return false;
}
public void paint(Graphics g) {
}
}
/**
* Sets the viewer initial image position to fill or to fit.
* @param imageInitialPosition values can be IMAGE_FILL or IMAGE_FIT
*/
public void setImageInitialPosition(int imageInitialPosition) {
this.imageInitialPosition = imageInitialPosition;
}
}