Detecting a long press in Android - java

I am currently using onTouchEvent(MotionEvent event) { } to detect when the user presses my glSurfaceView is there a way to detect when a long click is made.
I'm guessing if I can't find much in the dev docs then it will be some sort of work around method. Something like registering ACTION_DOWN and seeing how long it is before ACTION_UP.
How do you detect long presses on Android using opengl-es?

GestureDetector is the best solution.
Here is an interesting alternative. In onTouchEvent on every ACTION_DOWN schedule a Runnable to run in 1 second. On every ACTION_UP or ACTION_MOVE, cancel scheduled Runnable. If cancelation happens less than 1s from ACTION_DOWN event, Runnable won't run.
final Handler handler = new Handler();
Runnable mLongPressed = new Runnable() {
public void run() {
Log.i("", "Long press!");
}
};
#Override
public boolean onTouchEvent(MotionEvent event, MapView mapView){
if(event.getAction() == MotionEvent.ACTION_DOWN)
handler.postDelayed(mLongPressed, ViewConfiguration.getLongPressTimeout());
if((event.getAction() == MotionEvent.ACTION_MOVE)||(event.getAction() == MotionEvent.ACTION_UP))
handler.removeCallbacks(mLongPressed);
return super.onTouchEvent(event, mapView);
}

Try this:
final GestureDetector gestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
public void onLongPress(MotionEvent e) {
Log.e("", "Longpress detected");
}
});
public boolean onTouchEvent(MotionEvent event) {
return gestureDetector.onTouchEvent(event);
};

I have a code which detects a click, a long click and movement.
It is fairly a combination of the answer given above and the changes i made from peeping into every documentation page.
//Declare this flag globally
boolean goneFlag = false;
//Put this into the class
final Handler handler = new Handler();
Runnable mLongPressed = new Runnable() {
public void run() {
goneFlag = true;
//Code for long click
}
};
//onTouch code
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
handler.postDelayed(mLongPressed, 1000);
//This is where my code for movement is initialized to get original location.
break;
case MotionEvent.ACTION_UP:
handler.removeCallbacks(mLongPressed);
if(Math.abs(event.getRawX() - initialTouchX) <= 2 && !goneFlag) {
//Code for single click
return false;
}
break;
case MotionEvent.ACTION_MOVE:
handler.removeCallbacks(mLongPressed);
//Code for movement here. This may include using a window manager to update the view
break;
}
return true;
}
I confirm it's working as I have used it in my own application.

I have created a snippet - inspired by the actual View source - that reliably detects long clicks/presses with a custom delay. But it's in Kotlin:
val LONG_PRESS_DELAY = 500
val handler = Handler()
var boundaries: Rect? = null
var onTap = Runnable {
handler.postDelayed(onLongPress, LONG_PRESS_DELAY - ViewConfiguration.getTapTimeout().toLong())
}
var onLongPress = Runnable {
// Long Press
}
override fun onTouch(view: View, event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
boundaries = Rect(view.left, view.top, view.right, view.bottom)
handler.postDelayed(onTap, ViewConfiguration.getTapTimeout().toLong())
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
handler.removeCallbacks(onLongPress)
handler.removeCallbacks(onTap)
}
MotionEvent.ACTION_MOVE -> {
if (!boundaries!!.contains(view.left + event.x.toInt(), view.top + event.y.toInt())) {
handler.removeCallbacks(onLongPress)
handler.removeCallbacks(onTap)
}
}
}
return true
}

When you mean user presses, do you mean a click? A click is when the user presses down and then immediately lifts up finger. Therefore it is encompassing two onTouch Events. You should save the use of onTouchEvent for stuff that happens on the initial touch or the after release.
Thus, you should be using onClickListener if it is a click.
Your answer is analogous: Use onLongClickListener.

