Search code examples
android-studioontouchlistener

How to resize a view by dragging it's edges or sides in Android Studio


I have an ImageView and I wand to resize it by dragging it's edges or sides. I have two problems.

  1. I can drag just the bottom right corner.
  2. When I drag the corner the entire ImageView scales.

How can I do it for every corner and how can I drag just one side or corner without scaling the entire view?

For example with this code on dragging the bottom right corner I scale the entire view.

How can I set for example an anchor for every corner? How can I just drag the corner or the side without scaling everything?

Here is my code:

MainActivity:

public class MainActivity extends AppCompatActivity {

private int dpToPx(int dp) {
    return (int) (dp * getResources().getDisplayMetrics().density);
}


private static final int CIRCLE_SIZE_DP = 20;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Drawable circle = ContextCompat.getDrawable(this, R.drawable.circle);

    int circleSize = dpToPx(CIRCLE_SIZE_DP);

    RelativeLayout viewToBeResized = findViewById(R.id.customView);

    ImageView bottomRightAnchor = new ImageView(this);
    bottomRightAnchor.setImageDrawable(circle);
    RelativeLayout.LayoutParams bottomRightParms =
            new RelativeLayout.LayoutParams(circleSize, circleSize);
    bottomRightParms.addRule(RelativeLayout.ALIGN_PARENT_END, viewToBeResized.getId());
    bottomRightParms.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, viewToBeResized.getId());
    viewToBeResized.addView(bottomRightAnchor, bottomRightParms);

    bottomRightAnchor.setOnTouchListener(
            new AnchorTouchListener(viewToBeResized, ((TextView) findViewById(R.id.status))));
}

XML:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/relativeLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<RelativeLayout
    android:id="@+id/customView"
    android:layout_width="150dp"
    android:layout_height="150dp"
    android:layout_centerHorizontal="true"
    android:layout_centerVertical="true"
    android:background="@android:color/holo_green_light" />

<TextView
    android:id="@+id/status"
    android:layout_width="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_centerVertical="true"
    tools:text="Status"
    android:layout_height="wrap_content" /></RelativeLayout>

AnchorTouchListener:

public class AnchorTouchListener implements View.OnTouchListener {
private int _xDelta;
private int _yDelta;
private int actualWidth;
private int actualHeight;
private View viewToResize;
private TextView lblStatus;

public AnchorTouchListener(View viewToResize, TextView lblStatus) {
    this.viewToResize = viewToResize;
    this.lblStatus = lblStatus;
}

private int initialHeight;
private int initialWidth;
private int initialX;
private int initialY;

@Override
public boolean onTouch(View view, MotionEvent event) {
    final int X = (int) event.getRawX();
    final int Y = (int) event.getRawY();

    Log.d("Anchor", "Updating X & Y");

    switch (event.getAction() & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
            lblStatus.setText("Action down");
            // Capture initial conditions of the view to resize.
            initialHeight = viewToResize.getHeight();
            initialWidth = viewToResize.getWidth();
            // Capture initial touch point.
            initialX = X;
            initialY = Y;
            break;

        case MotionEvent.ACTION_UP:
            lblStatus.setText("Drag finished x: "+actualWidth+" y:"+actualHeight);
            break;

        case MotionEvent.ACTION_POINTER_DOWN:
            break;

        case MotionEvent.ACTION_POINTER_UP:
            break;

        case MotionEvent.ACTION_MOVE:
            //lblStatus.setText("Moving around");
            RelativeLayout.LayoutParams lp =
                    (RelativeLayout.LayoutParams) viewToResize.getLayoutParams();
            // Compute how far we have moved in the X/Y directions.
            _xDelta = X - initialX;
            _yDelta = Y - initialY;
            // Adjust the size of the targeted view. Note that we don't have to position
            // the resize handle since it will be positioned correctly due to the layout.
            actualWidth = lp.width = initialWidth + _xDelta;
            actualHeight = lp.height = initialHeight + _yDelta;
            lblStatus.setText("Moving around x: "+actualWidth+" y: "+actualHeight+"");
            viewToResize.setLayoutParams(lp);
            break;
    }

    return true;
}

Solution

  • to only allow the the image to be draggable from the top left corner we can use the leftMargin and topMargin of the layoutParams of the image view.

        var top = 0
          var left = 0
    
          var lastTouchX = 0f
          var lastTouchY = 0f
          var isDragging = false
          image.setOnTouchListener(object : OnTouchListener{
              override fun onTouch(v: View?, event: MotionEvent?): Boolean {
                  val params = image.layoutParams as ViewGroup.MarginLayoutParams
    
                  top = params.topMargin
                  left = params.leftMargin
                  val width = image.width
                  val height = image.height
                  val x = event!!.x
                  val y = event!!.y
                  when (event!!.action) {
                      MotionEvent.ACTION_DOWN -> {
    
                          Log.d("left","$left")
                          if (x >= left && x <= left + 40 && y >= top && y <= top + 50) {
                              lastTouchX = x
                              lastTouchY = y
                              isDragging = true
                              return true
                          }
    
                      }
    
                      MotionEvent.ACTION_MOVE -> {
                          if (isDragging) {
                              val deltaX: Float = x - lastTouchX
                              val deltaY: Float = y - lastTouchY
                              val newLeft = left + deltaX
                              val newWidth = width - deltaX.toInt()
                              val newHeight = height - deltaY.toInt()
    
                    //          Log.d("lasttou","$lastTouchX,$lastTouchY")
    
                              // Adjust the position and size of the image view
                              params.leftMargin = newLeft.toInt()
                              params.width = newWidth
                              params.height = newHeight
                              image.layoutParams = params
                              lastTouchX = x
                              lastTouchY = y
                              return true
                          }
                      }
    
                      MotionEvent.ACTION_UP -> if (isDragging) {
                          isDragging = false
                          return true
                      }
                  }
                 return true
              }
    
          })
    
      }
    

    And I have used a flag isDragging which is true only when the user touch satisfies this condition (x >= left && x <= left + 40 && y >= top && y <= top + 50) the x coord must be between the left most margin and 40 coords after that and the y coord must be between the top most margin and 50 coords below that which would give us the top left top corner of the image and to resize only the top left corner i have tried something in the on_move event , I don't think it will fully satisfy you needs but try changing various params of the imageView until you find the perfect match.