Rotate and scale a view based on a single handle on Android

I am trying to rotate and scale a view based on a single "drag" handle in Android. When rotating the layout and resizing the window, the end result should be that the drag handle follows the user's finger wherever they move.

This is based on the combination of these two questions:

The only thing that seems to be 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>

      

+2


source to share


1 answer


If you need to resize and rotate an image at the same time with a single handler icon, you need to do some trigonometric calculations.

It is not that hard to calculate the angle at which the image should rotate based on its starting angle and the angle at which the vector that connects the center of the image and the current finger position is rotated. An even more difficult task is to position the handler in the right place on the screen so that it always connects the corner of the 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 am I doing?

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

      

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



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

      

is the angle at which the diagonal was first rotated (since the image does not have to be square, this angle is not always 45 degrees.)

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

      

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

Same with Y, but using sin

instead of cos

.

+2


source







All Articles