【问题标题】:How to put all action items on the left, taking as much space as possible, and yet have overflow on the right?如何将所有操作项放在左侧,占用尽可能多的空间,而右侧有溢出?
【发布时间】:2017-08-13 07:26:03
【问题描述】:

背景

假设我有一个工具栏和多个操作项。有些可能是自定义的(例如:带有图像的 TextView)。

我需要做的是将它们全部对齐到左侧,而不是右侧,但右侧仍然有溢出项。

我还尝试为操作项留出尽可能多的空间。

问题

我发现的都不起作用

我尝试过的

1.关于对齐,我在 StackOverflow 上找到了一些解决方案,在 Toolbar 中添加视图,但是由于某种原因这不会很好,因为按下一个项目不会显示对整个项目的影响(好像它的高度更小)。

我为此尝试过的其他事情:

  • android:layoutDirection="ltr" - 不对操作项做任何事情
  • android:gravity="left|start" - 一样

2.对于空间问题,我尝试过的都不起作用。我试图删除所有可能添加边距或填充的东西。

这是一个示例代码,展示了我如何测试这两个问题:

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>

这是我得到的:

问题

如何支持 Toolbar 的最大空间使用,同时让操作项向左对齐?


编辑:经过一番工作,我得到了部分工作的对齐解决方案:

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>

在代码中,唯一的区别是我使用的是ActionMenuView的菜单,而不是Toolbar:

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

它确实把溢出项放在最右边,而操作项放在左边。

但是,按下的效果不包括项目的整个高度,并且项目似乎比平时占用了更多空间。另外,我仍然没有弄清楚如何使用这里所有可能的空间:

编辑:

为了解决按压效果的问题,我只需要在循环中被充气的项目中添加 android:minHeight="?attr/actionBarSize" 即可。

关于按下效果的奇怪之处在于,如果我添加一个普通的操作项(只是文本/图标,没有膨胀),它会产生微小的涟漪效应,并且操作项本身与我相比占用了很多空间添加。

这导致的另一个新问题是,点击溢出菜单附近的任何地方都会触发点击它。

编辑:

此解决方案的另一个问题是,在某些情况下,项目之间存在空格,例如只有几个项目的情况:

所以,简而言之,这个解决方案根本不起作用。

【问题讨论】:

  • Android 材料设计指南说操作项应该在右侧。 material.io/guidelines/layout/structure.html#structure-app-bar
  • @just 这不是一个正常的操作栏。另外,设计应用程序的人不是我。我按照要求来。
  • 无论如何,我也许可以说服对齐,但是有什么办法可以克服空间问题?
  • 也许这可以帮助你:stackoverflow.com/questions/29807744/…
  • @just 这几乎很好用:它把项目放在左边,在我将 ActionMenuView 的宽度更改为“match_parent”后,它把溢出项目放在右边。但是,它对间距问题没有帮助。它还有一个紧迫的效果问题,即按下一个项目并没有显示整个项目的效果(好像它的高度更小)。稍后通过在膨胀布局中添加 minHeight 来修复。不过,我实际上对溢出菜单项持怀疑态度。我认为它根本不会出现,因为这看起来像一个黑客。更新问题

标签: android android-toolbar


【解决方案1】:

因此,如果我理解正确,您想在工具栏中添加一些操作。这些操作应从左侧开始,并占用所有可用空间。

您是否愿意使用自定义视图(ImageView 等)而不是 MenuItem 来执行操作?

向您的工具栏添加一个水平线性布局。并为所有孩子(动作)设置相同的权重。

<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>

您现在可以附加菜单以获得垂直的 3 点动作。或者您可以在水平布局的末尾添加另一个固定宽度的 ImageView。

编辑:

这是我很快想到的解决方案。您当然需要稍微改进代码。此解决方案使用自定义 LinearLayout 来测量每个孩子并决定是否需要溢出菜单。它将再次重新测量每个孩子,为所有人提供平等的空间。

它使用 PopupWindow 显示菜单和简单的 OnClickListener 和回调来检查单击了哪个菜单项。

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;
    }

}

灵活菜单

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;
        }
    }
}

布局

<?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;
    }
}

