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

27

我有一个状态栏,并在主题中设置了以下内容:

<!-- 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)。我想知道如何使这种行为不发生,并保持状态栏颜色/半透明度不变。
我尝试将<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转换为正确识别时是否有遗漏。


也许你的颜色 @color/wiw_greenR.color.myapp_green 不一样,是吗? - Eduardo
抱歉,是我的错。是的,这两种颜色是相同的。 - jwir3
另外,当悬赏到期时,我会重新启动它,因为我还没有真正解决这个问题。感谢迄今为止回复的所有人! - jwir3
这是一个错误,https://code.google.com/p/android/issues/detail?id=183887 https://code.google.com/p/android/issues/detail?id=184047 相关信息请参考 https://dev59.com/D47ea4cB1Zd3GeqPD6V7 - sojin
这个问题已经通过com.android.support:design:28.0.0-rc01得到了修复。 - Soren Stoutner
9个回答

6

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

  1. Enable windowTranslucentStatus and windowDrawsSystemBarBackgrounds in the main theme.

  2. Remove the call to DrawerLayout.setStatusBarBackgroundColor() inside of my NavigationDrawerActivity, from which all Activity classes derive.

  3. Set the following styles in my values-v21/styles.xml base theme:

    <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. Inside of the NavigationDrawerActivity class, in its onCreate() method, I execute the following (thanks @Tinadm for this part of the answer): getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);

  5. Inside of my class that presents the ActionMode, I added the following methods:

    @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时禁用此功能,以免使用户混淆。

4

你可以在需要或问题出现时调用以下代码作为临时解决方案:

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被销毁时,状态栏将恢复原来的状态,因此也需要覆盖它并重新设置状态栏颜色。希望这可以帮到您。

1
嗯,实际上这也没有帮助。我认为这可能是因为启用了半透明状态栏时,setStatusBarColor被忽略了。 - jwir3
哇!我看到了,请问您能不能创建一个场景演示,以便我在电脑上使用时进行测试?虽然问题不是很清楚,但还是给你点赞@jwir3。 - Elltz
好的,我会看看今天能否提供一个更完整的可编译解决方案。 - jwir3
所以,如果我在onPrepareActionMode()中取消设置windowTranslucentStatus(并且在销毁操作模式时不重置标志),它将保持未设置状态,表明标志正在生效。但是,如果我还调用setStatusBarColor()DrawerLayout.setStatusBarBackgroundColor(),它们没有任何效果(即仍然是黑色)。我的想法是,某个地方在样式链的下游显式地将其设置为黑色。您有什么想法如何解决这个问题? - jwir3

3

我之前遇到了同样的问题,我的解决方法是在 onCreateActionMode 和 onDestroyActionMode 中设置并还原状态栏颜色。

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

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

当然,在使用setStatusBarColor方法之前,您需要检查它是否在lolipop上可用,因为它不适用于早期版本。

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

mDrawerLayout.setStatusBarBackground(new ColorDrawable(color));

希望能帮到您!

2

我遇到了和你完全相同的问题,但我通过一种变通的方法解决了它。

  1. The first thing I did was to remove windowTranslucentStatus=true from my theme;
  2. Then I used getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); to make the content fill the area under the status bar.
  3. I set <item name="android:colorPrimaryDark">@color/primary_dark</item> where primary_dark is "#51000000". Note the alpha value to achieve the same trasparency that windowTranslucent provides. Since my Toolbar is blue, my status bar is now dark blue.
  4. This alone did not solve the problem. When action mode is active, the status bar was still black in my case. But now, not using translucentStatusBar, I'm able to apply colors to my status bar, so I used:

    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        getActivity().getWindow().setStatusBarColor(Color.parseColor("#026592"));
        return true;
    }
    
(请注意,我使用的颜色没有 alpha 通道。这是我暗蓝色半透明状态栏完全匹配的 RGB 颜色。)

