使用 ActionMode 时,Lollipop 状态栏变黑

Posted

技术标签:

【中文标题】使用 ActionMode 时,Lollipop 状态栏变黑【英文标题】:When using ActionMode, the status bar turns black on Lollipop 【发布时间】:2015-07-22 16:34:32 【问题描述】:

我有一个状态栏,上面设置了以下主题:

<!-- Base Theme for all "Material"-esque styles. We use NoActionBar
     so we can use the Toolbar at runtime.
-->
<style name="Material" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:windowDrawsSystemBarBackgrounds">true</item>
    <item name="android:windowTranslucentStatus">true</item>
    ...
</style>

对于我的大部分活动,我还有一个DrawerLayout,它可以根据我的喜好设置状态栏的颜色:

    mDrawerLayout.setStatusBarBackgroundColor(getResources().getColor(R.color.myapp_green));

我使用的是Toolbar,而不是默认的ActionBar,因此它存在于我的布局中(即,导航抽屉在其顶部绘制)。

一切正常,除了在我的一项活动中,我有一个带有ActionMode 的多选模式。当这个ActionMode 被激活时(使用长按),它会覆盖Toolbar 使用:

<item name="android:windowActionModeOverlay">true</item>
<item name="windowActionModeOverlay">true</item>
<item name="actionModeStyle">@style/Material.Widget.ActionMode</item>

Material.Widget.ActionMode 样式是:

<style name="Material.Widget.ActionMode" parent="@style/Widget.AppCompat.ActionMode">
    <item name="android:background">@color/myapp_green</item>
    <item name="background">@color/myapp_green</item>
</style>

现在的问题是,无论何时发生这种情况,状态栏都会从myapp_green 颜色变为黑色。这几乎就像状态栏半透明被关闭(我使用的是 Android 5.0)。我想知道如何才能让这种行为不发生,并保持状态栏颜色/半透明原样。

我尝试在动作模式的样式中添加&lt;item name="android:windowTranslucentStatus"&gt;true&lt;/item&gt;,以及在ActionMode 的样式中添加&lt;item name="android:statusBarColor"&gt;@color/myapp_green&lt;/item&gt;,均未成功。

更新:

我想知道这是否与我设置状态栏背景的古怪方式有关。我所有的 Activity 类都派生自 NavigationDrawerActivity.java:

/**
 * An @link Activity that supports a Navigation Drawer, which is a pull-out panel for navigation
 * menus. This drawer is pulled out from the left side of the screen (right side on RTL devices).
 */
public class NavigationDrawerActivity extends ActionBarActivity
  implements AdapterView.OnItemClickListener 

  private static final String LOGTAG = NavigationDrawerActivity.class.getSimpleName();

  private DrawerLayout mDrawerLayout;
  private ListView mDrawerList;
  private LayoutInflater mInflater;
  private NavigationDrawerItemAdapter mAdapter;
  private ActionBarDrawerToggle mDrawerToggle;

  private NavigationDrawerItem[] mNavigationDrawerItems;

  private Toolbar mAppBar;

  @Override
  protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
// We have to call super.setContentView() here because BaseActivity redefines setContentView(),
// and we don't want to use that.
super.setContentView(R.layout.navigation_drawer);

mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    setupNavigationDrawer();
  

  @Override
  protected void onPostCreate(Bundle savedInstanceState) 
