14

I'm trying to create an app that can move an ImageView on your device like dragging and when I put like 75% of the ImageView out of the screen show a Toast for example. I've been reading about MotionEvent and onTouchListener and I've followed this question, but it doesn't convince me.

Edit

My current code is :

public class MainActivity extends AppCompatActivity implements View.OnTouchListener { int windowwidth; int windowheight; private ImageView mImageView; private ViewGroup mRrootLayout; private int _xDelta; private int _yDelta; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DisplayMetrics displaymetrics = new DisplayMetrics(); this.getWindowManager().getDefaultDisplay().getMetrics(displaymetrics); windowwidth = displaymetrics.widthPixels; windowheight = displaymetrics.heightPixels; mRrootLayout = (ViewGroup) findViewById(R.id.root); mImageView = (ImageView) mRrootLayout.findViewById(R.id.im_move_zoom_rotate); RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(150, 150); mImageView.setLayoutParams(layoutParams); mImageView.setOnTouchListener(this); } public boolean onTouch(View view, MotionEvent event) { final int X = (int) event.getRawX(); final int Y = (int) event.getRawY(); if(X == 0){ Toast.makeText(this, "OUT", Toast.LENGTH_SHORT).show(); } else if (Y == 0){ Toast.makeText(this, "OUT", Toast.LENGTH_SHORT).show(); } switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: RelativeLayout.LayoutParams lParams = (RelativeLayout.LayoutParams) view.getLayoutParams(); _xDelta = X - lParams.leftMargin; _yDelta = Y - lParams.topMargin; break; case MotionEvent.ACTION_UP: break; case MotionEvent.ACTION_POINTER_DOWN: break; case MotionEvent.ACTION_POINTER_UP: break; case MotionEvent.ACTION_MOVE: RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view .getLayoutParams(); layoutParams.leftMargin = X - _xDelta; layoutParams.topMargin = Y - _yDelta; layoutParams.rightMargin = -250; layoutParams.bottomMargin = -250; view.setLayoutParams(layoutParams); break; } mRrootLayout.invalidate(); return true; } } 

I did those if and else if just to know if the ImageView is getting out of the device, on the left and right side of the device seems like it's okay, but I'd like to make it cleaner and not hardwritted, also I don't get the LayoutParams(150,150) why 150? Also I don't get why I have to create a RelativeLayout.LayoutParams and why I have to put

layoutParams.rightMargin = -250; layoutParams.bottomMargin = -250; 

I did the if/else if because I want to delete when the user want to put the ImageView out of the device, so I need to control when he tries to, at the moment I only got it TOP/LEFT/RIGHT not down, I also get the dimensions of my device just to try if X or Y is the same as height or widht just show the Toast but it's not doing it correctly.

Now my ImageView is the ic_launcher but it will be bigger (almost middle screen).

NOTE

If you know any other way to do that easier or cleanest, feel free to put it here, I don't care about my code, I can adapt it, I just want it to be clear and not hardcoded.

2
  • I came across a similar problem and then got a solution. Checking Drag Out of Bounds Commented Oct 20, 2017 at 7:18
  • If you know how to solve it (IF TESTED) feel free to put an answer and I'll test it out :) Commented Oct 20, 2017 at 8:53

6 Answers 6

11
+100

Your routine works for the most part. In the following code, I have commented out sections that are not needed and made notations for those parts that need some explanation. Here is what the finished product looks like:

enter image description here

This graphic explains how the left margin is calculated. The same type of calculation applies to the top margin.

enter image description here

MainActivity.java

