Notifications(通知)

通知

通知是能在應用的普通用戶界面外顯示給用戶的一種消息。當你告訴系統發佈一條通知時,它首先在通知欄中表現爲一枚圖標。用戶打開通知抽屜後就能查看通知的細節了。通知欄和通知抽屜都是由系統控制的區域,用戶可以任何時間都能查看他們。

圖1. 在通知欄裏的通知。

圖2. 在通知抽屜裏的通知。

通知的設計

通知,作爲Android用戶界面裏的非常重要的一部分有着它自己的設計指南。閱讀Android設計指南 Notifications 學習如何設計通知和它們的交互。

註解:除非特別指明,指南里引用的是版本號爲4的支持庫中的 NotificationCompat.Builder 類。Notification.Builder 類直到Android 3.0才被引入。

通知的顯示元素


根據通知抽屜的版本和的狀態,在通知抽屜裏的通知會有兩種視覺樣式:

普通視圖
在通知抽屜裏的通知的標準視圖。
大視圖
當通知展開時可見的大視圖。大視圖是展開的通知的一部分,Android 4.1以上可用。

下面的章節會具體介紹這些樣式。

普通視圖

普通視圖的通知顯示在一個高度不超過64dp的區域裏。即使你創建了一個大視圖樣式的通知,在展開前它依舊以普通視圖顯示。下面是普通視圖的事例:


圖3. 普通視圖的通知。

圖例中的編號請參考下面的說明:

  1. 內容標題
  2. 大號圖標
  3. 內容文本
  4. 內容信息
  5. 小號圖標
  6. 通知出現的時間。你可以使用 setWhen() 明確指定數值,如果沒指定的話,默認時間是系統接受到通知的時間。

大視圖

通知的大視圖僅當通知展開時才顯示,也就是當通知在通知抽屜的頂部或用戶用手勢展開通知時。展開通知功能從Android 4.1開始可用。

下面的截圖展示的是一個收件箱樣式的通知:


圖4. 大視圖通知。

注意大視圖和普通視圖的大部分視覺元素都相同。唯一的不同是插圖7的詳情區域。每種大視圖樣式以不同的點綴。可用的樣式爲:

大圖樣式
這個細節區域包含了一張最大256dp高度的位圖。
大文本樣式
在細節區域顯示一個大號文本塊。
收件箱樣式
在細節區域顯示文本行。

所有的大視圖樣式同樣擁有下面的內容選項,這些選項不會在普通視圖中生效:

大內容標題
允許你重寫普通視圖的內容標題,該標題僅在展開的視圖中顯示。
摘要文本
允許你在細節區域下添加一行文本。

將大視圖樣式應用到通知中在 Applying a big view style to a notification 章節中有詳細描述。

創建通知


你可以使用 NotificationCompat.Builder 對象指定通知的用戶界面信息和操作。調用  NotificationCompat.Builder.build() 創建通知會返回一個包含你要求的 Notification 對象。通過調用 NotificationManager.notify() 來把 Notification  對象傳遞給系統就能發佈一個通知了。

必要的通知內容

Notification 對象必須包含下列內容:

可選的通知內容和設置

除必要的通知內容之外其他的通知內容和設置都是可選的。瞭解更多內容,請閱讀 NotificationCompat.Builder 的參考文檔。

通知操作

儘管他們是可選的,你應該至少爲你的通知添加一個操作。操作可以使用戶從通知直接進入你應用中的 Activity ,在那裏他們能直觀的看到一個或更多的事件或做更進一步的事。

通知也可以提供多個操作。你應該總是定義用戶點擊通知時觸發的操作,通常這種操作會打開你應用中的一個 Activity。你也可以把按鈕添加到通知裏來處理比如暫停鬧鐘或立即回覆文本信息的附加操作,該功能從Android 4.1起可用。如果使用附加操作按鈕,你同樣必須使他們在在應用中的 Activity 裏功能可用,閱讀 Handling compatibility 章節獲取更多詳細信息。