super.onPostCreate(savedInstanceState);

    // Sync the toggle state after onRestoreInstanceState has occurred.
    mDrawerToggle.syncState();
  

  @Override
  public void onConfigurationChanged(Configuration newConfig) 
    super.onConfigurationChanged(newConfig);
    mDrawerToggle.onConfigurationChanged(newConfig);
  

  @Override
  public boolean onCreateOptionsMenu(Menu menu) 
    return true;
  

  @Override
  public boolean onOptionsItemSelected(MenuItem item) 
    int id = item.getItemId();

    switch(id) 
      case android.R.id.home:
        return mDrawerToggle.onOptionsItemSelected(item);
    

    return super.onOptionsItemSelected(item);
  

  /**
   * Toggles the state of the navigation drawer (i.e. closes it if it's open, and opens it if
   * it's closed).
   */
  public void toggleNavigationDrawer() 
    if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) 
      closeNavigationDrawer();
     else 
      openNavigationDrawer();
    
  

  /**
   * Opens the navigation drawer.
   */
  public void openNavigationDrawer() 
    mDrawerLayout.openDrawer(GravityCompat.START);
  

  /**
   * Closes the navigation drawer.
   */
  public void closeNavigationDrawer() 
    mDrawerLayout.closeDrawer(GravityCompat.START);
  

  /**
   * Initializes items specific to the navigation drawer.
   */
  private void setupNavigationDrawer() 
    mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerLayout.setStatusBarBackgroundColor(getResources().getColor(R.color.wiw_green));

        mAppBar = (Toolbar) findViewById(R.id.app_bar);
        setSupportActionBar(mAppBar);

        ActionBar actionBar = getSupportActionBar();
        actionBar.setDisplayHomeAsUpEnabled(true);
        actionBar.setHomeButtonEnabled(true);
        actionBar.setDisplayShowHomeEnabled(false);

        mDrawerToggle = new ActionBarDrawerToggle(
          this,                  /* Our context (Activity that hosts this drawer) */
          mDrawerLayout,         /* The DrawerLayout where the nav drawer will be drawn */
          R.string.drawer_open,  /* Description of "open drawer", for accessibility */
          R.string.drawer_close  /* Description of "close drawer", for accessibility */
        ) 

          /**
           * Called when a drawer has settled in a completely closed state.
           */
          public void onDrawerClosed(View view) 
            super.onDrawerClosed(view);
            supportInvalidateOptionsMenu();
          

          /**
           * Called when a drawer has settled in a completely open state.
           */
          public void onDrawerOpened(View drawerView) 
            super.onDrawerOpened(drawerView);
            supportInvalidateOptionsMenu();
          
        ;

        mDrawerList = (ListView) mDrawerLayout.findViewById(R.id.drawer_list);

        mNavigationDrawerItems = buildNavDrawerItemsList();

        setupAdapter(mNavigationDrawerItems);

        setupNavigationDrawerHeader();

        mDrawerLayout.setDrawerListener(mDrawerToggle);
        mDrawerList.setOnItemClickListener(this);
      

      @Override
      public void onItemClick(AdapterView<?> parent, View aView, int aPosition, long aId) 
        // Code not relevant
      

      /**
       * Set the inner content view of this @link NavigationDrawerActivity to have a given layout.
       *
       * @param aLayoutId The id of the layout to load into the inner content view of this activity.
       */
      public void setDrawerContent(int aLayoutId) 
        LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        ViewGroup root = (ViewGroup)findViewById(R.id.drawer_content);
        inflater.inflate(aLayoutId, root);
        
    

我实际上必须运行DrawerLayout.setStatusBarBackgroundColor() 才能生效。只是在values-v21/styles.xml 中更改colorPrimaryDark 对状态栏没有影响。我觉得这可能是问题的根源......这些类正在从非 Material 主题转换为新的、类似 Material 的主题,所以我想知道在转换到colorPrimaryDark 被正确识别。

【问题讨论】:

也许你的颜色@color/wiw_greenR.color.myapp_green不一样,是吗? 糟糕,抱歉。是的,这两种颜色是一样的。 另外,赏金到期后我会重新启动,因为我还没有真正解决这个问题。感谢到目前为止所有回复的人! 这是一个错误,code.google.com/p/android/issues/detail?id=183887 code.google.com/p/android/issues/detail?id=184047 相关***.com/questions/32318563/… 这个问题已经用com.android.support:design:28.0.0-rc01修复了。 【参考方案1】:

