16

How can I set a long click listener on a MenuItem?

I tried this answer, but the method doesn't exist for me. Any solutions?

Code:

Menu menu = navigationView.getMenu();
MenuItem menuItem = menu.findItem(R.id.menu_item);

// TODO set a long click listener on the menuItem.
menuItem.setOnLongClickListener(...); // Method does not exist, any other solutions?

Edit: I don't want to set a custom ActionView, I want the long click listener to the whole MenuItem, without a custom View.

Community
  • 1
  • 1
Thomas Vos
  • 12,271
  • 5
  • 33
  • 71

7 Answers7

8

one of many ways (assuming we use Toolbar) - this example should give you the idea how to implement long click on toolbar button :

class MyActivity extends Activity {    

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        /** get menu inflater */
        MenuInflater menuInflater = getMenuInflater();
        /** Inflate the menu
         * this adds items to the action bar if it is present. */
        menuInflater.inflate(R.menu.menu_home, menu);
        /** find interesting item */
        MenuItem item = menu.findItem(R.id.itemId);
        /** set action view */
        item.setActionView(new ImageButton(this)); // this is a Context.class object
        /** set listener  on action view */
        item.getActionView().setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                return false;
            }
        });

        return super.onCreateOptionsMenu(menu);
    }
}

in method onCreateOptionsMenu - or any other method when you can get menu item reference (omit step 1-2):

  1. create menu / for example by inflate
  2. get menu item
  3. create action view for example ImageButton
  4. set long click listener on action view
  5. set on menu item an action view

above i set an action view then i get it back from menu item and set listener (the order is no matter it could be also in such way):

ImageButton imageButton = new ImageButton(Context);
imageButton.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                return false;
            }
        });
item.setActionView(imageButton);

ps. you can also set image view in xml as attribute on menu item:

 <item
    ... 
    android:actionViewClass="android.widget.ImageButton"
 />

then you can get the action view by cast 

   View menuItemActionView = menu.findItem(R.id.itemId).getActionView();
   if(menuItemActionView != null 
            && ImageButton.class.isAssignableFrom(menuItemActionView.getCLass())) {
        ImageButton imageButton = (ImageButton) menuItemActionView;
   }

But then you set the long click listener to the action view only, not the whole item. – SuperThomasLab

-- no you are setting an action view on single element in this case you change a default view (to ImageButton widget) for an menu item - action view could be simple or complex view type

But what if u don't want to change the view, but keep the default view? – SuperThomasLab

example (this is one way of many by using a layout tree observer / by setting a layout change listener):

    private View.OnLongClickListener onLongClickListener = new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            return false;
        }
    };


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        /** get menu inflater */
        MenuInflater menuInflater = getMenuInflater();
        /** Inflate the menu
         * this adds items to the action bar if it is present. */
        menuInflater.inflate(R.menu.menu_home, menu);
        /** geta menu item using findItem(int itemid) */
        MenuItem item = menu.findItem(R.id.itemLogOut);
        /** check if we have item */
        if(item!=null) {
            /** try get its action view */
            View actionView = item.getActionView();
             /** check if action view is already set? */
            if(actionView==null) {
                /** get item id  to comparte later in observer listener*/
                final int itemId = item.getItemId();
                /** if not set on top most window an layout changes listener */
                getWindow().getDecorView()
                           .addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
                    @Override
                    public void onLayoutChange(View v, int left, int top, int right, 
                      int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                           /** try get view by id we have stored few line up */
                           View viewById = v.getRootView().findViewById(itemId);
                           /** check if we have any result */
                           if(viewById!=null) {
                                /** set our listener */                     
                                viewById.setOnLongClickListener(onLongClickListener);
                                /** remove layout observer listener */
                                v.removeOnLayoutChangeListener(this);
                           }
                    }
                });
            } else {
                /** if set we can add our on long click listener */
                actionView.setOnLongClickListener(onLongClickListener);
            }
        }
  } 

I tried the OnLayoutChangeListener, but it still doesn't work Nothing changed. – SuperThomasLab

YES IT DOES - but i know the reason why it't is not working in your case ??? - in my example we check if view item is laid out instead of that change if menu view is laid out and then check if menu contain item

here is working code for you to study:

https://github.com/c3ph3us/LongClickOnMenuItem

in reply to other comments:

@ SuperThomasLab It's not obvious for me why setOnLongClickListener(...) method is not available for you. – Hossein Seifi

@HosseinSeifi - look at android.view.MenuItem interface - it doesn't provide a such method - so for good programmer this should be obvious :) why he can't reach implementing class method.

ceph3us
  • 7,326
  • 3
  • 36
  • 43
  • Thanks you for your answer. But where is the long click listener? And how am I supposed to set this to a `MenuItem`? – Thomas Vos Jun 18 '16 at 18:41
  • But then you set the long click listener to the action view only, not the whole item. – Thomas Vos Jun 21 '16 at 18:04
  • @SuperThomasLab - no to menu item (visible widget = in this case ImageButton) ! – ceph3us Jun 21 '16 at 18:07
  • But what if u don't want to change the view, but keep the default view? – Thomas Vos Jun 21 '16 at 19:03
  • I tried the `OnLayoutChangeListener`, but it still doesn't work. Nothing changed. – Thomas Vos Jun 22 '16 at 15:30
  • @SuperThomasLab -there is no such thing like "can't / doesn't etc" study the example i provided on github (set ob layout listener for menu view then look if item view is laid out ) – ceph3us Jun 27 '16 at 17:43
  • 2
    I believe the workarounds provided here are the only way to get this to work. I don't think you can get long-press to work without providing your own ActionView, or the treeview solution. Ideally getActionView() on the default MenuItem and setting a long-click listener would just work, but Android sucks sometimes :-( – SoftWyer Jun 11 '17 at 16:02
  • You need to set different id's per item. – EmmanuelMess Jan 04 '18 at 16:19
  • Excellent answer. Do you know if there is a way (using your method which does not modify the default menu item view) to force the long press to prevent onOptionsItemSelected from being called? – snapfractalpop Aug 08 '18 at 06:34
