0

I am developing an application in which the user can add images in the canvas and can play with them.

Following is my requirement :

  1. To add bitmaps in the canvas one after the another and if a bitmap has been added in the canvas and moved after adding on the canvas then the next time the user adds another bitmap , the position of the already added bitmap needs to be preserved ----- Canvas already saves it by default

  2. I have three classes for detecting the gestures

    1. MoveGestureDetector
    2. ScaleGestureDetector
    3. RotateGestureDetector

PROBLEM: I am able to successfully detect the touch events on the bitmap which I add on the canvas but not on the previous bitmaps that are previously added on the canvas. So if I add one bitmap on the canvas I can successfully detect touch events on it, but if I add another bitmap then I would not be able to detect touch events on the older one and would be able to detect touch events on the new one only

I have tried almost everything but I am not able to get any insights.

This is how I added the canvas view

FrameLayout gcfl = (FrameLayout) findViewById(R.id.gestureControlledFrameLayout);       
    final GestureViewPort gvp = new GestureViewPort(this);
    gcfl.addView(gvp);

This is my GestureViewPort.java

public class GestureViewPort extends View {
final static int FLIP_VERTICAL = 1;
final static int FLIP_HORIZONTAL = 2;  
private Context context;
private static Bitmap bitmap;
public static List<Layer> layers = new ArrayList<Layer>();  
private List<Bitmap> bitmapList;

public GestureViewPort(Context context) {
    super(context);  
    this.context = context;

    this.bitmapList = ProductDetailActivity.bitmapList;      

    if(bitmapList!=null)
    {  
        for (int bitmapIndex = 0; bitmapIndex < bitmapList.size();bitmapIndex++)
        {                           
            if(bitmapList.get(bitmapIndex)!=null )
            {
                Layer l2 = new Layer(context, this, bitmapList.get(bitmapIndex));
                for(int layerIndex = 0 ; layerIndex < layers.size();layerIndex++)
                {
                    if(bitmapList.get(bitmapIndex).sameAs(layers.get(layerIndex).getBitmap()))
                    {                                                                   
                        if(layers.get(layerIndex).getMoveMatrix()!=null)
                        {   
                            l2.setMoveMatrix(layers.get(layerIndex).getMoveMatrix());
                        }
                        layers.remove(layers.get(layerIndex));  
                        break;
                    }
                }                       
                layers.add(l2);
                invalidate();
            }
        }
        }
    }

@Override
protected void onDraw(Canvas canvas) {
        for(Layer l : layers)
        {
            if(l.getMoveMatrix() != null)
            {
                l.draw(canvas,l.getMoveMatrix());
            }
            else
            {
                l.draw(canvas,null);    
            }
        } 

private Layer target;

@SuppressLint("ClickableViewAccessibility") @Override
public boolean onTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        target = null;
        for (int i = layers.size() - 1; i >= 0; i--)        
        {
            Layer l = layers.get(i);
            if (l.contains(event)) {
                target = l;
                layers.remove(l);
                layers.add(l);
                invalidate();
                break;
            }
        }
    }
    if (target == null) {
        return false;
    }       
    return target.onTouchEvent(event);
}

public int getTopLayer(){
    return layers.size() -1;
}

public void removeLayer(int topLayer){
    layers.remove(topLayer);
    invalidate();
}

public void addLayer(Bitmap drawable){   
    Layer l = new Layer(context, this, drawable);
    layers.add(l);
    invalidate();
}

public void flip(int type){
    target.flip(type);
}    

}

This is my Layer.java

public class Layer {
 Matrix matrix = new Matrix();
 Matrix inverse = new Matrix();
 RectF bounds;
 View parent;
 Bitmap bitmap;   
 Context context;   
 MoveGestureDetector mgd;
 ScaleGestureDetector sgd;
 RotateGestureDetector rgd;
 Matrix moveMatrix ;

 public Layer(){}

public Layer(Context ctx, View p, Bitmap b) {
    this.context = ctx;
    parent = p;
    bitmap = b;
    bounds = new RectF(0, 0, b.getWidth(), b.getHeight());

    mgd = new MoveGestureDetector(context, mgl);
    sgd = new ScaleGestureDetector(context, sgl);
    rgd = new RotateGestureDetector(context, rgl);
    matrix.postTranslate(50 + (float) Math.random() * 50, 50 + (float) Math.random() * 50);
}

