I have a custom view that extends FrameLayout
and implements ScaleGestureDetector.OnScaleGestureListener
. This View, as the class name suggests, is zoomable + pannable. Heres the custom views class: https://gist.github.com/Orbyt/23c82ce9002df6c318d4
I've been trying to find a way to detect long clicks on this View. I know that, usually, I could do something like this in the Activity:
GestureDetector mGestureDetector = new GestureDetector(this, this);
mZoomableLayout.setOnTouchListener(new View.OnTouchListener()
{
@Override
public boolean onTouch(View v, MotionEvent event)
{
return mGestureDetector.onTouchEvent(event);
}
});
mGestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener()
{
@Override
public void onLongPress(MotionEvent e)
{
// do tasks here
}
});
Using this, the View is no longer zoomable, presumably because its intercepting all the onTouch events instead of the implementation inside the Views class.
So my question is, what is the cleanest way to detect long clicks on this view?
For example, to move an app icon on your home screen, touch & hold, then drag it to the new location. Sometimes touch & hold is called a "long press."
Some examples of default views present in the Android Framework are EditText, TextView, Button, CheckBox, RadioButton, etc. ViewGroup is a special view that can contain other views (called children). We can create custom views and use them in our Application.
Your custom view can also extend View directly, or you can save time by extending one of the existing view subclasses, such as Button . To allow Android Studio to interact with your view, at a minimum you must provide a constructor that takes a Context and an AttributeSet object as parameters.
I had a pinch zoom circle on which I detected the normal click and long click.Code snippet is given below. In this I have detected long click and normal click by the time interval between MotionEvent.ACTION_DOWN and MotionEvent.ACTION_UP.
May this help you.
private static final int MAX_CLICK_DURATION = 200;
private float mScaleFactor = 1.0000000f;
private long mStartClickTime;
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
boolean right = x > (screenWidthPX / 2 + ((mLayoutHeight / 4) + 20) * mScaleFactor);
boolean left = x < (screenWidthPX / 2 - ((mLayoutHeight / 4) + 20) * mScaleFactor);
boolean top = y > (mLayoutHeight / 2 + ((mLayoutHeight / 4) + 20) * mScaleFactor);
boolean bottom = y < (mLayoutHeight / 2 - ((mLayoutHeight / 4) + 20) * mScaleFactor);
if (event.getPointerCount() > 1) {
if (left || right || top || bottom) {
// You may not need this condtion, I needed this because I had custom view of pinch zoom circle and, this condition detects the touch at outer area of circle.
} else {
mScaleGestureDetector.onTouchEvent(event);
}
} else {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
mStartClickTime = Calendar.getInstance().getTimeInMillis();
break;
}
case MotionEvent.ACTION_UP: {
long clickDuration = Calendar.getInstance().getTimeInMillis() - mStartClickTime;
if (clickDuration < MAX_CLICK_DURATION) {
if (left || right || top || bottom) {
} else {
Toast.makeText(mContext, "Normal CLick Detected", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(mContext, "Long CLick Detected", Toast.LENGTH_SHORT).show();
}
}
}
}
return true;
}
First of all, you'll need to use touch slop to differentiate between actual movement and unintentional user finger movement (see ACTION_MOVE). Second if you're extending FrameLayout
it's cleaner to override onTouchEvent
instead of this.setOnTouchListener
in init()
.
Add variables to your custom View:
private final Handler mHandler = new Handler();
private ScaleGestureDetector mScaleDetector;
private int mTouchSlop;
private Runnable mLongPressed = new Runnable() {
public void run() {
Log.i(TAG, "Long press!");
//Do your long press stuff here.
}
};
Inside init()
:
mScaleDetector = new ScaleGestureDetector(context, this);
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
In switch statement:
switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
//Whatever you were doing previously here
mHandler.postDelayed(mLongPressed, ViewConfiguration.getLongPressTimeout());
break;
case MotionEvent.ACTION_MOVE:
if (mode == Mode.DRAG) {
dx = motionEvent.getX() - startX;
dy = motionEvent.getY() - startY;
if(Math.abs(dx) > mTouchSlop || Math.abs(dy) > mTouchSlop) {
//Actual movement
mHandler.removeCallbacks(mLongPressed);
} else {
//Below touch slop, not a movement
dx = 0;
dy = 0;
}
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mHandler.removeCallbacks(mLongPressed);
//Whatever you were doing previously here
break;
case MotionEvent.ACTION_POINTER_UP:
mHandler.removeCallbacks(mLongPressed);
//Whatever you were doing previously here
break;
case MotionEvent.ACTION_UP:
mHandler.removeCallbacks(mLongPressed);
//Whatever you were doing previously here
break;
}
//Whatever you were doing previously here
Now all three functionalities are working.
If point of long click is needed make an abstract class that implements Runnable
with floats x and y and fill them in ACTION_DOWN
, then use coordinates in run()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With