How to put all action items on the left, taking as much space as possible, and yet have overflow on the right?

纵饮孤独 提交于 2020-01-24 09:38:07

问题


Background

Suppose I have a Toolbar, and multiple action items. Some might be customized (example: TextView with image).

What I need to do is to align them all to the left, instead of to the right, yet still have the overflow item on the right side.

I also try to have as much space as possible to the action items.

The problem

None of what I've found works

What I've tried

1.For the alignment, I've found some solutions on StackOverflow, of adding views inside the Toolbar, but this won't work well for some reason, because pressing an item doesn't show the effect on the whole item (as if it's smaller in height).

Other things I tried for this:

  • android:layoutDirection="ltr" - doesn't do anything to the action items
  • android:gravity="left|start" - same

2.For the space issue, none of what I tried work. I tried to remove all things that might add margins or padding.

Here's a sample code to show how I tested both issues :

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" tools:context="com.example.user.myapplication.MainActivity">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize"
        android:layoutDirection="ltr" android:padding="0px" android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
        app:contentInsetEnd="0px" app:contentInsetEndWithActions="0px" app:contentInsetLeft="0px"
        app:contentInsetRight="0px" app:contentInsetStart="0px" app:contentInsetStartWithNavigation="0px"
        app:logo="@null" app:title="@null" app:titleMargin="0px" app:titleTextColor="#757575"
        tools:ignore="UnusedAttribute" tools:title="toolbar"/>

</FrameLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {

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

        Toolbar mainToolbar = findViewById(R.id.toolbar);
        for (int i = 0; i < 10; ++i) {
            final View menuItemView = LayoutInflater.from(this).inflate(R.layout.action_item, mainToolbar, false);
            ImageView imageView = (ImageView) menuItemView.findViewById(android.R.id.icon);
            String text = "item" + i;
            final int itemIconResId = R.drawable.ic_launcher_background;
            imageView.setImageResource(itemIconResId);
            ((TextView) menuItemView.findViewById(android.R.id.text1)).setText(text);
            final OnClickListener onClickListener = new OnClickListener() {
                @Override
                public void onClick(final View view) {
                    //do something on click
                }
            };
            menuItemView.setOnClickListener(onClickListener);
            final MenuItem menuItem = mainToolbar.getMenu()
                    .add(text).setActionView(menuItemView).setIcon(itemIconResId)
                    .setOnMenuItemClickListener(new OnMenuItemClickListener() {
                        @SuppressLint("MissingPermission")
                        @Override
                        public boolean onMenuItemClick(final MenuItem menuItem) {
                            onClickListener.onClick(menuItemView);
                            return true;
                        }
                    });
            MenuItemCompat.setShowAsAction(menuItem, MenuItem.SHOW_AS_ACTION_IF_ROOM);

        }
    }
}

action_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content" android:layout_height="match_parent"
    android:background="?android:attr/selectableItemBackground" android:clickable="true" android:focusable="true"
    android:focusableInTouchMode="false" android:gravity="center" android:orientation="horizontal">

    <ImageView
        android:id="@android:id/icon" android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:scaleType="center" tools:src="@android:drawable/sym_def_app_icon"/>

    <TextView
        android:id="@android:id/text1" android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:layout_marginLeft="6dp" android:layout_marginStart="6dp" android:gravity="center"
        android:textColor="#c2555555" android:textSize="15sp" tools:text="text"/>

</LinearLayout>

This is what I got:

The question

How can I support max space usage of the Toolbar, and also make the action items align to the left?


EDIT: after a bit work, I got the alignment solution to partially work:

activity_main.xml

<android.support.design.widget.AppBarLayout
    android:layout_width="match_parent" android:layout_height="wrap_content"
    android:theme="@style/AppTheme.AppBarOverlay">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize"
        android:background="#fff" android:gravity="center_vertical|start"
        android:layoutDirection="ltr" android:padding="0px" android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
        app:contentInsetEnd="0px" app:contentInsetEndWithActions="0px" app:contentInsetLeft="0px"
        app:contentInsetRight="0px" app:contentInsetStart="0px" app:contentInsetStartWithNavigation="0px"
        app:logo="@null" app:title="@null" app:titleMargin="0px" app:titleTextColor="#757575"
        tools:ignore="UnusedAttribute" tools:title="toolbar">

        <android.support.v7.widget.ActionMenuView
            android:id="@+id/amvMenu" android:layout_width="match_parent" android:layout_height="match_parent"/>
    </android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>