  /**
 * @return the bitmap
 */
public Bitmap getBitmap() {
    return bitmap;
}

/**
 * @return the moveMatrix
 */
public Matrix getMoveMatrix() {
    return moveMatrix;
}

/**
 * @param moveMatrix the moveMatrix to set
 */
public void setMoveMatrix(Matrix moveMatrix) {
    this.moveMatrix = moveMatrix;
}


public boolean contains(MotionEvent event) {
    matrix.invert(inverse);
    float[] pts = {event.getX(), event.getY()};      
    Log.e("points",String.valueOf(pts[0])+" " + String.valueOf(pts[1]));
    Log.e("bounds",String.valueOf(bounds.right)+" " + String.valueOf(bounds.bottom));
    inverse.mapPoints(pts);       
        if (!bounds.contains(pts[0], pts[1])) {
            return false;
        }

    return Color.alpha(bitmap.getPixel((int) pts[0], (int) pts[1])) != 0;

}

public boolean onTouchEvent(MotionEvent event) {
    mgd.onTouchEvent(event);
    sgd.onTouchEvent(event);
    rgd.onTouchEvent(event);
    return true;
}
  public void draw(Canvas canvas, Matrix modifiedMatrix) {
    if(modifiedMatrix !=null)
    {
        canvas.drawBitmap(bitmap, modifiedMatrix, null);    
    }
    else
    {
        canvas.drawBitmap(bitmap, matrix, null);
    }
}
   SimpleOnMoveGestureListener mgl = new SimpleOnMoveGestureListener() {
    @Override
    public boolean onMove(MoveGestureDetector detector) {
        PointF delta = detector.getFocusDelta();            
        matrix.postTranslate(delta.x, delta.y);  
        setMoveMatrix(matrix);                     
        parent.invalidate();
        return true;
    }
};

SimpleOnScaleGestureListener sgl = new SimpleOnScaleGestureListener() {
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        float scale = detector.getScaleFactor();
        matrix.postScale(scale, scale, detector.getFocusX(), detector.getFocusY());
        parent.invalidate();
        return true;
    }
};

SimpleOnRotateGestureListener rgl = new SimpleOnRotateGestureListener() {
    @Override
    public boolean onRotate(RotateGestureDetector detector) {
        matrix.postRotate(-detector.getRotationDegreesDelta(), detector.getFocusX(), detector.getFocusY());
        parent.invalidate();
        return true;
    };
};
}

This is my MoveGestureDetector.java

 public class MoveGestureDetector extends BaseGestureDetector {

/**
 * Listener which must be implemented which is used by MoveGestureDetector
 * to perform callbacks to any implementing class which is registered to a
 * MoveGestureDetector via the constructor.
 * 
 * @see MoveGestureDetector.SimpleOnMoveGestureListener
 */
public interface OnMoveGestureListener {
    public boolean onMove(MoveGestureDetector detector);
    public boolean onMoveBegin(MoveGestureDetector detector);
    public void onMoveEnd(MoveGestureDetector detector);
}

/**
 * Helper class which may be extended and where the methods may be
 * implemented. This way it is not necessary to implement all methods
 * of OnMoveGestureListener.
 */
public static class SimpleOnMoveGestureListener implements OnMoveGestureListener {
    public boolean onMove(MoveGestureDetector detector) {
        return false;
    }

    public boolean onMoveBegin(MoveGestureDetector detector) {
        return true;
    }

    public void onMoveEnd(MoveGestureDetector detector) {
        // Do nothing, overridden implementation may be used
    }
}

private static final PointF FOCUS_DELTA_ZERO = new PointF();

private final OnMoveGestureListener mListener;

private PointF mCurrFocusInternal;
private PointF mPrevFocusInternal;  
private PointF mFocusExternal = new PointF();
private PointF mFocusDeltaExternal = new PointF();


public MoveGestureDetector(Context context, OnMoveGestureListener listener) {
    super(context);
    mListener = listener;
}

@Override
protected void handleStartProgressEvent(int actionCode, MotionEvent event){
    switch (actionCode) { 
        case MotionEvent.ACTION_DOWN: 
            resetState(); // In case we missed an UP/CANCEL event

            mPrevEvent = MotionEvent.obtain(event);
            mTimeDelta = 0;

            updateStateByEvent(event);
            break;

        case MotionEvent.ACTION_MOVE:
            mGestureInProgress = mListener.onMoveBegin(this);
            break;
    }
}

@Override
protected void handleInProgressEvent(int actionCode, MotionEvent event){    
    switch (actionCode) {
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            mListener.onMoveEnd(this);
            resetState();
            break;

        case MotionEvent.ACTION_MOVE:
            updateStateByEvent(event);

            // Only accept the event if our relative pressure is within
            // a certain limit. This can help filter shaky data as a
            // finger is lifted.
            if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
                final boolean updatePrevious = mListener.onMove(this);
                if (updatePrevious) {
                    mPrevEvent.recycle();
                    mPrevEvent = MotionEvent.obtain(event);
                }
            }
            break;
    }
}