在通知裏,操作是由一個 PendingIntent 對象定義的,該對象包含了一個啓動應用裏某個 Activity 的 Intent 對象。調用 NotificationCompat.Builder 裏的合適的方法就能把手勢和 PendingIntent 關聯起來。例如,如果你想在用戶點擊通知抽屜裏的通知文本時打開 Activity,你可以調用 setContentIntent() 添加 PendingIntent 來實現。

當用戶點擊通知時打開 Activity 是最常用的操作方案。你還可以在用戶忽略通知時打開 Activity。在Android 4.1和以上版本中還可以從操作按鈕中打開 Activity。瞭解更多內容,請閱讀 NotificationCompat.Builder 的參考指南。

創建簡單的通知

下面的代碼舉例創建了一個簡單的通知,當用戶點擊通知時會打開activity。注意這段代碼創建了一個 TaskStackBuilder 對象然後用它爲操作創建 PendingIntent。這種模式在 Preserving Navigation when Starting an Activity 章節中有更詳細的解釋。

NotificationCompat.Builder mBuilder =
        new NotificationCompat.Builder(this)
        .setSmallIcon(R.drawable.notification_icon)
        .setContentTitle("My notification")
        .setContentText("Hello World!");
// 爲你應用中的activity創建一個顯式的intent
Intent resultIntent = new Intent(this, ResultActivity.class);

// 堆棧創建器對象會爲將要啓動的Activity創建一個導航回退棧。
// 這樣可以確保在Activity後退導航時可以直接把應用回退到主屏幕上。
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
// 爲這個Intent添加回退棧
stackBuilder.addParentStack(ResultActivity.class);
// 把開啓Activity的Intent添加到棧頂
stackBuilder.addNextIntent(resultIntent);
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent( 0, PendingIntent.FLAG_UPDATE_CURRENT);
mBuilder.setContentIntent(resultPendingIntent);
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// mId可以使你以後再更新通知。
mNotificationManager.notify(mId, mBuilder.build());

就是這樣。你的用戶現在被通知到了。

爲通知應用大視圖樣式

想要使通知在展開時以大視圖樣式呈現,首先使用你想要的普通視圖選項去創建一個 NotificationCompat.Builder 對象,接着用大視圖樣式對象作爲參數調用 Builder.setStyle()

切記展開的通知在Android 4.1之前的平臺上無法使用。閱讀 Handling compatibility 章節,學習如何在Android 4.1和更早的平臺上處理通知。

例如,下面的代碼片段演示瞭如何使用收件箱的大視圖樣式修改前面代碼片段創建的通知:

NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
    .setSmallIcon(R.drawable.notification_icon)
    .setContentTitle("Event tracker")
    .setContentText("Events received")
NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
String[] events = new String[6];
// 爲收件箱樣式的大視圖設置標題
inboxStyle.setBigContentTitle("Event tracker details:");
...
// 填充大視圖
for (int i=0; i < events.length; i++) {
    inboxStyle.addLine(events[i]);
}
// 將大視圖樣式對象傳遞給通知對象。
mBuilder.setStyle(inBoxStyle);
...
// 發佈通知

處理兼容性

儘管使用了支持庫裏 NotificationCompat.Builder 的方法設置了通知,但是對於某些特別的版本來說,並不是所有的通知特徵都能生效。例如,操作按鈕依賴於展開的通知,因爲展開的通知僅在Android 4.1或更高的平臺上可用,所以它也只能在Android 4.1或更高的平臺上出現。

