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