我在这里发布答案是因为,虽然其他解决方案很有帮助,但没有一个能准确回答这个问题。我发现是对DrawerLayout.setStatusBarBackgroundColor() 的调用造成了麻烦。我的解决方案如下:

    启用windowTranslucentStatuswindowDrawsSystemBarBackgrounds 在主题中。

    在我的NavigationDrawerActivity 中删除对DrawerLayout.setStatusBarBackgroundColor() 的调用,所有Activity 类都从该调用派生。

    在我的values-v21/styles.xml 基本主题中设置以下样式:

    <item name="android:colorPrimary">@color/app_green</item>
    <item name="colorPrimary">@color/app_green</item>
    <item name="android:colorPrimaryDark">@color/app_green_dark</item>
    <item name="colorPrimaryDark">@color/app_green_dark</item>
    <item name="android:colorPrimaryDark">?attr/colorPrimaryDark</item>
    <item name="android:statusBarColor">?attr/colorPrimaryDark</item>
    

    NavigationDrawerActivity 类内部,在其onCreate() 方法中,我执行以下操作(感谢@Tinadm 提供这部分答案): getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);

    在呈现ActionMode 的类中,我添加了以下方法:

    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) 
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) 
        getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        // This is to highlight the status bar and distinguish it from the action bar,
        // as the action bar while in the action mode is colored app_green_dark
        getActivity().getWindow().setStatusBarColor(getResources().getColor(R.color.app_green_darker));
      
    
      // Other stuff...
      return true;
    
    
    @Override
    public void onDestroyActionMode(ActionMode mode) 
      mActionMode = null;
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) 
        getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        getActivity().getWindow().setStatusBarColor(getResources().getColor(R.color.app_green_dark));
      
    
    

ActionMode 被销毁时重新启用半透明状态栏,可以在状态栏下方绘制我的导航抽屉。还值得注意的是ActionMode 覆盖了操作栏,因此如果在启用ActionMode 时(通过从左到右滑动)拉出导航抽屉,它将覆盖导航抽屉。我将在启用ActionMode 时禁用此功能,以免混淆用户。

【讨论】:

【参考方案2】:

您的临时解决方案是在您最需要或问题开始时调用此代码

if(Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP)
    getWindow().setStatusBarColor(Color.BLUE); 
    // or Color.TRANSPARENT or your preferred color

编辑

你试过了吗

// actionMode.getCustomView().setBackgroundColor.(Color.TRANSPARENT);

@Override
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu)     
    //but this time you let the actionmode's view change the statusbar's
    // visibility
    actionMode.getCustomView().setSystemUiVisibility(
        View.SYSTEM_UI_FLAG_LOW_PROFILE); //or preferred one
    return true;

当您的actionMode 被销毁时,状态栏将恢复,因此也将其覆盖并重新设置您的状态栏颜色。

希望对你有帮助

【讨论】:

嗯,实际上这也无济于事。我认为这可能是因为启用半透明状态栏时忽略了 setStatusBarColor。 哇!我知道你能不能像我在电脑上时可以打的场景演示一样创建一个场景演示?尽管为这个问题 +1@jwir3 是的,我看看今天能不能得到一个更完整的可编译解决方案。 所以,如果我在onPrepareActionMode() 内取消设置windowTranslucentStatus,(并且在我销毁操作模式时不重置标志),它将保持未设置状态,向我表明该标志正在生效。但是,如果我还添加了对 setStatusBarColor()DrawerLayout.setStatusBarBackgroundColor() 的调用,则它们没有任何效果(即它仍然是黑色的)。我的想法是,有些东西在样式链下游的某个地方明确地将其设置为黑色。关于如何解决此问题的想法?【参考方案3】:

我不久前遇到了同样的问题,我的解决方案是在 onCreateActionMode 和 onDestroyActionMode 设置并恢复状态栏颜色。

//onCreateActionMode
mStartColor =  activity.getWindow().getStatusBarColor();
getWindow().setStatusBarColor(color);

//onDestroyActionMode
activity.getWindow().setStatusBarColor(mStartColor);

当然,在使用 setStatusBarColor 方法之前,您需要检查它是否在棒棒糖上,因为它在 L 之前不可用。

额外:如果您使用 DrawerLayout,您还需要在其上设置 statusBarColor。

mDrawerLayout.setStatusBarBackground(new ColorDrawable(color));

希望有帮助!

【讨论】:

【参考方案4】:

