2

I'm trying to rotate and scale a view based on one "drag" handle in Android. With the layout rotating, and the view resizing, the end result should be that the drag handle follows the users finger where ever it moves.

This is based on combining these two questions:

  1. How to scale a view in android using a handle?
  2. Rotating around two points in android not working

The only thing that seems wrong is the rotation code.

Here is my activity code:

public class MainActivity extends Activity {
    ImageView imageView;
    ImageView dragHandle;
    RelativeLayout layout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView = (ImageView) findViewById(R.id.imageView1);
        imageView.setBackgroundColor(Color.MAGENTA);
        dragHandle = (ImageView) findViewById(R.id.imageView2);
        dragHandle.setBackgroundColor(Color.CYAN);
        layout = (RelativeLayout) findViewById(R.id.relativeLayout2);
        layout.setBackgroundColor(Color.YELLOW);
        setUpResize();

    }

    public void setUpResize() {
        dragHandle.setOnTouchListener(new View.OnTouchListener() {

            float centerX, centerY, startR, startScale, startX, startY;

            float startAngle;
            float zeroAngle;
            int firstPointX;
            int firstPointY;

            public boolean onTouch(View v, MotionEvent e) {

                if (e.getAction() == MotionEvent.ACTION_DOWN) {

                    // calculate center of image
                    centerX = (imageView.getLeft() + imageView.getRight()) / 2f;
                    centerY = (imageView.getTop() + imageView.getBottom()) / 2f;

                    // recalculate coordinates of starting point
                    startX = e.getRawX() - dragHandle.getX() + centerX;
                    startY = e.getRawY() - dragHandle.getY() + centerY;

                    // get starting distance and scale
                    startR = (float) Math.hypot(e.getRawX() - startX, e.getRawY() - startY);
                    startScale = imageView.getScaleX();

                    /*
                     * Rotate code
                     */

                    int[] locationOfLayout = new int[2];
                    int[] locationOfDrag = new int[2];

                    layout.getLocationOnScreen(locationOfLayout);
                    dragHandle.getLocationOnScreen(locationOfDrag);

                    firstPointX = locationOfLayout[0];
                    firstPointY = locationOfLayout[1];

                    float secondPointX = e.getRawX();
                    float secondPointY = e.getRawY();

                    zeroAngle = findRotation(firstPointX, firstPointY, secondPointX, secondPointY); // remember
                                                                                                    // "zero"
                                                                                                    // angle
                    startAngle = layout.getRotation(); // remember angle at
                                                        // which layout is
                                                        // rotated at the start

                } else if (e.getAction() == MotionEvent.ACTION_MOVE) {

                    // calculate new distance
                    float newR = (float) Math.hypot(e.getRawX() - startX, e.getRawY() - startY);

                    // set new scale
                    float newScale = newR / startR * startScale;
                    imageView.setScaleX(newScale);
                    imageView.setScaleY(newScale);

                    // move handler image
                    dragHandle.setX(centerX + imageView.getWidth() / 2f * newScale);
                    dragHandle.setY(centerY + imageView.getHeight() / 2f * newScale);

                    /*
                     * Rotate code
                     */

                    layout.setRotation(findRotation(firstPointX, firstPointY, e.getRawX(), e.getRawY()) - zeroAngle
                            + startAngle); // rotate relative to start and zero
                                            // angle

                } else if (e.getAction() == MotionEvent.ACTION_UP) {

                }
                return true;
            }
        });
    }

    private float findRotation(float firstPointX, float firstPointY, float secondPointX, float secondPointY) {
        double delta_x = (secondPointX - firstPointX);
        double delta_y = (secondPointY - firstPointY);
        double radians = Math.atan2(delta_y, delta_x);
        return (float) Math.toDegrees(radians);
    }
}

xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <RelativeLayout
        android:id="@+id/relativeLayout2"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true" >

        <ImageView
            android:id="@+id/imageView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:layout_centerVertical="true"
            android:src="@drawable/ic_launcher" />

        <ImageView
            android:id="@+id/imageView2"
            android:layout_width="20dp"
            android:layout_height="20dp"
            android:layout_below="@+id/imageView1"
            android:layout_toRightOf="@+id/imageView1"
            android:src="@drawable/meanicons" />

    </RelativeLayout>

</RelativeLayout>
Community
  • 1
  • 1

1 Answers1

3

In case you need to resize and rotate image simultaneously using one handler icon, some trigonometrical calculations should be performed.

It is not so difficult to calculate angle by which image should be rotated, based on its initial angle and angle, by which the vector, that connects center of image and current finger position, is rotated. A little bit more complicated task is to position the handler in a proper place of the screen to make it always connect the corner of main image.

