美文网首页ITBOXAndroid快速开发AndroidHot
Android自定义通知样式适配

Android自定义通知样式适配

作者: hackware | 来源:发表于2016-06-09 12:57 被阅读12350次

    今天是端午节,祝大家节日快乐


    这篇文章着实酝酿了许久,一直懒得写。网上关于通知栏样式适配的文章很多,但还不够完美,这也是我写这篇文章的动力所在。

    温故而知新


    Android通知有两种,默认通知与自定义通知。默认通知简单调用系统接口就能实现,如下:

    发送默认通知 默认通知效果

    自定义通知就稍微麻烦一些,需要定义一个layout文件,使用RemoteViews加载它并设置一些点击事件,再设置到builder,如下:

    自定义通知代码 自定义通知效果

    这个通知很简单,就是两行文本加上一个按钮,按钮具有单独的点击事件,点击后跳转到AnotherActivity。

    注意:smallIcon对于自定义通知和默认通知都是必须的,否则通知显示不出来。道理很简单,smallIcon需要在状态栏上显示,不设置怎么行。在5.0及以上,smallIcon必须符合Material Design风格,即白色内容,透明背景。不然系统会使用默认的图片替换。具体可参考Android通知栏微技巧,那些你所没关注过的小细节 标签: android通知通知栏微技巧。后面我会有一篇更详细的文章来介绍这个。contentIntent对于2.3及以下的系统是必须的,否则发送通知时会抛异常。道理也很简单,Android 2.3及以下系统不支持给自定义通知上的元素绑定单独的点击事件,因此必须设置整个通知的点击事件。

    为什么要进行样式适配?


    默认通知不存在样式适配的问题,因为默认通知的布局、颜色、背景什么的都是系统的,系统总会正确的显示默认通知。但自定义通知就不一样了,自定义通知的布局完全由我们自己掌控,我们可以为元素设置任何背景、颜色。那么,问题来了。Android通知栏的背景各种各样,不同的ROM有不同的背景,白色、黑色、透明等。不同的Android版本通知栏背景也不一样,一旦我们为自定义通知上的元素设置了特定背景或颜色,就肯定会带来兼容性问题(主要是文本啦)。这样的应用一大把,贴个图大家就明白了:

    未适配的自定义通知

    怎么适配?


    适配的方式大概有两种,一种简单粗暴:为自定义通知设置固定的背景(上图中的360卫士就这么干的),比如黑色。那么内容自然就是白色或近似白色。这样,在所有的手机上都能正常显示,不会出现在黑色背景通知栏上显示良好,到了白色背景通知栏上就几乎啥也看不见。使用这种方案的应用太多了。我个人很不推崇这种方式,这样会使得自定义通知在将近一半的手机上显示得很突兀,和系统的通知栏不够沉浸,影响整体美观。另一种方案就稍微合理一些:通过读取系统的通知栏样式文件,获取到title和content的颜色,进而将这个颜色设置到自定义通知上。读取通知栏样式文件本身有兼容性问题,不同Android版本的样式文件有变,具体可参考这篇博客 通知栏设置系统字体颜色 ,这种方式也不是在所有手机上生效,实际测试发现,还是有小部分机型没法读取或是读取到的是错误的。拿到title和content的颜色后,还可以通过算法(后面细说)判断这个颜色是近似白色还是近似黑色,进而能判断出通知栏的背景是近似黑色还是近似白色,这样就能根据不同的通知栏背景加载不同的自定义通知布局。进而做到良好的适配。

    更好的适配


    现在切入主题,谈谈如何来更好的适配自定义通知。有过锁屏开发经验的人应该知道,如果你的应用有读取系统通知栏的权限,那么每当应用程序发出一个通知,你的应用都会收到对应的notification对象,这个时候,我们一般会执行以下操作:

    获取并展示app通知

    调用addView之后,应用程序的通知就会显示在我们的应用里。显然,上面的代码并没有对apply返回的notificationItemLayout做任何其他操作,但确实这个View显示出来时就是样式良好的,可见,notificationItemLayout本身就是带有样式的,即便是默认通知。那么方案来了!我们先构造一个默认通知:

    获取通知栏title的颜色

    通知并不发送出去,只是用来获取通知栏title的颜色,如果你还想获取content的颜色,抱歉,不能通过查找android.R.id.text来获取,这个字段是访问不到的。可通过反射获取,更好的办法是先预先设置一个content,然后遍历viewGoup根据content内容找到对应的TextView再获取颜色。

    拿到颜色后,可根据算法判断这个颜色是近似白色还是近似黑色,我们使用黑色作为基准色,使用方差来计算这个颜色是否近似黑色:

    比较两个颜色是否近似

    baseColor传入Color.BLACK,color传入刚刚获取到的title的颜色,根据我实测,阈值为180.0较为合理。上述方法返回true,即表示title的颜色近似黑色,也就是说通知栏背景近似白色。

    额,经验丰富的同学应该已经洞察到第二段代码存在的兼容性问题了:根据android.R.id.title去找到title对应的TextView是不靠谱的,因为有些ROM厂商会把id改掉,导致找到的title为空。

    同时还有另外一个问题:使用上述方法,Activity不能继承自AppCompatActivity(实测5.0以下机型可以,5.0及以上机型不行),大致的原因是默认通知布局文件中的ImageView(largeIcon和smallIcon)被替换成了AppCompatImageView,而在5.0及以上系统中,AppCompatImageView的setBackgroundResource(int)未被标记为RemotableViewMethod,导致apply时抛异常。

    为了解决这两个问题,我们改进getNotificationColor方法:

    改进后的方法

    在getNotificationColorInternal中,设置一个默认的title文本,如果根据id找不到title,则遍历notificationRoot根据设置的title文本找到title:

    兼容厂商改id

    在getNotificationColorCompat中,我们先构造一个默认通知,获取到默认通知的布局文件id,并将布局加载到notificationRoot,此时,如果根据id找不到title,显然设置默认title的办法已经失效了。如何从notificationRoot中找到title是个问题。我的解决办法是:反正都已经拿到notificationRoot了,不如就遍历它,先找到其中的所有TextView,取字体最大的TextView作为title(这是合理的,因为默认通知中最多也就4个TextView,分别是title、content、info、when,title肯定是字体最大,最显眼的),并返回其颜色:

    兼容AppCompatActivity

    实际测试


    拿到了通知栏背景的颜色后,我们就可以加载不同样式的布局,达到适配的目的。代码如下:

    适配代码

    效果:

    Android 4.4黑色背景的通知栏 坚果手机白色版白色通知栏

    好了,我的第一篇技术博客到此为止,大家假期玩得Happy!

    相关文章

      网友评论

      • 魏魏魏魏:楼主你好,请教一下自定义状态栏位置和预览图差距较大,你们是怎么解决的?(华为和小米等手机是显示是正常的)具体表现就是靠下的内容无法使用下边距,直接挨着最下边了!
      • 1琥珀川1:2018-4-25学习
      • 一杯酒几分愁:楼主你这个图片字体太小了看着有点吃力,干嘛不直接贴代码呢
      • 艾途_just:couldn't inflate view for notification com.i2f.emallmobile/0xe426733c
        android.widget.RemoteViews$ActionException: view: android.widget.TextView doesn't have method: 给你第二个(int)
        at android.widget.RemoteViews.getMethod(RemoteViews.java:1023)
        at android.widget.RemoteViews.-wrap5(RemoteViews.java)
        at android.widget.RemoteViews$ReflectionAction.apply(RemoteViews.java:1583)
        at android.widget.RemoteViews.performApply(RemoteViews.java:4071)
        at android.widget.RemoteViews.apply(RemoteViews.java:3808)
        at com.android.systemui.statusbar.BaseStatusBar.inflateViews(BaseStatusBar.java:2382)
        at com.android.systemui.statusbar.BaseStatusBar.createNotificationViews(BaseStatusBar.java:3195)

        楼主为什么我的直接就奔溃了呢
      • Joke誓言:楼主,int layoutId = notification.contentView.getLayoutId();这句会报空指针,因为Android N之后,contentView会为空
        lsfzlj:N之后怎么获取通知栏底色的?
      • 蜗牛1:博主我在获取自定义通知栏颜色时报 Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'int android.widget.RemoteViews.getLayoutId()' on a null object reference
        at com.renyu.itooth.common.NotiManager.getNotificationColor(NotiManager.java:210)
        at com.renyu.itooth.common.NotiManager.isDarkNotificationTheme(NotiManager.java:197)
        这个错误,我看了说是 int layoutId = notification.contentView.getLayoutId()为空,你知道是怎么破吗?
      • jihf:大神,你好,我今天再看你的【Android自定义通知样式适配】的文章,有个疑惑问下,第三方修改了title的ID后,在getNotificationColorCompat方法中是使用获取最大字体的TextView认为是TitleView,而在getNotificationColorInternal方法中是通过设置默认的Title值来获取的,为什么不能统一用 后面的方法呢?
      • e0c4892ba2f8:请教博主一个问题,有没有什么办法能适配通知栏的布局,我发现华为手机通知栏上图标的margin和其他手机不一样,这样显示出来通知栏的图标和其他图标没有对齐
        森林雪峰:我也遇到这种情况了,你是怎么解决的
      • 0f21d343f688:楼主现在公司这边有一个需求。 也是自定义的通知栏样式,但是获取的网络大图,然后再推送的展开大布局的通知,有两个左右的按钮,可以切换图片,能够提供一下思路吗。我这边做的就是自定义remoteView 然后把按钮设置广播的PendingIntent,在广播中用相同ID,更改图片,重新推送一条通知。。但是会有明显的闪动(因为又推了一条通知出去)
        hackware:@0f21d343f688 技术上应该是这样做的,用同样的ID,重发一遍。你说的闪动也许是不可避免的,可能是通知栏UI默认的fadeIn效果吧,这个估计去除不了。
      • 老年追梦人:楼主真的好6,工作挺久了,一直在用别人的东西,打算跟着楼主学学:stuck_out_tongue_winking_eye:
      • e1db934a9946:楼主代码贴一个,谢谢
        hackware: @xiongyingjun 自己照着写一下,印象更深刻哦
      • Mansoul:学习了,但是博主如果你用markdown编辑博客的话,代码块的编辑是不是可以做的更好。
        hackware:@Mansoul 嗯,后面的博文都用markdown了,主要是简书不支持将已发表的富文本文章转换为markdown
      • 6207e5d1fbaa:对于的楼主的这个思路,我表示敬佩 :+1:
        6207e5d1fbaa:@hackware 看来是同道中人,幸会幸会 :smiley:
        hackware:@ReoJ 你也是bruce lee的粉丝?
      • b7e9763ffd86:TextView title = (TextView) viewGroup.findViewById(android.R.id.title);
        你的这一句在魅族note3 5.1系统上为null呢,具体原因,现在没空,明天看看
        皮球二二:@hackware 如果用你之前提到的layoutId去处理的话,那么遍历viewGroup找到的TextView均为空字符串,所以我还是建议你直接遍历找出那个颜色出来即可
        b7e9763ffd86:@hackware :grin: thanks,确实是厂商更改了id,小米和魅族是毒瘤啊,虽然我挺喜欢这两家的手机的。
        hackware:@白一辰 估计是厂商将id改掉了,试试builder.setContent("DUMMY_CONTENT"),然后遍历viewGroup找到text为"DUMMY_CONTENT"的TextView并获取其颜色,应该没问题了。我的应用中就这么用的,目前没有在任何手机上发现问题。
      • 242e60066e39:楼主,你好。我遇到个问题:在应用关闭的时候,一下子推送过来多条notification通知,点击其中一条能正常跳转,然后再点其他的没反应。应用在前台(正在和用户交互)或者后台(按home键到桌面)却不会发生这种bug。这是为什么呀,困惑了我好久。
        242e60066e39:@r17171709 情况是这样的:打开程序,点击推送多条消息,然后关闭应用。下拉通知,点击打开指定页面,第一次能跳转,后续继续点击跳转不了。但是,如果这样:打开程序,点击推送多条消息,不关闭应用。下拉通知,点击打开指定页面,每次都可以跳转到指定页面。
        242e60066e39:@r17171709 是的,requestCode是唯一的。flags也是FLAG_UPDATE_CURRENT。如下是DEMO代码(和我项目中的代码一样):int requestCode = (int) (Math.random() * 10000);
        NotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this);

        Intent splash = new Intent(this, MainActivity.class); // 自定义打开的界面
        splash.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        PendingIntent contentIntent = PendingIntent.getActivity(this, requestCode, splash, PendingIntent.FLAG_UPDATE_CURRENT);

        mBuilder.setContentTitle("WonderBuy")//设置通知栏标题
        .setContentText("床前明月光,疑是地上霜。举头望明月,低头思故乡。--李白")
        .setContentIntent(contentIntent)
        .setTicker("订单详情")
        .setWhen(System.currentTimeMillis())
        .setPriority(Notification.PRIORITY_DEFAULT)
        .setAutoCancel(true)
        .setOngoing(false)
        .setDefaults(Notification.DEFAULT_ALL)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher))
        .setSmallIcon(R.drawable.ic_launcher)
        .setContentInfo("订单详情");

        mNotificationManager.notify(requestCode, mBuilder.build());
        皮球二二:@kieedi 你pendingIntent的requestCode是不是唯一?并且flags是不是FLAG_UPDATE_CURRENT
      • 皮球二二:博主你AppCompatImageView的问题解决了吗?
        hackware: @r17171709 可以的
        皮球二二:@hackware 你好,这样也可以解决notification的remoteViews上的imageview?我以为只是ContentView上的
        hackware:@r17171709 已解决,你看看
      • 王小贱_ww:学习了
      • 570e37344076:还有其他方案不 目前我们的方案也类似
        hackware:@玩命er 系统没有提供获取通知栏背景色的方法,只能间接获取title的颜色。获取title颜色最便捷的办法估计就这样了
      • waidsw86:不错
        hackware:@1903fa6805d4 这几天有时间争取再写两篇
      • Daxiang:666,加油

      本文标题:Android自定义通知样式适配

      本文链接:https://www.haomeiwen.com/subject/bbnjdttx.html