My solution to the problem


#1

please feel free to correct me if u feel im wrong.
first : ive read other post and ive seen different style of implementation for the first challenge. i want to say that using onSavedInstanceState and onRestoreInstanceState feel time saving rather than using Json.
secondly the rotation of the rectangle is so easy. since it was specified that the all the rectangles should be rotated.
-----u simply set the angle for the box as a member field and the center of the box to be fixed during rotation. then u save,rotate and restore the canvas
see the code below.

package com.example.draganddraw;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;

public class BoxDrawingView extends View{
public static final String TAG = “BoxDrawingView”;
public static final String SERIAL_TAG=“serializable”;
public static final String PERCEA_TAG=“perciable”;
private Box mCurrentBox;
private ArrayList mBoxes=new ArrayList();
private Paint mBoxPaint;
private Paint mBackgroundPaint;
private boolean mShouldRotate=false;
private float mCurrentAngle;
private PointF center;

//used when creating view from code
public BoxDrawingView(Context context) {
	super(context,null);
}
//used   when inflating from layout
public BoxDrawingView(Context context, AttributeSet attrs) {
	super(context, attrs);
	//paint in the xml
	// Paint the boxes a nice semitransparent red (ARGB)
	mBoxPaint= new Paint();
	mBoxPaint.setColor(0x22ff0000);
	
	
	// Paint the background off-white
	mBackgroundPaint= new Paint();
	mBackgroundPaint.setColor(0xaaa8efe0);
	
}
@Override
protected void onFinishInflate() {
	super.onFinishInflate();
	setSaveEnabled(true);
	setId(R.id.boxid);
}

@Override
protected Parcelable onSaveInstanceState() {
	Bundle bundle= new Bundle();
	bundle.putParcelable(PERCEA_TAG, super.onSaveInstanceState());
	bundle.putSerializable(SERIAL_TAG,  getBoxes());
	return bundle;
}

@Override
protected void onRestoreInstanceState(Parcelable state) {
	Bundle bundle=(Bundle)state;
	Parcelable perceable=bundle.getParcelable(PERCEA_TAG);
	setBoxes((ArrayList<Box>) bundle.getSerializable(SERIAL_TAG));
	super.onRestoreInstanceState(perceable);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
	int type=event.getAction();
	 int actionIndex=event.getActionIndex();

	PointF pointF=new PointF(event.getX(),event.getY());
	Log.i(TAG, "Received event at x=" + pointF.x+", y=" + pointF.y + ":");
	switch (type) {
	case MotionEvent.ACTION_CANCEL:
    	Log.i(TAG, " ACTION_CANCEL");
    	mCurrentBox= null;
    	return true;
    case MotionEvent.ACTION_DOWN:
    	Log.i(TAG, " ACTION_DOWN");
    	mCurrentBox= new Box(pointF);
    	mBoxes.add(mCurrentBox);
    	return true;
    case MotionEvent.ACTION_MOVE:
    	Log.i(TAG, " ACTION_MOVE");
    	if(mCurrentBox !=null){
    		mCurrentBox.setCurrent(pointF);
    		invalidate();
    	}
    	rotate(event,actionIndex,true);
    	return true;
    case MotionEvent.ACTION_UP:
    	Log.i(TAG, " ACTION_UP");
    	mCurrentBox= null;

		return true;
    case MotionEvent.ACTION_POINTER_DOWN:
    	rotate(event,actionIndex,false);
     	return true;
    
    	
    case MotionEvent.ACTION_POINTER_UP:
    	stopRotation(event,actionIndex);
    	return true;
	default:
		return super.onTouchEvent(event);
	}
	
}
private void stopRotation(MotionEvent event, int actionIndex) {
	 invalidate();
}
private void rotate(MotionEvent event,int pointerIndex,boolean move){
	//for use in action_pointer up and down
	 int actionMask=event.getActionMasked();
	 float startX = 0;
	 float startY = 0;
	 float endX;
	 float endY;
	 
	 if(event.getPointerCount()==1 && actionMask==MotionEvent.ACTION_POINTER_DOWN){
		 //start the rotation starting point
		 startX=event.getX(pointerIndex);
		 startY=event.getY(pointerIndex);
	 }
	if(event.getPointerCount() ==2 && move){
		mShouldRotate=move;
		int pointerId=event.getPointerId(pointerIndex);
		  center= new  PointF(event.getX(pointerIndex),event.getY(pointerIndex));
		 
    		PointF currentBoxPosition=mCurrentBox.getCurrent();
    		endX=currentBoxPosition.x;
    		endY=currentBoxPosition.y;
    		float adjacent=endX-startX;
    		float opposite=endY-startY;
    		mCurrentAngle= (float)Math.toDegrees(Math.atan(opposite/adjacent)) ;
    		Log.d(TAG,"presetting angle"+mCurrentAngle);
			Toast.makeText(getContext(), "presetting angle"+mCurrentAngle, Toast.LENGTH_SHORT).show();

		
	}
}

@Override
protected void onDraw(Canvas canvas) {
	 //fill the background
	canvas.drawPaint(mBackgroundPaint);
	 Toast.makeText(getContext(), "number of boxes"+mBoxes.size(), Toast.LENGTH_LONG).show();

	for (Box box : mBoxes) {
		float left=Math.min(box.getOrigin().x, box.getCurrent().x);
		float right=Math.max(box.getOrigin().x, box.getCurrent().x);
		float top=Math.min(box.getOrigin().y,box.getCurrent().y);
		float bottom=Math.max(box.getOrigin().y,box.getCurrent().y);

		if(box.equals(mCurrentBox))  
			box.setAngle(mCurrentAngle);
			 
		box.setCenter(center);
		canvas.save();
		if(mShouldRotate){
			canvas.rotate(box.getAngle(), box.getCenter().x, box.getCenter().y);
		}
		canvas.drawRect(left, top, right, bottom, mBoxPaint);
		canvas.restore();
	}
	
 
}
public ArrayList<Box> getBoxes() {
	return mBoxes;
}
public void setBoxes(ArrayList<Box> boxes) {
	mBoxes = boxes;
}

}