爲了得到最好的兼容性,請使用 NotificationCompat 和它的子類來創建通知, 尤其是 NotificationCompat.Builder。除此之外,在創建通知時請遵守下面的步驟:

  1. 無論用戶正在使用的是什麼版本的平臺,爲他們提供全部通知的功能。要達到這種效果,需要在你應用中的一個 Activity 裏驗證所有功能是否可用。你可能需要添加一個新的 Activity 來驗證。

    例如,如果你想使用 addAction() 來添加一個可以開關媒體回放功能的控制器,首先你應該在你應用中的一個 Activity 裏實現這個控制器。

  2. 確保所有用戶可以在這個  Activity 裏通過點擊通知可以接觸到這個功能。要達到這種效果,爲這個 Activity 創建一個 PendingIntent,通過調用 setContentIntent() 來把這個 PendingIntent 添加到通知中。
  3. 現在把你想要使用的擴展通知特徵添加到通知中。切記當用戶點擊通知時觸發啓動的所有你添加的功能必須是在 Activity 裏可用。

管理通知


當你需要爲同樣類型的事件多次發佈通知時,你應該避免創建一個全新的通知。相反,你應該考慮更新先前的通知,你可以改變它的一些值或添加新的值。

例如,Gmail通過新增未讀消息數目和把每個郵件的概要添加到通知來通知用戶已接受到新的郵件。這杯稱爲“堆疊”通知,在 Notifications 設計指南中有更詳細的描述。

註解:這種Gmail特性 需要”收件箱“的大視圖樣式, 它是展開通知特徵的一部分,自Android 4.1起可用。

下面的章節講解了如何更新通知和移除通知。

更新通知

創建一個通知並通過調用 NotificationManager.notify(ID, notification) 和通知ID發佈通知,這樣就能更新通知了。要想更新你曾經發布的通知,可以更新或創建一個 NotificationCompat.Builder 對象,然後用它構建一個 Notification 對象,使用你先前用過的相同ID發佈這個 Notification 就可以了。如果先前的通知依然可見,系統會更新 Notification 對象的內容。如果先前的通知已經消失了,就會新建一個通知。

下面的代碼演示了更新一個通知來反映已發生事件的個數。它把通知摺疊起來,只顯示一個概要:

mNotificationManager =
        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// 爲通知設置一個ID以便可以更新它
int notifyID = 1;
mNotifyBuilder = new NotificationCompat.Builder(this)
    .setContentTitle("New Message")
    .setContentText("You've received new messages.")
    .setSmallIcon(R.drawable.ic_notify_status)
numMessages = 0;
// 開始循環處理數據並通知用戶
...
    mNotifyBuilder.setContentText(currentText)
        .setNumber(++numMessages);
    // 由於ID保持不變,已存在的通知被更新了
    mNotificationManager.notify(
            notifyID,
            mNotifyBuilder.build());
...

就會像這樣產生一個通知:


圖5.更新在通知抽屜裏顯示的通知。

移除通知

當下列情形之一發生時通知就不再可見:

  • 用戶單個取消通知或通過使用“清除全部”取消通知(如果通知可以被清除的話)。
  • 用戶點擊通知並且當你創建通知時調用了 setAutoCancel()
  • 你爲一個指定的通知ID調用了 cancel()。這個方法也會刪除進行中的通知。
  • 你調用了cancelAll(),這樣就會移除你先前發佈的所有通知。

啓動Activity時維持導航


當你從一個通知裏啓動一個 Activity 時,你必須維持用戶期望的導航體驗。點擊回退鍵應該把用戶從應用正常工作流帶回到主屏幕並且點擊最近打開應用可以看到這個 Activity 作爲一個獨立的任務顯示。爲了維持導航體驗,你應該在一個全新的任務棧中啓動 Activity。如何創建 PendingIntent 來獲得一個新的任務棧依賴於你正在啓動的 Activity 的性質。有兩種概況:

普通的activity
你正要開啓一個屬於應用的正常工作流一部分的 Activity。在這種情況下,創建 PendingIntent 來開啓一個新的任務棧並把應用的正常的回退行爲複製到一個回退棧中供 PendingIntent 使用。

Gmail應用裏的通知演示了這種效果。當你點擊通知查看單條郵件信息時,你會直接看到信息本身。按下回退鍵將會帶你穿過Gmail回到主屏幕,恰如你是從主屏幕進入到Gmail的而不是通過通知進入的。