In code, the only difference is that I use the menu of ActionMenuView, instead of the Toolbar:

    final ActionMenuView amvMenu = (ActionMenuView) toolbar.findViewById(R.id.amvMenu);
    final Menu menu =amvMenu.getMenu();
    ...
       final MenuItem menuItem = menu.add...

It does put the overflow item on the far right, while the action items are on the left.

However, the effect of pressing doesn't include the whole height of the items, and it seems as if the items take more space than usual. Plus, I still didn't figure out how to use all the possible space there is here:

EDIT:

In order to fix the issue of the pressing effect, all I had to do is to add android:minHeight="?attr/actionBarSize" to the items that are being inflated in the loop.

What's still weird about the pressing effect is that if I add a normal action item (just text/icon, without inflating), it has a tiny ripple effect, and the action item itself take a lot of space compared to what I add.

Another new issue that this has caused, is that clicking on anywhere near the overflow menu will trigger clicking on it.

EDIT:

Yet another issue from this solution, is that there are spaces between items in some cases, such as one in the case that there are only a few items:

So, in short, this solution doesn't work well at all.


回答1:


Here is a solution that will left-justify the menu items while keeping the overflow menu icon to the right. This solution uses the standard implementation of the toolbar/action bar but anticipates how action views will be laid out so they will be positioned as we wish in the toolbar.

Most of the code below is what you have presented. I have moved the for loop that creates the menu items into onCreateOptionsMenu() so I could make use of the ActionMenuView that is already part of the toolbar's menu structure instead of adding another one.

In onCreateOptionsMenu() a running tally of space consumed by menu items is maintained as menu items are laid into the menu. As long as there is space, menu items will be flagged as "shown" (MenuItem.SHOW_AS_ACTION_ALWAYS). If the item will encroach on the area reserved for the overflow menu icon, the item is laid in but is targeted for the overflow menu (MenuItem.SHOW_AS_ACTION_NEVER).

After all views are laid into the menu, the slack space is computed. This is the area on the screen between the last visible menu item and the overflow icon (if overflow is in used) or between the last visible item and the end of the tool bar (if overflow is not in use.)

Once the slack space is computed, a Space widget is created and laid into the menu. This widget forces all other items to be left-justified.

Most of the changes have been made to MainActivity.java, but I may have changed a thing or two in the XML files. I include them here for completeness.

