草庐IT

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

coder 2023-11-24 原文

我正在向操作栏项目添加菜单。菜单将包含每个项目的垂直文本。菜单包含什么并不重要。我基本上只想创建自己的 View ,当我按下操作栏项目时会弹出该 View 。所以为了这个问题的目的,你可以把我的观点想象成一个大黑盒子。

右边的图像是用 Gimp 制作的。这是我正在尝试做的,而不是我已经完成的。

我尝试过的

为了使用 Material Design 主题更新旧应用,我一直在浏览所有 lessons in the Android documentation for adding the app bar .由于我的垂直文本菜单不适合常见情况,因此我必须制作一个自定义 Action Provider。 documentation但是,没有提供自定义操作提供程序的完整示例。我能找到的最好的是 this Stack Overflow answer .

下图显示了我能做的最好的(黑色 View 代表我 future 的菜单):

上图中的星星目前有 Action 提供者。但是,自定义 View 在操作栏中被剪掉了。我如何让它漂浮在一切之上?另外,在单击操作栏项目之前,我不希望它出现。不过目前,它只是立即显示。

代码

主 Activity .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"/>

...

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

我很高兴看到任何功能齐全的自定义操作提供程序在操作栏框架之外显示 View 的示例。

最佳答案

如果您没有找到基于自定义操作提供程序的解决方案,可能是您想要使用自定义 Toolbar & PopupWindow -based 解决方法,这意味着:

1) 使用 ImageButton 作为菜单按钮创建自定义 Toolbar 并用它替换 ActionBar(如 that 中的 Machado 帖子) );

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

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

自定义工具栏(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 作为“主弹出菜单”按钮在 main_menu.xml 文件中以这种方式描述(更多内容在 this 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,如来自thisVerticalLabelViewkostmo :

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”以获得更好的填充)

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

最终,作为结果,您应该收到类似的东西:

附言当然,对于 createPopupMenu(),您需要更优雅的解决方案。

关于带有基于自定义操作提供程序的垂直(旋转)文本项的 Android ActionBar 菜单,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39304883/

有关带有基于自定义操作提供程序的垂直(旋转)文本项的 Android ActionBar 菜单的更多相关文章

  1. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  2. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  3. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  4. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  5. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  6. ruby - 定义方法参数的条件 - 2

    我有一个只接受一个参数的方法:defmy_method(number)end如果使用number调用方法,我该如何引发错误??通常,我如何定义方法参数的条件?比如我想在调用的时候报错:my_method(1) 最佳答案 您可以添加guard在函数的开头,如果参数无效则引发异常。例如:defmy_method(number)failArgumentError,"Inputshouldbegreaterthanorequalto2"ifnumbereputse.messageend#=>Inputshouldbegreaterthano

  7. ruby - 如何在 Grape 中定义哈希数组? - 2

    我使用Ember作为我的前端和GrapeAPI来为我的API提供服务。前端发送类似:{"service"=>{"name"=>"Name","duration"=>"30","user"=>nil,"organization"=>"org","category"=>nil,"description"=>"description","disabled"=>true,"color"=>nil,"availabilities"=>[{"day"=>"Saturday","enabled"=>false,"timeSlots"=>[{"startAt"=>"09:00AM","endAt"=>

  8. ruby - 获取模块中定义的所有常量的值 - 2

    我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c

  9. ruby - 这两个 Ruby 类初始化定义有什么区别? - 2

    我正在阅读一本关于Ruby的书,作者在编写类初始化定义时使用的形式与他在本书前几节中使用的形式略有不同。它看起来像这样:classTicketattr_accessor:venue,:datedefinitialize(venue,date)self.venue=venueself.date=dateendend在本书的前几节中,它的定义如下:classTicketattr_accessor:venue,:datedefinitialize(venue,date)@venue=venue@date=dateendend在第一个示例中使用setter方法与在第二个示例中使用实例变量之间是

  10. ruby-on-rails - 如何生成传递一些自定义参数的 `link_to` URL? - 2

    我正在使用RubyonRails3.0.9,我想生成一个传递一些自定义参数的link_toURL。也就是说,有一个articles_path(www.my_web_site_name.com/articles)我想生成如下内容:link_to'Samplelinktitle',...#HereIshouldimplementthecode#=>'http://www.my_web_site_name.com/articles?param1=value1¶m2=value2&...我如何编写link_to语句“alàRubyonRailsWay”以实现该目的?如果我想通过传递一些

随机推荐