public void setUpResize() {
    dragHandle.setOnTouchListener(new View.OnTouchListener() {

        float centerX, centerY, startR, startScale, startX, startY, startRotation, startA ;

        public boolean onTouch(View v, MotionEvent e) {

            if (e.getAction() == MotionEvent.ACTION_DOWN) {

                centerX = (imageView.getLeft() + imageView.getRight()) / 2f;
                centerY = (imageView.getTop() + imageView.getBottom()) / 2f;

                startX = e.getRawX() - dragHandle.getX() + centerX;
                startY = e.getRawY() - dragHandle.getY() + centerY; 

                startR = (float) Math.hypot(e.getRawX() - startX, e.getRawY() - startY);
                startA = (float) Math.toDegrees(Math.atan2(e.getRawY() - startY, e.getRawX() - startX));

                startScale = imageView.getScaleX();
                startRotation = imageView.getRotation();

            } else if (e.getAction() == MotionEvent.ACTION_MOVE) {

                float newR = (float) Math.hypot(e.getRawX() - startX, e.getRawY() - startY);
                float newA = (float) Math.toDegrees(Math.atan2(e.getRawY() - startY, e.getRawX() - startX));
                float newScale = newR / startR * startScale;
                float newRotation = newA - startA + startRotation;

                imageView.setScaleX(newScale);
                imageView.setScaleY(newScale);
                imageView.setRotation(newRotation);


                // ----- this part takes some effort to understand... ------
                dragHandle.setX((float) (centerX + Math.hypot(imageView.getWidth(), imageView.getHeight())/2f * newScale 
                        * Math.cos(Math.toRadians(newRotation) + Math.atan2(imageView.getHeight(), imageView.getWidth()))));

                dragHandle.setY((float) (centerY + Math.hypot(imageView.getWidth(), imageView.getHeight())/2f * newScale 
                        * Math.sin(Math.toRadians(newRotation) + Math.atan2(imageView.getHeight(), imageView.getWidth()))));
                //-----------------------------------------------------------

                dragHandle.setPivotX(0);
                dragHandle.setPivotY(0);
                dragHandle.setRotation(newRotation);


            } else if (e.getAction() == MotionEvent.ACTION_UP) {

            }
            return true;
        }
    });
}

So, what I'm doing?

Math.hypot(imageView.getWidth(), imageView.getHeight()) / 2f * newScale

--this calculates half length of diagonal of the main image, i. e. distance between its center and corner point

Math.atan2(imageView.getHeight(), imageView.getWidth())

--this is angle by which diagonal was rotated initially (since image must not be square, this angle is not always 45 deg.)

Math.cos(Math.toRadians(newRotation) + Math.atan2(imageView.getHeight(), imageView.getWidth()))

--this gives us projection onto X axis of unit vector, rotated by an angle composed with angle, at which image is rotated, and angle of initial rotation of its diagonal. After multiplying it to half length of diagonal, we get X of image corner.

The same with Y, but using sin instead of cos.

Alex Salauyou
  • 14,185
  • 5
  • 45
  • 67
  • Great that works well. Trying to understand X and rawX by trying to move a view with my finger. Do you have any idea why this code only works the first time to move a view? http://pastebin.com/zNmaNhqw –  Apr 17 '14 at 19:56
  • in addition to remembering start point it touch, you also should remember translation that was applied to the view on previous move, and use it in calculating new translation during motion_move event – Alex Salauyou Apr 17 '14 at 20:28
  • Should I remember it in action_up? I don't see how this works the first time. but not the second. –  Apr 17 '14 at 20:36
  • @user2676468 you should consider x and y translation that were made before by previous moves. See how I use `startScale` and `startRotation` for analogy – Alex Salauyou Apr 17 '14 at 22:00
  • @user2676468 You should create a kind of `startMoveX` and `startMoveY` variables and use them the similar way that `startScale` and `startRotation` are used. – Alex Salauyou Apr 17 '14 at 22:03
  • I should do this on Action_down? –  Apr 18 '14 at 15:51
  • I got it. Thank you. I am trying to work more with scaling and setTranslation and am having trouble here. http://stackoverflow.com/questions/23159579/programmatically-line-up-two-views-at-a-corner?noredirect=1#comment35415678_23159579 –  Apr 18 '14 at 19:02
  • I believe you can find answers by yourself. It is just small brain work. Good luck! – Alex Salauyou Apr 18 '14 at 19:54
  • Back with one more question. I want to place a view in the upper left. I'm trying to edit this code `dragHandle.setX((float) (centerX + Math.hypot(imageView.getWidth(), imageView.getHeight())/2f * newScale * Math.cos(Math.toRadians(newRotation) + Math.atan2(imageView.getHeight(), imageView.getWidth()))));` but I'm not sure how to edit it to give me the top left instead of bottom right X,Y. –  May 16 '14 at 17:33
  • `setX(centerX - ...); setY(centerY - ...)` (replace + by -) – Alex Salauyou May 26 '14 at 10:16