自定义一个有按钮的通知栏及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

    1. NotificationCompat.BigPictureStyle,
    2. NotificationCompat.BigTextStyle
    3. NotificationCompat.InboxStyle , 
    4. NotificationCompat.MessagingStyle,
    5. 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
posted @ 2015-12-12 21:22  f9q  阅读(1174)  评论(0编辑  收藏  举报