public class MainActivity extends AppCompatActivity implements View.OnTouchListener { int windowwidth; // Actually the width of the RelativeLayout. int windowheight; // Actually the height of the RelativeLayout. private ImageView mImageView; private ViewGroup mRrootLayout; private int _xDelta; private int _yDelta; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // We are interested when the image view leaves its parent RelativeLayout // container and not the screen, so the following code is not needed. // DisplayMetrics displaymetrics = new DisplayMetrics(); // this.getWindowManager().getDefaultDisplay().getMetrics(displaymetrics); // windowwidth = displaymetrics.widthPixels; // windowheight = displaymetrics.heightPixels; mRrootLayout = (ViewGroup) findViewById(R.id.root); mImageView = (ImageView) mRrootLayout.findViewById(R.id.im_move_zoom_rotate); // These these following 2 lines that address layoutparams set the width // and height of the ImageView to 150 pixels and, as a side effect, clear any // params that will interfere with movement of the ImageView. // We will rely on the XML to define the size and avoid anything that will // interfere, so we will comment these lines out. (You can test out how a layout parameter // can interfere by setting android:layout_centerInParent="true" in the ImageView. // RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(150, 150); // RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(150, 150); // mImageView.setLayoutParams(layoutParams); mImageView.setOnTouchListener(this); // Capture the width of the RelativeLayout once it is laid out. mRrootLayout.post(new Runnable() { @Override public void run() { windowwidth = mRrootLayout.getWidth(); windowheight = mRrootLayout.getHeight(); } }); } // Tracks when we have reported that the image view is out of bounds so we // don't over report. private boolean isOutReported = false; public boolean onTouch(View view, MotionEvent event) { final int X = (int) event.getRawX(); final int Y = (int) event.getRawY(); // Check if the image view is out of the parent view and report it if it is. // Only report once the image goes out and don't stack toasts. if (isOut(view)) { if (!isOutReported) { isOutReported = true; Toast.makeText(this, "OUT", Toast.LENGTH_SHORT).show(); } } else { isOutReported = false; } switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: // _xDelta and _yDelta record how far inside the view we have touched. These // values are used to compute new margins when the view is moved. _xDelta = X - view.getLeft(); _yDelta = Y - view.getTop(); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_DOWN: case MotionEvent.ACTION_POINTER_UP: // Do nothing break; case MotionEvent.ACTION_MOVE: RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) view .getLayoutParams(); // Image is centered to start, but we need to unhitch it to move it around. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { lp.removeRule(RelativeLayout.CENTER_HORIZONTAL); lp.removeRule(RelativeLayout.CENTER_VERTICAL); } else { lp.addRule(RelativeLayout.CENTER_HORIZONTAL, 0); lp.addRule(RelativeLayout.CENTER_VERTICAL, 0); } lp.leftMargin = X - _xDelta; lp.topMargin = Y - _yDelta; // Negative margins here ensure that we can move off the screen to the right // and on the bottom. Comment these lines out and you will see that // the image will be hemmed in on the right and bottom and will actually shrink. lp.rightMargin = view.getWidth() - lp.leftMargin - windowwidth; lp.bottomMargin = view.getHeight() - lp.topMargin - windowheight; view.setLayoutParams(lp); break; } // invalidate is redundant if layout params are set or not needed if they are not set. // mRrootLayout.invalidate(); return true; } private boolean isOut(View view) { // Check to see if the view is out of bounds by calculating how many pixels // of the view must be out of bounds to and checking that at least that many // pixels are out. float percentageOut = 0.50f; int viewPctWidth = (int) (view.getWidth() * percentageOut); int viewPctHeight = (int) (view.getHeight() * percentageOut); return ((-view.getLeft() >= viewPctWidth) || (view.getRight() - windowwidth) > viewPctWidth || (-view.getTop() >= viewPctHeight) || (view.getBottom() - windowheight) > viewPctHeight); } } 

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/root" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <ImageView android:id="@+id/im_move_zoom_rotate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:src="@drawable/circle" /> </RelativeLayout> 
Sign up to request clarification or add additional context in comments.

14 Comments

nice answers and well explained, I did it with my ic_launcher and it's not working at 100% it says OUT when is going out by TOP/BOTTOM but left/right it's not working 100%, it's necessary to put layout_widht 150dp? couldn't be wrap_content? my image will be almost the same size of the screen, but now I'm testing it with ic_launcher....
@Skizo-ozᴉʞS That is curious. It works OK for me, but our environments must differ. Can you post the relevant XML layout that you are using and a description of your ic_launcher? Is it just a standard ic_launcher icon? I am wondering if your image isn't larger than it appears. btw, I updated how the negative margins are set in the code. See updated answer.
Yes, see my XML LAYOUT, now it doesn't move because I put it on the middle of the screen... (I want it on the middle of the screen though), and I put 250sp but I'd like to put it bigger....
Look @Cheticamp, I'm using this image now, as I'm seeing 75% is too much, could it be less? like 50% or a little bit less? I've edited the xml layout
@Skizo-ozᴉʞS Code changed to allow for initial placement in center of screen. See the removeRule() statements in onTouch(). Added parameter to vary the percentage of the image that must be "out" before toast is shown. See isOut().
|
3

The framework has a class called View.OnDragListener. See the Drag & Drop tutorial.

See also additionally the DraggablePanel project if you want to study how this can be done.

6 Comments

I've tried the last example, but how can I determine when the view is going to "leave" the device? Also I have this on a ConstraintLayout it will affect on this? I'm trying this example do you think it's ok? Also how do I detect if it's going out of the device? I thought to compare X and Y with windowheight and windowwidth
I've edited my question with my current code, could you help me out of what I'm asking?
have you studied my case?
Yes Y.S. I did it, for sure, but when I try the example it doesn't move here's the sample code
@Skizo-ozᴉʞS: Sorry for the delay, friend. Will look at it at the soonest.
|
3

A working example of how to move all the views contained in a RelativeLayout using onTouch. Hope it will help :

public class MainActivity extends AppCompatActivity implements View.OnTouchListener { private RelativeLayout mRelLay; private float mInitialX, mInitialY; private int mInitialLeft, mInitialTop; private View mMovingView = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mRelLay = (RelativeLayout) findViewById(R.id.relativeLayout); for (int i = 0; i < mRelLay.getChildCount(); i++) mRelLay.getChildAt(i).setOnTouchListener(this); } @Override public boolean onTouch(View view, MotionEvent motionEvent) { RelativeLayout.LayoutParams mLayoutParams; switch (motionEvent.getAction()) { case MotionEvent.ACTION_DOWN: mMovingView = view; mLayoutParams = (RelativeLayout.LayoutParams) mMovingView.getLayoutParams(); mInitialX = motionEvent.getRawX(); mInitialY = motionEvent.getRawY(); mInitialLeft = mLayoutParams.leftMargin; mInitialTop = mLayoutParams.topMargin; break; case MotionEvent.ACTION_MOVE: if (mMovingView != null) { mLayoutParams = (RelativeLayout.LayoutParams) mMovingView.getLayoutParams(); mLayoutParams.leftMargin = (int) (mInitialLeft + motionEvent.getRawX() - mInitialX); mLayoutParams.topMargin = (int) (mInitialTop + motionEvent.getRawY() - mInitialY); mMovingView.setLayoutParams(mLayoutParams); } break; case MotionEvent.ACTION_UP: mMovingView = null; break; } return true; } } 