所以,我有一个带有图像的导航抽屉,当导航栏在状态栏下滑时,它会显示出来。禁用半透明将禁用此功能,我认为。(虽然值得一试看看是否有效-我今天稍后会尝试)。我知道这种行为是可能的,因为GMail应用程序正是我想要的,我只需要弄清楚如何实现。 - jwir3
嗯,我觉得我在这个解决方案中漏掉了什么。我按照您的建议完全执行,并从我的values-v21/styles.xml主题中删除了windowTranslucentStatuswindowDrawsSystemBarBackgrounds,然后在我的基类(NavigationDrawerActivity)的onCreate中添加了系统UI可见性调用,根据建议适当设置了颜色,并在使用操作模式的片段中添加了onPrepareActionMode()调用。现在,我的状态栏始终是黑色的,这很奇怪。我可能错过了什么吗? - jwir3
我注意到 colorPrimaryDark 似乎无法控制状态栏的颜色。如果我只改变这个(并禁用其他 windowDrawsSystemBarBackgroundswindowTranslucentStatus 标志),它不应该改变系统栏的颜色吗(在我编写的其他应用程序中确实如此)?也许这与我支持的不仅仅是5.0(向下兼容到API 14)有关系吗? - jwir3
是的,我认为只要不使用windowTranslucentStatus,colorPrimaryDark就应该改变状态栏的颜色。这确实很奇怪,因为我和你完全一样的设计,一个带有一些片段的导航抽屉,还有一个需要操作模式的特殊片段。如果您愿意,请给我您的电子邮件地址,以便我可以为您提供更详细的说明和一些代码示例; - Tinadm
你能否把代码示例发布到gist.github.com上,这样未来阅读此帖的读者也会从中受益吗? - jwir3
显示剩余2条评论

1

浏览半透明系统栏的Android文档

如果您正在创建自定义主题,请将这些主题(Theme.Holo.NoActionBar.TranslucentDecor和Theme.Holo.Light.NoActionBar.TranslucentDecor)设置为父主题或在您的主题中包含windowTranslucentNavigation和windowTranslucentStatus样式属性。

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


感谢您的回复。我编辑了上面的问题,以便更好地解释我的样式方面的一些背景。我只在values-v21/styles.xml(即Lollipop及更高版本)中设置windowTranslucentStatus。主题确实是从一个AppCompat样式继承而来的。我尝试添加windowTranslucentNavigation,但没有成功。当我进入“ActionMode”时,状态栏仍然会变成黑色。 - jwir3
但是你是否同时使用了 windowTranslucentNavigationwindowTranslucentStatus - Eduardo

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

0

虽然之前的解决方案我之前用过,但是在添加导航抽屉时会有更多问题,特别是如果你想让抽屉在系统栏后面绘制,还有很多与设置和清除窗口标志或状态栏颜色相关的故障(即使官方的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,它的动画效果并不完全像上下文操作栏那样,但对我来说已经足够了。感谢https://dev59.com/D47ea4cB1Zd3GeqPD6V7#38702953提出这个想法。

请注意,只有在您使用导航抽屉或以其他方式在values-v21/theme.xml中设置了这些属性时才需要此操作:

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

否则,只需在onSupportActionModeStarted()onSupportActionModeFinished()中调用setStatusBarColor(...)可能就可以正常工作了。在添加导航抽屉之前,这就是我所做的。

0

问题可能出在这里。

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

"

android:background

是正确的语法,我认为"background"不是。你为什么需要两次指定背景颜色呢?把第二个去掉就可以了。如果还不行,那么我希望下面关于setBackgroundColor方法的建议能够帮到你。

"

$0.02

根据我的经验,我发现在Android中,setBackgroundColor()方法有时会表现得很奇怪。当出现这种情况时,我使用setBackground()代替,并以可绘制的形式指定颜色:getResources().getDrawable(R.color.xxxx)。我从未弄清楚为什么这可以解决我过去遇到的类似问题,但到目前为止,我一直使用这个解决方法而没有明显的副作用。这可能不是最好的解决方案(特别是如果您已经设置了背景可绘制),但当其他方法似乎无效时,它非常方便。

我只是为了尝试组合而添加了“background”。我注意到有时Android系统会响应带有“android”命名空间的一个,有时则不带。我已经尝试过使用命名空间、不使用命名空间以及两者都使用,但似乎都不起作用。如果我要尝试您关于在代码中设置它的建议,那么我应该在哪里设置背景可绘制对象?是在操作栏上吗? - jwir3
不,你在原始帖子中提到的状态栏。mDrawerLayout有几个setBackground方法可供使用。尝试使用mDrawerLayout.setBackground(getResources().getDrawable(R.color.myapp_green)); - user3829751

0

我遇到了同样的问题,以下是我解决方法:

问题出在覆盖所有视图的相同颜色上。但我不能使用 R.color.mycolor,而是使用 Color.parseColor("#412848") 来解决问题。


所以,我不确定 setActivityBackgroundColor() 是在哪里定义的。它似乎没有在 ToolbarActionBar 上定义... - jwir3
尝试直接使用颜色代码,例如Color.parseColor("#412848"),而不是使用R.color.yourcolor,因为在一些操作系统中,颜色覆盖可能无法正常工作。 - Karthika PB

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接