Android onInterceptTouchEvent doesn't get action when have child view - java

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);
}

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: How to tell if a user presses up, down, left or right?

I created an onTouchEvent to find where the user touches and move my object to that position. what I would like to do is if the user presses up on the screen, the object moves a certain distance straight up. and the same for the other directions. I know that I need a few if statements to do this but I don't know how to do it. does anyone have any advice or know how to do this, thanks
public boolean onTouchEvent(MotionEvent ev) {
if(ev.getAction() == MotionEvent.ACTION_DOWN) {
// the new image position became where you touched
x = ev.getX();
y = ev.getY();
// if statement to detect where user presses down
if(){
}
// redraw the image at the new position
Draw.this.invalidate();
}
return true;
}
Try this
public boolean onTouchEvent(MotionEvent ev) {
initialise boolean actionDownFlag = false;
if(ev.getAction() == MotionEvent.ACTION_DOWN) {
// the new image position became where you touched
x = (int) ev.getX();
y = (int) ev.getY();
// if statement to detect where user presses down
if(actionDownFlag){
catcher.moveDown();
}
// redraw the image at the new position
Draw.this.invalidate();
}
return true;
}
public void moveDown(){
posX -= //WhereEver you want to move the position (-5);
}
try this:
layout_counter.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event)
{
if (currentState != State.EDIT_MOVE) return false;
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) view.getLayoutParams();
if (view.getId() != R.id.layout_counter) return false;
switch (event.getAction())
{
case MotionEvent.ACTION_MOVE:
params.topMargin = (int) event.getRawY() - view.getHeight();
params.leftMargin = (int) event.getRawX() - (view.getWidth() / 2);
view.setLayoutParams(params);
break;
case MotionEvent.ACTION_UP:
params.topMargin = (int) event.getRawY() - view.getHeight();
params.leftMargin = (int) event.getRawX() - (view.getWidth() / 2);
view.setLayoutParams(params);
break;
case MotionEvent.ACTION_DOWN:
view.setLayoutParams(params);
break;
}
return true;
}
});
You need to use OnLongClickListener which suits better with drag and drop framework of android:
Observe the following example:
public class MainActivity extends Activity {
private ImageView myImage;
private static final String IMAGEVIEW_TAG = "The Android Logo";
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myImage = (ImageView)findViewById(R.id.image);
// Sets the tag
myImage.setTag(IMAGEVIEW_TAG);
// set the listener to the dragging data
myImage.setOnLongClickListener(new MyClickListener());
findViewById(R.id.toplinear).setOnDragListener(new MyDragListener());
findViewById(R.id.bottomlinear).setOnDragListener(new MyDragListener());
}
private final class MyClickListener implements OnLongClickListener {
// called when the item is long-clicked
#Override
public boolean onLongClick(View view) {
// TODO Auto-generated method stub
// create it from the object's tag
ClipData.Item item = new ClipData.Item((CharSequence)view.getTag());
String[] mimeTypes = { ClipDescription.MIMETYPE_TEXT_PLAIN };
ClipData data = new ClipData(view.getTag().toString(), mimeTypes, item);
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
view.startDrag( data, //data to be dragged
shadowBuilder, //drag shadow
view, //local data about the drag and drop operation
0 //no needed flags
);
view.setVisibility(View.INVISIBLE);
return true;
}
}
class MyDragListener implements OnDragListener {
Drawable normalShape = getResources().getDrawable(R.drawable.normal_shape);
Drawable targetShape = getResources().getDrawable(R.drawable.target_shape);
#Override
public boolean onDrag(View v, DragEvent event) {
// Handles each of the expected events
switch (event.getAction()) {
//signal for the start of a drag and drop operation.
case DragEvent.ACTION_DRAG_STARTED:
// do nothing
break;
//the drag point has entered the bounding box of the View
case DragEvent.ACTION_DRAG_ENTERED:
v.setBackground(targetShape); //change the shape of the view
break;
//the user has moved the drag shadow outside the bounding box of the View
case DragEvent.ACTION_DRAG_EXITED:
v.setBackground(normalShape); //change the shape of the view back to normal
break;
//drag shadow has been released,the drag point is within the bounding box of the View
case DragEvent.ACTION_DROP:
// if the view is the bottomlinear, we accept the drag item
if(v == findViewById(R.id.bottomlinear)) {
View view = (View) event.getLocalState();
ViewGroup viewgroup = (ViewGroup) view.getParent();
viewgroup.removeView(view);
//change the text
TextView text = (TextView) v.findViewById(R.id.text);
text.setText("The item is dropped");
LinearLayout containView = (LinearLayout) v;
containView.addView(view);
view.setVisibility(View.VISIBLE);
} else {
View view = (View) event.getLocalState();
view.setVisibility(View.VISIBLE);
Context context = getApplicationContext();
Toast.makeText(context, "You can't drop the image here",
Toast.LENGTH_LONG).show();
break;
}
break;
//the drag and drop operation has concluded.
case DragEvent.ACTION_DRAG_ENDED:
v.setBackground(normalShape); //go back to normal shape
default:
break;
}
return true;
}
}
}
The above code taken from here demonstrates how to drag and drop and how to do something while dragging..
I've used something similar as well.

Detect swipe gesture when there's a ScrollView Android

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.

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?

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.

Categories