Here are some screen captures of the results.

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private Toolbar mToolbar;

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

        mToolbar = findViewById(R.id.toolbar);
        mToolbar.setTitle("");
        setSupportActionBar(mToolbar); // Ensures that onCreateOptionsMenu is called
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        final float density = getResources().getDisplayMetrics().density;
        final int overflowCellSize = (int) (OVERFLOW_CELL_WIDTH * density);
        // Other than the overflow icon, this is how much real estate we have to fill.
        int widthLeftToFill = mToolbar.getWidth() - overflowCellSize;
        // slackWidth is what is left over after we are done adding our action views.
        int slackWidth = -1;

        for (int i = 0; i < 10; ++i) {
            final View menuItemView =
                    LayoutInflater.from(this).inflate(R.layout.action_item, mToolbar, false);
            ImageView imageView = menuItemView.findViewById(android.R.id.icon);
            final int itemIconResId = R.drawable.ic_launcher_background;
            imageView.setImageResource(itemIconResId);
            final String text = "item" + i;
            ((TextView) menuItemView.findViewById(android.R.id.text1)).setText(text);
            final View.OnClickListener onClickListener = new View.OnClickListener() {
                @Override
                public void onClick(final View view) {
                    Toast.makeText(MainActivity.this, text,
                            Toast.LENGTH_SHORT).show();
                }
            };
            menuItemView.setOnClickListener(onClickListener);
            final MenuItem menuItem = menu
                    .add(text).setActionView(menuItemView).setIcon(itemIconResId)
                    .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
                        @SuppressLint("MissingPermission")
                        @Override
                        public boolean onMenuItemClick(final MenuItem menuItem) {
                            onClickListener.onClick(menuItemView);
                            return true;
                        }
                    });
            // How wide is this ActionView?
            menuItemView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
            widthLeftToFill -= menuItemView.getMeasuredWidth();
            if (widthLeftToFill >= 0) {
                // The item will fit on the screen.
                menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
            } else {
                // The item will not fit. Force it to overflow.
                menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
                if (slackWidth < 0) {
                    // Just crossed over the limit of space to fill - capture the slack space.
                    slackWidth = widthLeftToFill + menuItemView.getMeasuredWidth();
                }
            }
        }
        if (slackWidth < 0) {
            // Didn't have enough action views to fill the width.
            slackWidth = widthLeftToFill + overflowCellSize;
        }
        if (slackWidth > 0) {
            // Create a space widget to consume the slack. This slack space widget makes sure
            // that the action views are left-justified with the overflow on the right.
            // As an alternative, this space could also be distributed among the action views.
            Space space = new Space(this);
            space.setMinimumWidth(slackWidth);
            final MenuItem menuItem = menu.add("").setActionView(space);
            menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
        }
        return true;
    }

    private static final int OVERFLOW_CELL_WIDTH = 40; // dips
}

activity_main.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:layoutDirection="ltr"
        android:padding="0px"
        android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
        app:contentInsetEnd="0px"
        app:contentInsetEndWithActions="0px"
        app:contentInsetLeft="0px"
        app:contentInsetRight="0px"
        app:contentInsetStart="0px"
        app:contentInsetStartWithNavigation="0px"
        app:logo="@null"
        app:title="@null"
        app:titleMargin="0px"
        app:titleTextColor="#757575"
        tools:ignore="UnusedAttribute"
        tools:title="toolbar">
    </android.support.v7.widget.Toolbar>
</FrameLayout>

action_item.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:background="?android:attr/selectableItemBackground"
    android:clickable="true"
    android:focusable="true"
    android:focusableInTouchMode="false"
    android:gravity="center"
    android:orientation="horizontal"
    android:paddingLeft="8dp">
    <ImageView
        android:id="@android:id/icon"
        android:layout_width="wrap_content"
        android:layout_height="?attr/actionBarSize"
        android:scaleType="center"
        tools:src="@android:drawable/sym_def_app_icon" />
    <TextView
        android:id="@android:id/text1"
        android:layout_width="wrap_content"
        android:layout_height="?attr/actionBarSize"
        android:layout_marginLeft="6dp"
        android:layout_marginStart="6dp"
        android:gravity="center"
        android:textColor="#c2555555"
        android:textSize="15sp"
        tools:text="text" />
</LinearLayout>

Update: To use the tool bar without setting it up as an action bar, add a global layout listener to wait until the tool bar is setup.

MainActivity.java - using a global layout listener instead of an action bar

public class MainActivity extends AppCompatActivity {
    private Toolbar mToolbar;

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