Box.java

package com.example.draganddraw;

import android.graphics.PointF;

public class Box {
private PointF mOrigin;
private PointF mCurrent;
private PointF mCenter;
private float mAngle;
public Box(PointF origin) {
mOrigin=mCenter=mCurrent=origin;
mAngle=0;
}
public PointF getOrigin() {
return mOrigin;
}
public void setOrigin(PointF origin) {
mOrigin = origin;
}
public PointF getCurrent() {
return mCurrent;
}
public void setCurrent(PointF current) {
mCurrent = current;
}
public PointF getCenter() {
return mCenter;
}
public void setCenter(PointF center) {
mCenter = center;
}
public float getAngle() {
return mAngle;
}
public void setAngle(float currentAngle) {
mAngle = currentAngle;
}

}


#2

Perhaps a simpler approach for the rotation. Note I am coding all of the examples in C# using Xamarin.Android/MonoDroid, but you should be able to follow along.

I just added a Rotation and a PointerId property to the Box class:

public class Box
    {
...
                int mPointerId;  // <---
		float mRotation; // <---

		public Box(PointF origin, int pointerId) // <---
        {
			mOrigin = mCurrent = origin;
			mPointerId = pointerId;// <---
			mRotation = 0; // <---
        }

...
                public int PointerId { // <---
			get {return mPointerId;} // <---
		} // <---

		public float Rotation { // <---
			get {return mRotation;} // <---
			set {mRotation = value;} // <---
		} // <---
    }

Then I modified OnDraw to honor the Rotation property when drawing the boxes:

protected override void OnDraw(Android.Graphics.Canvas canvas)
		{
			base.OnDraw(canvas);

			// Fill the background
			canvas.DrawPaint(mBackgroundPaint);

			foreach (Box box in mBoxes) {
				float left = Math.Min(box.Origin.X, box.Current.X);
				float right = Math.Max(box.Origin.X, box.Current.X);
				float top = Math.Min(box.Origin.Y, box.Current.Y);
				float bottom = Math.Max(box.Origin.Y, box.Current.Y);
                                // Save the canvas on the stack
				canvas.Save(); // <---
                                // Draw the box after rotating the canvas, setting the rotation point to the box's center
				canvas.Rotate(box.Rotation, (box.Origin.X + box.Current.X)/2, (box.Origin.Y + box.Current.Y)/2 ); // <---
				canvas.DrawRect(left, top, right, bottom, mBoxPaint);
                                // Restore the canvas to its state before the rotation
				canvas.Restore(); // <---
			}
		}

