Android align menu items to left in action bar

前端 未结 2 1142
暗喜
暗喜 2021-02-07 10:30

i have action bar in my application that displays menu items defined in my res/menu/activity_main.xml

My menu items are aligned to right on action bar. I wa

2条回答
  •  不思量自难忘°
    2021-02-07 11:02

    Well, i was curious about this, so i dug deep inside the SDK's source. I used AppCompatActivity with 3 menu item in it's XML file, and i used the default onCreateOptionMenu method, which was this:

     @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            // Inflate the menu; this adds items to the action bar if it is present.
            getMenuInflater().inflate(R.menu.menu_main, menu);
            return true;
        }
    

    After i move on from the inflate method with the debugger, i went through the following stack:

    updateMenuView():96, BaseMenuPresenter (android.support.v7.internal.view.menu)
    updateMenuView():231, ActionMenuPresenter (android.support.v7.widget)
    dispatchPresenterUpdate():284, MenuBuilder (android.support.v7.internal.view.menu)
    onItemsChanged():1030, MenuBuilder (android.support.v7.internal.view.menu)
    startDispatchingItemsChanged():1053, MenuBuilder (android.support.v7.internal.view.menu)
    preparePanel():1303, AppCompatDelegateImplV7 (android.support.v7.app)
    doInvalidatePanelMenu():1541, AppCompatDelegateImplV7 (android.support.v7.app)
    access$100():92, AppCompatDelegateImplV7 (android.support.v7.app)
    run():130, AppCompatDelegateImplV7$1 (android.support.v7.app)
    handleCallback():739, Handler (android.os)
    dispatchMessage():95, Handler (android.os)
    loop():148, Looper (android.os)
    main():5417, ActivityThread (android.app)
    invoke():-1, Method (java.lang.reflect)
    run():726, ZygoteInit$MethodAndArgsCaller (com.android.internal.os)
    main():616, ZygoteInit (com.android.internal.os)
    

    It ended in BaseMenuPresenter's updateMenuView method, this is where the revelant work is done.

    the method's code:

       public void updateMenuView(boolean cleared) {
            final ViewGroup parent = (ViewGroup) mMenuView;
            if (parent == null) return;
    
            int childIndex = 0;
            if (mMenu != null) {
                mMenu.flagActionItems();
                ArrayList visibleItems = mMenu.getVisibleItems();
                final int itemCount = visibleItems.size();
                for (int i = 0; i < itemCount; i++) {
                    MenuItemImpl item = visibleItems.get(i);
                    if (shouldIncludeItem(childIndex, item)) {
                        final View convertView = parent.getChildAt(childIndex);
                        final MenuItemImpl oldItem = convertView instanceof MenuView.ItemView ?
                                ((MenuView.ItemView) convertView).getItemData() : null;
                        final View itemView = getItemView(item, convertView, parent);
                        if (item != oldItem) {
                            // Don't let old states linger with new data.
                            itemView.setPressed(false);
                            ViewCompat.jumpDrawablesToCurrentState(itemView);
                        }
                        if (itemView != convertView) {
                            addItemView(itemView, childIndex);
                        }
                        childIndex++;
                    }
                }
            }
    
            // Remove leftover views.
            while (childIndex < parent.getChildCount()) {
                if (!filterLeftoverView(parent, childIndex)) {
                    childIndex++;
                }
            }
        }
    

    Here the getItemView and the addItemView methods do what their's name say. The first inflate a new view, and the second add it to parent. What is more important, under the debugger the parent object can be checked, it's an ActionMenuView, which inherits from the LinearLayout and inflated form abc_action_menu_layout.xml.

    This means if you can get this view, you can do what you want. Theoretically, i think it can be done with lots of reflection, but it would painful. Instead of that, you can reproduce it in your code. Implementations can be found here.

    According to the things above, the answer for your question is YES, it can be done, but it will be tricky.

    Edit:

    I created a proof of concept for doing this with reflection. I've used com.android.support:appcompat-v7:23.1.0.

    I've tried this on an emulator(Android 6.0) and on my Zuk Z1(CM Android 5.1.1), on both it works fine.

    Menu XML:

    
    
        
        
        
    
    

    Activty XML:

    
    
        

    Activity:

    public class Main2Activity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //only a linear layout with one button
            setContentView(R.layout.activity_main2);
    
            Button b = (Button) findViewById(R.id.button);
    
            // do the whole process for a click, everything is inited so we dont run into NPE
            b.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
    
                    AppCompatDelegate delegate = getDelegate();
    
                    Class delegateImpClass = null;
                    Field menu = null;
                    Method[] methods = null;
    
                    try {
    
                        //get objects based on the stack trace
                        delegateImpClass = Class.forName("android.support.v7.app.AppCompatDelegateImplV7");
    
                        //get delegate->mPreparedPanel
                        Field mPreparedPanelField = delegateImpClass.getDeclaredField("mPreparedPanel");
                        mPreparedPanelField.setAccessible(true);
                        Object mPreparedPanelObject = mPreparedPanelField.get(delegate);
    
                        //get delegate->mPreparedPanel->menu
                        Class PanelFeatureStateClass = Class.forName("android.support.v7.app.AppCompatDelegateImplV7$PanelFeatureState");
                        Field menuField = PanelFeatureStateClass.getDeclaredField("menu");
                        menuField.setAccessible(true);
                        Object menuObjectRaw = menuField.get(mPreparedPanelObject);
                        MenuBuilder menuObject = (MenuBuilder) menuObjectRaw;
    
                        //get delegate->mPreparedPanel->menu->mPresenter(0)
                        Field mPresentersField = menuObject.getClass().getDeclaredField("mPresenters");
                        mPresentersField.setAccessible(true);
                        CopyOnWriteArrayList> mPresenters = (CopyOnWriteArrayList>) mPresentersField.get(menuObject);
                        ActionMenuPresenter presenter0 = (ActionMenuPresenter) mPresenters.get(0).get();
    
                        //get the view from the presenter
                        Field mMenuViewField = presenter0.getClass().getSuperclass().getDeclaredField("mMenuView");
                        mMenuViewField.setAccessible(true);
                        MenuView menuView = (MenuView) mMenuViewField.get(presenter0);
                        ViewGroup menuViewParentObject = (ViewGroup) ((View) menuView);
    
                        //check the menu items count
                        int a = menuViewParentObject.getChildCount();
                        Log.i("ChildNum", a + "");
    
    
    
    
                        //set params as you want
                        Toolbar.LayoutParams params = (Toolbar.LayoutParams) menuViewParentObject.getLayoutParams();
    
                        params.gravity = Gravity.LEFT;
    
                        menuViewParentObject.setLayoutParams(params);
    
    
    
    
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    } catch (NoSuchFieldException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            // Inflate the menu; this adds items to the action bar if it is present.
            getMenuInflater().inflate(R.menu.menu_main, menu);
            return true;
        }
    }
    

    Although the gravity has been changed here, on the screen this does not make any revelant difference. To get a real visible change other layout params(e.g. width ) should be tuned.

    All in all, a custom layout is much easier to use.

提交回复
热议问题