        mToolbar = findViewById(R.id.toolbar);
        mToolbar.setTitle("");
//        setSupportActionBar(mToolbar); // Ensures that onCreateOptionsMenu is called
        mToolbar.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mToolbar.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                setupMenu(mToolbar.getMenu());
            }
        });
    }

    public boolean setupMenu(Menu menu) {
        final float density = getResources().getDisplayMetrics().density;
        int mOverflowCellSize = (int) (OVERFLOW_CELL_WIDTH * density);
        // Other than the overflow icon, this is how much real estate we have to fill.
        int widthLeftToFill = mToolbar.getWidth() - mOverflowCellSize;
        // slackWidth is what is left over after we are done adding our action views.
        int slackWidth = -1;

        for (int i = 0; i < 10; ++i) {
            final View menuItemView =
                    LayoutInflater.from(this).inflate(R.layout.action_item, mToolbar, false);
            ImageView imageView = menuItemView.findViewById(android.R.id.icon);
            final int itemIconResId = R.drawable.ic_launcher_background;
            imageView.setImageResource(itemIconResId);
            String text = "item" + i;
            ((TextView) menuItemView.findViewById(android.R.id.text1)).setText(text);
            final View.OnClickListener onClickListener = new View.OnClickListener() {
                @Override
                public void onClick(final View view) {
                    Toast.makeText(MainActivity.this, text ,
                            Toast.LENGTH_SHORT).show();
                }
            };
            menuItemView.setOnClickListener(onClickListener);
            final MenuItem menuItem = menu
                    .add(text).setActionView(menuItemView).setIcon(itemIconResId)
                    .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
                        @SuppressLint("MissingPermission")
                        @Override
                        public boolean onMenuItemClick(final MenuItem menuItem) {
                            onClickListener.onClick(menuItemView);
                            return true;
                        }
                    });
            // How wide is this ActionView?
            menuItemView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
            widthLeftToFill -= menuItemView.getMeasuredWidth();
            if (widthLeftToFill >= 0) {
                // The item will fit on the screen.
                menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
            } else {
                // The item will not fit. Force it to overflow.
                menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
                if (slackWidth < 0) {
                    // Just crossed over the limit of space to fill - capture the slack space.
                    slackWidth = widthLeftToFill + menuItemView.getMeasuredWidth();
                }
            }
        }
        if (slackWidth < 0) {
            // Didn't have enough action views to fill the width.
            slackWidth = widthLeftToFill + mOverflowCellSize;
        }
        if (slackWidth > 0) {
            // Create a space widget to consume the slack. This slack space widget makes sure
            // that the action views are left-justified with the overflow on the right.
            // As an alternative, this space could also be distributed among the action views.
            Space space = new Space(this);
            space.setMinimumWidth(slackWidth);
            final MenuItem menuItem = menu.add("").setActionView(space);
            menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
        }
        return true;
    }

    private static final int OVERFLOW_CELL_WIDTH = 40; // dips
}

The following sample app separates out menu creation from the left justification of the menu by introducing the method notifyMenuItemsChanged. In the app, click on the button to remove the menu item at position 1.

This code is basically the same as above, but the Space widget needs an id so it can be removed to be re-added when the menu changes.