And now for the touch events:

// Stores the initial location of the second finger that will control location
Box mRotationStart; // <---
// Stores the initial value from the current box's rotation property.
float mInitialRotation; // <---

public override bool OnTouchEvent(MotionEvent e)
		{
			// Get current position of pointer at index 0.
			System.Drawing.PointF curr = new System.Drawing.PointF(e.GetX(), e.GetY());

			switch (e.Action) {
				case MotionEventActions.PointerDown:
				case MotionEventActions.Down:
					// Set the current box and add it to the List<Box> mBoxes
					if (mCurrentBox == null) {
						mCurrentBox = new Box(curr, e.GetPointerId(0));
						mBoxes.Add(mCurrentBox);
					}
					break;
				case MotionEventActions.Pointer2Down: // <---
					// Handle 2nd touch. Set the start point of the rotation
					if (mRotationStart == null) {
						// Get coordinates of pointer 2
						MotionEvent.PointerCoords pCoords = new MotionEvent.PointerCoords();
						e.GetPointerCoords(1, pCoords);
						// Set the starting coordinates for the rotation
						mRotationStart = new Box(new System.Drawing.PointF(pCoords.X, pCoords.Y), e.GetPointerId(1));
                                                //Need to store the boxes current rotation so we can add it to the difference between where the pointer is now and where it is wen moved.
                                                // This allows one to smoothly start and stop rotating by lifting the second finger and replacing it without losing any previous rotation.
						mInitialRotation = mCurrentBox.Rotation;
					}
					break;
				case MotionEventActions.Move:
					// Handle first pointer move, set end point for rectangle
					if (mCurrentBox != null && mCurrentBox.PointerId == e.GetPointerId(0)) {
						mCurrentBox.Current = curr;
					}
					// Handle second pointer move, set rotation amount
					if (mRotationStart != null && mRotationStart.PointerId == e.GetPointerId(1)) { // <---
						// Get coordinates of pointer 2
						MotionEvent.PointerCoords pCoords = new MotionEvent.PointerCoords();
						e.GetPointerCoords(1, pCoords);
						// Set the rotation of the box to the difference between the origin of mRotation and the current position of pointer 2 plus any initial rotation value of the current box.
						mCurrentBox.Rotation = mInitialRotation + pCoords.Y - mRotationStart.Origin.Y;
					}
					Invalidate();
					break;
				case MotionEventActions.Pointer2Up: // <---
					mRotationStart = null;
					break;
				case MotionEventActions.PointerUp:
				case MotionEventActions.Up:
					mCurrentBox = null;
					mRotationStart = null;
					break;
				case MotionEventActions.Cancel:
					mCurrentBox = null;
					mRotationStart = null;
					break;
			}

			return true;
		}

This solution keeps the drawing aspects in the OnDraw method and lets each box have, and store, its own rotation state.

For the saving of state on rotation, I serialized my list of boxes to a JSON string and saved it in a bundle:

protected override Android.OS.IParcelable OnSaveInstanceState()
		{
			// Get the state of the view
			IParcelable superState = base.OnSaveInstanceState();
			Bundle bundle = new Bundle();
			// put the state in the bundle
			bundle.PutParcelable("parcelable", superState);
			// serialize mBoxes to JSON and put that in the bundle
			bundle.PutString("json", JsonConvert.SerializeObject(mBoxes));
			return bundle;
		}

		protected override void OnRestoreInstanceState(IParcelable state)
		{
			Bundle bundle = (Bundle)state;
			// Get the IParcelable object
			IParcelable parcelable = (IParcelable)bundle.GetParcelable("parcelable");
			// Deserialize the JSON string from the bundle into mBoxes
			mBoxes = JsonConvert.DeserializeObject<List<Box>>(bundle.GetString("json"));
			// Restore the View state from the IParcelable state object
			base.OnRestoreInstanceState(parcelable);
		}