Android ActionBar menu with vertical (rotated) text elements based on Custom Action Provider

I am adding a menu to an action bar item. The menu will contain vertical text for each item. What the menu contains is not important. I just want to create my own view, which will appear when I click on the action bar item. Therefore, for the purposes of this question, you can imagine my view as a big black box.

enter image description here

The image on the right was taken with Gimp. This is what I am trying to do, not what I still did.

What i tried

To upgrade an old app with a Material Design theme, I looked through all the tutorials in the Android documentation to add an app bar . Since my vertical text menu does not correspond to general cases, I have to create my own action provider. However, the documentation does not provide a complete example for a custom action provider. The best I could find is this answer .

The best I could do (with a black view representing my future menu) is shown in the following image:

enter image description here

The star in the image above currently has an action provider. However, the custom view is cropped inside the action bar. How to make it float over everything? In addition, I do not want it to appear until I click on the action bar item. Currently, however, it just shows right away.

the code

MainActivity.java

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // setup toolbar Toolbar myToolbar = (Toolbar) findViewById(R.id.my_toolbar); setSupportActionBar(myToolbar); ... } public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_settings: // User chose the "Settings" item, show the app settings UI... return true; case R.id.action_favorite: // User chose the "Favorite" action, mark the current item // as a favorite... return true; default: // If we got here, the user action was not recognized. // Invoke the superclass to handle it. return super.onOptionsItemSelected(item); } } ... } 

activity_main.xml

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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" xmlns:app="http://schemas.android.com/apk/res-auto"> <android.support.v7.widget.Toolbar android:id="@+id/my_toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:elevation="4dp" android:theme="@style/ThemeOverlay.AppCompat.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/> ... 

menu.xml

 <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/action_favorite" android:icon="@drawable/ic_star_black_24dp" android:title="@string/menu_favorites" app:actionProviderClass="com.example.chimee.MyActionProvider" app:showAsAction="ifRoom"/> <item android:id="@+id/action_settings" android:title="@string/menu_item_settings" app:showAsAction="never"/> </menu> 

MyActionProvider.java

 import android.content.Context; import android.support.v4.view.ActionProvider; import android.util.Log; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; public class MyActionProvider extends ActionProvider { private Context mContext; public MyActionProvider(Context context) { super(context); mContext = context; } // for versions older than api 16 @Override public View onCreateActionView() { // Inflate the action provider to be shown on the action bar. LayoutInflater layoutInflater = LayoutInflater.from(mContext); View providerView = layoutInflater.inflate(R.layout.my_action_provider, null); View myView = (View) providerView.findViewById(R.id.blackView); myView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.d("myTag", "black view was clicked"); } }); return providerView; } @Override public View onCreateActionView(MenuItem forItem) { // TODO: don't just repeat all this code here from above. // Inflate the action provider to be shown on the action bar. LayoutInflater layoutInflater = LayoutInflater.from(mContext); View providerView = layoutInflater.inflate(R.layout.my_action_provider, null); View myView = (View) providerView.findViewById(R.id.blackView); myView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.d("myTag", "black view was clicked"); } }); return providerView; } } 

my_action_provider.xml

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" style="?attr/actionButtonStyle" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="center" android:background="?attr/actionBarItemBackground" android:focusable="true" > <View android:id="@+id/blackView" android:layout_width="200dp" android:layout_height="150dp" android:background="#000000" /> </LinearLayout> 

I would be happy to see an example of any fully operational custom action provider that displays a view outside the scope of the action bar.

+7
android android-actionbar android-menu vertical-text
source share
1 answer

If you did not find a solution based on a custom action, perhaps you want to use the Custom Toolbar and PopupWindow , which means:

1) create a custom Toolbar using ImageButton as a menu button and replace the ActionBar with it (for example, which was published by Machado );

2) create a PopupWindow with a custom arrangement of menu items with vertical text;

3) add onClickListener to ImageButton from p.1, which show PopupWindow from p.2.

The layout of the custom Toolbar ( action_bar.xml ) might look something like this:

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:layout_gravity="fill_horizontal" android:orientation="vertical"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:background="@color/colorPrimary" android:elevation="4dp" android:layout_height="?attr/actionBarSize"> </android.support.v7.widget.Toolbar> </RelativeLayout> 