MainActivity.Java: Sample app

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final Toolbar toolbar = findViewById(R.id.toolbar);
        toolbar.setTitle("");
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Menu menu = toolbar.getMenu();
                // Remove item at position 1 on click of button.
                if (menu.size() > 1) {
                    menu.removeItem(menu.getItem(1).getItemId());
                    notifyMenuItemsChanged(toolbar);
                }
            }
        });
        toolbar.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                toolbar.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                setupMenu(toolbar);
            }
        });
    }

    private void setupMenu(Toolbar toolbar) {
        Menu menu = toolbar.getMenu();

        // Since we are resetting the menu, get rid of what may have been placed there before.
        menu.clear();
        for (int i = 0; i < 10; ++i) {
            final View menuItemView =
                    LayoutInflater.from(this).inflate(R.layout.action_item, toolbar, false);
            ImageView imageView = menuItemView.findViewById(android.R.id.icon);
            final int itemIconResId = R.drawable.ic_launcher_background;
            imageView.setImageResource(itemIconResId);
            String text = "item" + i;
            ((TextView) menuItemView.findViewById(android.R.id.text1)).setText(text);
            final View.OnClickListener onClickListener = new View.OnClickListener() {
                @Override
                public void onClick(final View view) {
                    Toast.makeText(MainActivity.this, text ,
                            Toast.LENGTH_SHORT).show();
                }
            };
            menuItemView.setOnClickListener(onClickListener);
            menu.add(Menu.NONE, View.generateViewId(), Menu.NONE, text)
                    .setActionView(menuItemView)
                    .setIcon(itemIconResId)
                    .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
                        @SuppressLint("MissingPermission")
                        @Override
                        public boolean onMenuItemClick(final MenuItem menuItem) {
                            onClickListener.onClick(menuItemView);
                            return true;
                        }
                    });
        }
        // Now take the menu and left-justify it.
        notifyMenuItemsChanged(toolbar);
    }

    /**
     * Call this routine whenever the Toolbar menu changes. Take all action views and
     * left-justify those that fit on the screen. Force to overflow those that don't.
     *
     * @param toolbar The Toolbar that holds the menu.
     */
    private void notifyMenuItemsChanged(Toolbar toolbar) {
        final int OVERFLOW_CELL_WIDTH = 40; // dips
        final Menu menu = toolbar.getMenu();
        final float density = getResources().getDisplayMetrics().density;
        final int mOverflowCellSize = (int) (OVERFLOW_CELL_WIDTH * density);
        // Other than the overflow icon, this is how much real estate we have to fill.
        int widthLeftToFill = toolbar.getWidth() - mOverflowCellSize;
        // slackWidth is what is left over after we are done adding our action views.
        int slackWidth = -1;
        MenuItem menuItem;
        // Index of the spacer that will be removed/replaced.
        int spaceIndex = View.NO_ID;

        if (menu.size() == 0) {
            return;
        }

        // Examine each MenuItemView to determine if it will fit on the screen. If it can,
        // set its MenuItem to always show; otherwise, set the MenuItem to never show.
        for (int i = 0; i < menu.size(); i++) {
            menuItem = menu.getItem(i);
            View menuItemView = menuItem.getActionView();
            if (menuItemView instanceof Space) {
                spaceIndex = menuItem.getItemId();
                continue;
            }
            if (!menuItem.isVisible()) {
                continue;
            }
            // How wide is this ActionView?
            menuItemView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
            widthLeftToFill -= menuItemView.getMeasuredWidth();
            if (widthLeftToFill >= 0) {
                // The item will fit on the screen.
                menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
            } else {
                // The item will not fit. Force it to overflow.
                menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
                if (slackWidth < 0) {
                    // Just crossed over the limit of space to fill - capture the slack space.
                    slackWidth = widthLeftToFill + menuItemView.getMeasuredWidth();
                }
            }
        }
        if (spaceIndex != View.NO_ID) {
            // Assume that this is our spacer. It may need to change size, so eliminate it for now.
            menu.removeItem(spaceIndex);
        }
        if (slackWidth < 0) {
            // Didn't have enough action views to fill the width, so there is no overflow.
            slackWidth = widthLeftToFill + mOverflowCellSize;
        }
        if (slackWidth > 0) {
            // Create a space widget to consume the slack. This slack space widget makes sure
            // that the action views are left-justified with the overflow on the right.
            // As an alternative, this space could also be distributed among the action views.
            Space space = new Space(this);
            space.setMinimumWidth(slackWidth);
            // Need an if for the spacer so it can be deleted later if the menu is modified.
            // Need API 17+ for generateViewId().
            menuItem = menu.add(Menu.NONE, View.generateViewId(), Menu.NONE, "")
                    .setActionView(space);
            menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
        }
    }
}

activity_main.xml: Sample app

<LinearLayout 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:orientation="vertical"
    tools:context=".MainActivity">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Click the button to add/remove item #1 from the menu."/>

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Click to modify menu" />

</LinearLayout>



回答2:


So if I understand this correctly, you want to add some actions in Toolbar. These actions should start from left and take all the space that is available.

Are you open to using custom views (ImageView, etc) for actions instead of MenuItem?

Add a horizontal LinearLayout to your Toolbar. And set equal weight to all the children (actions).

<Toolbar>
    <LinearLayout horizontal>
        <ImageView layout_width="0dp" layout_weight="1" />
        <ImageView layout_width="0dp" layout_weight="1" />
        <ImageView layout_width="0dp" layout_weight="1" />
    </LinearLayout>
</Toolbar>

You can now attach menu to get vertical 3 dots action. Or you can add another ImageView at the end of the horizontal layout with fixed width.

EDIT:

Here's a solution that I quickly came up with. You will of course need to refine the code a bit. This solution uses a custom LinearLayout which measures each child and decides if overflow menu will be required or not. It will remeasure each child again to give equal space to all.

It uses PopupWindow to show menu and simple OnClickListener and callback to check which menu item was clicked.

FlexibleMenuContainer

public class FlexibleMenuContainer extends LinearLayout {

    private List<FlexibleMenu.MenuItem> items;

    private List<FlexibleMenu.MenuItem> drawableItems;
    private List<FlexibleMenu.MenuItem> overflowItems;

    private List<FlexibleMenu.MenuItem> overflowItemsTempContainer;

    private ImageView overflow;

    private int overflowViewSize;
    private boolean isOverflowing;

    public FlexibleMenuContainer(Context context) {
        this(context, null);
    }

    public FlexibleMenuContainer(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

    private void init(Context context, @Nullable AttributeSet attrs) {
        setOrientation(HORIZONTAL);
        items = new ArrayList<>();
        overflowItems = new ArrayList<>();
        drawableItems = new ArrayList<>();
        overflowItemsTempContainer = new ArrayList<>();

        overflowViewSize = getResources().getDimensionPixelOffset(R.dimen.menu_more_size);

        overflow = new ImageView(context);
        overflow.setImageResource(R.drawable.ic_more_vert_white_24dp);
        overflow.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                showOverflowMenu();
            }
        });
//      overflow.setVisibility(GONE);

        LinearLayout.LayoutParams params = new LayoutParams(overflowViewSize, overflowViewSize);
        params.gravity = Gravity.CENTER_VERTICAL;

        addView(overflow, params);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthRequired = 0;
        isOverflowing = false;
        overflowItems.clear();
        drawableItems.clear();

        if (items.size() == 0) {
            return;
        }

        int availableWidth = MeasureSpec.getSize(widthMeasureSpec) - overflowViewSize;

        for (int i=0; i<items.size(); i++) {
            View child = items.get(i).getView();
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            widthRequired += child.getMeasuredWidth();

            if (widthRequired > availableWidth) {
                isOverflowing = true;
                overflowItems.add(items.get(i));
            } else {
                drawableItems.add(items.get(i));
            }
        }

        int drawableWidth = MeasureSpec.getSize(widthMeasureSpec) - (isOverflowing ? overflowViewSize : 0);
        int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(drawableWidth/drawableItems.size(), MeasureSpec.EXACTLY);

        for (int i=0; i<drawableItems.size(); i++) {
            View child = drawableItems.get(i).getView();
            child.measure(childWidthMeasureSpec, heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int left = 0;
        for (int i=0; i<drawableItems.size(); i++) {
            View child = drawableItems.get(i).getView();
            int height = Math.min(child.getMeasuredHeight(), b - t);
            int top = (b - t - height)/2;
            child.layout(left, top, left + child.getMeasuredWidth(), top + height);
            left += child.getMeasuredWidth();
        }

        if (isOverflowing) {
            overflow.layout(getMeasuredWidth() - overflowViewSize, t, getMeasuredWidth(), b);
        }

        // After opening the menu and dismissing it, the views are still laid out
        for (int i=0; i<overflowItems.size(); i++) {
            View child = overflowItems.get(i).getView();
            if (child.getParent() == this) {
                child.layout(0, 0, 0, 0);
            }
        }
    }

    public void addItem(FlexibleMenu.MenuItem item) {
        items.add(item);
        _addView(item.getView());
    }

    private void _addView(View view) {
        LinearLayout.LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        params.gravity = Gravity.CENTER_VERTICAL;
        addView(view, getChildCount() - 1, params);
    }

    private void showOverflowMenu() {
        if (overflowItems.size() == 0) {
            return;
        }

        final ViewGroup contentView = prepareContentViewForPopup();
        final PopupWindow popup = new PopupWindow(contentView, 400, 300, true);
        popup.setOutsideTouchable(false);
        popup.setFocusable(true);
        popup.showAsDropDown(overflow);

        popup.setOnDismissListener(new PopupWindow.OnDismissListener() {
            @Override
            public void onDismiss() {
                contentView.removeAllViews();
                for (int i=0; i<overflowItemsTempContainer.size(); i++) {
                    View view = overflowItemsTempContainer.get(i).getView();
                    _addView(view);
                }

                overflowItemsTempContainer.clear();
            }
        });
    }

    private ViewGroup prepareContentViewForPopup() {
        overflowItemsTempContainer.clear();
        LinearLayout layout = new LinearLayout(getContext());
        layout.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.colorAccent));
        layout.setOrientation(VERTICAL);
        for (int i=0; i<overflowItems.size(); i++) {
            overflowItemsTempContainer.add(overflowItems.get(i));
            View view = overflowItems.get(i).getView();
            removeView(view);
            layout.addView(view);
        }

        return layout;
    }

}