當你點擊通知時不管你是不是在應用中都會像這樣。例如,如果你正在Gmail裏編輯信息時點擊一個查看單條郵件的通知,你會立刻進入那條信息的詳情界面,點擊回退鍵會把你帶回到收件箱然後到主屏幕,而不是你剛纔正在編輯消息的界面。

特別的activity
如果它是從通知啓動的那麼用戶只會看到這個 Activity。從某種意義上講,Activity 通過展示那些很難在通知裏顯示的信息擴展了通知。針對這種情況,創建 PendingIntent 來開啓一個新的任務棧,可是不需要創建回退棧,因爲已啓動的 Activity 不屬於應用的activity流的一部分,點擊回退鍵會直接把用戶帶回主屏幕。

創建一個普通的activity PendingIntent

請按照下列步驟來創建一個可以直接啓動 Activity 的 PendingIntent

  1. 在清單中定義應用的 Activity 層級。
    1. 爲Android 4.0.3或更早的版本添加支持。通過在  <activity> 裏添加一個 <meta-data> 子元素來爲你準備啓動的 Activity 指定父級來實現。

      對於這個元素,設置 android:name="android.support.PARENT_ACTIVITY" 和 android:value="<parent_activity_name>"<parent_activity_name>是用來指定父級 <activity> 元素的 android:name 的值。參見下面的XML事例。

    2. 同樣要爲Android 4.1或更高的版本添加支持。在你要啓動的 Activity 的 <activity> 元素裏添加 android:parentActivityName 屬性來實現。

    最終的XML應該像這樣:

    <activity
        android:name=".MainActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <activity
        android:name=".ResultActivity"
        android:parentActivityName=".MainActivity">
        <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value=".MainActivity"/>
    </activity>
  2. 基於啓動 Activity 的 Intent 創建一個回退棧:
    1. 創建啓動 Activity 的 Intent
    2. 通過調用 TaskStackBuilder.create() 創建stack builder。
    3. 通過調用 addParentStack() 來把回退棧添加到stack builder中。對於你在清單中定義的層級中的每一個 Activity,回退棧包含了啓動這個 Activity 的 Intent 對象。這個方法同樣添加了在一個新的任務棧中開啓回退棧的標識。

      註解:儘管 addParentStack() 的參數是將要啓動的 Activity 的引用,這個方法被調用時卻不添加開啓這個 Activity 的 Intent,而是在下一步實現。

    4. 通過調用 addNextIntent() 把啓動這個 Activity 的 Intent 添加進來。把你在第一步裏創建的 Intent 作爲參數傳遞給 addNextIntent()
    5. 如果需要的話,通過調用 TaskStackBuilder.editIntentAt() 爲堆棧上的 Intent 對象添加參數。有時候確保目標 Activity 顯示有意義的數據是很必要的,例如當用戶使用回退鍵導航到它時。
    6. 通過調用 getPendingIntent() 爲這個回退棧獲取 PendingIntent。然後你可以用這個 PendingIntent 作爲 setContentIntent() 的參數使用。

下面的代碼片段演示了這個過程:

...
Intent resultIntent = new Intent(this, ResultActivity.class);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
// 添加回退棧
stackBuilder.addParentStack(ResultActivity.class);
// 把Intent添加到棧頂
stackBuilder.addNextIntent(resultIntent);
// 獲取包含整個回退棧的 PendingIntent
PendingIntent resultPendingIntent =
        stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
...
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setContentIntent(resultPendingIntent);
NotificationManager mNotificationManager =
    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.notify(id, builder.build());

創建一個特別的activity PendingIntent

下面的章節講解如何創建一個特別的activity PendingIntent

