Android - Let user drag/move an image around - java

I have looked around the net and stackover flow and seen some answers on how to create a motionevent/drag for an image in my app.
So far I have implemented the following:
private void dragAndDrop(){
image.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) image
.getLayoutParams();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
int x_cord = (int) event.getRawX();
int y_cord = (int) event.getRawY();
if (x_cord > windowwidth) {
x_cord = windowwidth;
}
if (y_cord > windowheight) {
y_cord = windowheight;
}
layoutParams.leftMargin = x_cord - 25;
layoutParams.topMargin = y_cord - 75;
image.setLayoutParams(layoutParams);
break;
default:
break;
}
return true;
}
});
}
Now this works to an extent, but every time I click on the image, the image will jump below about a few centimetres below my finger, I am not sure why
But I can move the image around just fine.
But my question is, is this now the updated way to do it? Have I missed an easy to use class to let the user move the image around?
Thanks

Related

onTouchListener not accepting layout

Code
private RelativeLayout mToolbar;
mToolbar = (RelativeLayout) myview.findViewById(R.id.toolbar);
mToolbar.setOnTouchListener(new View.OnTouchListener() {
WindowManager.LayoutParams updatedParameters = finalParameters;
double x;
double y;
double pressedX;
double pressedY;
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x = updatedParameters.x;
y = updatedParameters.y;
pressedX = event.getRawX();
pressedY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
updatedParameters.x = (int) (x + (event.getRawX() - pressedX));
updatedParameters.y = (int) (y + (event.getRawY() - pressedY));
wm.updateViewLayout(myview, updatedParameters);
default:
break;
}
return false;
}
});
I have a service and this above code can move around that service in the screen... It worked before when i had to touch a button and move it around. I switched it to layout now and it does.t work anymore
Why doesn't this work? The same code i used it with a button instead and it worked fine. Does onTouch always have to be button or can it be layouts or textviews as well?
You should return true from onTouch method
If you return false when you get MotionEvent.ACTION_DOWN,
Android assumes that you're not interested in handling touch events and simply doesn't pass any other motion events to you (including MotionEvent.ACTION_MOVE that you're expecting)
You need to update the return part of onTouch Method
public boolean onTouch(View v, MotionEvent event) {
// if return is true means touch enabled
// If return is false means touch is disabled
return true;
}

Android: Drag and drop button (button disappears; position of the button)

I’m working on an app in which I want to drag a button. I have written two programs. The problem is that each of them is fulfilling only a specific part of what I want. I want a button which can be moved freely on the screen (no disappearing and stuff) and I also need the program to give me the coordinates of the local button position while moving (permanent).
In the first program is the problem that the button disappears while dragging. This happens for the negative values of x and y and also for positive x values (not for positive y values; here is everything perfect). Positive y values are everything above the button, positive x values everything on the right side of the button and so on. For the negative values the button disappears like there is wall which covers the button. For the positive x values the button vanishes with rising x value (a little bit like a fizzing tablet in water).
Here is the code:
seat_leftright = (ImageButton) findViewById(R.id.seat_leftright);
seat_leftright.setOnTouchListener(new OnTouchListener() {
int k = 0;
int prevX,prevY;
int x=0;
int y=0;
#Override
public boolean onTouch(final View v,final MotionEvent event)
{
final LinearLayout.LayoutParams par=(LinearLayout.LayoutParams)v.getLayoutParams();
switch(event.getAction())
{
case MotionEvent.ACTION_MOVE:
{
if(k == 0){ //otherwise there would be an offset when touching the button the first time
prevY=(int)event.getRawY();
prevX=(int)event.getRawX();
}
y+=prevY -(int)event.getRawY();
prevY=(int)event.getRawY();
par.bottomMargin = y;
x+=(int)event.getRawX()-prevX;
prevX=(int)event.getRawX();
par.leftMargin = x;
Log.i("LOG","x: "+ x +", y: " + y );
k++;
v.setLayoutParams(par);
return true;
}
case MotionEvent.ACTION_UP:
{
par.bottomMargin=0;
par.leftMargin=0;
k=0;
y=0;
x=0;
v.setLayoutParams(par);
return true;
}
}
return false;
}
});
}
In the second program I used the DragShadowBuilder function. The button works perfectly in this program, so it is not disappearing or the like. Here I have problems with receiving the values. I constantly need the x and y position of the button while moving. I tried it with the Action_drag_location, but it only returns the coordinates when I’m dragging the button above another button (here it is the button “arrow_down”). Replacing the “arrow_down” button with my background for constantly receiving the coordinates didn’t work at all. I also tried to combine my first program with the second with the result that I didn’t received any values at all.
I hope you can help me with this. I’m grateful for every kind of help!
Below the code of the second program.
OnTouchListener myOnTouchListener = new OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(
view);
view.startDrag(null, shadowBuilder, view, 0);
view.setVisibility(View.INVISIBLE);
return true;
}
if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
view.setVisibility(View.VISIBLE);}
return true;
}
};
OnDragListener myDragListener = new OnDragListener() {
#Override
public boolean onDrag(View layoutview, DragEvent dragevent) {
int action = dragevent.getAction();
View view = (View) dragevent.getLocalState();
switch (action) {
case DragEvent.ACTION_DRAG_STARTED:
Log.i("LOG","DragStarted");
break;
case DragEvent.ACTION_DRAG_ENTERED:
break;
case DragEvent.ACTION_DRAG_LOCATION:
float x = (int)layoutview.getX();
float y = (int)layoutview.getY();
Log.i("COORDS","X: " + x +" Y: " + y);
break;
case DragEvent.ACTION_DRAG_EXITED:
break;
case DragEvent.ACTION_DROP:
break;
case DragEvent.ACTION_DRAG_ENDED:
Log.d("LOG", "Drag ended");
if (dropEventNotHandled(dragevent)) {
view.setVisibility(View.VISIBLE);
}
break;
default:
break;
}
return true;
}
private boolean dropEventNotHandled(DragEvent dragEvent) {
return !dragEvent.getResult();
}
};
findViewById(R.id.arrow_up).setOnTouchListener(myOnTouchListener);
findViewById(R.id.arrow_down).setOnDragListener(myDragListener);
#Jay: Thanks for your help, but that didn't solve the problem. The disappearing buttons were due to my layout, where I had this:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#drawable/seat" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginLeft="20dp"
android:layout_marginBottom="160dp"
android:orientation="vertical" >
<ImageButton
android:id="#+id/seat_updown"
android:layout_width="120dp"
android:layout_height="140dp"
android:scaleType="fitCenter"
android:src="#drawable/doublearrow"
android:rotation="90" /> />
</LinearLayout>
Because the ImageButton was imbedded in the LinearLayout it disappeared!
For button drag & drop I share my code here check it.
Here button touch listener.
MultiTouchListener touchListener = new MultiTouchListener(this);
onButton.setOnTouchListener(touchListener);
Touch listener class.
public class MultiTouchListener implements OnTouchListener {
private float mPrevX;
private float mPrevY;
public MainActivity mainActivity;
public MultiTouchListener(MainActivity mainActivity1) {
mainActivity = mainActivity1;
}
#Override
public boolean onTouch(View view, MotionEvent event) {
float currX, currY;
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN: {
mPrevX = event.getX();
mPrevY = event.getY();
break;
}
case MotionEvent.ACTION_MOVE: {
currX = event.getRawX();
currY = event.getRawY();
MarginLayoutParams marginParams = new MarginLayoutParams(
view.getLayoutParams());
marginParams.setMargins((int) (currX - mPrevX),
(int) (currY - mPrevY), 0, 0);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
marginParams);
view.setLayoutParams(layoutParams);
break;
}
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
}
Sure, you can get these, make sure the views are drawn atleast once before you try to get the positions. You could try to get the positions in onResume() and try these functions
view.getLocationInWindow() or view.getLocationOnScreen()
or if you need something relative to the parent, use
view.getLeft(), view.getTop()
Follow this link for coordinates.
Retrieve the X & Y coordinates of a button in android?

