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;
}
So, I'm making a simple android game in Android Studio, and I am trying to make the player x-coordinate follow the x-coordinate of my finger. However, I only get it to where it "teleports" to the x-coordinate I want it to. How can I make it follow my finger when I swipe over the screen?
#Override
public boolean onTouchEvent(MotionEvent event)
{
if(event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) {
player.setX((int)event.getX());
}
return super.onTouchEvent(event);
}
You'll have to use android.view.View$OnTouchListener .
Here is an example.
private final class TileOnTouchListener implements OnTouchListener {
#Override
public boolean onTouch(View view, MotionEvent e) {
float x = e.getX();
float y = e.getY();
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
player.setX((int)e.getX());
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return true;
}
}
For more info check this.
I have parent view v extends linear layout. And child one is LinearLayout, child two is ScrollView.
I want to make MyParent View intercept vertical MotionEvent.ACTION_MOVE (only down direction, child scrollview.getScrollY()==0) and processing it in parent, and other MotionEvent is processed by children
Here is MyView.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<LinearLayout
android:id="#+id/child_linear>
android:height="50dp"
android:width="50dp">
...something...
</LinearLayout>
<ScrollView
android:id="#+id/child_scrollview>
<LinearLayout/>
</LinearLayout>
</ScrollView>
</merge>
And this is my code below
public class MyCustomView extends LinearLayout{
public MyCustomView(Context context) {
super(context);
init();
}
private void init(){
setOrientation(LinearLayout.VERTICAL);
LayoutInflater inflater =
(LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflater.inflate(R.layout.MyView, this, true);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
final int action = event.getAction();
Log.d(log,"onInterceptTouchEvent : "+action);
if (action == MotionEvent.ACTION_CANCEL || action ==
MotionEvent.ACTION_UP) {
mIsBeingDragged = false;
return false;
}
if (action != MotionEvent.ACTION_DOWN && mIsBeingDragged) {
return true;
}
switch (action) {
case MotionEvent.ACTION_MOVE: {
if (isReadyForPull()) {
final float y = event.getY(), x =
event.getX();
final float diff, oppositeDiff, absDiff;
diff = y - mLastMotionY;
oppositeDiff = x - mLastMotionX;
absDiff = Math.abs(diff);
ViewConfiguration config =
ViewConfiguration.get(getContext());
if (absDiff > config.getScaledTouchSlop() &&
absDiff > Math.abs(oppositeDiff) && diff >= 1f) {
mLastMotionY = y;
mLastMotionX = x;
mIsBeingDragged = true;
Log.d(log,"Flag setting");
}
}
break;
}
case MotionEvent.ACTION_DOWN: {
if (isReadyForPull()) {
mLastMotionY = mInitialMotionY = event.getY();
mLastMotionX = event.getX();
mIsBeingDragged = false;
}
break;
}
}
return mIsBeingDragged;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
final int action=event.getAction();
Log.d(log,"onTouchEvent : "+action);
if (event.getAction() == MotionEvent.ACTION_DOWN && event.getEdgeFlags() != 0) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE: {
Log.d(log,"ACTION MOVE RECEIVE");
if (mIsBeingDragged) {
mLastMotionY = event.getY();
mLastMotionX = event.getX();
pullEvent();
return true;
}
break;
}
case MotionEvent.ACTION_DOWN: {
if (isReadyForPull()) {
mLastMotionY = mInitialMotionY = event.getY();
return true;
}
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
if (mIsBeingDragged) {
mIsBeingDragged = false;
if (mState == State.RELEASE_TO_REFRESH ) {
setState(State.REFRESHING);
return true;
}
if (isRefreshing()) {
smoothScrollTo(-mHeaderHeight , null);
return true;
}
setState(State.RESET);
return true;
}
break;
}
}
return false;
}
isReadyForPull() function check only this
private boolean isReadyForPull(){
return mScrollView.getScrollY()==0;
}
My code works well.
If I move down my child scrollview, onInterceptTouchEvent first get MotionEvent.ACTION_DOWN and initialize values, and get sequentially MotionEvent.ACTION_MOVE so set mIsBeingDragged flag to true and return true. So I can process event in my ParentView's onTouchEvent.
Otherwise, if I move up child scrollview, then onInterceptTouchEvent return false and my child scrollview get that MotionEvent and scrolling down.
This is expected work. Good.
But, when I touch my child linearlayout, it doesn't not work!
If I touch and drag my child linearlayout, my CustomView's onInterceptTouchEvent get MotionEvent.ACTION_DOWN, but couldn't get second MotionEvent.ACTION_MOVE.
Why this not work on only child linearlayout?
Note:
I tested several times and know that (default)touchable view or viewgroup (like button, scrollview etc.) do work with my code, while (default)untouchable view or widget(like imageview, framelayout) don't work with my code.
The solution relates to this answer, as it is a result of the standard MotionEvent handling.
In my code, when I touch the child LinearLayout, the parent CustomViewGroup doesn't intercept the MotionEvent as it returns false, but my child LinearLayout doesn't consume the MotionEvent either, so the MotionEvent is returned to the parent's onTouchEvent, not onInterceptTouchEvent.
On the other hand, when I touch my child ScrollView, it consumes the MotionEvent whether scrolling is enabled or disabled.
==> I think because Android doesn't generate a MotionEvent again until the original one is either consumed or finished, so the parent CustomViewGroup doesn't get the ACTION_MOVE MotionEvent via onInterceptTouchEvent, instaed it is piped to onTouchEvent when a child doesn't consume the MotionEvent.
I found two solutions
Solution one
Forcibly make my LinearLayout consume the MotionEvent. This solution is available only when the child LinearLayout has no touchable View, ViewGroup or Widget. Like this:
LinearLayout mLinearLayout = (LinearLayout)findViewById(R.id.child_linear);
mLinearLayout.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
Solution two
Process the MotionEvent returning from a child in my CustomViewGroup's onTouchEvent. Like this: (Just add else if case in onTouchEvent).
case MotionEvent.ACTION_MOVE: {
if (mIsBeingDragged) {
mLastMotionY = event.getY();
mLastMotionX = event.getX();
pullEvent();
return true;
}
else if (isReadyForPull()) {
final float y = event.getY(), x = event.getX();
final float diff, oppositeDiff, absDiff;
diff = y - mLastMotionY;
oppositeDiff = x - mLastMotionX;
absDiff = Math.abs(diff);
ViewConfiguration config = ViewConfiguration.get(getContext());
if (absDiff > config.getScaledTouchSlop() && absDiff >
Math.abs(oppositeDiff) && diff >= 1f) {
mLastMotionY = y;
mLastMotionX = x;
mIsBeingDragged = true;
}
}
break;
}
While solution 1 is a quick fix for certain situations, solution two is the most flexible and reliable.
There is very good answer
onInterceptTouchEvent only gets ACTION_DOWN
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
MyLog.d(MyLog.DEBUG, "dispatchTouchEvent(): "+event.getAction());
if (isEnabled()) {
MyLog.d(MyLog.DEBUG, "dispatchTouchEvent()2: "+event.getAction());
processEvent(event);//here you get all events include move & up
super.dispatchTouchEvent(event);
return true; //to keep receive event that follow down event
}
return super.dispatchTouchEvent(event);
}
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.
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;
}