特別的 Activity 不需要回退棧,所以你不必在清單中定義它的 Activity 層級,且不需要調用 addParentStack() 構建回退棧,而是使用清單來創建 Activity 任務棧選項,這樣通過調用 getActivity() 來創建 PendingIntent

  1. 在清單文件中,爲 Activity 的 <activity> 元素添加下列屬性
    android:name="activityclass"
    activity的全限定類名。
    android:taskAffinity=""
    結合你在代碼中設置的 FLAG_ACTIVITY_NEW_TASK 標識就可以確保這個 Activity 不會進入應用默認的任務棧。任何擁有應用默認affinity的已存在的任務棧都不會受到影響。
    android:excludeFromRecents="true"
    從最近使用列表裏排除這個新的任務棧防止用戶一不小心回退到它。

    This snippet shows the element:

    <activity
        android:name=".ResultActivity"
    ...
        android:launchMode="singleTask"
        android:taskAffinity=""
        android:excludeFromRecents="true">
    </activity>
    ...
  2. 構建併發布通知:
    1. 創建啓動 Activity 的 Intent
    2. 通過調用 setFlags() 並使用 FLAG_ACTIVITY_NEW_TASK 和 FLAG_ACTIVITY_CLEAR_TASK 標識符爲這個 Activity 開啓一個新的且空的任務棧。
    3. 爲這個 Intent 設置其他你需要的參數。
    4. 通過調用 getActivity() 從這個 Intent 創建一個 PendingIntent。然後你就能把這個 PendingIntent 作爲 setContentIntent() 的參數使用。

    下面的代碼片段演示了這個過程:

    // 實例化一個Builder對象。
    NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
    // 爲這個Activity創建一個Intent
    Intent notifyIntent =
            new Intent(new ComponentName(this, ResultActivity.class));
    // 爲這個Activity設置開啓一個新的且空的任務棧
    notifyIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
    // 創建PendingIntent
    PendingIntent notifyIntent =
            PendingIntent.getActivity(
            this,
            0,
            notifyIntent
            PendingIntent.FLAG_UPDATE_CURRENT
    );
    
    // 把 PendingIntent 填充到通知的builder裏
    builder.setContentIntent(notifyIntent);
    // 通過把通知發送到NotificationManager系統服務中來發布通知。
    NotificationManager mNotificationManager =
        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    // 使用builder構建匿名的通知對象,然後傳遞給NotificationManager
    mNotificationManager.notify(id, builder.build());

在通知中顯示進度條


通知可以包含一個活動的進度條來向用戶顯示正在進行中的操作的狀態。如果你能估計操作會執行多久和任何時刻完成了多少的話,使用“確定時長的”指示器 (進度條)。如果你不能估計操作的執行時長,使用“不確定時長的”指示器(活動指示器)。

使用平臺的 ProgressBar 類的擴展來顯示進度條指示器。

在Android 4.0以上的平臺使用進度條指示器,調用 setProgress()。對於更早的版本,你必須創建自己的包含 ProgressBar 視圖的自定義通知佈局。

下面的章節講述瞭如何使用 setProgress() 在通知中顯示進度條。

顯示固定時長的進度條指示器

調用 setProgress() setProgress(max, progress, false) 把進度條添加到通知中然後發佈就能顯示一個確定時長的進度條了。在操作執行時,增加progress並更新通知。在操作執行到最後,progress應該達到max。一個通用的調用 setProgress() 的方式是把max設置爲100且把增量progress作爲操作“完成度”的值。

你可以在操作完成時依舊顯示進度條或移除它。無論哪種方式,記得去更新通知文本去表示操作已經完成了。調用 setProgress() setProgress(0, 0, false) 來移除進度條。例如:

...
mNotifyManager =
        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mBuilder = new NotificationCompat.Builder(this);
mBuilder.setContentTitle("Picture Download")
    .setContentText("Download in progress")
    .setSmallIcon(R.drawable.ic_notification);