ImageView onTouch never fired when animation was used

I use a phone with Android 2.3 and an animation to move an ImageView.
Here is the animation:
TranslateAnimation anim = new TranslateAnimation(0, xDest, yDest, yDest);
anim.setDuration(1000);
anim.setFillAfter(true);
MoveImagesOutsideOfScreen();
view.setVisibility(View.VISIBLE);
view.startAnimation(anim);
After the animation, when I use the following code to detect touch on my imageview and move it, nothing happens:
ImageView imageView=(ImageView)findViewById(R.id.imageSimple);
imageView.setOnTouchListener(new View.OnTouchListener()
{
PointF DownPT = new PointF(); // Record Mouse Position When Pressed Down
PointF StartPT = new PointF(); // Record Start Position of 'img'
#Override
public boolean onTouch(View v, MotionEvent event)
{
Log.i(TAG,"Touch started");
int eid = event.getAction();
switch (eid)
{
case MotionEvent.ACTION_MOVE:
PointF mv = new PointF(event.getX() - DownPT.x, event.getY() - DownPT.y);
v.setX((int) (StartPT.x + mv.x));
v.setY((int) (StartPT.y + mv.y));
StartPT = new PointF(v.getX(), v.getY());
Log.i(TAG,"Moved ");
break;
case MotionEvent.ACTION_DOWN:
DownPT.x = event.getX();
DownPT.y = event.getY();
StartPT = new PointF(v.getX(), v.getY());
break;
case MotionEvent.ACTION_UP:
// Nothing have to do
break;
default:
break;
}
return true;
}
});
I checked the log and the message Touch started was never shown. What prevents it?
UPDATE: Holy #%*! It seems that an animation doesn't move the actual view but instead some sort of copy (I don't see the concept of it, I think creating a copy is wasteful and most of the times people want to use the moved object).
I saved the layout params at the onCreate, and when I set it back, my views are placed randomly. What should I do to make my view be there where the animation puts its wasteful twin?
paramsSimple= (RelativeLayout.LayoutParams) aq.id(R.id.imageSimple).getImageView().getLayoutParams();
paramsExpert= (RelativeLayout.LayoutParams) aq.id(R.id.imageExpert).getImageView().getLayoutParams();
public void onAnimationEnd(Animation animation)
{
view.setLayoutParams(view.getId()==R.id.imageSimple?paramsSimple:paramsExpert);
}

