自定义一个有按钮的通知栏及8.0适配
1.官方文档
1.1 通知相关全部、详细文档
https://developer.android.com/guide/topics/ui/notifiers/notifications
1.2 通知官方示例
https://github.com/googlesamples/android-Notifications
https://github.com/googlesamples/android-NotificationChannels
1.3 自定义通知文档
https://developer.android.com/training/notify-user/custom-notification
- https://developer.android.com/training/notify-user/expanded.html 已有通知的样式,可以扩展它们.
- NotificationCompat.BigPictureStyle,
- NotificationCompat.BigTextStyle
- NotificationCompat.InboxStyle ,
- NotificationCompat.MessagingStyle,
- NotificationCompat.MediaStyle 音乐播放.
- setStyle(new NotificationCompat.DecoratedCustomViewStyle()) 使用自定义样式.
- 何时用@style/TextAppearance.Compat.Notification.Title
- avoid setting a background image on your
RemoteViews
object, because your text color may become unreadable. - 完全自定义时: use setCustomBigContentView(), but do not call setStyle().
- setContent() 兼容4.1及以下.
1.4 通过通知栏启动activity的方式
https://developer.android.com/training/notify-user/navigation
- 启动常规Activity(含任务栈,保留原来顺序),使用TaskStackBuilder
- 启动特殊Activity ,intent设置 FLAG_ACTIVITY_NEW_TASK
1.5 通知组
https://developer.android.com/training/notify-user/group
- Note: Notification groups are not the same as notification channel groups.
- Note: If your app sends four or more notifications and does not specify a group, the system automatically groups them together on Android 7.0 and higher.
- 关键api : setGroup() setGroupSummary(true)
1.6 渠道管理
https://developer.android.com/training/notify-user/channels
- 8.0通过渠道设置通知优先级
- 如何打开通知设置页面. Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS); 等等...
- 删除渠道.
- 创建渠道组及何时有必要使用渠道组.
1.7 应用图标的通知角标
https://developer.android.com/training/notify-user/badges
- 8.0开始的新特性.
- 通过渠道参数设置开启这个功能
- setNumber() 设置通知角标数.
2.自定义通知示例
样式大致这样
2.1 自定义通知布局
1 <?xml version="1.0" encoding="utf-8"?> 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:app="http://schemas.android.com/apk/res-auto" 4 android:orientation="vertical" 5 android:layout_gravity="center" 6 android:layout_width="match_parent" 7 android:layout_height="64dp"> 8 9 <ImageView 10 android:id="@+id/notify_icon" 11 android:layout_width="22dp" 12 android:layout_height="22dp" 13 android:layout_alignParentLeft="true" 14 android:layout_alignParentTop="true" 15 android:layout_gravity="center_vertical" 16 android:layout_marginLeft="8dp" 17 android:layout_marginTop="8dp" 18 app:srcCompat="@mipmap/notify_icon" /> 19 20 <TextView 21 android:id="@+id/notify_title" 22 android:layout_width="wrap_content" 23 android:layout_height="wrap_content" 24 android:layout_gravity="center_vertical" 25 android:layout_marginLeft="8dp" 26 android:ellipsize="end" 27 android:gravity="center_vertical" 28 android:maxEms="9" 29 android:maxLines="1" 30 android:text="@string/app_name" 31 android:textColor="#333333" 32 android:layout_toRightOf="@+id/notify_icon" 33 android:layout_alignTop="@+id/notify_icon" 34 android:layout_alignBottom="@+id/notify_icon" 35 android:textSize="10sp" /> 36 37 <TextView 38 android:id="@+id/notify_progress_title" 39 android:layout_width="wrap_content" 40 android:layout_height="wrap_content" 41 android:layout_below="@+id/notify_icon" 42 android:layout_alignLeft="@+id/notify_icon" 43 android:layout_marginTop="4dp" 44 android:ellipsize="end" 45 android:gravity="left|center_vertical" 46 android:maxEms="9" 47 android:maxLines="1" 48 android:text="title" 49 android:layout_toLeftOf="@+id/notify_progress_status" 50 android:textAllCaps="false" 51 android:textColor="#333333" 52 android:textSize="12sp" 53 android:textStyle="bold" /> 54 55 <ProgressBar 56 android:id="@+id/notify_progress" 57 style="?android:attr/progressBarStyleHorizontal" 58 android:layout_width="wrap_content" 59 android:layout_height="4dp" 60 android:layout_below="@+id/notify_progress_title" 61 android:layout_alignLeft="@+id/notify_progress_title" 62 android:layout_alignRight="@+id/notify_progress_status" 63 android:layout_marginTop="4dp" 64 android:progressDrawable="@drawable/notify_progress_bar" /> 65 66 <TextView 67 android:id="@+id/notify_progress_status" 68 android:layout_width="wrap_content" 69 android:layout_height="wrap_content" 70 android:layout_marginRight="25dp" 71 android:layout_alignBottom="@+id/notify_progress_title" 72 android:layout_alignTop="@+id/notify_progress_title" 73 android:layout_toLeftOf="@+id/notify_down_btn" 74 android:maxLines="1" 75 android:maxEms="9" 76 android:ellipsize="end" 77 android:textAllCaps="false" 78 android:textColor="#999999" 79 android:textSize="10sp" 80 android:gravity="center" 81 android:text="status" /> 82 83 <ImageView 84 android:layout_width="15dp" 85 android:layout_height="15dp" 86 android:id="@+id/notify_down_btn" 87 android:background="@null" 88 android:layout_alignTop="@+id/notify_close_btn" 89 android:layout_marginRight="20dp" 90 android:layout_toLeftOf="@+id/notify_close_btn" 91 android:src="@mipmap/notify_start" 92 /> 93 94 <ImageView 95 android:layout_width="15dp" 96 android:layout_height="15dp" 97 android:id="@+id/notify_close_btn" 98 android:layout_marginRight="15dp" 99 android:layout_alignTop="@+id/notify_progress_title" 100 android:src="@mipmap/notify_close" 101 android:background="@null" 102 android:layout_alignParentRight="true" 103 android:layout_alignParentEnd="true" 104 /> 105 <LinearLayout 106 android:orientation="vertical" 107 android:layout_width="40dp" 108 android:layout_height="match_parent" 109 android:id="@+id/notify_close" 110 android:background="@color/hot_color" 111 android:layout_alignBottom="@+id/notify_progress" 112 android:layout_alignParentRight="true" 113 android:visibility="visible" 114 /> 115 <LinearLayout 116 android:id="@+id/notify_down" 117 android:layout_width="50dp" 118 android:layout_height="match_parent" 119 android:layout_alignBottom="@+id/notify_close" 120 android:layout_toLeftOf="@+id/notify_close" 121 android:background="@color/hot_color" 122 android:src="@mipmap/notify_start" 123 android:orientation="vertical" 124 android:visibility="visible" /> 125 126 </RelativeLayout>
注意事项:
- 参考官方文档,考虑不同屏幕大小,通知栏的高度推荐(不强制)为:收起时为64dp,展开时为256dp
- notify根布局目前(sdk api 28)还不能用约束布局。
- 布局内不能出现 <View ....>
- ImageView不要使用 app:srcCompat,用 android:src
2.2 初始化
1 NotificationManagerCompat mNotificationManager; 2 RemoteViews notifyView; 3 NotificationCompat.Builder builder; 4 5 private void initNotify() { 6 //1.得到 NotificationManagerCompat 7 8 mNotificationManager = NotificationManagerCompat.from(getApplicationContext()); 9 //2.构造Notify上的View 10 notifyView = new RemoteViews(getPackageName(), R.layout.notify); 11 12 //自定义notify上按钮的事件 13 Intent downIntent = new Intent(getApplicationContext(), NotifyReceiver.class); 14 downIntent.setAction(NotifyReceiver.DOWN_ACTION); 15 //用广播接收按钮事件,也可以用服务组件或者activity PendingIntent有一系列函数可选。 16 PendingIntent down = PendingIntent.getBroadcast(getApplicationContext(), 1, downIntent, PendingIntent.FLAG_UPDATE_CURRENT); 17 notifyView.setOnClickPendingIntent(R.id.notify_down, down); 18 19 Intent closeIntent = new Intent(getApplicationContext(), NotifyReceiver.class); 20 closeIntent.setAction(NotifyReceiver.CLOSE_ACTION); 21 22 PendingIntent close = PendingIntent.getBroadcast(getApplicationContext(), 2, closeIntent, PendingIntent.FLAG_UPDATE_CURRENT); 23 notifyView.setOnClickPendingIntent(R.id.notify_close, close); 24 25 //3.构造Notify,设置通知的参数 26 String channelId = getString(R.string.notify_id);//在 strings.xml中定义 27 builder = new NotificationCompat.Builder(getApplicationContext(),channelId); 28 29 30 //点击整个通知栏时,响应通知的应用组件。 31 Intent intent = new Intent(getApplicationContext(), MainActivity.class); 32 PendingIntent notifyIntent = PendingIntent.getActivity(getApplicationContext(), 1, intent, PendingIntent.FLAG_UPDATE_CURRENT); 33 34 //设置通知栏参数 35 builder.setWhen(System.currentTimeMillis()) 36 .setPriority(NotificationCompat.PRIORITY_MAX) //优先级. 37 .setAutoCancel(true) 38 .setOngoing(false) 39 .setContentIntent(notifyIntent) 40 .setCustomContentView(notifyView) //完全自定义,不调用.setStyle() 41 .setContent(notifyView) //兼容4.1及以下 42 .setVisibility(NotificationCompat.VISIBILITY_PRIVATE) 43 .setOnlyAlertOnce(true) 44 .setSmallIcon(R.mipmap.notify_logo); 45 }
- 初始notify上自定义的view,重点在按钮事件
- 如果在一定时间内通知的频繁,系统会忽略其中的一部分.
- OnlyAlterOnce保证频频调用时,声音等警示事件只1次.
- RemoteViews和NotificationCompat.Builder 保存成成员变量是因为notify的频次高,不用每次都构造.
2.3 发出通知
1 public synchronized void showDownloadingNotify() { 2 //4.根据业务逻辑控制notify上控件的状态 3 //...这里略 4 int icon = downloading ? R.mipmap.notify_pause : R.mipmap.notify_start; 5 6 int pro = (int) (downloaded * 100.00F / total); 7 Log.e(TAG, "showDownloadingNotify: downloading progress = " + pro); 8 9 notifyView.setTextViewText(R.id.notify_progress_title, title); 10 notifyView.setTextViewText(R.id.notify_progress_status,getString(R.string.downloading_downloaded) + " " + pro + "%" ); 11 notifyView.setImageViewResource(R.id.notify_down_btn, icon); 12 notifyView.setProgressBar(R.id.notify_progress,100, pro,false); 13 14 Notification notify = builder.build(); 15 16 //5.发出通知 17 String channelId = getString(R.string.notify_id);//在 strings.xml中定义 18 mNotificationManager.notify(Integer.valueOf(channelId), notify); 19 }
注意控制发出通知的频率,不要太快,如:
1 if(last < 1){ 2 last = SystemClock.elapsedRealtime() / 1000; 3 } 4 long now = SystemClock.elapsedRealtime() / 1000; 5 if (now - last > 1){ 6 last = now; 7 showDownloadingNotify(); 8 }
2.4 接收通知按钮事件的组件
可接收事件的组件:静态广播、导出的服务(onStartCommand会被调用)、activity
本示例中使用的是静态广播,动态广播不行。
1 public class NotifyReceiver extends BroadcastReceiver { 2 public final static String DOWN_ACTION = "com.cnblogs.sjjg.notifications.intent.action.DownClick"; 3 public final static String CLOSE_ACTION = "com.cnblogs.sjjg.notifications.intent.action.CloseClick"; 4 5 @Override 6 public void onReceive(Context context, Intent intent) { 7 String action = intent.getAction(); 8 Log.e("NotifyReceiver", "onReceive: action = " + action); 9 switch (action) { 10 case DOWN_ACTION: 11 //处理第1个按钮事件,开始下载 12 break; 13 case CLOSE_ACTION: 14 //处理第2个按钮事件,关闭通知 15 break; 16 default: 17 break; 18 } 19 } 20 }
在AndroidManifest.xml中注册
1 ... 2 <receiver 3 android:name=".main.NotifyReceiver" 4 android:enabled="true" 5 android:exported="true"> 6 <intent-filter> 7 <action android:name="com.cnblogs.sjjg.notifications.intent.action.DownClick" /> 8 <action android:name="com.cnblogs.sjjg.notifications.intent.action.CloseClick" /> 9 </intent-filter> 10 </receiver> 11 ...
2.5 取消通知
取消的id要与发出的id相同。
1 public void cancelNotify(){ 2 String channelId = getString(R.string.notify_id); 3 mNotificationManager.cancel(Integer.valueOf(channelId)); 4 }
3.android 8.0适配
3.1 变化
- 必须将各个通知放入特定渠道中,否则不显示.一个应用可以有多个渠道.
- 用户可以按渠道关闭通知,而非关闭来自某个应用的所有通知。
- 包含有效通知的应用将在主屏幕/启动器屏幕上相应应用图标的上方显示通知“标志”。
- 用户可以从抽屉式通知栏中暂停某个通知。可以为通知设置自动超时时间。
- 可以设置通知的背景颜色。
- 部分与通知行为相关的 API 从
Notification
移至了NotificationChannel
。例如,在搭载 Android 8.0 及更高版本的设备中,使用NotificationChannel.setImportance()
,而非NotificationCompat.Builder.setPriority()
。
3.2 创建通知渠道
选择适当时机,创建渠道.
1 @RequiresApi(api = Build.VERSION_CODES.O) 2 private void createNotifyChannel(){ 3 NotificationManager nm = (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE); 4 String channelId = getString(R.string.notify_id); 5 NotificationChannel channel = nm.getNotificationChannel(channelId); 6 if (channel == null){ 7 channel = new NotificationChannel(channelId, "Downloader", NotificationManager.IMPORTANCE_DEFAULT); 8 channel.enableLights(true); 9 channel.setLightColor(Color.RED); 10 channel.setImportance(NotificationManager.IMPORTANCE_DEFAULT); 11 channel.setShowBadge(true); 12 nm.createNotificationChannel(channel); 13 } 14 }
3.3 检测通知权限是否打开
1 NotificationManagerCompat mNotificationManager; 2 mNotificationManager = NotificationManagerCompat.from(getContext()); 3 boolean enable = mNotificationManager.areNotificationsEnabled(); 4 if (!enable){ 5 //打开通知权限 6 ... 7 }
3.4 打开通知权限
1 final int NOTIFY_PER = 10293; 2 Intent intent = new Intent(); 3 Context context = getContext(); 4 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) { 5 intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS); 6 intent.putExtra("android.provider.extra.APP_PACKAGE", context.getPackageName()); 7 }else if(Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT_WATCH){ 8 intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS); 9 intent.putExtra("app_package", context.getPackageName()); 10 intent.putExtra("app_uid", context.getApplicationInfo().uid); 11 }else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT){ 12 intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 13 intent.addCategory(Intent.CATEGORY_DEFAULT); 14 intent.setData(Uri.parse("package:" + context.getPackageName())); 15 }else{ 16 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 17 intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 18 intent.setData(Uri.fromParts("package", context.getPackageName(), null)); 19 } 20 startActivityForResult(intent, NOTIFY_PER);
3.5 接收打开结果
1 @Override 2 public void onActivityResult(int requestCode, int resultCode, Intent data) { 3 final int NOTIFY_PER = 10293; 4 if (requestCode == NOTIFY_PER){ 5 NotificationManagerCompat notifyMgr = NotificationManagerCompat.from(getContext()); 6 boolean enable = notifyMgr.areNotificationsEnabled(); 7 Log.e("MainPresenter", "onActivityResult: request notify = " + enable ); 8 if (!enable){ 9 Snackbar.make(more,"通知功能没有打开,无法在通知栏上显示进度!",Snackbar.LENGTH_SHORT).show(); 10 } 11 } 12 }
4.系统自带通知栏
1 fun showNotify(warning: Warning){ 2 var mgr = NotificationManagerCompat.from(applicationContext) 3 var notifyId = getString(R.string.notify_id) 4 5 var content = warning.oldie.name + " " + warning.getTypeString() 6 var builder = NotificationCompat.Builder(applicationContext,notifyId) 7 var intent = Intent(applicationContext, MainActivity::class.java) 8 var pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); 9 builder.setContentIntent(pendingIntent); 10 builder.setWhen(System.currentTimeMillis()) 11 .setPriority(NotificationCompat.PRIORITY_MAX) //优先级. 12 .setAutoCancel(true) 13 .setOngoing(false) //常驻通知栏 14 .setContentText(content) 15 .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.warning)) 16 .setContentTitle("监护警报") 17 .setVisibility(NotificationCompat.VISIBILITY_PRIVATE) 18 .setOnlyAlertOnce(true) 19 .setSmallIcon(R.mipmap.icon) 20 var notify = builder.build() 21 mgr.notify(notifyId.toInt(),notify) 22 } 23 fun cancelNotify(){ 24 var mgr = NotificationManagerCompat.from(applicationContext) 25 26 val channelId = getString(R.string.notify_id) 27 28 mgr.cancel(channelId.toInt()) 29 }
onStartCommand