【讨论】:

  • 我已经为菜单项使用了自定义视图。但我不认为您的解决方案在没有更多空间的情况下有效,因此它们会出现在溢出菜单中。
  • 哇,感谢您的所有努力。可悲的是,我已经标记了答案,并给予了赏金。我能做的就是感谢你所做的所有工作,所以这就是我现在所做的。我希望尽快尝试此代码。它是否支持更新菜单项?添加、删除、隐藏/显示、更改它们?
  • 您在此解决方案中缺少一些资源:menu_more_size, ic_more_vert_white_24dp。另外,您应该更喜欢使用 getDimensionPixelSize 而不是 getDimensionPixelOffset 。 “setupMenu”有什么作用?
【解决方案2】:

这是一种解决方案,它可以使菜单项左对齐,同时将溢出菜单图标保持在右侧。此解决方案使用工具栏/操作栏的标准实现,但预计操作视图将如何布局,以便它们在工具栏中按我们希望的方式定位。

下面的大部分代码都是您提供的。我已将创建菜单项的 for 循环移动到 onCreateOptionsMenu() 中,这样我就可以利用已经是工具栏菜单结构一部分的 ActionMenuView 而不是添加另一个。

onCreateOptionsMenu() 中,随着菜单项被放入菜单中,菜单项消耗的空间的运行计数被维护。只要有空间,菜单项就会被标记为“已显示”(MenuItem.SHOW_AS_ACTION_ALWAYS)。如果该项目将侵占为溢出菜单图标保留的区域,则该项目被放置,但作为溢出菜单的目标 (MenuItem.SHOW_AS_ACTION_NEVER)。

在所有视图都放入菜单后,计算松弛空间。这是屏幕上最后一个可见菜单项和溢出图标之间的区域(如果使用溢出)或最后一个可见项和工具栏末尾之间的区域(如果未使用溢出)。

计算松弛空间后,将创建一个Space 小部件并将其放入菜单中。此小部件强制所有其他项目左对齐。

大部分更改已对MainActivity.java 进行,但我可能在 XML 文件中更改了一两件事。为了完整起见,我将它们包括在这里。

这里是一些结果的屏幕截图。

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>

更新:要使用工具栏而不将其设置为操作栏,请添加一个全局布局侦听器以等待工具栏设置完成。

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
        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
}

以下示例应用程序通过引入方法notifyMenuItemsChanged 将菜单创建与菜单左对齐分开。在应用程序中,单击按钮以删除位置 1 的菜单项。

这段代码与上面基本相同,但Space 小部件需要一个id,以便在菜单更改时可以将其删除以重新添加。

MainActivity.Java:示例应用

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:示例应用

<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>

【讨论】:

  • 这行得通,但这里有两个问题:当我尝试使用 mToolbar.getMenu().add(..) 而不是你所做的,它会将所有项目放在溢出菜单中?为什么它没有默认的间距,您必须在操作项布局中添加填充?
  • @androiddeveloper 我在onCreateOptionsMenu 的开头插入了menu=mToolbar.getMenu(),它的工作原理是一样的。你在哪里使用它?当我尝试在onCreate 中设置工具栏时确实遇到了问题,但是当我将菜单创建移至onCreateOptionsMenu() 时问题就消失了。我不相信事情的设置足以操纵onCreate 中的菜单。此外,SHOW_AS_ACTION_ALWAYS 似乎尽最大努力将项目放到屏幕上,而SHOW_AS_ACTION_IF_ROOM 有自己的想法。如果我让 Android 决定“是否有空间”,事情的布局就不一样了。
  • @androiddeveloper 我还要补充一点,我无法让主布局中定义的ActionMenuView 发挥作用,我放弃了这种方法,转而使用工具栏中固有的ActionMenuView。使用工具栏的版本避免了两个ActionMenuViews 可能(?)导致问题。
  • @androiddeveloper 我认为这可行。我将很快将notifyMenuItemsChanged() 代码添加到答案中,以了解它的价值。
  • @androiddeveloper 用一个小示例应用替换了最近添加的内容,以演示 notifyMenuItemsChanged()
猜你喜欢
  • 1970-01-01
  • 2022-01-22
  • 1970-01-01
  • 2021-06-15
  • 1970-01-01
  • 2018-04-23
  • 2020-06-11
  • 2011-08-04
  • 1970-01-01
相关资源
最近更新 更多