Меню Android ActionBar с вертикальными (повернутыми) текстовыми элементами на основе поставщика пользовательских действий

Я добавляю меню в элемент панели действий. Меню будет содержать вертикальный текст для каждого элемента. То, что содержит меню, не имеет значения. Я просто хочу создать собственное представление, которое появится, когда я нажимаю элемент панели действий. Поэтому для целей этого вопроса вы можете представить себе мой взгляд как большой черный ящик.

Введите описание изображения здесь

Изображение справа было сделано с Gimp. Это то, что я пытаюсь сделать, а не то, что я еще сделал.

Что я пробовал

Чтобы обновить старое приложение с темой Material Design, я прошел все уроки в документации по Android для добавления панели приложений . Поскольку мое вертикальное текстовое меню не соответствует общим случаям, я должен создать собственный поставщик действий. Однако документация не дает полного примера для поставщика пользовательских действий. Лучшее, что я мог найти, – это ответ «Переполнение стека» .

Лучшее, что я смог сделать (с черным представлением, представляющим мое будущее меню), показано на следующем изображении:

Введите описание изображения здесь

У звезды на изображении выше в настоящее время есть поставщик действия. Тем не менее, пользовательское представление обрезается внутри панели действий. Как заставить его всплывать над всем? Кроме того, я не хочу, чтобы он появлялся, пока я не нажму на элемент панели действий. В настоящее время, однако, это просто показывает сразу.

Код

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

Я был бы рад увидеть пример любого полностью функционирующего поставщика пользовательских действий, который отображает представление вне рамки панели действий.

Solutions Collecting From Web of "Меню Android ActionBar с вертикальными (повернутыми) текстовыми элементами на основе поставщика пользовательских действий"

Если вы не нашли решение на основе пользовательского действия на основе действия, возможно, вы хотите использовать обходное решение Custom Toolbar & PopupWindow, которое означает:

1) создать пользовательскую Toolbar с ImageButton качестве кнопки меню и заменить ActionBar на нее (как в этом сообщении Machado );

2) создать PopupWindow с настраиваемой компоновкой элементов меню с вертикальным текстом;

3) добавьте onClickListener в ImageButton из п. 1, которые показывают PopupWindow из п. 2.

Макет пользовательской Toolbar ( action_bar.xml ) может выглядеть примерно так:

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

Макет MainActivity ( activity_main.xml ), который его использует:

 <?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 как ImageButton «главного всплывающего меню», описанная в файле main_menu.xml таким образом (подробнее в этом сообщении ASH ):

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

Для вертикального текста элементов меню Вы можете использовать, например, пользовательский View такой как VerticalLabelView из этого кода :

 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: может быть, вам нужно настроить paddings VerticalLabelView : on line result = text_bounds.height() + getPaddingLeft() + getPaddingRight() + 16; добавить «+16» для лучшего заполнения)

И attrs.xml для класса VerticalLabelView :

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

Макет для PopupWindow ( menu_layout.xml ) в этом случае может выглядеть так:

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

Класс MainActivity может быть следующим:

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

Ультимативно, в результате вы должны получить что-то вроде этого:

Вертикальные пункты меню

PS Конечно, вам нужно более элегантное решение для createPopupMenu() .