protected void updateStateByEvent(MotionEvent curr) {
    super.updateStateByEvent(curr);

    final MotionEvent prev = mPrevEvent;

    // Focus intenal
    mCurrFocusInternal = determineFocalPoint(curr);
    mPrevFocusInternal = determineFocalPoint(prev);

    // Focus external
    // - Prevent skipping of focus delta when a finger is added or removed
    boolean mSkipNextMoveEvent = prev.getPointerCount() != curr.getPointerCount();
    mFocusDeltaExternal = mSkipNextMoveEvent ? FOCUS_DELTA_ZERO : new PointF(mCurrFocusInternal.x - mPrevFocusInternal.x,  mCurrFocusInternal.y - mPrevFocusInternal.y);

    // - Don't directly use mFocusInternal (or skipping will occur). Add 
    //   unskipped delta values to mFocusExternal instead.
    mFocusExternal.x += mFocusDeltaExternal.x;
    mFocusExternal.y += mFocusDeltaExternal.y;        
}

/**
 * Determine (multi)finger focal point (a.k.a. center point between all
 * fingers)
 * 
 * @param MotionEvent e
 * @return PointF focal point
 */
private PointF determineFocalPoint(MotionEvent e){
    // Number of fingers on screen
    final int pCount = e.getPointerCount(); 
    float x = 0f;
    float y = 0f;

    for(int i = 0; i < pCount; i++){
        x += e.getX(i);
        y += e.getY(i);
    }

    return new PointF(x/pCount, y/pCount);
}

public float getFocusX() {
    return mFocusExternal.x;
}

public float getFocusY() {
    return mFocusExternal.y;
}

public PointF getFocusDelta() {
    return mFocusDeltaExternal;
}

}

This is RotateGestureDetector.java

public class RotateGestureDetector extends TwoFingerGestureDetector {

/**
 * Listener which must be implemented which is used by RotateGestureDetector
 * to perform callbacks to any implementing class which is registered to a
 * RotateGestureDetector via the constructor.
 * 
 * @see RotateGestureDetector.SimpleOnRotateGestureListener
 */
public interface OnRotateGestureListener {
    public boolean onRotate(RotateGestureDetector detector);
    public boolean onRotateBegin(RotateGestureDetector detector);
    public void onRotateEnd(RotateGestureDetector detector);
}

/**
 * Helper class which may be extended and where the methods may be
 * implemented. This way it is not necessary to implement all methods
 * of OnRotateGestureListener.
 */
public static class SimpleOnRotateGestureListener implements OnRotateGestureListener {
    public boolean onRotate(RotateGestureDetector detector) {
        return false;
    }

    public boolean onRotateBegin(RotateGestureDetector detector) {
        return true;
    }