The solution by MSquare works only if you hold a specific pixel, but that's an unreasonable expectation for an end user unless they use a mouse (which they don't, they use fingers).
So I added a bit of a threshold for the distance between the DOWN and the UP action in case there was a MOVE action inbetween.
final Handler longPressHandler = new Handler();
Runnable longPressedRunnable = new Runnable() {
public void run() {
Log.e(TAG, "Long press detected in long press Handler!");
isLongPressHandlerActivated = true;
}
};
private boolean isLongPressHandlerActivated = false;
private boolean isActionMoveEventStored = false;
private float lastActionMoveEventBeforeUpX;
private float lastActionMoveEventBeforeUpY;
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN) {
longPressHandler.postDelayed(longPressedRunnable, 1000);
}
if(event.getAction() == MotionEvent.ACTION_MOVE || event.getAction() == MotionEvent.ACTION_HOVER_MOVE) {
if(!isActionMoveEventStored) {
isActionMoveEventStored = true;
lastActionMoveEventBeforeUpX = event.getX();
lastActionMoveEventBeforeUpY = event.getY();
} else {
float currentX = event.getX();
float currentY = event.getY();
float firstX = lastActionMoveEventBeforeUpX;
float firstY = lastActionMoveEventBeforeUpY;
double distance = Math.sqrt(
(currentY - firstY) * (currentY - firstY) + ((currentX - firstX) * (currentX - firstX)));
if(distance > 20) {
longPressHandler.removeCallbacks(longPressedRunnable);
}
}
}
if(event.getAction() == MotionEvent.ACTION_UP) {
isActionMoveEventStored = false;
longPressHandler.removeCallbacks(longPressedRunnable);
if(isLongPressHandlerActivated) {
Log.d(TAG, "Long Press detected; halting propagation of motion event");
isLongPressHandlerActivated = false;
return false;
}
}
return super.dispatchTouchEvent(event);
}

The idea is creating a Runnable for execute long click in a future, but this execution can be canceled because of a click, or move.
You also need to know, when long click was consumed, and when it is canceled because finger moved too much. We use initialTouchX & initialTouchY for checking if the user exit a square area of 10 pixels, 5 each side.
Here is my complete code for delegating Click & LongClick from Cell in ListView to Activity with OnTouchListener:
ClickDelegate delegate;
boolean goneFlag = false;
float initialTouchX;
float initialTouchY;
final Handler handler = new Handler();
Runnable mLongPressed = new Runnable() {
public void run() {
Log.i("TOUCH_EVENT", "Long press!");
if (delegate != null) {
goneFlag = delegate.onItemLongClick(index);
} else {
goneFlag = true;
}
}
};
#OnTouch({R.id.layout})
public boolean onTouch (View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
handler.postDelayed(mLongPressed, ViewConfiguration.getLongPressTimeout());
initialTouchX = motionEvent.getRawX();
initialTouchY = motionEvent.getRawY();
return true;
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_CANCEL:
if (Math.abs(motionEvent.getRawX() - initialTouchX) > 5 || Math.abs(motionEvent.getRawY() - initialTouchY) > 5) {
handler.removeCallbacks(mLongPressed);
return true;
}
return false;
case MotionEvent.ACTION_UP:
handler.removeCallbacks(mLongPressed);
if (goneFlag || Math.abs(motionEvent.getRawX() - initialTouchX) > 5 || Math.abs(motionEvent.getRawY() - initialTouchY) > 5) {
goneFlag = false;
return true;
}
break;
}
Log.i("TOUCH_EVENT", "Short press!");
if (delegate != null) {
if (delegate.onItemClick(index)) {
return false;
}
}
return false;
}
ClickDelegateis an interface for sending click events to the handler class like an Activity
public interface ClickDelegate {
boolean onItemClick(int position);
boolean onItemLongClick(int position);
}
And all what you need is to implement it in your Activity or parent Viewif you need to delegate the behavior:
public class MyActivity extends Activity implements ClickDelegate {
//code...
//in some place of you code like onCreate,
//you need to set the delegate like this:
SomeArrayAdapter.delegate = this;
//or:
SomeViewHolder.delegate = this;
//or:
SomeCustomView.delegate = this;
#Override
public boolean onItemClick(int position) {
Object obj = list.get(position);
if (obj) {
return true; //if you handle click
} else {
return false; //if not, it could be another event
}
}
#Override
public boolean onItemLongClick(int position) {
Object obj = list.get(position);
if (obj) {
return true; //if you handle long click
} else {
return false; //if not, it's a click
}
}
}

setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
int action = MotionEventCompat.getActionMasked(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
longClick = false;
x1 = event.getX();
break;
case MotionEvent.ACTION_MOVE:
if (event.getEventTime() - event.getDownTime() > 500 && Math.abs(event.getX() - x1) < MIN_DISTANCE) {
longClick = true;
}
break;
case MotionEvent.ACTION_UP:
if (longClick) {
Toast.makeText(activity, "Long preess", Toast.LENGTH_SHORT).show();
}
}
return true;
}
});

Here is an approach, based on MSquare's nice idea for detecting a long press of a button, that has an additional feature: not only is an operation performed in response to a long press, but the operation is repeated until a MotionEvent.ACTION_UP message is received. In this case, the long-press and short-press actions are the same, but they could be different.
Note that, as others have reported, removing the callback in response to a MotionEvent.ACTION_MOVE message prevented the callback from ever getting executed since I could not keep my finger still enough. I got around that problem by ignoring that message.
private void setIncrementButton() {
final Button btn = (Button) findViewById(R.id.btn);
final Runnable repeater = new Runnable() {
#Override
public void run() {
increment();
final int milliseconds = 100;
btn.postDelayed(this, milliseconds);
}
};
btn.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_DOWN) {
increment();
v.postDelayed(repeater, ViewConfiguration.getLongPressTimeout());
} else if (e.getAction() == MotionEvent.ACTION_UP) {
v.removeCallbacks(repeater);
}
return true;
}
});
}
private void increment() {
Log.v("Long Press Example", "TODO: implement increment operation");
}

option: custom detector class
abstract public class
Long_hold
extends View.OnTouchListener
{
public#Override boolean
onTouch(View view, MotionEvent touch)
{
switch(touch.getAction())
{
case ACTION_DOWN: down(touch); return true;
case ACTION_MOVE: move(touch);
}
return true;
}
private long
time_0;
private float
x_0, y_0;
private void
down(MotionEvent touch)
{
time_0= touch.getEventTime();
x_0= touch.getX();
y_0= touch.getY();
}
private void
move(MotionEvent touch)
{
if(held_too_short(touch) {return;}
if(moved_too_much(touch)) {return;}
long_press(touch);
}
abstract protected void
long_hold(MotionEvent touch);
}
use
private double
moved_too_much(MotionEvent touch)
{
return Math.hypot(
x_0 -touch.getX(),
y_0 -touch.getY()) >TOLERANCE;
}
private double
held_too_short(MotionEvent touch)
{
return touch.getEventTime()-time_0 <DOWN_PERIOD;
}
where
TOLERANCE is the maximum tolerated movement
DOWN_PERIOD is the time one has to press
import
static android.view.MotionEvent.ACTION_MOVE;
static android.view.MotionEvent.ACTION_DOWN;
in code
setOnTouchListener(new Long_hold()
{
protected#Override boolean
long_hold(MotionEvent touch)
{
/*your code on long hold*/
}
});

I found one solution and it does not require to define runnable or other things and it's working fine.
var lastTouchTime: Long = 0
// ( ViewConfiguration.#.DEFAULT_LONG_PRESS_TIMEOUT =500)
val longPressTime = 500
var lastTouchX = 0f
var lastTouchY = 0f
view.setOnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
lastTouchTime = SystemClock.elapsedRealtime()
lastTouchX = event.x
lastTouchY = event.y
return#setOnTouchListener true
}
MotionEvent.ACTION_UP -> {
if (SystemClock.elapsedRealtime() - lastTouchTime > longPressTime
&& Math.abs(event.x - lastTouchX) < 3
&& Math.abs(event.y - lastTouchY) < 3) {
Log.d(TAG, "Long press")
}
return#setOnTouchListener true
}
else -> {
return#setOnTouchListener false
}
}
}

