How to add marquee text on items of NavigationView?

Issue

The names of my items are quite long, so I would like to make sure that their names scroll horizontally.
I have searched on several SO posts, but I have not found a solution to my problem
But I can’t, I tried this:

my activity_main.xml :

    <com.google.android.material.navigation.NavigationView
    android:id="@+id/nav_view"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    android:ellipsize="marquee"
    android:focusable="true"
    android:focusableInTouchMode="true"
    android:marqueeRepeatLimit="marquee_forever"
    android:scrollHorizontally="true"
    android:singleLine="true"
    app:headerLayout="@layout/nav_header"
    app:menu="@menu/menuDrawer">

</com.google.android.material.navigation.NavigationView>

My XML "menuDrawer":

<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
<group android:checkableBehavior="single">
    <item
        android:id="@+id/nav_welcome"
        android:icon="@drawable/ic_folder_black_24dp"
        android:title="@string/menu_welcome" />
    <item
        android:id="@+id/nav_dataset1"
        android:icon="@drawable/ic_folder_black_24dp"
        android:title="@string/menu_dataset1"/>
    <item
        android:id="@+id/nav_dataset2"
        android:icon="@drawable/ic_folder_black_24dp"
        android:title="@string/menu_dataset2" />
    <item
        android:id="@+id/nav_dataset3"
        android:icon="@drawable/ic_folder_black_24dp"
        android:title="@string/menu_dataset3" />
</group>

My java :

private AppBarConfiguration mAppBarConfiguration;
private TextView tvDataset1;
private TextView tvDataset2;
private TextView tvDataset3;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = findViewById(R.id.toolbar);
    tvDataset1 = this.findViewById(R.id.nav_dataset1);
    tvDataset1.setSelected(true);
    setSupportActionBar(toolbar);
    DrawerLayout drawer = findViewById(R.id.drawer_layout);
    NavigationView navigationView = findViewById(R.id.nav_view);
    // Passing each menu ID as a set of Ids because each
    // menu should be considered as top level destinations.
    mAppBarConfiguration = new AppBarConfiguration.Builder(
            R.id.nav_dataset1, R.id.nav_dataset2, R.id.nav_dataset3)
            .setDrawerLayout(drawer)
            .build();
    NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
    NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
    NavigationUI.setupWithNavController(navigationView, navController);


}

But no change, does anyone have an idea ?
Please help

EDIT :

I tried to override this :

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
 <CheckedTextView
    android:id="@+id/design_menu_item_text"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="1"
    android:drawablePadding="@dimen/design_navigation_icon_padding"
    android:gravity="center_vertical|start"
    android:textAppearance="@style/TextAppearance.AppCompat.Body2"
    android:ellipsize="marquee"
    android:marqueeRepeatLimit="marquee_forever"
    android:scrollHorizontally="true"
    android:focusable="true"
    android:focusableInTouchMode="true"/>
 <ViewStub
    android:id="@+id/design_menu_item_action_area_stub"
    android:inflatedId="@+id/design_menu_item_action_area"
    android:layout="@layout/design_menu_item_action_area"
    android:layout_width="wrap_content"
    android:layout_height="match_parent" />
</merge>

I have some changes :
AFTER the override:

enter image description here

BEFORE the override :

enter image description here

Solution

There is no official way to add marquee text on items of NavigationView i think the best approach is to implement your own custom RecyclerView to act as the NavigationView but if you want to stay with the default NavigationView below i will describe a workaround:

Short Description:

The key point is to find the NavigationMenuView which is a RecyclerView containing all the Navigation Menu items and from there to find all RecyclerView.ViewHolder visible children of type of NavigationMenuItemView to be able to get access to each menu item CheckedTextView to change its properties to marquee text.

Implementation

Below i have implemented two helper functions the setNavigationViewItemsMarquee(NavigationView navigationView) which is responsible to find the NavigationMenuView from the NavigationView and the second one the setNavigationMenuViewItemsMarquee(NavigationMenuView menuRecyclerView, boolean startMarquee) which is responsible to find all CheckedTextView visible items to change their properties to marquee text.