The MainActivity layout ( activity_main.xml ) that uses it:

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout android:id="@+id/activity_main" 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:padding="0dp" tools:context="<your_package_name>.MainActivity"> <include android:id="@+id/tool_bar" layout="@layout/action_bar"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" android:layout_marginStart="31dp" android:layout_below="@+id/tool_bar" android:layout_alignParentStart="true" android:layout_marginTop="31dp"/> </RelativeLayout> 

ImageButton as the "main popup menu" button described in the main_menu.xml file main_menu.xml this way (more details in this ASH article):

 <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/menu_button" android:icon="@drawable/ic_more_vert" android:title="" app:showAsAction="always" app:actionViewClass="android.widget.ImageButton"/> </menu> 

For vertical text of menu items, you can use, for example, a custom View as VerticalLabelView from this kostmo :

 public class VerticalLabelView extends View { private TextPaint mTextPaint; private String mText; private int mAscent; private Rect text_bounds = new Rect(); final static int DEFAULT_TEXT_SIZE = 15; public VerticalLabelView(Context context) { super(context); initLabelView(); } public VerticalLabelView(Context context, AttributeSet attrs) { super(context, attrs); initLabelView(); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.VerticalLabelView); CharSequence s = a.getString(R.styleable.VerticalLabelView_text); if (s != null) setText(s.toString()); setTextColor(a.getColor(R.styleable.VerticalLabelView_textColor, 0xFF000000)); int textSize = a.getDimensionPixelOffset(R.styleable.VerticalLabelView_textSize, 0); if (textSize > 0) setTextSize(textSize); a.recycle(); } private final void initLabelView() { mTextPaint = new TextPaint(); mTextPaint.setAntiAlias(true); mTextPaint.setTextSize(DEFAULT_TEXT_SIZE); mTextPaint.setColor(0xFF000000); mTextPaint.setTextAlign(Align.CENTER); setPadding(3, 3, 3, 3); } public void setText(String text) { mText = text; requestLayout(); invalidate(); } public void setTextSize(int size) { mTextPaint.setTextSize(size); requestLayout(); invalidate(); } public void setTextColor(int color) { mTextPaint.setColor(color); invalidate(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mTextPaint.getTextBounds(mText, 0, mText.length(), text_bounds); setMeasuredDimension( measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); } private int measureWidth(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { // We were told how big to be result = specSize; } else { // Measure the text result = text_bounds.height() + getPaddingLeft() + getPaddingRight(); if (specMode == MeasureSpec.AT_MOST) { // Respect AT_MOST value if that was what is called for by measureSpec result = Math.min(result, specSize); } } return result; } private int measureHeight(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); mAscent = (int) mTextPaint.ascent(); if (specMode == MeasureSpec.EXACTLY) { // We were told how big to be result = specSize; } else { // Measure the text result = text_bounds.width() + getPaddingTop() + getPaddingBottom(); if (specMode == MeasureSpec.AT_MOST) { // Respect AT_MOST value if that was what is called for by measureSpec result = Math.min(result, specSize); } } return result; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); float text_horizontally_centered_origin_x = getPaddingLeft() + text_bounds.width()/2f; float text_horizontally_centered_origin_y = getPaddingTop() - mAscent; canvas.translate(text_horizontally_centered_origin_y, text_horizontally_centered_origin_x); canvas.rotate(-90); canvas.drawText(mText, 0, 0, mTextPaint); } } 

(NB: maybe you need to set up paddings VerticalLabelView : on line result = text_bounds.height() + getPaddingLeft() + getPaddingRight() + 16; add "+16" for better filling)

and attrs.xml for the VerticalLabelView class:

 <resources> <declare-styleable name="VerticalLabelView"> <attr name="text" format="string" /> <attr name="textColor" format="color" /> <attr name="textSize" format="dimension" /> </declare-styleable> </resources> 

The layout for PopupWindow ( menu_layout.xml ) in this case may be as follows:

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/menu_root" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="@dimen/activity_horizontal_margin"> <<your_package_name>.VerticalLabelView android:id="@+id/menu_item1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@android:color/white" android:textSize="18sp" android:layout_margin="16dp" android:padding="4dp" android:text="Vertical menu item 1"/> <<your_package_name>.VerticalLabelView android:id="@+id/menu_item2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@android:color/white" android:textSize="18sp" android:layout_margin="16dp" android:padding="4dp" android:text="Vertical menu item 2"/> <<your_package_name>.VerticalLabelView android:id="@+id/menu_item3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@android:color/white" android:textSize="18sp" android:layout_margin="16dp" android:padding="4dp" android:text="Vertical menu item 3"/> <<your_package_name>.VerticalLabelView android:id="@+id/menu_item4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@android:color/white" android:textSize="18sp" android:layout_margin="16dp" android:padding="4dp" android:text="Vertical menu item 4"/> </LinearLayout> 

And the MainActivity class can be as follows:

 public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); private Toolbar mToolbar; private int mToolbarTitleColor; private ImageButton mMainMenuButton; private int mActionBarSize; private PopupWindow mPopupMenu; private int mTextSize = 48; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TypedValue tv = new TypedValue(); if (getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) { mActionBarSize = TypedValue.complexToDimensionPixelSize(tv.data,getResources().getDisplayMetrics()); } mToolbarTitleColor = Color.WHITE; mToolbar = (Toolbar) findViewById(R.id.toolbar); mToolbar.setTitleTextColor(mToolbarTitleColor); setSupportActionBar(mToolbar); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); Drawable menuIcon = ContextCompat.getDrawable(this, R.drawable.ic_more_vert); menuIcon.setColorFilter(mToolbarTitleColor, PorterDuff.Mode.SRC_ATOP); getMenuInflater().inflate(R.menu.main_menu, menu); mMainMenuButton = (ImageButton) menu.findItem(R.id.menu_button).getActionView(); mMainMenuButton.setBackground(null); mMainMenuButton.setImageDrawable(menuIcon); mMainMenuButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mPopupMenu != null && mPopupMenu.isShowing()) { mPopupMenu.dismiss(); } mPopupMenu = createPopupMenu(); mPopupMenu.showAtLocation(v, Gravity.TOP | Gravity.RIGHT, 0, mActionBarSize); } }); return true; } public PopupWindow createPopupMenu() { final PopupWindow popupWindow = new PopupWindow(this); LayoutInflater inflater = getLayoutInflater(); View popupView = inflater.inflate(R.layout.menu_layout, null); VerticalLabelView menuItem1 = (VerticalLabelView)popupView.findViewById(R.id.menu_item1); menuItem1.setOnClickListener(mOnMenuItemClickListener); menuItem1.setText("Vertical menu item 1"); menuItem1.setTextColor(Color.WHITE); menuItem1.setTextSize(mTextSize); VerticalLabelView menuItem2 = (VerticalLabelView)popupView.findViewById(R.id.menu_item2); menuItem2.setOnClickListener(mOnMenuItemClickListener); menuItem2.setText("Vertical menu item 2"); menuItem2.setTextColor(Color.WHITE); menuItem2.setTextSize(mTextSize); VerticalLabelView menuItem3 = (VerticalLabelView)popupView.findViewById(R.id.menu_item3); menuItem3.setOnClickListener(mOnMenuItemClickListener); menuItem3.setText("Vertical menu item 3"); menuItem3.setTextColor(Color.WHITE); menuItem3.setTextSize(mTextSize); VerticalLabelView menuItem4 = (VerticalLabelView)popupView.findViewById(R.id.menu_item4); menuItem4.setOnClickListener(mOnMenuItemClickListener); menuItem4.setText("Vertical menu item 4"); menuItem4.setTextColor(Color.WHITE); menuItem4.setTextSize(mTextSize); popupWindow.setFocusable(true); popupWindow.setWidth(WindowManager.LayoutParams.WRAP_CONTENT); popupWindow.setHeight(WindowManager.LayoutParams.WRAP_CONTENT); popupWindow.setContentView(popupView); return popupWindow; } private View.OnClickListener mOnMenuItemClickListener = new View.OnClickListener() { @Override public void onClick(View view) { switch (view.getId()) { case R.id.menu_item1: { Log.d(TAG, "menu_item1"); } break; case R.id.menu_item2: { Log.d(TAG, "menu_item2"); } break; case R.id.menu_item3: { Log.d(TAG, "menu_item3"); } case R.id.menu_item4: { Log.d(TAG, "menu_item4"); } break; default: { } } if (mPopupMenu != null && mPopupMenu.isShowing()) { mPopupMenu.dismiss(); } } }; } 

Ultimately, you should end up with something like this:

Vertical menu items screenshot

PS Of course, you need a more elegant solution for createPopupMenu() .

+10
source share

All Articles