How can I detect a click in an onTouch listener?

I have a ViewPager inside a ScrollView. I need to be able to scroll horizontally as well as vertically. In order to achieve this had to disable the vertical scrolling whenever my ViewPager is touched (v.getParent().requestDisallowInterceptTouchEvent(true);), so that it can be scrolled horizontally.
But at the same time I need to be able to click the viewPager to open it in full screen mode.
The problem is that onTouch gets called before onClick and my OnClick is never called.
How can I implement both on touch an onClick?
viewPager.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
System.out.println("TOUCHED ");
if(event.getAction() == MotionEvent.???){
//open fullscreen activity
}
v.getParent().requestDisallowInterceptTouchEvent(true); //This cannot be removed
return false;
}
});
viewPager.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
System.out.println("CLICKED ");
Intent fullPhotoIntent = new Intent(context, FullPhotoActivity.class);
fullPhotoIntent.putStringArrayListExtra("imageUrls", imageUrls);
startActivity(fullPhotoIntent);
}
});
Masoud Dadashi's answer helped me figure it out.
here is how it looks in the end.
viewPager.setOnTouchListener(new OnTouchListener() {
private int CLICK_ACTION_THRESHOLD = 200;
private float startX;
private float startY;
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = event.getX();
startY = event.getY();
break;
case MotionEvent.ACTION_UP:
float endX = event.getX();
float endY = event.getY();
if (isAClick(startX, endX, startY, endY)) {
launchFullPhotoActivity(imageUrls);// WE HAVE A CLICK!!
}
break;
}
v.getParent().requestDisallowInterceptTouchEvent(true); //specific to my project
return false; //specific to my project
}
private boolean isAClick(float startX, float endX, float startY, float endY) {
float differenceX = Math.abs(startX - endX);
float differenceY = Math.abs(startY - endY);
return !(differenceX > CLICK_ACTION_THRESHOLD/* =5 */ || differenceY > CLICK_ACTION_THRESHOLD);
}
}
I did something really simple by checking the time the user touches the screen.
private static int CLICK_THRESHOLD = 100;
#Override
public boolean onTouch(View v, MotionEvent event) {
long duration = event.getEventTime() - event.getDownTime();
if (event.getAction() == MotionEvent.ACTION_UP && duration < CLICK_THRESHOLD) {
Log.w("bla", "you clicked!");
}
return false;
}
Also worth noting that GestureDetector has something like this built-in. Look at onSingleTapUp
Developing both is the wrong idea. when user may do different things by touching the screen understanding user purpose is a little bit nifty and you need to develop a piece of code for it.
Two solutions:
1- (the better idea) in your onTouch event check if there is a motion. You can do it by checking if there is any movement using:
ACTION_UP
ACTION_DOWN
ACTION_MOVE
do it like this
if(event.getAction() != MotionEvent.ACTION_MOVE)
you can even check the distance of the movement of user finger on screen to make sure a movement happened rather than an accidental move while clicking. do it like this:
switch(event.getAction())
{
case MotionEvent.ACTION_DOWN:
if(isDown == false)
{
startX = event.getX();
startY = event.getY();
isDown = true;
}
Break;
case MotionEvent.ACTION_UP
{
endX = event.getX();
endY = event.getY();
break;
}
}
consider it a click if none of the above happened and do what you wanna do with click.
2) if rimes with your UI, create a button or image button or anything for full screening and set an onClick for it.
Good luck
don't try to REINVENT the wheel !
Elegant way to do it :
public class CustomView extends View {
private GestureDetectorCompat mDetector;
public CustomView(Context context) {
super(context);
mDetector = new GestureDetectorCompat(context, new MyGestureListener());
}
#Override
public boolean onTouchEvent(MotionEvent event){
return this.mDetector.onTouchEvent(event);
}
class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
#Override
public boolean onDown(MotionEvent e) {return true;}
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
//...... click detected !
return false;
}
}
}
You might need to differentiate between the user clicking and long-clicking. Otherwise, you'll detect both as the same thing. I did this to make that possible:
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = event.getX();
startY = event.getY();
bClick = true;
tmrClick = new Timer();
tmrClick.schedule(new TimerTask() {
public void run() {
if (bClick == true) {
bClick = false;
Log.d(LOG_TAG, "Hey, a long press event!");
//Handle the longpress event.
}
}
}, 500); //500ms is the standard longpress response time. Adjust as you see fit.
return true;
case MotionEvent.ACTION_UP:
endX = event.getX();
endY = event.getY();
diffX = Math.abs(startX - endX);
diffY = Math.abs(startY - endY);
if (diffX <= 5 && diffY <= 5 && bClick == true) {
Log.d(LOG_TAG, "A click event!");
bClick = false;
}
return true;
default:
return false;
}
}
The answers above mostly memorize the time. However, MotionEvent already has you covered. Here a solution with less overhead. Its written in kotlin but it should still be understandable:
private const val ClickThreshold = 100
override fun onTouch(v: View, event: MotionEvent): Boolean {
if(event.action == MotionEvent.ACTION_UP
&& event.eventTime - event.downTime < ClickThreshold) {
v.performClick()
return true // If you don't want to do any more actions
}
// do something in case its not a click
return true // or false, whatever you need here
}
This solution is good enough for most applications but is not so good in distinguishing between a click and a very quick swipe.
So, combining this with the answers above that also take the position into account is probably the best one.
Rather than distance / time diff based approaches, You can make use of GestureDetector in combination with setOnTouchListener to achieve this.
GestureDetector would detect the click while you can use OnTouchListener for other touch based events, e.g detecting drag.
Here is a sample code for reference:
Class MyCustomView() {
fun addClickAndTouchListener() {
val gestureDetector = GestureDetector(
context,
object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
// Add your onclick logic here
return true
}
}
)
setOnTouchListener { view, event ->
when {
gestureDetector.onTouchEvent(event) -> {
// Your onclick logic would be triggered through SimpleOnGestureListener
return#setOnTouchListener true
}
event.action == MotionEvent.ACTION_DOWN -> {
// Handle touch event
return#setOnTouchListener true
}
event.action == MotionEvent.ACTION_MOVE -> {
// Handle drag
return#setOnTouchListener true
}
event.action == MotionEvent.ACTION_UP -> {
// Handle Drag over
return#setOnTouchListener true
}
else -> return#setOnTouchListener false
}
}
}
}
I think combined solution time/position should be better:
private float initialTouchX;
private float initialTouchY;
private long lastTouchDown;
private static int CLICK_ACTION_THRESHHOLD = 100;
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastTouchDown = System.currentTimeMillis();
//get the touch location
initialTouchX = event.getRawX();
initialTouchY = event.getRawY();
return true;
case MotionEvent.ACTION_UP:
int Xdiff = (int) (event.getRawX() - initialTouchX);
int Ydiff = (int) (event.getRawY() - initialTouchY);
if (System.currentTimeMillis() - lastTouchDown < CLICK_ACTION_THRESHHOLD && (Xdiff < 10 && Ydiff < 10)) {
//clicked!!!
}
return true;
}
return false;
}
});
I believe you're preventing your view from receiving the touch event this way because your TouchListener intercepts it.
You can either
Dispatch the event inside your ToucheListener by calling v.onTouchEvent(event)
Override instead ViewPager.onTouchEvent(MotionEvent) not to intercept the event
Also, returning true means that you didn't consume the event, and that you're not interrested in following events, and you won't therefore receive following events until the gesture completes (that is, the finger is up again).
you can use OnTouchClickListener
https://github.com/hoffergg/BaseLibrary/blob/master/src/main/java/com/dailycation/base/view/listener/OnTouchClickListener.java
usage:
view.setOnTouchListener(new OnTouchClickListener(new OnTouchClickListener.OnClickListener() {
#Override
public void onClick(View v) {
//perform onClick
}
},5));
if (event.eventTime - event.downTime < 100 && event.actionMasked == MotionEvent.ACTION_UP) {
view.performClick()
return false
}
This code will do both touch events and click event.
viewPager.setOnTouchListener(new View.OnTouchListener() {
private int initialX;
private int initialY;
private float initialTouchX;
private float initialTouchY;
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//remember the initial position.
initialX = params.x;
initialY = params.y;
//get the touch location
initialTouchX = event.getRawX();
initialTouchY = event.getRawY();
return true;
case MotionEvent.ACTION_UP:
int Xdiff = (int) (event.getRawX() - initialTouchX);
int Ydiff = (int) (event.getRawY() - initialTouchY);
//The check for Xdiff <10 && YDiff< 10 because sometime elements moves a little while clicking.
if (Xdiff < 10 && Ydiff < 10) {
//So that is click event.
}
return true;
case MotionEvent.ACTION_MOVE:
//Calculate the X and Y coordinates of the view.
params.x = initialX + (int) (event.getRawX() - initialTouchX);
params.y = initialY + (int) (event.getRawY() - initialTouchY);
//Update the layout with new X & Y coordinate
mWindowManager.updateViewLayout(mFloatingView, params);
return true;
}
return false;
}
});
Here is the source.
I think your problem comes from the line:
v.getParent().requestDisallowInterceptTouchEvent(true); //This cannot be removed
Take a look to the documentation.
Have you tried to remove the line? What is the requeriment for not removing this line?
Take into account, according to the documentation, that if you return true from your onTouchListener, the event is consumed, and if you return false, is propagated, so you could use this to propagate the event.
Also, you should change your code from:
System.out.println("CLICKED ");
to:
Log.d("MyApp", "CLICKED ");
To get correct output in logcat.

GridView inside a ViewPager

I can't swipe/move to next page without me putting my finger to an empty space in the gridview, anyone encountered this?
Can you subclass ViewPager and overriding the onInterceptTouchEvent() method. The following checks for swipes in the X direction to allow for vertical scrolling if necessary. Not sure if it solves your problem completely but you can give an attempt.
private static final int minSwipeDistance = 30;
private float mTouchX;
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean response = super.onInterceptTouchEvent(event);
float x = event.getX();
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mTouchX = x;
break;
case MotionEvent.ACTION_MOVE:
float dX = Math.abs(x - mTouchX);
if (dX > minSwipeDistance)
return true;
break;
}
return response;
}

Categories