3 Comments

seems like it's working but looks like on the left it's not detecting it, right? I'd like to get a toast or something when user tries to move the image out of the device TOP/DOWN/RIGHT/LEFT you got me?
Then you should limit movement adding in ACTION_MOVE something like if(topMargin < 0) topMargin = 0; and if(topMargin + height > windowheight) topMargin = windowheight - height; and the same for left margin and width.
Could you make a sample of that so I can test and I can approve it?
3

I'm using this method to drag an ImageView , I hope that this can help you : So I defined those attributes of the class :

 private float xCoOrdinate, yCoOrdinate; private double screenCenterX, screenCenterY; 

Then I implement this code under the OnCreate() method of the activity :

 mRrootLayout.getBackground().setAlpha(255); /** * Calculate max hypo value and center of screen. */ final DisplayMetrics display = getResources().getDisplayMetrics(); screenCenterX = display.widthPixels / 2; screenCenterY = (display.heightPixels - getStatusBarHeight()) / 2; final double maxHypo = Math.hypot(screenCenterX, screenCenterY); mImageView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { /** * Calculate hypo value of current imageview position according to center */ double centerYPos = mImageView.getY() + (mImageView.getHeight() / 2); double centerXPos = mImageView.getX() + (mImageView.getWidth() / 2); double a = screenCenterX - centerXPos; double b = screenCenterY - centerYPos; double hypo = Math.hypot(a, b); /** * change alpha of background of layout */ alpha = (int) (hypo * 255) / (int) maxHypo; if (alpha < 255) mRrootLayout.getBackground().setAlpha(255 - alpha); switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: xCoOrdinate = mImageView.getX() - event.getRawX(); yCoOrdinate = mImageView.getY() - event.getRawY(); break; case MotionEvent.ACTION_MOVE: mImageView.animate().x(event.getRawX() + xCoOrdinate).y(event.getRawY() + yCoOrdinate).setDuration(0).start(); break; case MotionEvent.ACTION_UP: if (alpha > 50) { Toast.makeText(ImageViewerActivity.this, "Out", Toast.LENGTH_SHORT).show(); return false; } else { Toast.makeText(ImageViewerActivity.this, "In", Toast.LENGTH_SHORT).show(); mImageView.animate().x(0).y((float) screenCenterY - mImageView.getHeight() / 2).setDuration(100).start(); mRrootLayout.getBackground().setAlpha(255); } default: return false; } return true; } }); 

5 Comments

Nmm could you put a simple toast instead of animate it? I mean if you are going to get out of the device a toast (when the image is arround 50% out of the screen)?
I added for you the toasts sis . Try it and if you have question don't hesitate to ask me
PS: The Toasts will be shown when you invoke ACTION_UP and it will display "Out" if you are 50% out of the screen and "in" if you still in 50% of the screen
Well the 50% of the image was a random number, but yes, this night I'll make a test and I'll let you know, but it can not be caught on action_move?
I've tested it and seems like it does not work, as a getStatusBarHeight() method I got this from this question, it's okay? It does say OUT if I'm on the middle of the screen
3

