Android Lollipop 中的通知背景为白色。我该如何改变它?

Notification background in Android Lollipop is white. How can I change it?

我想在我的应用程序中显示消息通知。在 Android 之前的版本中一切正常,但在 Lollipop 中通知背景为白色。 我在 layout_message_notification.xml:

中将此 XML 代码用于我的通知布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout_messageNotification"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent">

    <LinearLayout
        android:layout_width="0dp"
        android:layout_weight=".2"
        android:layout_height="wrap_content"
        android:background="@android:color/transparent">

        <ImageView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:scaleType="fitCenter"
            android:src="@drawable/message_icon"/>

    </LinearLayout>

    <LinearLayout
        android:layout_width="0dp"
        android:layout_weight=".8"
        android:layout_height="wrap_content"
        android:background="@android:color/transparent">

        <TextView
            android:id="@+id/textView_notification_title"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:gravity="right|center_vertical"
            android:layout_margin="15dp"/>

    </LinearLayout>

</LinearLayout>

我在棒棒糖中的通知是这样显示的:

如何使通知背景变暗或透明,就像以前版本的 Android?

这是正确的通知行为。

如果您想直接控制这方面,我建议您执行以下操作:

  • 在文件夹 res/layout-v21

  • 中创建 layout_message_notification.xml 的替代版本
  • 通过更改外部 布局的背景颜色对这个新版本稍作改动:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout_messageNotification"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/gray">
//...

最后,如果你喜欢遵循官方material设计specification

Always use style resources for the text of a custom notification

并使用通知的自定义布局,您可能会考虑不覆盖默认背景,而是根据 API 版本更改文本样式。您可以通过创建两个资源样式文件并根据当前API版本使用它们来实现它:

values-v21/notification_styles.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <!-- according to official recommendation custom notifications should has the same text style as default one-->
  <style name="NotificationHeader" parent="@android:style/TextAppearance.Material.Notification.Title" />
  <style name="NotificationContent" parent="@android:style/TextAppearance.Material.Notification.Line2" />
</resources>

values/notification_styles.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <!-- according to official recommendation custom notifications should has the same text style as default one-->
  <style name="NotificationHeader" parent="@android:style/TextAppearance.StatusBar.EventContent.Title" />
  <style name="NotificationContent" parent="@android:style/TextAppearance.StatusBar.EventContent" />
</resources>

此答案描述了一种 hacky 更改通知背景颜色的方法。
注意:这是一个 未记录的解决方法 ;它基于反射,可能会在自定义固件上被破坏;它仅适用于 Android Lollipop 和 Marshmallow 版本;它 将无法在 Android N 和更高版本上工作 。我建议坚持使用默认颜色,除非您有充分的理由避免使用它。

为什么
没有合法的方法来为自定义通知设置背景颜色。 Google 根据 Material 设计 决定通知必须是白色或浅灰色,具体取决于其优先级。但是,Google 也对这条规则做了两个例外:

  1. 旧应用程序的通知显示为黑色 背景;
  2. 使用 MediaStyle 创建的通知可以是任何颜色。

由于第二个例外,这样的限制看起来不合逻辑且没有道理,这是您仍然想使用自定义颜色而不是 [推荐(或强制?)的颜色的唯一可能借口=110=].

里面有什么
让我们看看 BaseStatusBar 看看这个限制是如何施加的。计算通知背景颜色的唯一地方是 applyColorsAndBackgrounds method.
if 语句的第一个分支用于遗留应用程序。到达此处的唯一方法是在 Build.VERSION_CODES.LOLLIPOP 下面设置您的应用程序的目标 SDK。这种情况下背景会变黑。
我们对 entry.row.setTintColor 语句感兴趣。要达到它,应该通过几项检查,包括 isMediaNotification method 中包含的一项。他们在这里:

  1. 通知必须包含常规视图和大视图。
  2. 两个视图中的顶级布局必须以 com.android.internal.R.id.status_bar_latest_event_content 作为其 ID。
  3. 大布局必须包含一个以 com.android.internal.R.id.media_actions 作为其 ID 的小部件。

如何
问题最大的是 ID,只要它们是在内部资源中声明的,并且不能从应用程序的布局中访问 XML.
第二个问题是通知中使用的RemoteViews只是应用程序内部布局资源的ID,无法在代码中构造。因此,我们无法添加具有通过上述所有检查所需 ID 的布局。

但是,Google 将 addViewremoveAllViews 方法添加到 RemoteViews 中以满足他们的需要(它们在 MediaStyle 通知中使用)并且忘记了将它们设为私有。

所以,最后的想法很简单:

  • 根据Google定义的内部布局构建通知(以通过第二次检查)
  • 删除所有 removeAllViews
  • 使用 addView
  • 添加我们的自定义布局
  • 大视图的特殊情况:将包含 media_actions 的 Google 定义的布局添加到我们自定义布局内的不可见视图(以通过第 3 次检查)

缺点:

  • 膨胀未使用的视图(它们在膨胀后立即被删除)
  • 复杂且更深的布局层次

解决方案
我们的自定义大视图必须包含 FrameLayoutandroid.R.id.empty 作为其 ID。实际上,这里可以使用任何 ID,只要确保在代码中引用相同的 ID(见下文)即可。

// We need theese ids to use internal Android resources
int topId = Resources.getSystem().getIdentifier("status_bar_latest_event_content", "id", "android");
int topBigLayout = Resources.getSystem().getIdentifier("notification_template_material_big_media_narrow", "layout", "android");
int topSmallLayout = Resources.getSystem().getIdentifier("notification_template_material_media", "layout", "android");

RemoteViews viewSmall = ...; // Create our custom view here
RemoteViews viewBig = ...; // Create our custom big view here

// This is invisible inner view - to have media_actions in hierarchy
RemoteViews innerTopView = new RemoteViews("android", topBigLayout);
viewBig.addView(android.R.id.empty, innerTopView);

// This should be on top - we need status_bar_latest_event_content as top layout
RemoteViews topBigView = new RemoteViews("android", topBigLayout);
topBigView.removeAllViews(topId);
topBigView.addView(topId, viewBig);

// This should be on top - we need status_bar_latest_event_content as top layout
RemoteViews topSmallView = new RemoteViews("android", topSmallLayout);
topSmallView.removeAllViews(topId);
topSmallView.addView(topId, viewSmall);

Notification.Builder builder = new Notification.Builder(this);

builder.setSmallIcon(R.drawable.ic_notification)
        .setTicker("Some text")
        .setColor(0xff000000) // The desired color!
        .setContent(topSmallView);

Notification n = builder.build();
n.bigContentView = topBigView;

// Use our notification "n" here as usual

可以在顶层使用另一种布局而不是 notification_template_material_big_media_narrow 来操纵大视图的高度。在 notification_template_xxx.xml 个文件中搜索合适的 here 个。但不要忘记将 media_actions 放入层次结构中。