FlexibleMenu

public class FlexibleMenu {

    private final List<MenuItem> items;
    private final MenuCallback callback;

    public FlexibleMenu(List<MenuItem> items, MenuCallback callback) {
        this.items = items;
        this.callback = callback;
    }

    public void inflate(FlexibleMenuContainer container) {
        for (int i=0; i<items.size(); i++) {
            final MenuItem item = items.get(i);
            container.addItem(item);
            item.getView().setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    callback.onItemClicked(item);
                }
            });
        }
    }

    public interface MenuCallback {
        void onItemClicked(MenuItem item);
    }

    public static class MenuItem {

        private final int id;
        private final View view;

        public MenuItem(int id, View view) {
            this.id = id;
            this.view = view;
        }

        public View getView() {
            return view;
        }

        public int getId() {
            return id;
        }
    }
}

layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.fenchtose.flexiblemenu.MainActivity">

    <android.support.v7.widget.Toolbar
        android:layout_width="match_parent"
        android:layout_height="56dp"
        android:paddingStart="0dp"
        android:background="@color/colorPrimary">

        <com.fenchtose.flexiblemenu.FlexibleMenuContainer
            android:id="@+id/menu_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

    </android.support.v7.widget.Toolbar>

    <android.support.v7.widget.Toolbar
        android:layout_width="match_parent"
        android:layout_height="56dp"
        android:paddingStart="0dp"
        android:background="@color/colorPrimary">

        <com.fenchtose.flexiblemenu.FlexibleMenuContainer
            android:id="@+id/menu_container1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

    </android.support.v7.widget.Toolbar>

    <android.support.v7.widget.Toolbar
        android:layout_width="match_parent"
        android:layout_height="56dp"
        android:paddingStart="0dp"
        android:background="@color/colorPrimary">

        <com.fenchtose.flexiblemenu.FlexibleMenuContainer
            android:id="@+id/menu_container2"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

    </android.support.v7.widget.Toolbar>

</LinearLayout>

MainActivity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setupMenu(R.id.menu_container, 6);
        setupMenu(R.id.menu_container1, 2);
        setupMenu(R.id.menu_container2, 4);
    }

    private void setupMenu(int id, int size) {
        FlexibleMenuContainer container = (FlexibleMenuContainer) findViewById(id);
        FlexibleMenu menu = new FlexibleMenu(populate(size), new FlexibleMenu.MenuCallback() {
            @Override
            public void onItemClicked(FlexibleMenu.MenuItem item) {
                Toast.makeText(MainActivity.this, "menu selected: " + item.getId(), Toast.LENGTH_SHORT).show();
            }
        });
        menu.inflate(container);
    }

    private List<FlexibleMenu.MenuItem> populate(int size) {
        List<FlexibleMenu.MenuItem> items = new ArrayList<>();
        for (int i=0; i<size; i++) {
            View view = createView("Menu Item " + (i + 1));
            items.add(new FlexibleMenu.MenuItem(i, view));
        }

        return items;
    }

    private TextView createView(String text) {
        TextView view = new TextView(this);
        view.setText(text);
        view.setGravity(Gravity.CENTER);
        view.setTextColor(0xffffffff);
        return view;
    }
}


来源:https://stackoverflow.com/questions/45658107/how-to-put-all-action-items-on-the-left-taking-as-much-space-as-possible-and-y

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!