使用 ActionMode 时,状态栏在 Lollipop 上变黑

When using ActionMode, the status bar turns black on Lollipop

我有一个状态栏,主题设置如下:

<!-- 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,所以它存在于我的布局中(即导航抽屉在它上面绘制)。

一切正常,除了在我的一项活动中,我有一个多 select 模式和一个 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)。我想知道如何才能让这种行为不发生,并保持状态栏 color/translucency 原样。

我尝试将 <item name="android:windowTranslucentStatus">true</item> 添加到动作模式的样式中,并在 ActionMode 的样式中添加 <item name="android:statusBarColor">@color/myapp_green</item>,但都没有成功。

更新:

我想知道这是否与我设置状态栏背景的方式不正确有关。我所有的 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 以便被正确识别时是否遗漏了什么。

正在查看关于 Translucent system bars

的 Android 文档

If you're creating a custom theme, set one of these themes ( Theme.Holo.NoActionBar.TranslucentDecor and Theme.Holo.Light.NoActionBar.TranslucentDecor) as the parent theme or include the windowTranslucentNavigation and windowTranslucentStatus style properties in your theme.

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

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

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 被销毁时,状态栏将被恢复,所以也覆盖它并重新设置你的状态栏颜色。

希望对您有所帮助

问题可能出在这里

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

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

0.02 美元

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

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

问题是为所有 views.But 覆盖相同的颜色我无法使用 R.color.mycolor 并且 Color.parseColor("#412848") 救了我。

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

  1. 我做的第一件事是从我的主题中删除 windowTranslucentStatus=true
  2. 然后我用getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);让内容填满了状态栏下面的区域
  3. 我设置 <item name="android:colorPrimaryDark">@color/primary_dark</item> 其中 primary_dark 是“#51000000”。请注意 alpha 值以实现 windowTranslucent 提供的相同透明度。由于我的工具栏是蓝色的,所以我的状态栏现在是深蓝色的。
  4. 仅此并没有解决问题。当动作模式处于活动状态时,状态栏在我的情况下仍然是黑色的。但是现在,不使用 translucentStatusBar,我可以将颜色应用到我的状态栏,所以我使用了:

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

(注意我用的颜色没有alpha,这是RGB颜色,正好和我的深蓝色半透明状态栏匹配)。

我 运行 前一段时间遇到了同样的问题,我的解决方案是在 onCreateActionMode 和 onDestroyActionMode 中设置并恢复状态栏颜色。

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

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

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

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

mDrawerLayout.setStatusBarBackground(new ColorDrawable(color));

希望对您有所帮助!

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

  1. 启用 windowTranslucentStatuswindowDrawsSystemBarBackgrounds在主旋律中。

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

  3. 在我的 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>
    
  4. NavigationDrawerActivity class 的 onCreate() 方法中,我执行以下命令(感谢@Tinadm 提供这部分答案): getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);

  5. 在我的 class 中显示 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 时禁用此功能,这样它就不会混淆用户。

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

虽然我之前使用了之前的解决方案,但在添加导航抽屉时存在更多问题,如果您希望抽屉在系统栏后面绘制,以及与设置和清除 window 标志相关的许多故障或状态栏颜色(即使是官方 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);
    }
}

这显然是一个大 hack,它的动画效果与上下文操作栏不完全一样,但它对我有用。支持 提出这个想法。

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

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

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