private void setNavigationViewItemsMarquee(NavigationView navigationView){

    //find the NavigationMenuView RecyclerView id
    int designNavigationViewId = getResources().getIdentifier("design_navigation_view", "id", getPackageName());
    if(designNavigationViewId!=0) {
        NavigationMenuView menuRecyclerView = navigationView.findViewById(designNavigationViewId);
        if(menuRecyclerView!=null) {
            //register ViewTreeObserver.OnGlobalLayoutListener to be informed for changes in the global layout state or the visibility of views within the view tree
            menuRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener(){
                @Override
                public void onGlobalLayout() {
                    //remove the ViewTreeObserver.OnGlobalLayoutListener() to be called only once
                    menuRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    //set the initial Marquee state to false for all visible items
                    setNavigationMenuViewItemsMarquee(menuRecyclerView, false);
                }
            });
            this.mMenuRecyclerView = menuRecyclerView;
        }
    }
}

private void setNavigationMenuViewItemsMarquee(NavigationMenuView menuRecyclerView, boolean startMarquee){

    if(menuRecyclerView!=null){
        //for every visible child in RecyclerView get its ViewHolder as NavigationMenuItemView and from there find the CheckedTextView using the design_menu_item_text id
        for (int i = 0; i < menuRecyclerView.getChildCount(); i++) {
            View child = menuRecyclerView.getChildAt(i);
            if (child!=null) {
                RecyclerView.ViewHolder holder = menuRecyclerView.getChildViewHolder(child);
                if(holder!=null && holder.itemView instanceof NavigationMenuItemView){
                    NavigationMenuItemView navigationMenuItemView = (NavigationMenuItemView)holder.itemView;
                    int designMenuItemTextId = getResources().getIdentifier("design_menu_item_text", "id", getPackageName());
                    if(designMenuItemTextId!=0){
                        //CheckedTextView found change it to MARQUEE
                        CheckedTextView textView = navigationMenuItemView.findViewById(designMenuItemTextId);
                        if(textView!=null) {
                            textView.setSingleLine(true);
                            textView.setEllipsize(TextUtils.TruncateAt.MARQUEE);
                            textView.setHorizontalFadingEdgeEnabled(true);
                            textView.setMarqueeRepeatLimit(startMarquee ? -1 : 0);
                            textView.setSelected(startMarquee);
                        }
                    }
                }
            }
        }
    }
}

Usage:

private NavigationMenuView mMenuRecyclerView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    setSupportActionBar(findViewById(R.id.toolbar));

    //get DrawerLayout and NavigationView
    DrawerLayout drawer = findViewById(R.id.drawer_layout);
    NavigationView navigationView = findViewById(R.id.nav_view);

    //set the NavController with AppBarConfiguration
    mAppBarConfiguration = new AppBarConfiguration.Builder(
            R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow)
            .setOpenableLayout(drawer)
            .build();
    NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
    NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
    NavigationUI.setupWithNavController(navigationView, navController);

    //set NavigationView Items Marquee
    setNavigationViewItemsMarquee(navigationView);

    //add also the DrawerLayout.DrawerListener to start/stop the Marquee based on onDrawerOpened/onDrawerClosed callbacks
    drawer.addDrawerListener(new DrawerLayout.DrawerListener() {
        @Override
        public void onDrawerSlide(@NonNull View view, float v) {}

        @Override
        public void onDrawerStateChanged(int i) {}

        @Override
        public void onDrawerOpened(@NonNull View view) {
            //start Marquee effect for all visible items
            setNavigationMenuViewItemsMarquee(mMenuRecyclerView, true);
        }

        @Override
        public void onDrawerClosed(@NonNull View view) {
            //stop Marquee effect for all visible items
            setNavigationMenuViewItemsMarquee(mMenuRecyclerView, false);
        }
    });
}

From the above code i have used the DrawerLayout.DrawerListener to start the marquee effect only when the Drawer is opened and to stop the marquee effect when the Drawer is closed.

In case also you have menu items which are not visible currently in the screen and you need to add the marquee effect during a RecyclerView scroll you can listen to RecyclerView.OnScrollListener() like: mMenuRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() and in onScrolled() callback you can start the marque effect to the new visible items using the above helper like setNavigationMenuViewItemsMarquee(mMenuRecyclerView, true); Of Course you can modify further the code based on your needs.

Result:

drawer

Answered By – MariosP

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published