    public void onRotateEnd(RotateGestureDetector detector) {
        // Do nothing, overridden implementation may be used
    }
}


private final OnRotateGestureListener mListener;
private boolean mSloppyGesture;

public RotateGestureDetector(Context context, OnRotateGestureListener listener) {
    super(context);
    mListener = listener;
}

@Override
protected void handleStartProgressEvent(int actionCode, MotionEvent event){
    switch (actionCode) {
        case MotionEvent.ACTION_POINTER_DOWN:
            // At least the second finger is on screen now

            resetState(); // In case we missed an UP/CANCEL event
            mPrevEvent = MotionEvent.obtain(event);
            mTimeDelta = 0;

            updateStateByEvent(event);

            // See if we have a sloppy gesture
            mSloppyGesture = isSloppyGesture(event);
            if(!mSloppyGesture){
                // No, start gesture now
                mGestureInProgress = mListener.onRotateBegin(this);
            } 
            break;

        case MotionEvent.ACTION_MOVE:
            if (!mSloppyGesture) {
                break;
            }

            // See if we still have a sloppy gesture
            mSloppyGesture = isSloppyGesture(event);
            if(!mSloppyGesture){
                // No, start normal gesture now
                mGestureInProgress = mListener.onRotateBegin(this);
            }

            break;

        case MotionEvent.ACTION_POINTER_UP:
            if (!mSloppyGesture) {
                break;
            }

            break; 
    }
}


@Override
protected void handleInProgressEvent(int actionCode, MotionEvent event){    
    switch (actionCode) {
        case MotionEvent.ACTION_POINTER_UP:
            // Gesture ended but 
            updateStateByEvent(event);

            if (!mSloppyGesture) {
                mListener.onRotateEnd(this);
            }

            resetState();
            break;

        case MotionEvent.ACTION_CANCEL:
            if (!mSloppyGesture) {
                mListener.onRotateEnd(this);
            }

            resetState();
            break;

        case MotionEvent.ACTION_MOVE:
            updateStateByEvent(event);

            // Only accept the event if our relative pressure is within
            // a certain limit. This can help filter shaky data as a
            // finger is lifted.
            if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
                final boolean updatePrevious = mListener.onRotate(this);
                if (updatePrevious) {
                    mPrevEvent.recycle();
                    mPrevEvent = MotionEvent.obtain(event);
                }
            }
            break;
    }
}

@Override
protected void resetState() {
    super.resetState();
    mSloppyGesture = false;
}


/**
 * Return the rotation difference from the previous rotate event to the current
 * event. 
 * 
 * @return The current rotation //difference in degrees.
 */
public float getRotationDegreesDelta() {
    double diffRadians = Math.atan2(mPrevFingerDiffY, mPrevFingerDiffX) - Math.atan2(mCurrFingerDiffY, mCurrFingerDiffX);
    return (float) (diffRadians * 180 / Math.PI);
}

public float getFocusX() {
    return mCurrEvent.getX() + mCurrFingerDiffX * 0.5f;
}

public float getFocusY() {
    return mCurrEvent.getY() + mCurrFingerDiffY * 0.5f;
}

}