Related

Android OnTouchListener for incrementing a value doesn't stop when touch ceases

Here is the code for the OnTouchListener:
launchableSelectionView.setOnTouchListener(new View.OnTouchListener()
{
final CountDownTimer countDownTimer = new CountDownTimer(Long.MAX_VALUE,100)
{
#Override
public void onTick(long l)
{
MissileSystem system = bFittedToPlayer ? game.GetOurPlayer().GetInterceptorSystem() : game.GetSAMSite(lFittedToStructureID).GetInterceptorSystem();
int lFreeSlots = system.GetEmptySlotCount() - Types.size();
int lAvailableFunds = game.GetOurPlayer().GetWealth() - lTotalCost;
if (lFreeSlots == 0)
{
activity.ShowBasicOKDialog(context.getString(R.string.insufficient_slots));
}
else if (lAvailableFunds < game.GetConfig().GetInterceptorCost(type))
{
activity.ShowBasicOKDialog(context.getString(R.string.insufficient_funds));
}
else
{
launchableSelectionView.IncrementNumber();
AddType(type.GetID());
}
}
#Override
public void onFinish()
{
}
};
#Override
public boolean onTouch(View view, MotionEvent motionEvent)
{
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN)
{
postDelayed(new Runnable()
{
#Override
public void run()
{
countDownTimer.start();
}
}, 750);
}
if (motionEvent.getAction() == MotionEvent.ACTION_UP)
{
countDownTimer.cancel();
}
return false;
}
});
This code is for players to purchase missiles.
What I want it to do is increment the number of missiles to purchase when the user long-presses the button after .75 seconds, and cease incrementing the number when the player lifts their finger.
What it does instead: If the user taps the button, it will wait .75 seconds and then begin incrementing the number as though the user were still touching the button. (Before I added PostDelayed, it would begin incrementing the number immediately, and not stop.)
I would be using a android.os.Handler to intercept the java.lang.Runnable before it can ever run. After that, ditch the CountDownTimer altogether.
//Recreate a Handler over and over again.
Handler hand=new Handler();
Runnable run=new Runnable(){
#Override
public void run(){
MissileSystem system = bFittedToPlayer ? game.GetOurPlayer().GetInterceptorSystem() : game.GetSAMSite(lFittedToStructureID).GetInterceptorSystem();
int lFreeSlots = system.GetEmptySlotCount() - Types.size();
int lAvailableFunds = game.GetOurPlayer().GetWealth() - lTotalCost;
if (lFreeSlots == 0)
{
activity.ShowBasicOKDialog(context.getString(R.string.insufficient_slots));
}
else if (lAvailableFunds < game.GetConfig().GetInterceptorCost(type))
{
activity.ShowBasicOKDialog(context.getString(R.string.insufficient_funds));
}
else
{
launchableSelectionView.IncrementNumber();
AddType(type.GetID());
//Increment every 1ms
hand.postDelayed(this, 1);
}};
hand.postDelayed(run, 750);
//To intercept if user just didn't held on long enough.
hand.removeCallbacks(run);

Disable swipe on some fragments in ViewPager

I have a ViewPager that can disable or enable swipe touches:
public class ConfigurablePager extends ViewPager {
private final AtomicBoolean touchesAllowed = new AtomicBoolean();
...
private boolean touchesAllowed() {
return touchesAllowed.get();
}
public void enableTouches() {
touchesAllowed.set(true);
}
public void disableTouches() {
touchesAllowed.set(false);
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
return touchesAllowed() && super.onTouchEvent(ev);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return touchesAllowed() && super.onInterceptTouchEvent(ev);
}
}
Some fragments can be swiped but other can't. Pager adapter aware of swipe behaviour for each fragment. This behaviour can be changed in ViewPager.OnPageChangeListener:
#Override
public void onPageSelected(int position) {
if (adapter.isTouchesAllowed(position)) {
views.pager.enableTouches();
} else {
views.pager.disableTouches();
}
}
The problem
Sometimes, when I swipe fragments very fast and click on tab for other fragment simultaneously viewpager can throw IllegalArgumentException:
FATAL EXCEPTION:
main java.lang.IllegalArgumentException: pointerIndex out of range
at android.view.MotionEvent.nativeGetAxisValue(Native Method)
at android.view.MotionEvent.getX(MotionEvent.java:1979)
at android.support.v4.view.MotionEventCompatEclair.getX(MotionEventCompatEclair.java:32)
at android.support.v4.view.MotionEventCompat$EclairMotionEventVersionImpl.getX(MotionEventCompat.java:110)
at android.support.v4.view.MotionEventCompat.getX(MotionEventCompat.java:462)
at android.support.v4.view.ViewPager.onTouchEvent(ViewPager.java:2080)
at com.test.debugpager.ConfigurablePager.onTouchEvent(ConfigurablePager.java:39)
at android.view.View.dispatchTouchEvent(View.java:7384)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2203)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1938)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2231)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1952)
at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2209)
It's happend because ViewPager save last pointerId and get inconsistent state (some touch events dropped by onInterceptTouchEvent) e.g. ACTION_MOVE with incorrect mActivePointerId from last touch event (see sources of ViewPager.java)
The question
Is it posible to disable swipe on some fragments in other way, maybe without overriding onInterceptTouchEvent?
ViewPager sources (onTouchEvent):
case MotionEvent.ACTION_MOVE:
if (!mIsBeingDragged) {
final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
final float x = MotionEventCompat.getX(ev, pointerIndex);
final float xDiff = Math.abs(x - mLastMotionX);
final float y = MotionEventCompat.getY(ev, pointerIndex);
final float yDiff = Math.abs(y - mLastMotionY);
if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
if (xDiff > mTouchSlop && xDiff > yDiff) {
if (DEBUG) Log.v(TAG, "Starting drag!");
mIsBeingDragged = true;
requestParentDisallowInterceptTouchEvent(true);
mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
mInitialMotionX - mTouchSlop;
mLastMotionY = y;
setScrollState(SCROLL_STATE_DRAGGING);
setScrollingCacheEnabled(true);
// Disallow Parent Intercept, just in case
ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
}
Solved
I've read intently android guide about gesture recognizing in a ViewGroup and analyzed ViewPager onTouchEvent sources. Here I recognize that ViewPager do swipe only for ACTION_MOVE event so we shouldn't call touch callbacks only for this action and we should obey base ViewGroup onInterceptTouchEvent result before calling base class onTouchEvent.
According to this rules I changed my ViewPager code:
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (touchesAllowed()) {
return super.onInterceptTouchEvent(ev);
} else {
if (MotionEventCompat.getActionMasked(ev) == MotionEvent.ACTION_MOVE) {
// ignore move action
} else {
if (super.onInterceptTouchEvent(ev)) {
super.onTouchEvent(ev);
}
}
return false;
}
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
if (touchesAllowed()) {
return super.onTouchEvent(ev);
} else {
return MotionEventCompat.getActionMasked(ev) != MotionEvent.ACTION_MOVE && super.onTouchEvent(ev);
}
}

