4

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

Louis Chabert
  • 399
  • 2
  • 15

3 Answers3

2

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

MariosP
  • 8,300
  • 1
  • 9
  • 30
  • Should I modify "getIdentifier("design_navigation_view", "id", getPackageName());" by "getIdentifier("nav_view", "id", getPackageName());" cause is the id of my NavigatIonView ? – Louis Chabert Jun 10 '22 at 07:52
  • @LouisChabert no this is the id of NavigationMenuView (app:id/design_navigation_view) which is the RecyclerView needed to get access to the menu items. – MariosP Jun 10 '22 at 08:05
  • Ok thank you, I don't really understand how to use the drawerListener, should I call onDrawerOpened or onDrawerClosed ? – Louis Chabert Jun 10 '22 at 08:07
  • 1
    @LouisChabert in the above example i am starting the marque effect only when the drawer is going to be opened which will be visible to the user and i am stopping the marquee effect when the drawer is going to be closed which is not necessary any more to marquee as the drawer now is fully hidden. – MariosP Jun 10 '22 at 08:13
  • Well, I missing something, It does not work :( – Louis Chabert Jun 10 '22 at 08:16
  • @LouisChabert what error are you getting? I have tested this on the latest Material Design Library com.google.android.material:material:1.6.1 and it works as expected. If you don't want to start the marquee when the drawer is going to be opened you can start it also in onGlobalLayout() using setNavigationMenuViewItemsMarquee(menuRecyclerView, true); – MariosP Jun 10 '22 at 08:24
  • I don't have any error, It's just not working :( I'm pretty sur I missed something between the identifier of different view, I got this uncommented code and I think I'm getting lost in the views, I have 2 different views but with identical Drawerlayout ids. It seems weird to me. I can edit my post and put more code but I'm afraid it's not very readable – Louis Chabert Jun 10 '22 at 08:28
  • @LouisChabert can you upload a minimal reproducible example of your code on Github so i can check what are your missing? – MariosP Jun 10 '22 at 08:44
  • Sure, I will do this :) – Louis Chabert Jun 10 '22 at 09:29
  • Where can I send you the gitHub link ? – Louis Chabert Jun 10 '22 at 11:20
  • @LouisChabert add it here as a comment – MariosP Jun 10 '22 at 11:27
  • https://github.com/LoulouChabChab/DebugApp.git – Louis Chabert Jun 10 '22 at 11:40
  • @LouisChabert in your MenuDeroulant Activity you have wrong resource ids change the findViewById(R.id.drawer_layout) to findViewById(R.id.drawer_layout_deroulant) and findViewById(R.id.nav_view) to findViewById(R.id.nav_view_menu) and the code above works fine. – MariosP Jun 10 '22 at 12:35
  • Also in your MainActivity if you add the code i have mentioned on my answer it will works too. – MariosP Jun 10 '22 at 12:47
  • I changed it, and its not working :(, I pushed it – Louis Chabert Jun 10 '22 at 12:57
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/245489/discussion-between-mariosp-and-louis-chabert). – MariosP Jun 10 '22 at 13:00
1

Styling Widget.MaterialComponents.NavigationView will not cause it to scroll.

One would have to replace the whole NavigationView with a ListView or RecyclerView.
And OnNavigationItemSelectedListener(MenuItem item) would need to be implemented.

Martin Zeitler
  • 1
  • 19
  • 155
  • 216
  • I already use the navigation view in a certain way in the activity_main.xml I just added the code at the beginning of my post – Louis Chabert Jun 08 '22 at 13:00
1

The trick is using styles. Attribute works for direct item, for ex, when you have a TextView where you want to marquee, you add these changes directly onto the item.

In examples, where your view is actually custom (for ex. as you may know custom view might be set of mutliple different base views) you have to change it in styles in order for these attributes to propagate to corresponding items, in your example TextView.

So the changes should be in styles, something like next. Add a new style to the styles.xml:

res / values / styles.xml

<style name="TextAppearance">
    <item name="android:ellipsize">marquee</item>
    <item name="android:focusable">true</item>
    <item name="android:focusableInTouchMode>true</item>
    <item name="android:marqueeRepeatLimit">marquee_forever</item>

</style>

Set the new style to the NavigationView:

res / layout / activity_main.xml

<android.support.design.widget.NavigationView
    android:id="@+id/nav_view"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    android:fitsSystemWindows="true"
    app:headerLayout="@layout/nav_header_main"
    app:menu="@menu/activity_main_drawer"
    app:theme="@style/TextAppearance" />

PS. Another very simple approach it's by using custom entries for Navigation View. You can find more information on how to build it from here.

GensaGames
  • 5,538
  • 4
  • 24
  • 53
  • What is the different between "" ? I tried to modify my code but it's still not working :( – Louis Chabert Jun 09 '22 at 06:54
  • 1
    They a both to represent the same view. when you see something startin wtih com.google.android - it means the latest version of this view (for new Android devices). When you see android.support.design - it means for all devices (new and backport for old Android version) – GensaGames Jun 14 '22 at 01:12