Update

Add right/bottom margin in step 3 to prevent image zoomed. You can see if you not change right/bottom margin, image will zoomed by relative layout. preview solution old and new

  1. getMeasuredHeight/Width avoid MATCH_PARENT and WRAP_CONTENT.
  2. If there is a toolbar/actionbar then topMargin + height > relativeLayout's height also applies to out of bottom determination.
  3. record state of out of bound avoid toast continuous appear.

    public class MainActivity extends AppCompatActivity implements View.OnTouchListener { Point lastPoint = new Point(); RelativeLayout relativeLayout; boolean lastOutOfTop = false; boolean lastOutOfLeft = false; boolean lastOutOfRight = false; boolean lastOutOfBottom = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); (findViewById(R.id.imageView)).setOnTouchListener(this); relativeLayout = (RelativeLayout)findViewById(R.id.relativeLayout); } @Override public boolean onTouch(View view, MotionEvent event) { //1. user's finger final Point point = new Point((int) event.getRawX(), (int) event.getRawY()); switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: // 2. record the last touch point lastPoint = point; break; case MotionEvent.ACTION_UP: break; case MotionEvent.ACTION_POINTER_DOWN: break; case MotionEvent.ACTION_POINTER_UP: break; case MotionEvent.ACTION_MOVE: // 3. get the move offset final Point offset = new Point(point.x-lastPoint.x, point.y-lastPoint.y); RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view .getLayoutParams(); layoutParams.leftMargin += offset.x; layoutParams.topMargin += offset.y; // * also check right/bottom Margin layoutParams.rightMargin = relativeLayout.getMeasuredWidth() - layoutParams.leftMargin+view.getMeasuredWidth(); layoutParams.bottomMargin = relativeLayout.getMeasuredHeight() - layoutParams.topMargin+view.getMeasuredHeight(); view.setLayoutParams(layoutParams); // 4. record the last touch point lastPoint = point; break; } // 5. check bounds RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view.getLayoutParams(); boolean outOfTop = layoutParams.topMargin < 0; boolean outOfLeft = layoutParams.leftMargin < 0; boolean outOfBottom = layoutParams.topMargin+view.getMeasuredHeight() > relativeLayout.getMeasuredHeight(); boolean outOfRight = layoutParams.leftMargin+view.getMeasuredWidth() > relativeLayout.getMeasuredWidth(); // 6. if out of bound if (outOfLeft&&!lastOutOfLeft) Toast.makeText(this, "OUT Left", Toast.LENGTH_SHORT).show(); if (outOfTop&&!lastOutOfTop) Toast.makeText(this, "OUT Top", Toast.LENGTH_SHORT).show(); if (outOfBottom&&lastOutOfBottom) Toast.makeText(this, "OUT Bottom", Toast.LENGTH_SHORT).show(); if (outOfRight&&lastOutOfRight) Toast.makeText(this, "OUT Right", Toast.LENGTH_SHORT).show(); // 7. record lastOutOfTop = outOfTop; lastOutOfLeft = outOfLeft; lastOutOfBottom = outOfBottom; lastOutOfRight = outOfRight; return true; } } 

5 Comments

I said Toast to let me know when I'm going out of the screen
Yes toast prompted beyond the border. My example is only one time when the border is exceeded
I'll test int in about 1h and I'll let you know ^^
I've tested it and I don't understand why if I drag it to the right it zooms out, I've tried it with another image (more little) and seems like it works, but when I put my real image, it zooms in/out whenever I try to move the image. Maybe because it's too big?
Yes if we don't change left/bottom margin, then image will zooms in/out. I've update my answer with a preview of the results.
1

You can achieve this via this code.

DisplayMetrics metrics = getResources().getDisplayMetrics(); int windowWidth = metrics.widthPixels; int windowHeight = metrics.heightPixels; 

Now in your onTouch method, calculate if the target location exceeds the above dimensions.

if( currentXLocation + deltaX > windowWidth ){

// this will ensure that target location // is always <= windowHeight deltaX = windowWidth - currentXLocation; } else if( currentXLocation + deltaX < 0){ deltaX = -(currentXLocation); } else if (...){ // perform similar calculations for the rest } 

3 Comments

Copy the same answer is not useful, so please if you put an answer and you don't want to being downvoted, test it on a sample project and post your real code.
Bro, this is the code that solved my problem, if you want a sample let me know, ill make you one :)
It's easier to me to test it, yes.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.