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);
}
}
}
Related
I am using hourglass library for a countdown. Lets say 5 minutes. During that time i am playing the same 5 audio files over and over. This is working great. The problem is with the pause. When its on the first audio. It pauses and resumes just fine. After it gets to the next song, and i pause it, it throws a IllegalStateException.
The code is a bit ugly. In onCompletion it keeps playing the files until it gets to the end. Then i call playAudio() and the cycle starts over until the hour glass has reached zero.
pause.setOnClickListener(new View.OnClickListener() is a floating action button and i toggle between pause icon and play icon
Java Class code
tracks[0] = R.raw.audio0;
tracks[1] = R.raw.audio1;
tracks[2] = R.raw.audio2;
tracks[3] = R.raw.audio3;
tracks[4] = R.raw.audio4;
countDown = (TextView) findViewById(R.id.txtCountdown);
cancel = (FloatingActionButton)findViewById(R.id.cancelMeditation);
pause = (FloatingActionButton)findViewById(R.id.pauseMeditation);
Intent intent = getIntent();
check = intent.getIntExtra("DURATION", -1);
milliseconds = check * 60000;
countDown.setText("" + String.format("%d min",
TimeUnit.MILLISECONDS.toMinutes(milliseconds)));
pause.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(hourglass == null){
startTimer(milliseconds);
currentTrack = 0;
isTimerDone = false;
playAudio();
pause.setImageResource(R.drawable.ic_pause);
return;
}
if(hourglass.isPaused()){
hourglass.resumeTimer();
pause.setImageResource(R.drawable.ic_pause);
isPaused = false;
mediaAffyPlayer.seekTo(length);
mediaAffyPlayer.start();
}else if(hourglass.isRunning()){
hourglass.pauseTimer();
pause.setImageResource(R.drawable.ic_play1);
isPaused = true;
if(mediaAffyPlayer != null) {
mediaAffyPlayer.pause();
length = mediaAffyPlayer.getCurrentPosition();
}
} else{
startTimer(milliseconds);
playAudio();
}
}
});
cancel.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
}
});
}
public void startTimer(long milliseconds){
hourglass = new Hourglass(milliseconds, 1000) {
#Override
public void onTimerTick(long timeRemaining) {
countDown.setText("" + String.format("%d min, %d sec",
TimeUnit.MILLISECONDS.toMinutes(timeRemaining),
TimeUnit.MILLISECONDS.toSeconds(timeRemaining) -
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(timeRemaining))));
if(TimeUnit.MILLISECONDS.toMinutes(timeRemaining) ==0){
return;
}
}
#Override
public void onTimerFinish() {
isTimerDone = true;
}
};
hourglass.startTimer();
}
private void playAudio(){
if(mediaAffyPlayer == null){
mediaAffyPlayer = MediaPlayer.create(getApplicationContext(), tracks[0]);
mediaAffyPlayer.setOnCompletionListener(this);
mediaAffyPlayer.start();
isPaused = false;
return;
}
if(mediaAffyPlayer != null) {
mediaAffyPlayer.release();
mediaAffyPlayer = MediaPlayer.create(getApplicationContext(), tracks[0]);
mediaAffyPlayer.setOnCompletionListener(this);
isPaused = false;
mediaAffyPlayer.start();
}
mediaAffyPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
public boolean onError(MediaPlayer mediaPlayer, int i, int i1) {
return false;
}
});
}
public void onCompletion(MediaPlayer mediaPlayer) {
if (currentTrack < tracks.length - 1) {
if(isTimerDone){
countDown.setText("" + String.format("%d min",
TimeUnit.MILLISECONDS.toMinutes(milliseconds)));
pause.setImageResource(R.drawable.ic_play1);
mediaPlayer.reset();
mediaPlayer.release();
mediaPlayer=null;
hourglass = null;
return;
}
currentTrack++;
mediaPlayer.release();
mediaPlayer = MediaPlayer.create(getApplicationContext(), tracks[currentTrack]);
mediaPlayer.setOnCompletionListener(ShowMeditationActivity.this);
mediaPlayer.start();
} else {
if(!isTimerDone){
currentTrack = 0;
playAudio();
}
}
}
Exception
java.lang.IllegalStateException
at android.media.MediaPlayer._pause(Native Method)
at android.media.MediaPlayer.pause(MediaPlayer.java:1512)
Which throws it at mediaAffyPlayer.pause(); in hourglass.isRunning()
I know the code is ugly, but I just think that when it plays the second track the media player object has changed and thats why it throws error. I just dont know how to fix it.
Any help is appreciated as i already burned through many hours trying to fix it.
Thanks
EDIT I fixed the issue.
I commented out the code in OnCompletion that created and played the mediaplayer. I called playAudio() and stuck the code in there.
private void playAudio(){
if(mediaAffyPlayer == null){
mediaAffyPlayer = MediaPlayer.create(getApplicationContext(), tracks[0]);
mediaAffyPlayer.setOnCompletionListener(this);
mediaAffyPlayer.start();
isPaused = false;
return;
}
if(mediaAffyPlayer != null) {
currentTrack++;
mediaAffyPlayer.release();
mediaAffyPlayer = MediaPlayer.create(getApplicationContext(), tracks[currentTrack]);
mediaAffyPlayer.setOnCompletionListener(ShowMeditationActivity.this);
mediaAffyPlayer.start();
}
Problem with isPaused() code
try with this!!
try{
if (mediaAffyPlayer.isPlaying()) {
mediaAffyPlayer.pause();
}
}catch(Exception we){
we.printStackTrace();
}
In app based on Vuforia I want to have some demo also based on Vuforia . So I stop one Vuforia (ARactivity) and use intent to start new activity (Md2activity.class) with something like this:
arActivity.stopAR();
Intent intent = new Intent(arActivity, Md2activity.class);
arActivity.startActivity(intent);
and .stopAR method:
public void stopAR(){
try {
vuforiaAppSession.stopAR();
} catch (SampleApplicationException e) {
e.printStackTrace();
}
vuforiaAppSession.stopCamera();
mTextures.clear();
mTextures = null;
}
I can see in consol that it's working fine. Camera recognizes targets etc. But I don't have camera view on the screen. Just ProgressBar.
edit:
public class Md2activity extends Activity implements SampleApplicationControl,
SampleAppMenuInterface
{
private static final String LOGTAG = "Md2activity";
SampleApplicationSession vuforiaAppSession;
private DataSet mCurrentDataset;
private int mCurrentDatasetSelectionIndex = 0;
private int mStartDatasetsIndex = 0;
private int mDatasetsNumber = 0;
private ArrayList<String> mDatasetStrings = new ArrayList<String>();
private Activity mActivity;
private SampleApplicationControl mSessionControl;
// Our OpenGL view:
private SampleApplicationGLView mGlView;
// Our renderer:
private ImageTargetRendererMd2 mRenderer;
private GestureDetector mGestureDetector;
// The textures we will use for rendering:
private Vector<Texture> mTextures;
private boolean mSwitchDatasetAsap = false;
private boolean mFlash = false;
private boolean mContAutofocus = false;
private boolean mExtendedTracking = false;
private View mFlashOptionView;
private RelativeLayout mUILayout;
private SampleAppMenu mSampleAppMenu;
LoadingDialogHandler loadingDialogHandler = new LoadingDialogHandler(this);
// Alert Dialog used to display SDK errors
private AlertDialog mErrorDialog;
private boolean mIsDroidDevice = false;
// Called when the activity first starts or the user navigates back to an
// activity.
#Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(LOGTAG, "onCreate");
super.onCreate(savedInstanceState);
//setContentView(R.layout.camera_md2);
vuforiaAppSession = new SampleApplicationSession(this);
startLoadingAnimation();
mDatasetStrings.add("StonesAndChips.xml");
mDatasetStrings.add("Tarmac.xml");
vuforiaAppSession
.initAR(this, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
mGestureDetector = new GestureDetector(this, new dr.ar.browser.Md2activity.GestureListener());
// Load any sample specific textures:
mTextures = new Vector<Texture>();
mIsDroidDevice = android.os.Build.MODEL.toLowerCase().startsWith(
"droid");
}
// Process Single Tap event to trigger autofocus
private class GestureListener extends
GestureDetector.SimpleOnGestureListener
{
// Used to set autofocus one second after a manual focus is triggered
private final Handler autofocusHandler = new Handler();
#Override
public boolean onDown(MotionEvent e)
{
return true;
}
#Override
public boolean onSingleTapUp(MotionEvent e)
{
// Generates a Handler to trigger autofocus
// after 1 second
autofocusHandler.postDelayed(new Runnable()
{
public void run()
{
boolean result = CameraDevice.getInstance().setFocusMode(
CameraDevice.FOCUS_MODE.FOCUS_MODE_TRIGGERAUTO);
if (!result)
Log.e("SingleTapUp", "Unable to trigger focus");
}
}, 1000L);
return true;
}
}
// Called when the activity will start interacting with the user.
#Override
protected void onResume()
{
Log.d(LOGTAG, "onResume");
super.onResume();
// This is needed for some Droid devices to force portrait
if (mIsDroidDevice)
{
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
try
{
vuforiaAppSession.resumeAR();
} catch (SampleApplicationException e)
{
Log.e(LOGTAG, e.getString());
}
// Resume the GL view:
if (mGlView != null)
{
mGlView.setVisibility(View.VISIBLE);
mGlView.onResume();
}
}
// Callback for configuration changes the activity handles itself
#Override
public void onConfigurationChanged(Configuration config)
{
Log.d(LOGTAG, "onConfigurationChanged");
super.onConfigurationChanged(config);
vuforiaAppSession.onConfigurationChanged();
}
// Called when the system is about to start resuming a previous activity.
#Override
protected void onPause()
{
Log.d(LOGTAG, "onPause");
super.onPause();
if (mGlView != null)
{
mGlView.setVisibility(View.INVISIBLE);
mGlView.onPause();
}
// Turn off the flash
if (mFlashOptionView != null && mFlash)
{
// OnCheckedChangeListener is called upon changing the checked state
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
{
((Switch) mFlashOptionView).setChecked(false);
} else
{
((CheckBox) mFlashOptionView).setChecked(false);
}
}
try
{
vuforiaAppSession.pauseAR();
} catch (SampleApplicationException e)
{
Log.e(LOGTAG, e.getString());
}
}
// The final call you receive before your activity is destroyed.
#Override
protected void onDestroy()
{
Log.d(LOGTAG, "onDestroy");
super.onDestroy();
try
{
vuforiaAppSession.stopAR();
} catch (SampleApplicationException e)
{
Log.e(LOGTAG, e.getString());
}
// Unload texture:
mTextures.clear();
mTextures = null;
System.gc();
}
// Initializes AR application components.
private void initApplicationAR()
{
// Create OpenGL ES view:
int depthSize = 16;
int stencilSize = 0;
boolean translucent = Vuforia.requiresAlpha();
mGlView = new SampleApplicationGLView(this);
mGlView.init(translucent, depthSize, stencilSize);
mRenderer = new ImageTargetRendererMd2(this, vuforiaAppSession);
mGlView.setRenderer(mRenderer);
}
private void startLoadingAnimation()
{
mUILayout = (RelativeLayout) View.inflate(this, R.layout.camera_overlay,
null);
mUILayout.setVisibility(View.VISIBLE);
mUILayout.setBackgroundColor(Color.BLACK);
// Gets a reference to the loading dialog
loadingDialogHandler.mLoadingDialogContainer = mUILayout
.findViewById(R.id.loading_indicator);
// Shows the loading indicator at start
loadingDialogHandler
.sendEmptyMessage(LoadingDialogHandler.SHOW_LOADING_DIALOG);
// Adds the inflated layout to the view
addContentView(mUILayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
}
// Methods to load and destroy tracking data.
#Override
public boolean doLoadTrackersData()
{
TrackerManager tManager = TrackerManager.getInstance();
ObjectTracker objectTracker = (ObjectTracker) tManager
.getTracker(ObjectTracker.getClassType());
if (objectTracker == null)
return false;
if (mCurrentDataset == null)
mCurrentDataset = objectTracker.createDataSet();
if (mCurrentDataset == null)
return false;
if (!mCurrentDataset.load(
mDatasetStrings.get(mCurrentDatasetSelectionIndex),
STORAGE_TYPE.STORAGE_APPRESOURCE))
return false;
if (!objectTracker.activateDataSet(mCurrentDataset))
return false;
int numTrackables = mCurrentDataset.getNumTrackables();
for (int count = 0; count < numTrackables; count++)
{
Trackable trackable = mCurrentDataset.getTrackable(count);
if(isExtendedTrackingActive())
{
trackable.startExtendedTracking();
}
String name = "Current Dataset : " + trackable.getName();
trackable.setUserData(name);
Log.d(LOGTAG, "UserData:Set the following user data "
+ (String) trackable.getUserData());
}
return true;
}
#Override
public boolean doUnloadTrackersData()
{
// Indicate if the trackers were unloaded correctly
boolean result = true;
TrackerManager tManager = TrackerManager.getInstance();
ObjectTracker objectTracker = (ObjectTracker) tManager
.getTracker(ObjectTracker.getClassType());
if (objectTracker == null)
return false;
if (mCurrentDataset != null && mCurrentDataset.isActive())
{
if (objectTracker.getActiveDataSet(0).equals(mCurrentDataset)
&& !objectTracker.deactivateDataSet(mCurrentDataset))
{
result = false;
} else if (!objectTracker.destroyDataSet(mCurrentDataset))
{
result = false;
}
mCurrentDataset = null;
}
return result;
}
#Override
public void onInitARDone(SampleApplicationException exception)
{
if (exception == null)
{
initApplicationAR();
mRenderer.mIsActive = true;
// Now add the GL surface view. It is important
// that the OpenGL ES surface view gets added
// BEFORE the camera is started and video
// background is configured.
addContentView(mGlView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
// Sets the UILayout to be drawn in front of the camera
mUILayout.bringToFront();
// Sets the layout background to transparent
mUILayout.setBackgroundColor(Color.TRANSPARENT);
try
{
vuforiaAppSession.startAR(CameraDevice.CAMERA_DIRECTION.CAMERA_DIRECTION_DEFAULT);
} catch (SampleApplicationException e)
{
Log.e(LOGTAG, e.getString());
}
boolean result = CameraDevice.getInstance().setFocusMode(
CameraDevice.FOCUS_MODE.FOCUS_MODE_CONTINUOUSAUTO);
if (result)
mContAutofocus = true;
else
Log.e(LOGTAG, "Unable to enable continuous autofocus");
mSampleAppMenu = new SampleAppMenu(this, this, "Image Targets",
mGlView, mUILayout, null);
setSampleAppMenuSettings();
} else
{
Log.e(LOGTAG, exception.getString());
showInitializationErrorMessage(exception.getString());
}
}
// Shows initialization error messages as System dialogs
private void showInitializationErrorMessage(String message)
{
final String errorMessage = message;
runOnUiThread(new Runnable()
{
public void run()
{
if (mErrorDialog != null)
{
mErrorDialog.dismiss();
}
}
});
}
#Override
public void onVuforiaUpdate(State state)
{
if (mSwitchDatasetAsap)
{
Log.e("onVuforiaUpdate","成功");
mSwitchDatasetAsap = false;
TrackerManager tm = TrackerManager.getInstance();
ObjectTracker ot = (ObjectTracker) tm.getTracker(ObjectTracker
.getClassType());
if (ot == null || mCurrentDataset == null
|| ot.getActiveDataSet(0) == null)
{
Log.d(LOGTAG, "Failed to swap datasets");
return;
}
doUnloadTrackersData();
doLoadTrackersData();
}
}
#Override
public boolean doInitTrackers()
{
// Indicate if the trackers were initialized correctly
boolean result = true;
TrackerManager tManager = TrackerManager.getInstance();
Tracker tracker;
// Trying to initialize the image tracker
tracker = tManager.initTracker(ObjectTracker.getClassType());
if (tracker == null)
{
Log.e(
LOGTAG,
"Tracker not initialized. Tracker already initialized or the camera is already started");
result = false;
} else
{
Log.i(LOGTAG, "Tracker successfully initialized");
}
return result;
}
#Override
public boolean doStartTrackers()
{
// Indicate if the trackers were started correctly
boolean result = true;
Tracker objectTracker = TrackerManager.getInstance().getTracker(
ObjectTracker.getClassType());
if (objectTracker != null)
objectTracker.start();
return result;
}
#Override
public boolean doStopTrackers()
{
// Indicate if the trackers were stopped correctly
boolean result = true;
Tracker objectTracker = TrackerManager.getInstance().getTracker(
ObjectTracker.getClassType());
if (objectTracker != null)
objectTracker.stop();
return result;
}
#Override
public boolean doDeinitTrackers()
{
// Indicate if the trackers were deinitialized correctly
boolean result = true;
TrackerManager tManager = TrackerManager.getInstance();
tManager.deinitTracker(ObjectTracker.getClassType());
return result;
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
// Process the Gestures
if (mSampleAppMenu != null && mSampleAppMenu.processEvent(event))
return true;
return mGestureDetector.onTouchEvent(event);
}
private boolean isExtendedTrackingActive() {
return mExtendedTracking;
}
final public static int CMD_BACK = -1;
final public static int CMD_EXTENDED_TRACKING = 1;
final public static int CMD_AUTOFOCUS = 2;
final public static int CMD_FLASH = 3;
final public static int CMD_CAMERA_FRONT = 4;
final public static int CMD_CAMERA_REAR = 5;
final public static int CMD_DATASET_START_INDEX = 6;
// This method sets the menu's settings
private void setSampleAppMenuSettings()
{
SampleAppMenuGroup group;
group = mSampleAppMenu.addGroup("", false);
group.addTextItem(getString(R.string.menu_back), -1);
group = mSampleAppMenu.addGroup("", true);
group.addSelectionItem(getString(R.string.menu_extended_tracking),
CMD_EXTENDED_TRACKING, false);
group.addSelectionItem(getString(R.string.menu_contAutofocus),
CMD_AUTOFOCUS, mContAutofocus);
mFlashOptionView = group.addSelectionItem(
getString(R.string.menu_flash), CMD_FLASH, false);
Camera.CameraInfo ci = new Camera.CameraInfo();
boolean deviceHasFrontCamera = false;
boolean deviceHasBackCamera = false;
for (int i = 0; i < Camera.getNumberOfCameras(); i++)
{
Camera.getCameraInfo(i, ci);
if (ci.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
deviceHasFrontCamera = true;
else if (ci.facing == Camera.CameraInfo.CAMERA_FACING_BACK)
deviceHasBackCamera = true;
}
if (deviceHasBackCamera && deviceHasFrontCamera)
{
group = mSampleAppMenu.addGroup(getString(R.string.menu_camera),
true);
group.addRadioItem(getString(R.string.menu_camera_front),
CMD_CAMERA_FRONT, false);
group.addRadioItem(getString(R.string.menu_camera_back),
CMD_CAMERA_REAR, true);
}
group = mSampleAppMenu
.addGroup(getString(R.string.menu_datasets), true);
mStartDatasetsIndex = CMD_DATASET_START_INDEX;
mDatasetsNumber = mDatasetStrings.size();
group.addRadioItem("Stones & Chips", mStartDatasetsIndex, true);
group.addRadioItem("Tarmac", mStartDatasetsIndex + 1, false);
mSampleAppMenu.attachMenu();
}
#Override
public boolean menuProcess(int command)
{
boolean result = true;
switch (command)
{
case CMD_BACK:
finish();
break;
case CMD_FLASH:
result = CameraDevice.getInstance().setFlashTorchMode(!mFlash);
if (result)
{
mFlash = !mFlash;
} else
{
showToast(getString(mFlash ? R.string.menu_flash_error_off
: R.string.menu_flash_error_on));
Log.e(LOGTAG,
getString(mFlash ? R.string.menu_flash_error_off
: R.string.menu_flash_error_on));
}
break;
case CMD_AUTOFOCUS:
if (mContAutofocus)
{
result = CameraDevice.getInstance().setFocusMode(
CameraDevice.FOCUS_MODE.FOCUS_MODE_NORMAL);
if (result)
{
mContAutofocus = false;
} else
{
showToast(getString(R.string.menu_contAutofocus_error_off));
Log.e(LOGTAG,
getString(R.string.menu_contAutofocus_error_off));
}
} else
{
result = CameraDevice.getInstance().setFocusMode(
CameraDevice.FOCUS_MODE.FOCUS_MODE_CONTINUOUSAUTO);
if (result)
{
mContAutofocus = true;
} else
{
showToast(getString(R.string.menu_contAutofocus_error_on));
Log.e(LOGTAG,
getString(R.string.menu_contAutofocus_error_on));
}
}
break;
case CMD_CAMERA_FRONT:
case CMD_CAMERA_REAR:
// Turn off the flash
if (mFlashOptionView != null && mFlash)
{
// OnCheckedChangeListener is called upon changing the checked state
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
{
((Switch) mFlashOptionView).setChecked(false);
} else
{
((CheckBox) mFlashOptionView).setChecked(false);
}
}
vuforiaAppSession.stopCamera();
try
{
vuforiaAppSession
.startAR(command == CMD_CAMERA_FRONT ? CameraDevice.CAMERA_DIRECTION.CAMERA_DIRECTION_FRONT
: CameraDevice.CAMERA_DIRECTION.CAMERA_DIRECTION_BACK);
} catch (SampleApplicationException e)
{
showToast(e.getString());
Log.e(LOGTAG, e.getString());
result = false;
}
doStartTrackers();
break;
case CMD_EXTENDED_TRACKING:
for (int tIdx = 0; tIdx < mCurrentDataset.getNumTrackables(); tIdx++)
{
Trackable trackable = mCurrentDataset.getTrackable(tIdx);
if (!mExtendedTracking)
{
if (!trackable.startExtendedTracking())
{
Log.e(LOGTAG,
"Failed to start extended tracking target");
result = false;
} else
{
Log.d(LOGTAG,
"Successfully started extended tracking target");
}
} else
{
if (!trackable.stopExtendedTracking())
{
Log.e(LOGTAG,
"Failed to stop extended tracking target");
result = false;
} else
{
Log.d(LOGTAG,
"Successfully started extended tracking target");
}
}
}
if (result)
mExtendedTracking = !mExtendedTracking;
break;
default:
if (command >= mStartDatasetsIndex
&& command < mStartDatasetsIndex + mDatasetsNumber)
{
mSwitchDatasetAsap = true;
mCurrentDatasetSelectionIndex = command
- mStartDatasetsIndex;
}
break;
}
return result;
}
private void showToast(String text)
{
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
}
}
Trying to run MIDI on my Android app. I'm following the midisuite example to configure my app and it works fine with the exception of aftertouch. Whenever I try to trigger aftertouch, I run into a threading exception type
InteruptedException. How should I prevent this threading issue? My knowledge on multithreading isn't the best or else I would've figured this out already. All I can really tell right now is that the message is sending too fast and the thread hasn't woken up yet from its sleep call.
I followed the github repo with my code as follows:
MidiReceiver subclass:
#TargetApi(Build.VERSION_CODES.M)
public class MidiEngine extends MidiReceiver {
public AudioActivity activity;
private MidiEventScheduler eventScheduler;
private MidiFramer midiFramer;
private MidiReceiver midiReceiver = new MyReceiver();
private Thread mThread;
private boolean go;
private int mProgram;
public MidiEngine() {
this(new AudioActivity());
}
public MidiEngine(AudioActivity activity) {
this.activity = activity;
midiReceiver = new MyReceiver();
midiFramer = new MidiFramer(midiReceiver);
}
public AudioActivity getActivity() {
return this.activity;
}
/* This will be called when MIDI data arrives. */
#Override
public void onSend(byte[] data, int offset, int count, long timestamp)
throws IOException {
if (eventScheduler != null) {
if (!MidiConstants.isAllActiveSensing(data, offset, count)) {
eventScheduler.getReceiver().send(data, offset, count,
timestamp);
}
}
}
// Custom Listener to send to correct methods
private class MyReceiver extends MidiReceiver {
#Override
public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException {
byte command = (byte)(msg[0] & MidiConstants.STATUS_COMMAND_MASK);
int channel = (byte)(msg[0] & MidiConstants.STATUS_CHANNEL_MASK);
switch (command) {
case MidiConstants.STATUS_NOTE_ON:
activity.keyDown(i, msg[1], msg[2]);
break;
case MidiConstants.STATUS_NOTE_OFF:
activity.keyUp(channel, msg[1]);
break;
case MidiConstants.STATUS_POLYPHONIC_AFTERTOUCH:
activity.keyDown(channel, msg[1], msg[2]);
break;
case MidiConstants.STATUS_PITCH_BEND:
activity.pitchBendAction(channel, (msg[2] << 7) + msg[1]);
break;
case MidiConstants.STATUS_CONTROL_CHANGE:
activity.ccAction(channel, msg[1], msg[2]);
break;
case MidiConstants.STATUS_PROGRAM_CHANGE:
mProgram = msg[1];
break;
default:
break;
}
}
}
class MyRunnable implements Runnable {
#Override
public void run() {
do {
try {
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
try {
processMidiEvents();
}
catch (Exception e) {
Log.e("Java", "SynthEngine background thread exception.", e);
}
}
});
Thread.sleep(100);
}
catch (InterruptedException e) {
Log.e("Java", "Threading exception", e);
}
}
while (go);
}
}
/**
* #throws IOException
*
*/
private void processMidiEvents() throws IOException {
long now = System.nanoTime();
MidiEventScheduler.MidiEvent event = (MidiEventScheduler.MidiEvent) eventScheduler.getNextEvent(now);
while (event != null) {
midiFramer.send(event.data, 0, event.count, event.getTimestamp());
eventScheduler.addEventToPool(event);
event = (MidiEventScheduler.MidiEvent) eventScheduler.getNextEvent(now);
}
}
public void start() {
stop();
go = true;
mThread = new Thread(new MyRunnable());
mThread.setPriority(6);
eventScheduler = new MidiEventScheduler();
mThread.start();
}
public void stop() {
go = false;
if (mThread != null) {
try {
mThread.interrupt();
mThread.join(500);
}
catch (Exception e) {
}
mThread = null;
eventScheduler = null;
}
}
}
Stack Trace Error (line 154 refers to the Thread.sleep part in my custom Runnable class):
Java: Threading exception
java.lang.InterruptedException
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:1031)
at java.lang.Thread.sleep(Thread.java:985)
at com.rfoo.midiapp.communication.MidiEngineInput$MyRunnable.run(MidiEngineInput.java:154)
at java.lang.Thread.run(Thread.java:818)
Thanks!
EDIT: Thread start
Midi Device Service subclass (thread will start whenever a device has connected or disconnected).
#TargetApi(Build.VERSION_CODES.M)
public class MidiSynthDeviceService extends MidiDeviceService {
private static final String TAG = "MidiSynthDeviceService";
private boolean midiStarted = false;
#Override
public void onCreate() {
super.onCreate();
}
#Override
public void onDestroy() {
AudioActivity.midiEngine.stop();
super.onDestroy();
}
#Override
// Declare the receivers associated with your input ports.
public MidiReceiver[] onGetInputPortReceivers() {
return new MidiReceiver[] { AudioActivity.midiEngine };
}
/**
* This will get called when clients connect or disconnect.
* You can use it to turn on your synth only when needed.
*/
#Override
public void onDeviceStatusChanged(MidiDeviceStatus status) {
if (status.isInputPortOpen(0) && !midiStarted) {
AudioActivity.midiEngine.start();
midiStarted = true;
} else if (!status.isInputPortOpen(0) && midiStarted){
AudioActivity.midiEngine.stop();
midiStarted = false;
}
}
}
Activity class:
public class AudioActivity extends AppCompatActivity {
private Thread thread;
public static MidiEngine midiEngine;
#Override
protected void onCreate(Bundle savedInstanceState) {
// Layout inits
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
// Setup MIDI:
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI)) {
Toast.makeText(this, "MIDI not supported!", Toast.LENGTH_LONG).show();
}
else {
midiEngine = new MidiEngine(this);
setupMidi();
}
// Setup audio thread:
if (thread == null) {
thread = new Thread() {
public void run() {
setPriority(Thread.MAX_PRIORITY);
// Runs an Open SL audio thread (C++)
// This generates a waveform.
// AudioEngine is a wrapper class connecting C++ to Java
AudioEngine.runProcess();
}
}
}
}
public void setupMidi() {
if (activity == null) activity = (AudioActivity) getContext();
mMidiManager = (MidiManager) activity.getSystemService(AudioActivity.MIDI_SERVICE);
if (mMidiManager == null) {
Toast.makeText(activity, "MidiManager is null!", Toast.LENGTH_LONG).show();
return;
}
// Get Device Info
MidiDeviceInfo deviceInfo = MidiTools.findDevice(mMidiManager, "RFOO", "AudioApp");
// MIDI Input
portIndex = 0;
inputPortSelector = new MidiOutputPortConnectionSelector(mMidiManager, activity, R.id
.inputListView, deviceInfo, portIndex);
inputPortSelector.setConnectedListener(new MyPortsConnectedListener());
midi_ch_input = 0;
midi_ch_output = 0;
}
// Bunch of UI code here....
}
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).
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
}
}
}