I am creating an android app in Java in which I have a lot of <TextView> around the screen, all of them with onTouchListeners defined. They are wrapped in a <ScrollView> because they occupy more space than available in the screen.
My problem is: when I scroll the app, up/down, by touching at the screen and moving my finger up/down, the scroll works as expected but the onTouchListener of the touched <TextView> is also fired (which is probably expected as well) - I don't want that to happen though. I want the onTouchListener to be ignored when I'm touching the screen to scroll it.
How can I accomplish this? I don't want my function to run when the user is scrolling and "accidentally" fires the onTouchListener on a certain <TextView>.
After searching more, I found this solution by Stimsoni. The idea is to check if the time between the ACTION_DOWN and ACTION_UP events is lower or higher than the value given by ViewConfiguration.getTapTimeout().
From the documentation:
[Returns] the duration in milliseconds we will wait to see if a touch event is a tap or a scroll. If the user does not move within this interval, it is considered to be a tap.
Code:
view.setOnTouchListener(new OnTouchListener() {
private long startClickTime;
#Override
public boolean onTouch(View view, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
startClickTime = System.currentTimeMillis();
} else if (event.getAction() == MotionEvent.ACTION_UP) {
if (System.currentTimeMillis() - startClickTime < ViewConfiguration.getTapTimeout()) {
// Touch was a simple tap. Do whatever.
} else {
// Touch was a not a simple tap.
}
}
return true;
}
});
I had the same problem as you, and I solved it with ACTION_CANCEL.
motionEvent.getActionMasked() is equal to ACTION_CANCEL when an action perceived previously (like ACTION_DOWN in your case) is "canceled" now by other gestures like scrolling, etc. your code may be like this:
view.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent e) {
if (e.getActionMasked() == MotionEvent.ACTION_DOWN) {
// perceive a touch action.
} else if(e.getActionMasked() == MotionEvent.ACTION_UP ||
e.getActionMasked() == MotionEvent.ACTION_CANCEL) {
// ignore the perceived action.
}
}
I hope this helps.
I had a similar problem but with one TextView, search led me here. The text-content potentially takes up more space than available on screen.
Simple working example: bpmcounter-android (Kotlin)
class MainActivity : AppCompatActivity() {
inner class GestureTap : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapUp(e: MotionEvent?): Boolean {
// Do your buttonClick stuff here. Any scrolling action will be ignored
return true
}
}
#SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView = findViewById<TextView>(R.id.textView)
textView.movementMethod = ScrollingMovementMethod()
val gestureDetector = GestureDetector(this, GestureTap())
textView.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event) }
}
}
1 METHOD:
I figured out that the best method to do this is detecting the first touch saving the points x and y and then confront it with the second touch. If the distance between the first click and the second one is quite close (I put 10% as an approximation) then the touch a simple click otherwise is a scrolling movement.
/**
* determine whether two numbers are "approximately equal" by seeing if they
* are within a certain "tolerance percentage," with `tolerancePercentage` given
* as a percentage (such as 10.0 meaning "10%").
*
* #param tolerancePercentage 1 = 1%, 2.5 = 2.5%, etc.
*/
fun approximatelyEqual(desiredValue: Float, actualValue: Float, tolerancePercentage: Float): Boolean {
val diff = Math.abs(desiredValue - actualValue) // 1000 - 950 = 50
val tolerance = tolerancePercentage / 100 * desiredValue // 20/100*1000 = 200
return diff < tolerance // 50<200 = true
}
var xPoint = 0f
var yPoint = 0f
#SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
when(event.action) {
MotionEvent.ACTION_DOWN -> {
xPoint = event.x
yPoint = event.y
return true
}
MotionEvent.ACTION_UP -> {
if (!approximatelyEqual(xPoint, event.x, 10f) || !approximatelyEqual(yPoint, event.y, 10f)) {
//scrolling
} else {
//simple click
}
}
}
return false
}
2 METHOD:
Another way to do the same thing is by using the GestureDetector class:
interface GestureInterface {
fun setOnScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float)
fun onClick(e: MotionEvent)
}
class MyGestureDetector(val gestureInterfacePar: GestureInterface) : SimpleOnGestureListener() {
override fun onSingleTapUp(e: MotionEvent): Boolean {
gestureInterfacePar.onClick(e)
return false
}
override fun onLongPress(e: MotionEvent) {}
override fun onDoubleTap(e: MotionEvent): Boolean {
return false
}
override fun onDoubleTapEvent(e: MotionEvent): Boolean {
return false
}
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
return false
}
override fun onShowPress(e: MotionEvent) {
}
override fun onDown(e: MotionEvent): Boolean {
return true
}
override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
gestureInterfacePar.setOnScroll(e1, e2, distanceX, distanceY)
return false
}
override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
return super.onFling(e1, e2, velocityX, velocityY)
}
}
and finally, bind it with your view:
val mGestureDetector = GestureDetector(context, MyGestureDetector(object : GestureInterface {
override fun setOnScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float) {
//handle the scroll
}
override fun onClick(e: MotionEvent) {
//handle the single click
}
}))
view.setOnTouchListener(OnTouchListener { v, event -> mGestureDetector.onTouchEvent(event) })
You can identify moving action like this:
view.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_MOVE)
{
}
return false;
}
});
Worked for me :
View.OnTouchListener() {
#Override
public boolean onTouch(View v,MotionEvent event)
{
if(event.getAction()!=MotionEvent.ACTION_DOWN)
{
// Before touch
}
else {
// When touched
}
return true
});
You dont need to go for such cómplicated method for capturing a "click" event. Just for this method :-
//Inside on touch listener of course :-
KOTLIN :-
if(event.action == MotionEvent.ACTION_UP && event.action != MotionEvent.ACTION_MOVE) {
// Click has been made...
// Some code
}
JAVA :-
Just replace event.action with event.getAction()
This works for me 😉
Related
I have two views which are siblings of each other. They both cover the full screen. So one is behind the other. If the upper one gets touched (onTouch), I delegate the touch events to the one underneath it (with dispatchTouchEvent).
But sometimes I want to delay that delegation, till the next time onTouch gets called. But somehow that does not work.
An example to clarify:
To viewA - which is in front of viewB - I have applied the following (simplified) code:
viewA.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
int touchType = event.getActionMasked();
if (touchType == MotionEvent.ACTION_DOWN) {
savedEvent = event;
return true; // I also tried returning false here
} else {
if (savedEvent != null) {
viewB.dispatchTouchEvent(savedEvent);
savedEvent = null;
}
viewB.dispatchTouchEvent(event);
return true; // I also tried returning false here
}
}
});
To test the dispatchTouchEvent call, I have the following code for viewB
viewB.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View arg0, MotionEvent event) {
Log.d("test", "test"); // this code gets logged, so it is being called, but the view seems to not execute any of the touch events
return true;
}
});
When I change to code for viewA to this:
viewA.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
viewB.dispatchTouchEvent(event);
return true;
}
});
everything works just fine.
But the thing is that for my use case, I sometimes have to call the dispatchTouchEvent method with the event parameter outside its originating onTouch method, so to speak.
Is this even possible? And if yes, how?
So I found out what prevents it from working. If you manually call dispatchTouchEvent you should pass through an event that you created yourself with MotionEvent.obtain().
Working example:
viewA.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
int touchType = event.getActionMasked();
if (touchType == MotionEvent.ACTION_DOWN) {
// do not do this, somehow passing on a reference of the event directly does not work:
// savedEvent = event;
// this works though:
savedEvent = MotionEvent.obtain(event);
return true;
} else {
if (savedEvent != null) {
viewB.dispatchTouchEvent(savedEvent);
savedEvent = null;
}
viewB.dispatchTouchEvent(event);
return true;
}
}
});
Although I don't understand why that does work and just passing the event reference directly does not, I tested this and it does work perfectly.
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 have an Osmdroid MapView. Even though I have set
mapView.setClickable(false);
mapView.setFocusable(false);
the map can still be moved around. Is there a simple way to disable all interactions with the map view?
A simple solution is to do like #Schrieveslaach but with the mapView:
mapView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
My solution is similar to #schrieveslaach and #sagix, but I just extend base MapView class and add new functionality:
class DisabledMapView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : MapView(context, attrs) {
private var isUserInteractionEnabled = true
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
if (isUserInteractionEnabled.not()) {
return false
}
return super.dispatchTouchEvent(event)
}
fun setUserInteractionEnabled(isUserInteractionEnabled: Boolean) {
this.isUserInteractionEnabled = isUserInteractionEnabled
}
}
I've found a solution. You need to handle the touch events directly by setting a OnTouchListener. For example,
public class MapViewLayout extends RelativeLayout {
private MapView mapView;
/**
* #see #setDetachedMode(boolean)
*/
private boolean detachedMode;
// implement initialization of your layout...
private void setUpMapView() {
mapView.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (detachedMode) {
if (event.getAction() == MotionEvent.ACTION_UP) {
// if you want to fire another event
}
// Is detached mode is active all other touch handler
// should not be invoked, so just return true
return true;
}
return false;
}
});
}
/**
* Sets the detached mode. In detached mode no interactions will be passed to the map, the map
* will be static (no movement, no zooming, etc).
*
* #param detachedMode
*/
public void setDetachedMode(boolean detachedMode) {
this.detachedMode = detachedMode;
}
}
You could try:
mapView.setEnabled(false);
Which should disable all interactions with the map view
Well I have made a custom Android UI, and I need my UI view to handle this control.
a) While my mouse button is pressed (onDown / Key pressed) , the view should keep doing a thing (For example : Key is down)
b) As soon as i release my mouse key , the view should say (example: Key is up).
The Sample Flow should be something like when I press the view.
Output:
(I press mouse button on the view and hold it)
Key is down
Key is down
Key is down
Key is down
Key is down
Key is down
(I now release mouse button)
Key is up.
To more explain my problem . I am posting a code snippet where the logic should go
#Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, event.getAction());
}
When I press my mouse button , it prints "0" (meaning mouse is down), while if i leave it , it prints on the log "1", meaning mouse is up. That should help with the logic.
Thanks for helping.
Well I have tried something like
private static int checkValue = 0;
#Override
public boolean onTouchEvent(MotionEvent event) {
checkValue = event.getAction();
while (checkValue == 0 ){
Log.d(TAG, "Pressed");
checkValue = checkMethod(event.getAction);
}
private int checkMethod(int test){
if (checkValue == 0){
checkValue = 0;
}
else checkValue = 1;
}
Just define an OnTouchEventListener...
private OnTouchListener onTouchListener = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (v.getId() == R.id.button) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
//start loop or background task
} else if (event.getAction() == MotionEvent.ACTION_UP) {
//do something different
}
}
return true;
}
}
...and assign it to your button.
button.setOnTouchListener(onTouchListener);
When the user touches the button the ACTION_DOWN event is fired. When the user releases the button, the ACTION_UP event is fired. If you want you can start a loop in a thread which can check against a boolean variable. I didn't check if it is correct but it should look like this:
private OnTouchListener onTouchListener = new OnTouchListener() {
private boolean flag = false;
#Override
public boolean onTouch(View v, MotionEvent event) {
if (v.getId() == R.id.button) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
flag = true;
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
while(flag) {
//do something
}
}
});
thread.start();
} else if (event.getAction() == MotionEvent.ACTION_UP) {
flag = false;
}
}
return true;
}
}
Maybe you could create and start a thread when the touch occurs that has a volatile boolean stop = false field and that checks for this value in every loop after sleeping for a while. When the touch ends, you can set the boolean variable to false.
Edit: added an example
private MyHandler h;
public void handleDown() {
System.out.println("touch began");
h = new MyHandler();
h.start();
}
public void handleUp() {
h.pleaseStop = true;
System.out.println("touch ended");
}
class MyHandler extends Thread {
volatile boolean pleaseStop = false;
public void run() {
while (!pleaseStop) {
System.out.println("touch is active...");
Thread.sleep(500);
}
}
}
checkValue = event.getAction();
while (checkValue == 0 ){
You should never ever be using magical integers. Use the constants as defined by the API:
http://developer.android.com/reference/android/view/MotionEvent.html#ACTION_DOWN
In fact your code isn't even accounting for move events; it seems to assume that if it isn't a down event it is an up... which is completely wrong.
Look at the documentation to see what action values you can expect, and do the right thing for the specific actions you are interested in. For example up:
http://developer.android.com/reference/android/view/MotionEvent.html#ACTION_UP
Create a thread which is initiated by the touch and have boolean flag to keep it alive. When the touch is released, set the flag to false so the thread joins (terminates).
Because the thread is independent from the main logic, the process is faster.