Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detecting long clicks on a custom view?

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?

like image 314
Orbit Avatar asked Jan 08 '16 06:01

Orbit


People also ask

Which function is used for long Press in Android?

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."

What are the options present while creating custom View?

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.

What are custom views in Android?

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.


2 Answers

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;
}
like image 86
Omji Mehrotra Avatar answered Oct 23 '22 09:10

Omji Mehrotra


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()

like image 20
InTwoMinds Avatar answered Oct 23 '22 09:10

InTwoMinds