// 在後臺線程執行一個漫長的操作
new Thread(
    new Runnable() {
        @Override
        public void run() {
            int incr;
            // 執行這個“漫長的”操作20次
            for (incr = 0; incr <= 100; incr+=5) {
                    // 設置進度條指示器的最大值,當前完成度和“確定時長的”狀態
                    mBuilder.setProgress(100, incr, false);
                    // 首次顯示進度條。
                    mNotifyManager.notify(0, mBuilder.build());
                        // 線程休眠,模擬操作耗費時間
                        try {
                            // 休眠5秒鐘
                            Thread.sleep(5*1000);
                        } catch (InterruptedException e) {
                            Log.d(TAG, "sleep failure");
                        }
            }
            // 當循環執行結束,更新通知
            mBuilder.setContentText("Download complete")
            // 移除進度條
                    .setProgress(0,0,false);
            mNotifyManager.notify(ID, mBuilder.build());
        }
    }
// 在Runnable裏調用run()方法執行線程
).start();

通知結果如圖6所示。左邊的是操作執行期間通知的截圖,右邊的是操作完成後的截圖。


圖6.執行期和執行後的通知。

顯示持續的活動指示器

使用 setProgress(0, 0, true) (前兩個參數可以忽略)把活動指示器添加到通知中然後發佈就可以顯示一個不確定時長的活動指示器。最終的指示器和進度條有一樣的樣式,只是它的動畫始終在執行。

在操作開始時發佈通知,動畫會一直持續到你修改通知。在操作執行完時調用 setProgress() setProgress(0, 0, false) 然後更新通知來移除活動指示器。請確保始終這樣執行,否則在操作完成時動畫還會繼續執行,別忘了更新通知文本去表示操作已經完成了。

瞭解活動指示器如何工作,參考前面的代碼片段。查找以下行:

// 設置進度條指示器的最大值,當前完成度和“確定時長的”狀態
mBuilder.setProgress(100, incr, false);
// 發佈通知
mNotifyManager.notify(0, mBuilder.build());

把你找到的這些行替換成以下行:

 // 爲不確定時長的操作設置活動指示器
mBuilder.setProgress(0, 0, true);
// 發佈通知
mNotifyManager.notify(0, mBuilder.build());

通知結果如圖7所示:


圖7.正在執行的活動指示器

自定義通知佈局


通知框架允許你在 RemoteViews 對象裏定義通知的外觀來自定義通知的佈局。自定義通知與普通通知很像,只是他們是基於在XML佈局文件中定義的 RemoteViews

自定義 通知佈局的可用高度依賴於通知視圖的類型。普通視圖佈局限制在64dp內,展開視圖的佈局限制在256dp內

定義自定義通知佈局,首先要實例化一個可以填充XML佈局文件的 RemoteViews 對象,然後調用 setContent() 而不是類似 setContentTitle() 的方法。通過使用 RemoteViews 裏的方法設置視圖子元素的值來設置自定義通知的內容詳情:

  1. 在一個單獨的文件中爲通知創建XML佈局。你可以使用任何你想用的名字,但是必須以.xml結尾。
  2. 在你的應用中,使用 RemoteViews 的方法定義通知的圖標和文本。通過調用 setContent() 把這個 RemoteViews 對象放入你的 NotificationCompat.Builder 中。要避免爲你的 RemoteViews 對象設置背景 Drawable 圖片,因爲這樣你的文本顏色可能變得無法辨別。

RemoteViews 類同樣也包含你可以很容易在你的通知佈局中添加 Chronometer 或 ProgressBar 的方法。請參考 RemoteViews 參考文檔瞭解更多關於爲通知創建自定義佈局的信息。

注意:當你使用自定義通知佈局時,請特別注意要確保你的自定義佈局能夠在不同方向和分辨率的設備上工作。雖然這條建議適用於所有的視圖佈局,對於通知來說尤其重要,因爲通知抽屜裏的空間要求非常嚴格。不要把自定義佈局弄的太複雜並且要保證在不同配置的設備上測試。

爲自定義通知的文本使用樣式資源

始終爲自定義通知的文本使用樣式資源。在不同設備和不同版本的平臺上通知的背景顏色會有差異,使用樣式資源可以幫助你解決這個問題。從Android 2.3開始,系統爲標準通知佈局的文本定義了樣式。如果你在目標版本爲Android 2.3或更高的應用中使用相同的樣式,你可以確保你的文本是區別於顯示的背景色可見。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章