Miki P
Miki P

Reputation: 652

How to add long press functionality to navigation drawer items?

As the title says, I'm trying to add long press functionality to items in my app's navigation drawer. These items are added dynamically (not inflated from navigation_drawer_menu.xml), so I can't solve this by specifying some attributes in the xml file.

I've looked at several questions on Stackoverflow, particularly this one: How to set a long click listener on a MenuItem (on a NavigationView)?. I've implemented the setActionView solution, but I end up getting a blank button on the right edge of the nav drawer item. When I long press the text, nothing happens. When I long press the little blank button, I get what I want.

How can I set a OnLongClickListener for the whole menuItem, and not just for its (I'm assuming it's a button) on its right side? Thank you for reading, and if any more info is needed, I'm happy to help you help me:)

Upvotes: 5

Views: 2315

Answers (3)

Robin Davies
Robin Davies

Reputation: 7817

We choose to do these things not because they are easy, but because they are hard. And because my UI would have come unglued if I couldn't do this.

Import the NavigationItemLongPressInterceptor class given below into your project.

Menu items for the NavigationView are declared as normal, with two additional attributes to add long-press behaviour.

<menu xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:showIn="navigation_view">

    <group android:id="@+id/home_views">
            <item
                android:id="@+id/nav_item_1"
                android:icon="@drawable/ic_item_1"
                android:title="Item 1"
                android:checkable="true"


                app:actionViewClass=
                   "com.twoplay.netplayer.controls.NavigationItemLongPressInterceptor"
                app:showAsAction="always"

                />
            <item
                android:id="@+id/nav_item_2"
                android:icon="@drawable/ic_item_2"
                android:title="Item 2"
                android:checkable="true"


                app:actionViewClass=
                   "com.twoplay.netplayer.controls.NavigationItemLongPressInterceptor"
                app:showAsAction="always"

                />
    </group>
</menu>

Add an implementation for NavigationItemLongPressInterceptor.OnNavigationItemLongClickListener to your activity, and implement the onNavigationItemLongClick method:

public class MainActivity extends MediaActivity
        implements
            NavigationView.OnNavigationItemSelectedListener,
            NavigationItemLongPressInterceptor.OnNavigationItemLongClickListener
    . . .
    @Override
    public void onNavigationItemLongClick(
        NavigationItemLongPressInterceptor.SelectedItem selectedItem,
        View view)
     {
        // supply your NavigationView as an argument.
        int menItemId = selectedItem.getItemId(mNavigationView);
        switch (id) {
            ...
            case R.id.nav_local_device:
            case R.id.nav_upnp_devices: {
                showNavigationItemSetAsHomePopupMenu(id,view);

            }
            break;
        }

    }

}

You probably have to add com.twoplay.netplayer.controls.NavigationItemLongPressInterceptor to your proguard rules.

NavigationItemLongPressInterceptor.java:

package com.twoplay.netplayer.controls;

import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.util.AttributeSet;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.ViewGroup;

import com.google.android.material.navigation.NavigationView;

import androidx.annotation.Nullable;

/**
 * Attach a long-click handler to a menu item in a NavigationView menu.
 *
 * To handle long-click of a Navigator menu item, declare the item as normal, and append
 * app:actionViewClass="com.twoplay.netplayer.controls.NavigationItemLongPressInterceptor" and
 * app:showAsAction="always" attributes:
 *
 * <menu xmlns:tools="http://schemas.android.com/tools"
 *     xmlns:app="http://schemas.android.com/apk/res-auto"
 *     >
 *
 *     <group android:id="@+id/home_views">
 *             <item
 *                 android:id="@+id/nav_item_1"
 *                 android:icon="@drawable/ic_item_1"
 *                 android:title="Item 1"
 *                 android:checkable="true"
 *                  app:actionViewClass=
 *                    "com.twoplay.netplayer.controls.NavigationItemLongPressInterceptor"
 *                 app:showAsAction="always"
 *             </item>
 *      </group>
 *
 *  Your Application class must implement <L NavigationItemLongPressInterceptor.OnNavigationItemLongClickListener/>
 *  in order to receive notification of long pressed menu items.
 *
 *  You can retrieve the item id of the menu by calling <L SelectedItem.getItemId/> on the
 *  <L SelectedItem/> provided as an argument to <L NavigationItemLongPressInterceptor.onNavigationItemLongClick/>
 *
 *                 />
 *
 */