Detecting swipes, click, hold on one View Android

I have an ImageView which fills my whole Activity. I need to be able to detect 4 touch events :
Hold (longer than 400 ms)
Click
Swipe Left
Swipe Right
At the moment I am able to detect the first two using the following code :
imageView.setOnTouchListener(new View.OnTouchListener() {
private Timer timerr = new Timer();
private long LONG_PRESS_TIMEOUT = 400; // TODO: your timeout here
private boolean wasLong = false;
#Override
public boolean onTouch(View v, MotionEvent event) {
Log.d(getClass().getName(), "touch event: " + event.toString());
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// HOLD DETECTED
timerr.schedule(new TimerTask() {
#Override
public void run() {
SlideShow.this.runOnUiThread(new Runnable() {
#Override
public void run() {
}, LONG_PRESS_TIMEOUT);
return true;
}
if (event.getAction() == MotionEvent.ACTION_UP) {
if (!isPaused && !wasLong) {
// CLICK DETECTED
timerr.cancel();
timerr = new Timer();
if (!wasLong) {
return false;
}
});
Now I am trying to use the code from this question to implement the swipe right and swipe left actions. Android Swipe on List The problem with this is that this works on listview, where you can implement an onTouchListener for the whole listview, and a seperate onItemClickListener for the list item. I do not know how to adapt this to this situation though where only one Listener is available.
Gesture Detector is the way to go.
Alternatively,
image.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x1 = event.getX();
y1 = event.getY();
t1 = System.currentTimeMillis();
return true;
case MotionEvent.ACTION_UP:
x2 = event.getX();
y2 = event.getY();
t2 = System.currentTimeMillis();
if (x1 == x2 && y1 == y2 && (t2 - t1) < CLICK_DURATION) {
Toast.makeText(getActivity(), "Click", Toast.LENGTH_SHORT).show();
} else if ((t2 - t1) >= CLICK_DURATION) {
Toast.makeText(getActivity(), "Long click", Toast.LENGTH_SHORT).show();
} else if (x1 > x2) {
Toast.makeText(getActivity(), "Left swipe", Toast.LENGTH_SHORT).show();
} else if (x2 > x1) {
Toast.makeText(getActivity(), "Right swipe", Toast.LENGTH_SHORT).show();
}
return true;
}
return false;
}

Android: Triggering MotionEvent.ACTION_CANCEL

The Requirements: Ensure that itemLookup is only performed if the button has been held for at least 1 second. Stop the button event when the button has been held for 3 seconds, so the recording used for the lookup will not contain unnecessary data.
The Problem: MotionEvent.ACTION_CANCEL is never called even though debugging confirms that TableLayoutForIntercept's onInterceptTouchEvent is being called before MainActivity's OnTouchListener event is called, as expected. Perhaps I am not understanding the purpose of onInterceptTouchEvent? I've looked at other posts about this matter, but all of them are dealing with swipe or drag events, not cancelling a button press. Perhaps this can't be done?
The Code: Only the relevant parts of MainActivity are shown, along with the full TableLayoutForIntercept class, and of course <com.company.myapplication.TableLayoutForIntercept></com.company.myapplication.TableLayoutForIntercept> tags surround my xml layout.
public class MainActivity extends Activity {
//...
DateTime recordingStartedTime;
DateTime recordingEndedTime;
boolean buttonHeldLongEnough = false;
PackageManager pm = getPackageManager();
boolean micPresent = pm.hasSystemFeature(PackageManager.FEATURE_MICROPHONE);
if (micPresent) {
recordBtn.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View recordView, MotionEvent recordEvent) {
switch (recordEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
// Try to record audio
try {
recordingOff.setVisibility(View.INVISIBLE);
recordingOn.setVisibility(View.VISIBLE);
recordingStartedTime = DateTime.now();
constructPrepareStartRecording();
}
catch (Exception ex) {
Log.e(MainActivity.class.getSimpleName(), "An unknown error occurred.");
}
return true;
case MotionEvent.ACTION_UP:
recordingOff.setVisibility(View.VISIBLE);
recordingOn.setVisibility(View.INVISIBLE);
recordingEndedTime = DateTime.now();
Seconds seconds = Seconds.secondsBetween(recordingStartedTime, recordingEndedTime);
int secondsButtonHeld = seconds.getSeconds();
// Button must have been held at least 1 second before running itemLookup
if (secondsButtonHeld > 0 ) {
buttonHeldLongEnough = true;
}
else {
buttonHeldLongEnough = false;
}
// Need to release resources regardless
stopReleaseResetRecording();
if (buttonHeldLongEnough) {
itemLookup();
}
return true;
case MotionEvent.ACTION_CANCEL:
// I think this is the event I have to trigger to halt the button press
boolean codeHasHitCancel = true;
return codeHasHitCancel;
}
return false;
}
});
}
else {
toastTitle = "Unable To Record";
toastMessage = "Device microphone not found.";
toast = new GenericCustomToast();
toast.show(toastTitle, toastMessage, MainActivity.this);
}
//...
}
public class TableLayoutForIntercept extends TableLayout {
public TableLayoutForIntercept (Context context) {
super(context);
}
public TableLayoutForIntercept (Context context, AttributeSet attrs) {
super(context, attrs);
}
private CancelPressTask cancelPressTask = null;
private boolean stopTouchEvent = false;
#Override
public boolean onInterceptTouchEvent (MotionEvent event) {
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
stopTouchEvent = false;
cancelPressTask = new CancelPressTask();
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
cancelPressTask.resetCancelPressTimer();
cancelPressTask.stopCancelPressTimer();
return stopTouchEvent;
}
return super.onInterceptTouchEvent(event);
}
#Override
public boolean onTouchEvent (MotionEvent event) {
if (!stopTouchEvent) {
return super.onTouchEvent(event);
}
return true;
}
private class CancelPressTask {
public final long CANCEL_PRESS_TIMEOUT = 3000; // 3 seconds
private Handler cancelPressHandler = new Handler(){
public void handleMessage(Message msg) {
}
};
private Runnable cancelPressCallback = new Runnable() {
#Override
public void run() {
stopTouchEvent = true;
}
};
public void resetCancelPressTimer(){
cancelPressHandler.removeCallbacks(cancelPressCallback);
cancelPressHandler.postDelayed(cancelPressCallback, CANCEL_PRESS_TIMEOUT);
}
public void stopCancelPressTimer(){
cancelPressHandler.removeCallbacks(cancelPressCallback);
}
}
}
I figured out another way to go about this. Instead of trying to trigger an event, I decided to steal it instead! The below code will stop the recording, then do the item look-up after 3 seconds has elapsed, no matter how long the user tries to hold the button down for. The only catch is for recordings that are too short (less than 1 second) it will still take the full 3 seconds to notify the user via toast message. But I'm willing to live with that.
public class Main Activity extends Activity {
DateTime recordingStartedTime;
DateTime recordingEndedTime = null;
boolean buttonHeldLongEnough = false;
LimitRecordingTask limitRecordingTask;
PackageManager pm = getPackageManager();
boolean micPresent = pm.hasSystemFeature(PackageManager.FEATURE_MICROPHONE);
if (micPresent) {
recordBtn.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View recordView, MotionEvent recordEvent) {
limitRecordingTask = new LimitRecordingTask();
switch (recordEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
// Try to record audio
try {
recordBtn.setBackgroundColor(Color.DKGRAY);
recordingOff.setVisibility(View.INVISIBLE);
recordingOn.setVisibility(View.VISIBLE);
recordingStartedTime = DateTime.now();
constructPrepareStartRecording();
limitRecordingTask.resetRecordingTimer();
}
catch (Exception ex) {
Log.e(MainActivity.class.getSimpleName(), "An unknown error occurred.");
limitRecordingTask.stopRecordingTimer();
}
return true;
case MotionEvent.ACTION_UP:
// After 3 seconds limitRecordingTask will 'steal' this event
recordBtn.setBackgroundResource(R.drawable.custom_button);
recordingOff.setVisibility(View.VISIBLE);
recordingOn.setVisibility(View.INVISIBLE);
recordingEndedTime = DateTime.now();
Seconds seconds = Seconds.secondsBetween(recordingStartedTime, recordingEndedTime);
int secondsButtonHeld = Math.abs(seconds.getSeconds());
if (secondsButtonHeld > 0 ) {
buttonHeldLongEnough = true;
}
else {
buttonHeldLongEnough = false;
}
return true;
}
return false;
}
});
}
else {
toastTitle = "Unable To Record";
toastMessage = "Device microphone not found.";
toast = new GenericCustomToast();
toast.show(toastTitle, toastMessage, MainActivity.this);
}
private class LimitRecordingTask {
public final long RECORDING_TIMEOUT = 3000; // 3 seconds
private Handler limitRecordingHandler = new Handler(){
public void handleMessage(Message msg) {
}
};
private Runnable limitRecordingCallback = new Runnable() {
#Override
public void run() {
// 'Stolen' MotionEvent.ACTION_UP
recordBtn.setBackgroundResource(R.drawable.custom_button);
recordingOff.setVisibility(View.VISIBLE);
recordingOn.setVisibility(View.INVISIBLE);
if (recordingEndedTime == null) {
recordingEndedTime = DateTime.now();
}
Seconds seconds = Seconds.secondsBetween(recordingStartedTime, recordingEndedTime);
int secondsButtonHeld = Math.abs(seconds.getSeconds());
if (secondsButtonHeld > 0 ) {
buttonHeldLongEnough = true;
}
else {
buttonHeldLongEnough = false;
}
limitRecordingTask.stopRecordingTimer();
stopReleaseResetRecording();
itemLookup();
}
};
public void resetRecordingTimer(){
limitRecordingHandler.removeCallbacks(limitRecordingCallback);
limitRecordingHandler.postDelayed(limitRecordingCallback, RECORDING_TIMEOUT);
}
public void stopRecordingTimer(){
limitRecordingHandler.removeCallbacks(limitRecordingCallback);
}
}
}