Smit Davda
  • 638
  • 5
  • 15
  • what is getMoveMatrix()? – pskink Apr 15 '15 at 10:59
  • what else can i ignore? – pskink Apr 15 '15 at 11:01
  • Just edited my question...sorry for the inconvenience caused..everything now is to be considered – Smit Davda Apr 15 '15 at 11:07
  • your onTouchEvent iterates over every Layer and checks, if the action is ACTION_DOWN, what Layer is under the pointer and moves that layer on top of other layers, so whats the problem? – pskink Apr 15 '15 at 11:10
  • The problem is that all the gestures are detected only by the topmost layer and not by the layers beneath them. I can rotate, move , scale the newest bitmap but not the bitmaps previously added.. – Smit Davda Apr 15 '15 at 11:14
  • of course you can: see my answer here: http://stackoverflow.com/a/21657145/2252830 – pskink Apr 15 '15 at 11:16
  • But in that case the position of the previously drawn bitmap would not be maintained. In the example you showed all the bitmaps are redrawn again at the default position. So lets say that you drew a bitmap on the canvas from the last element in an arraylist , then you changed the position of the bitmap on the canvas, then you added another element in the list and you want to draw it on the canvas , it gets drawn but the previous elements position would not be maintained. Checkout ProductDetailActivity.bitmapList in my code. – Smit Davda Apr 15 '15 at 11:28
  • i dont really understand what you want, sorry – pskink Apr 15 '15 at 11:35
  • I have a static list ProductDetailActivity.bitmapList which gets updated by some event. Everytime I want to draw the last element of that list in my canvas. So for example I drew a bitmap on the canvas and then I modify its position on the canvas by moving it or rotating it etc. Then by some event I add another bitmap to the ProductDetailActivity.bitmapList so I draw the newly added bitmap to the canvas . But the position of the previously drawn bitmap would not be maintained if I draw all the elements in the list again. Sorry for my english but I am not able to put across my problem – Smit Davda Apr 15 '15 at 11:46
  • Am I now clear ? can you please suggest ? Else can you give your email address? – Smit Davda Apr 15 '15 at 15:24
  • no, i still dont understand what you mean – pskink Apr 15 '15 at 15:28
  • What I am trying to explain is that every time I add a bitmap to ProductDetailActivity.bitmapList , I draw it to a canvas. My requirement is that the canvas should maintain the position of the bitmaps that were previously drawn on it. As you would see in my code I am only adding the last element of ProductDetailActivity.bitmapList in my layers list which is static. I am not drawing all the bitmaps at once just as you are drawing it from the array. bitmap = ProductDetailActivity.bitmapList.get(ProductDetailActivity.bitmapList.size()-1); – Smit Davda Apr 15 '15 at 15:44
  • so you have only one Layer in `layers` list, right? so only one Bitmap will be drawn then – pskink Apr 15 '15 at 15:53
  • I have a activity X that contains a canvasview.The X activity also has a drawer containing listview.On selecting a item from list user is redirected to activity Y and a image is shown to him.On selecting that image he is redirected to activity X ,and the image is shown on canvas.Now suppose he changes position of image on canvas.Then selects another item from list so is again redirected to activity Y showing a different image and on selecting that image is again redirected to X with new image being displayed on canvas but position of previous image if maintained doesnot respond to touch events – Smit Davda Apr 15 '15 at 16:11
  • The layers list is a static list so for the first time it would contain only one element but afterwards it will have multiple elements public static List layers = new ArrayList(); – Smit Davda Apr 15 '15 at 16:12
  • so when you have two or more Layers, you cannot bring any of them to front? – pskink Apr 15 '15 at 16:24
  • I can bring the latest one to the front but not the previous one. I can move,drag ,scale the latest one but not the previous one. – Smit Davda Apr 15 '15 at 16:26
  • in my version you have 3 Layers and you can resize/move only the last one or you can select any of them and then resize/move it? – pskink Apr 15 '15 at 16:28
  • In your version I can select/move any of them, but the position is not maintained of the moved layer once the user exits the activity containing the canvas . I want something similar to this app https://play.google.com/store/apps/details?id=com.polyvore&hl=en – Smit Davda Apr 15 '15 at 16:32
  • so save it somewhere, you need to only save the Matrix state nothing else – pskink Apr 15 '15 at 16:39
  • I am saving it and redrawing it again with the new matrix and I am successful but the problem is that sometimes a bitmap is not able to be touched as it is not satisying the contains condition ` if (!bounds.contains(pts[0], pts[1])) { return false; }` and hence unable to get moved/scaled/rotated – Smit Davda Apr 15 '15 at 16:48
  • I have updated the code to maintain the position of bitmaps in the canvas...can you please check it out ? I am saving the matrix in the onMove method matrix.postTranslate(delta.x, delta.y); setMoveMatrix(matrix); – Smit Davda Apr 16 '15 at 06:03
  • what is getMoveMatrix()? can i ignore that? why do you complicate such simple problem? why dont you just use one Matrix? – pskink Apr 16 '15 at 06:30
  • I have taken another matrix named "moveMatrix " in which I save the old matrix's state in the onMove method public boolean onMove(MoveGestureDetector detector) { PointF delta = detector.getFocusDelta(); matrix.postTranslate(delta.x, delta.y); setMoveMatrix(matrix); parent.invalidate(); return true; Then in the constructor of GestureViewPort I check if there already exists a moveMatrix for a layer via the getMoveMatrix() method and if yes I draw the layer at that matrix . Let me know if there is a better way to do it – Smit Davda Apr 16 '15 at 06:47
  • i already did: use ONE Matrix, dont make your life harder – pskink Apr 16 '15 at 06:49
  • Would it be possible for you to provide some example code ? – Smit Davda Apr 16 '15 at 06:51
  • it's not android specific problem, so you need to do it by yourself – pskink Apr 16 '15 at 06:55
  • I finally managed to do it....thanks for your support – Smit Davda Apr 16 '15 at 08:10

0 Answers0