基于自定义操作提供程序的垂直(旋转)文本项的Android ActionBar菜单

我正在添加一个菜单到一个操作栏项目。 菜单将包含每个项目的垂直文本。 菜单包含的内容并不重要。 我基本上只是想创build自己的视图,当我按下一个操作栏项目时会popup。 所以就这个问题而言,你可以把我的观点想象成一个大黑箱。

在这里输入图像说明

右边的图片是用GIMP制作的。 这是我正在做的,而不是我已经完成的。

我曾经尝试过

为了使用材质devise主题更新旧的应用程序,我一直在浏览Android文档中的所有课程,以添加应用程序栏 。 由于我的垂直文本菜单不适合常见的情况下,我必须做一个自定义的操作提供程序。 尽pipe如此,该文档没有提供自定义操作提供程序的完整示例。 我能find的最好的是这个堆栈溢出的答案 。

下图显示了我已经能够做的最好的事情(用一个黑色的视图代表我的未来菜单):

在这里输入图像说明

上图中的明星目前有行动提供者。 但是,自定义视图会在操作栏中被剪掉。 我如何让它漂浮在一切? 此外,我不希望它出现,直到我点击的行动栏项目。 目前,它只是马上显示。

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菜单"

如果您没有find基于自定义操作提供程序的解决scheme,可能是您要使用自定义工具栏和基于PopupWindow的解决方法,这意味着:

1)用ImageButton创build自定义的Toolbar作为菜单button,并用它replaceActionBar (就像在Machado的post中);

2)为垂直文本的菜单项创build带自定义布局的PopupWindow ;

3)添加onClickListener到p.2中的ImageButton ,它们显示p.2中的PopupWindow

自定义Toolbaraction_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> 

使用它的MainActivityactivity_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作为main_menu.xml文件中描述的“主popup式菜单”button这种方式(更多在ASH的 这个post中):

 <?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如来自kostmo的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); } } 

(注意:可能是你需要自定义填充VerticalLabelView :在result = text_bounds.height() + getPaddingLeft() + getPaddingRight() + 16;添加“+16”为更好的填充)

VerticalLabelView类的attrs.xml

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

这种情况下PopupWindowmenu_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()更优雅的解决scheme。