我遇到了和你一样的问题。我设法使用解决方法解决了它。

    我做的第一件事是从我的主题中删除 windowTranslucentStatus=true; 然后我用getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);让内容填满了状态栏下方的区域。 我将&lt;item name="android:colorPrimaryDark"&gt;@color/primary_dark&lt;/item&gt; 设置为primary_dark 为“#51000000”。注意 alpha 值以实现与 windowTranslucent 提供的相同的透明度。由于我的工具栏是蓝色的,我的状态栏现在是深蓝色的。

    仅此一项并不能解决问题。当动作模式处于活动状态时,在我的情况下状态栏仍然是黑色的。但是现在,不使用 translucentStatusBar,我可以将颜色应用到我的状态栏,所以我使用了:

    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) 
        getActivity().getWindow().setStatusBarColor(Color.parseColor("#026592"));
        return true;
    
    

(请注意,我使用的颜色没有 alpha。这是与我的深蓝色半透明状态栏完全匹配的 RGB 颜色)。

【讨论】:

所以,我有一个导航抽屉,标题中有一个图像,当导航栏在状态栏下方滑动时,它会立即显示。我认为禁用半透明将禁用此功能。 (虽然它可能值得一试,看看它是否有效 - 我今天晚些时候会试试这个)。我知道这种行为是可能的,因为 GMail 应用程序完全符合我的要求,我只需要弄清楚如何 是的,我有一个导航抽屉作为墙壁。使用全屏标志可以让抽屉的内容显示在状态栏下,即使它不是半透明的。 哦,太棒了。我会检查一下,并尽快回复您。 嗯,所以我觉得这个解决方案缺少一些东西。我完全按照你的建议做了,并从我的values-v21/styles.xml 主题中删除了windowTranslucentStatuswindowDrawsSystemBarBackgrounds,然后我在我的基类(NavigationDrawerActivity)中将系统ui可见性调用添加到onCreate,并设置根据建议适当地着色,并在使用操作模式的片段中添加onPrepareActionMode() 调用。现在,我的状态栏 100% 的时间都是黑色的,这很奇怪。我可能错过了什么吗? 我注意到 colorPrimaryDark 出于某种原因不能控制状态栏颜色。如果我改变的只是这个(并禁用windowDrawsSystemBarBackgroundswindowTranslucentStatus 的其他标志),它不应该改变系统栏的颜色(它在我编写的其他应用程序上)。这可能与我支持的不仅仅是 5.0(低至 API 14)有关吗?【参考方案5】:

查看Translucent system bars上的Android文档

如果您正在创建自定义主题,请将这些主题之一(Theme.Holo.NoActionBar.TranslucentDecor 和 Theme.Holo.Light.NoActionBar.TranslucentDecor)设置为父主题,或者在您的主题。

因此,如果您使用支持库,则您的样式必须扩展一些 AppCompat 样式并在 ActionBarStyle 上同时使用 windowTranslucentNavigation 和 windowTranslucentStatus。

【讨论】:

感谢您的回复。我编辑了上述问题,以包含更多关于我的风格的背景信息。我只在values-v21/styles.xml 中设置windowTranslucentStatus(即棒棒糖和更大)。主题确实继承自 AppCompat 样式。我尝试添加windowTranslucentNavigation,但没有任何运气。当我输入ActionMode 时,状态栏仍然变黑。 但您是否同时使用了windowTranslucentNavigationwindowTranslucentStatus【参考方案6】:

问题可能就在这里。

<item name="android:background">@color/myapp_green</item>
<item name="background">@color/myapp_green</item>

"android:background" 是正确的语法,我不认为"background" 是。为什么你需要两次指定背景颜色呢?删除第二个,它应该没问题。如果没有,那么我希望下面的 2 美分对 setBackgroundColor 方法的主题有所帮助。

0.02 美元

根据我的经验,我发现 Android 中的 setBackgroundColor() 方法有时会表现得很奇怪。发生这种情况时,我改用setBackground(),并以可绘制的形式指定颜色:getResources().getDrawable(R.color.xxxx)。我从来不知道为什么这解决了我过去遇到的类似问题,但到目前为止我已经使用了这个解决方法,没有任何明显的副作用。它可能不是最好的解决方案(特别是如果您已经设置了背景可绘制对象),但是当其他方法似乎不起作用时它会派上用场。

【讨论】:

我添加了“背景”只是为了尝试它作为一个组合。我注意到有时 android 系统会响应带有“android”命名空间的系统,有时会响应没有的系统。我已经尝试过使用名称空间,没有和两者都尝试过,但似乎都没有。如果我要尝试您在代码中设置它的建议,我会在什么背景上设置可绘制的背景?操作栏? 不,你在OP中提到的状态栏。 mDrawerLayout 可以访问一些 setBackground 方法。试试mDrawerLayout.setBackground(getResources().getDrawable(R.color.myapp_green));【参考方案7】:

我遇到了同样的问题,我通过以下方式解决了它

问题在于为所有视图覆盖相同的颜色。但我无法使用 R.color.mycolorColor.parseColor("#412848") 救了我。

【讨论】:

所以,我不确定setActivityBackgroundColor() 的定义位置。它似乎没有在 ToolbarActionBar 上定义... 尝试使用像 Color.parseColor("#412848") 这样的直接颜色代码,而不是使用 R.color.yourcolor,因为在某些操作系统中,颜色覆盖不起作用。【参考方案8】:
    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) 
        super.onPrepareActionMode(mode, menu);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) 
            Window w = getActivity().getWindow();
            w.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
            w.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        
        return true;
    

    @Override
    public void onDestroyActionMode(ActionMode mode) 
        super.onDestroyActionMode(mode);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) 
            Window w = getActivity().getWindow();
            w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION,
                    WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
            w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
                    WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        
    

【讨论】:

【参考方案9】:

虽然我之前使用了之前的解决方案,但添加导航抽屉时存在更多问题,如果您希望抽屉在系统栏后面绘制,以及与设置和清除窗口标志或状态栏颜色相关的许多故障(即使是官方的 Gmail 应用程序也有这些故障!)

这是我现在使用的解决方案:

@Override
public void onSupportActionModeStarted(@NonNull ActionMode mode) 
    super.onSupportActionModeStarted(mode);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) 
        // Post this so it ideally happens after the window decor callbacks.
        new Handler().post(new Runnable() 
            @Override
            public void run() 
                try 
                    final AppCompatDelegate delegate = getDelegate();
                    final Class clazz = Class.forName("android.support.v7.app.AppCompatDelegateImplV7");
                    final Field mStatusGuardField = clazz.getDeclaredField("mStatusGuard");
                    mStatusGuardField.setAccessible(true);
                    final View mStatusGuard = (View) mStatusGuardField.get(delegate);
                    if (mStatusGuard != null) 
                        mStatusGuard.setBackgroundColor(/* Should match your desired status bar color when the action mode is showing, or alternatively you could use Color.TRANSPARENT here and just update the colors through getWindow().setStatusBarColor() below. The key is to avoid the black status guard from appearing. */);
                    
                 catch (Exception e) 
                    /* Log or handle as desired. */
                
            
        );
        // Also set the status bar color. This is to avoid a momentary frame or 
        // two where the black will still be visible.
       getWindow().setStatusBarColor(/* Should match your desired status bar color when the action mode is showing. */);
    


@Override
public void onSupportActionModeFinished(@NonNull ActionMode mode) 
    super.onSupportActionModeFinished(mode);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) 
        // Reset to the theme's colorPrimaryDark. This would normally result in 
        // the black status guard being seen momentarily, but because we set it 
        // to the same color as the action mode background, this intermediate 
        // state is not visible.
        getWindow().setStatusBarColor(Color.TRANSPARENT);
    

这显然是一个大技巧,它的动画效果并不像上下文操作栏那样,但它对我有用。支持https://***.com/a/38702953/1317564 提出这个想法。

请注意,仅当您使用导航抽屉或以其他方式在 values-v21/theme.xml 中设置这些属性时才需要这样做:

<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>

否则,在onSupportActionModeStarted()onSupportActionModeFinished() 中调用setStatusBarColor(...) 可能会起作用,这就是我在添加导航抽屉之前所做的。

【讨论】:

以上是关于使用 ActionMode 时,Lollipop 状态栏变黑的主要内容,如果未能解决你的问题,请参考以下文章

当actionmode打开时,RecyclerView子项不会单击

TextView - setCustomSelectionActionModeCallback如何在为多个TextView选择文本时创建ActionMode.Callback

Android ActionMode模式使用

如何识别ActionMode中是不是点击了Done按钮

如何在ActionMode中禁用操作栏后退按钮

Android — 长按ListView 利用上下文菜单(ActionMode) 进行批量事件处理