Start onTouchEvent After onClick Event

I am trying to set the image button (pushClick) in my Activity to enable onTouchEvents used to rotate a needle graphic. Unfortunately, the onTouchEvent is active regardless of if I click on the image button or not. How can I prevent the onTouchEvent from firing until after the image button is clicked?
public void pushClick(View pushClick) {
switch (pushClick.getId()) {
case R.id.btn_push:
make(degrees);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startRotating();
break;
case MotionEvent.ACTION_UP:
stopRotating();
break;
}
return super.onTouchEvent(event);
}
private void startRotating() {
returnRotating = false;
if (!keepRotating) {
keepRotating = true;
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
if (keepRotating) {
degrees = (degrees + 1) % 360;
make(degrees);
handler.postDelayed(this, INTERVAL);
}
}
}, INTERVAL);
}
}
private void stopRotating() {
keepRotating = false;
if (!returnRotating) {
returnRotating = true;
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
if (returnRotating) {
degrees = (degrees - 1) % 360;
make(degrees);
handler.postDelayed(this, INTERVAL);
}
}
}, INTERVAL);
}
}
I'd bet that there's a better way to do this, but here off the top of my head:
private boolean buttonClicked = false;
public void pushClick(View pushClick) {
switch (pushClick.getId()) {
case R.id.btn_push:
buttonClicked = true;
make(degrees);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(buttonClicked)
{
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startRotating();
break;
case MotionEvent.ACTION_UP:
stopRotating();
break;
}
}
return super.onTouchEvent(event);
}
You could also add the OnTouchListener (from whatever view you want it attached to) when your image button is pressed, and remove it when it's clicked again (if that's what you want).

Categories