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;
}
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
I've been searching for some function that can give me x,y coordinates of the the point where I touch on screen.
I got a code of onTouch() but couldn't find a way to call that function.
public boolean onTouch(View v, MotionEvent event) {
int x = event.getX();
int y = event.getY();
return true;
}
What is View and MotionEvent that I'm supposed to pass in arguements?
Can anyone please help me and tell me how to get just x,y of the point of touch?
Try this for a view:
View yourView = findViewById(R.id.yourViewId)
yourView.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getActionMasked() == MotionEvent.ACTION_DOWN) {
// Do something
x = event.getX();
y = event.getY();
}
return true;
}
});
or Try this for an activity:
#Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getActionMasked() == MotionEvent.ACTION_DOWN) {
// Do something
x = event.getX();
y = event.getY();
}
return true;
}
By the way, you don't need to call this method, you just override it and any time the screen is touched, this method will be called by your activity.
Let me know if this doesn't work and I'll delete the answer
To get the coordinates for when you press try:
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: // when you touch
x = event.getRawX(); // gather x and y
y = event.getRawY();
break;
PS I would comment but have too low rep
PSS you if statement if this is all you want to do, switch if your trying to do multiple things ie dragging an item.
I need to detect when there's a swipe on my App, I used this code and it works fine:
private float x1,x2;
static final int MIN_DISTANCE = 150;
and override onTouchEvent () method:
#Override
public boolean onTouchEvent(MotionEvent event)
{
switch(event.getAction())
{
case MotionEvent.ACTION_DOWN:
x1 = event.getX();
break;
case MotionEvent.ACTION_UP:
x2 = event.getX();
float deltaX = x2 - x1;
if (Math.abs(deltaX) > MIN_DISTANCE)
{
Toast.makeText(this, "left2right swipe", Toast.LENGTH_SHORT).show ();
}
else
{
// consider as something else - a screen tap for example
}
break;
}
return super.onTouchEvent(event);
}
But if I have a scrollView on my Activity the code doesn't work anymore, How can I possibly fix this? Do I need to change completely the code i'm using?
EDIT:
I tried to add the following method inside the if that detects the swipe gesture:
if (getParent() != null) {
getParent().requestDisallowInterceptTouchEvent(true);
}
But I get an error on
requestDisallowInterceptTouchEvent
It says that I need to add cast to getParent()
yes you can fix this :-) And there are 3 things you need to do:
You need to add this method to your activity, this way you are making sure that your onTouchEvent function is always intercepting the event:
#Override
public boolean dispatchTouchEvent(MotionEvent event){
this.onTouchEvent(event);
return super.dispatchTouchEvent(event);
}
Add a global boolean variable as a flag. This is because while when there is a ListView, the super.dispatchTouchEvent lets the event consumed by the ListView. However, when there is not a ListView, the above code will dispatch the same swiping event to onTouchEvent twice (the second time is through the super.dispatchTouchEvent):
boolean swapped = false;
modify your onTouchEvent function to utilize the swapped flags:
#Override
public boolean onTouchEvent(MotionEvent event)
{
if(swapped){
/*Make sure you don't swap twice,
since the dispatchTouchEvent might dispatch your touch event to this function again!*/
swapped = false;
return super.onTouchEvent(event);
}
switch(event.getAction())
{
case MotionEvent.ACTION_DOWN:
x1 = event.getX();
break;
case MotionEvent.ACTION_UP:
x2 = event.getX();
float deltaX = x2 - x1;
if (Math.abs(deltaX) > MIN_DISTANCE)
{
Toast.makeText(this, "left2right swipe", Toast.LENGTH_SHORT).show();
//you already swapped, set flag swapped = true
swapped = true;
}
else
{
// not swapping
}
break;
}
return super.onTouchEvent(event);
}
Note: don't add the code you mentioned in your post, and your MIN_DISTANCE is a bit too small, set it to 250 maybe.
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.