@SuppressWarnings("unused")
public class NavigationItemLongPressInterceptor extends View {
    public NavigationItemLongPressInterceptor(Context context) {
        super(context);
        init();
    }



    public NavigationItemLongPressInterceptor(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public NavigationItemLongPressInterceptor(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init()
    {

        setVisibility(View.INVISIBLE);
        setLayoutParams(new ViewGroup.LayoutParams(0,0));
    }
    public interface OnNavigationItemLongClickListener {
        void onNavigationItemLongClick(SelectedItem itemHandle, View view);
    }


    public static class SelectedItem {
        private final View actionView;

        private SelectedItem(View actionView) {
            this.actionView = actionView;
        }

        public int getItemId(NavigationView navigationView)
        {
            return getItemId(navigationView.getMenu());
        }

        private int getItemId(Menu menu) {
            for (int i = 0; i < menu.size(); ++i) {
                MenuItem item = menu.getItem(i);
                if (item.getActionView() == actionView) {
                    return item.getItemId();
                }
                SubMenu subMenu = item.getSubMenu();
                if (subMenu != null) {
                    int itemId = getItemId(subMenu);
                    if (itemId != -1) {
                        return itemId;
                    }
                }
            }
            return -1;
        }
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        View parent = getMenuItemParent();
        parent.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                OnNavigationItemLongClickListener receiver = getReceiver();
                if (receiver == null)
                {
                    throw new RuntimeException("Your main activity must implement NavigationViewLongPressInterceptorView.OnNavigationItemLongClickListener");
                }
                View parent = getMenuItemParent();
                receiver.onNavigationItemLongClick(
                        new SelectedItem(NavigationItemLongPressInterceptor.this),parent);
                return true;
            }
        });
    }

    private Activity getActivity() {
        Context context = getContext();
        while (context instanceof ContextWrapper) {
            if (context instanceof Activity) {
                return (Activity)context;
            }
            context = ((ContextWrapper)context).getBaseContext();
        }
        return null;
    }
    private OnNavigationItemLongClickListener getReceiver() {
        Activity activity = getActivity();
        if (activity == null) return null;
        if (activity instanceof OnNavigationItemLongClickListener)
        {
            return (OnNavigationItemLongClickListener)activity;
        }
        return null;
    }

    private View getMenuItemParent() {
        View parent = (View)getParent();
        while (true)
        {
            if (parent.isClickable())
            {
                return parent;

            }
            parent = (View) parent.getParent();
        }
    }
}

This code targets androidx, but it can be trivially backported to AppCompat. Just delete the androidx imports, and replace them with corresponding AppCompat imports. Hopefully older versions of NavigationView lay out actionViews the same way.

Tested with 'androidx.appcompat:appcompat:1.0.2', 'com.google.android.material:material:1.0.0'. I'm reasonably confident that it is version-safe. Let me know if getMenuItemParent()needs adjustments for other AppCompat versions, and I will incorporate changes here.

Upvotes: 1

Thomas
Thomas

Reputation: 21

I had to do similar and went with onItemLongClick

https://developer.android.com/reference/android/widget/AdapterView.OnItemLongClickListener.html

my implementation was a little different though as I had an expandable list in the navigation drawer and each item had to have an onClick as well as an onLongClick method call, each item on the navigation drawer was also added dynamically by the user of the app.

 listView.setOnItemLongClickListener(new View.OnItemLongClickListener() {
        @Override
        public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {

         Toast.makeText(HomeActivity.this, id +"", Toast.LENGTH_LONG).show();

            return true;

        }

    });

Upvotes: 0

Ika
Ika

Reputation: 1738

I had the same problem and managed to solve it by digging into the NavigationView's view hierarchy.

The first step is to understand the view hierarchy of your NavigationView. You can use the piece of code from this post to print out the NavigationView's view hierarchy.

Then start digging for the view you're targeting. In my case:

@Override
public boolean onPrepareOptionsMenu(Menu menu) {

    // Start digging into the view hierarchy until the correct view is found
    NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
    ViewGroup navigationMenuView = (ViewGroup)navigationView.getChildAt(0);
    ViewGroup navigationMenuItemView = (ViewGroup)navigationMenuView.getChildAt(2);
    View appCompatCheckedTextView = navigationMenuItemView.getChildAt(0);

    // Attach click listener
    appCompatCheckedTextView.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            Log.i("test", "LONG CLICK");
            return true;
        }
    });

    return super.onPrepareOptionsMenu(menu);
}

Upvotes: 1

Related Questions