4

Solution

This isn't perfect at all, but still better than everyone is suggesting here, because it works on whole item layout, not only on actionview's part.

Notice:

  1. I tested it only in MaterialComponents library 1.1.0-alpha08, so I don't guarantee that it will work in the future
  2. I already asked devs to provide better API for this, but they closed the issue ‍♂️
  3. index is the position of an item, BUT it also contains position of: HeaderLayout, Divider, Category (Sub menu header)

Well, here is the code:

val navigationView = findViewById(R.id.navigation_view)
val navigationRecycler = navigationView[0] as RecyclerView // core-ktx extension for ViewGroup
val navigationLayoutManager = navigationRecycler.layoutManager as LinearLayoutManager
val navigationAdapter = navigationRecycler.adapter

if (navigationAdapter != null) {
    navigationRecycler.post { // this line is important
        for (index in 0 until navigationAdapter.itemCount) {
            val item = navigationLayoutManager.findViewByPosition(index)
            item?.setOnLongClickListener {
                Toast.makeText(this, "finally!", Toast.LENGTH_SHORT).show()
                true
            }
        }
    }
}
massivemadness
  • 100
  • 2
  • 8
  • But I think it has to be 'in 0..navigationAdapter.itemCount', otherwise the last item is ignored. – Cyb3rKo Oct 24 '20 at 19:54
1

You can do it this way:

        val homeTab = bottomNavigationView.findViewById<BottomNavigationItemView>(R.id.navigation_home) 
        //R.id.navigation_home id of navigation menu items you provided in menu file for BottomNavigationView

        homeTab.setOnLongClickListener {
              Toast.makeText(this@MainActivity,"Home Long clicked!",Toast.LENGTH_LONG).show()
              true
    }
Edalat Feizi
  • 1,371
  • 20
  • 32
0

You can achieve by doing this:

action_menu.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:support="http://schemas.android.com/apk/res-auto" >

<item
    android:id="@+id/item1"
    support:showAsAction="always">
</item>

custom_action_view.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="10dp"
    android:paddingRight="5dp" >

<ImageButton
    android:id="@+id/customActionItem"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerVertical="true"
    android:background="@drawable/abc_item_background_holo_dark"
    android:src="@drawable/bulb_icon" />

</RelativeLayout>

and menu inflater code is as follow:

public boolean onCreateOptionsMenu(Menu menu) {
    // TODO Auto-generated method stub
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.action_menu, menu);     

    final MenuItem item1= menu.findItem(R.id.item1);
    MenuItemCompat.setActionView(item1, R.layout.custom_action_view);
    View vItem1= MenuItemCompat.getActionView(item1);

    final ImageButton customActionItem= (ImageButton) vItem1.findViewById(R.id.customActionItem);
    customActionItem.setOnLongClickListener(new OnLongClickListener() {

        @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub
            // do something here
        }
    });

    return super.onCreateOptionsMenu(menu);
}
  • This is the same as the other answer. I don't want a custom ActionView. I want to set the long click listener to the normal item, without a custom view. – Thomas Vos Jun 27 '16 at 07:01
0
mBottomNavigationView.findViewById(menuItemId).setOnLongClickListener(new OnLongClickListener() {
    @Override
    public boolean onLongClick(View v) {
        return true;
    }
});
Pingolin
  • 3,161
  • 6
  • 25
  • 40
arjinmc
  • 3
  • 5
  • 4
    This might solve the question, but you might want to add why does your solution work. A bit of an explanation goes a long way. – Tavo Nov 26 '19 at 10:41
  • This answer works for me. Could you please add an explanation on why is this working? – Kristian Mar 13 '23 at 18:33
0

From massivemadness answer here is a slight variation to target a menu. I also don't think this is stable, it feels like a bodge but nest it in a try catch, keep calm and move on :)

if(navigationAdapter != null) {
            navigationRecycler.post { // this line is important
                for(index in 0 until navigationAdapter.itemCount) {
                    val item = navigationLayoutManager.findViewByPosition(index)
                    item?.setOnLongClickListener {
                        try{
                            Toast.makeText(this, "${nav_view.menu[index - 1 /*Number of children before your menu*/].title}!", Toast.LENGTH_SHORT).show()
                            true
                        } catch (e:Throwable){
                            false
                        }


                    }
                }
            }
        }

0

I have added the long press to the navigation view below. I wanted to get long press event on Support item in the view.

Steps:

Implemented DrawerLayout.DrawerListener interface in my activity.

Set my activity as Drawer Listener to the DrawerLayout.

DrawerLayout drawer = findViewById(R.id.drawer_layout);
drawer.addDrawerListener(this);

Implemented onDrawerOpened method where I set the long press to the item.

@Override
public void onDrawerOpened(@NonNull View drawerView) {
    View support = findViewById(R.id.nav_support);
    support.setOnLongClickListener(view -> {
        // TODO: Handle the long press here.
        return false;
    });
}

The main idea in this solution to get the View of the MenuItem and setOnLongClickListener on the View. I added it to the onDrawerOpened method because at this point I am sure that the View is created. I have tried to get the view earlier, but it was not available and findViewById method was returning null.

Result

Dima
  • 420
  • 2
  • 17