1. 程式人生 > >Android中通知欄的使用

Android中通知欄的使用

通知欄應該屬於Android最常用的功能之一。目前幾乎您認識的APP都會見過它們彈出的通知欄。通知欄的使用其實並不難,但您有沒有感覺到隨著整個Android的迭代會出現很多種用法,新版本的通知欄API無法相容老系統這就會是一個很頭疼的問題。本篇博文將會通過回顧歷史使用情況和通用程式碼情況以及自定義通知欄來解析通知欄的各種使用方法。

系統預設通知欄使用

這是Android 3.0以下的系統中(API Level < 11)

private void showNotification() {
   PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, newIntent(this, Main2Activity.class), PendingIntent.FLAG_UPDATE_CURRENT);
   Notification notification = new Notification();
   notification.icon = R.mipmap.ic_launcher;
   notification.setLatestEventInfo(this, "標題", "內容", pendingIntent);
   NotificationManager manager =(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
   manager.notify(1, notification);
}

這是Android 3.0 ~ 4.1.2系統中(11 <= API Level <= 16)

private void showNotification() {
   PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, newIntent(this, Main2Activity.class), PendingIntent.FLAG_UPDATE_CURRENT);
   Notification notification = new Notification.Builder(this)
           .setContentTitle("標題")
           .setContentText("內容")            
           .setSmallIcon(R.mipmap.ic_launcher)
           .setContentIntent(pendingIntent)
           .getNotification();
    NotificationManagermanager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
   manager.notify(1, notification);
}

這是Android 4.2及以上系統中(API Level >= 17)

private void showNotification() {
   PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, newIntent(this, Main2Activity.class), PendingIntent.FLAG_UPDATE_CURRENT);
   Notification notification = new Notification.Builder(this)
           .setContentTitle("標題")
           .setContentText("內容")
           .setSmallIcon(R.mipmap.ic_launcher)
           .setContentIntent(pendingIntent)
           .build();
   NotificationManager manager =(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
   manager.notify(1, notification);
}

……

使用NotificationCompat通用解決

雖然它的基本使用在這種各個版本變化不一,但Android在appcompat-v7庫中提供了一個NotificationCompat類來處理新舊系統中的相容問題,我們在開發過程中不再需要判斷API Level來寫出各種適配程式碼,只需要在上面對上面API Level >= 17的程式碼中稍修改一下即可。所以一段基本的通知欄使用的程式碼應該是這樣:

private void showNotification() {
   PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, newIntent(this, Main2Activity.class), PendingIntent.FLAG_UPDATE_CURRENT);
   NotificationCompat.Builder builder = newNotificationCompat.Builder(this);
   builder.setContentTitle("標題");
   builder.setContentText("內容");
    builder.setSmallIcon(R.mipmap.ic_launcher);
   builder.setContentIntent(pendingIntent);   
   Notification notification = builder.build();
   NotificationManager manager =(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
    manager.notify(1,notification);
}

NotificationCompat.Builder方法介紹

在上面程式碼中,我們可以看到builder物件呼叫了四個set方法:

setContentTitle(CharSequence title)                                                      設定標題(必需)

setContentText(CharSequence title)                                                      設定內容(必需)

setSmallIcon(int icon)                                                                                設定小圖示(必需)

setContentIntent(PendingIntent intent)                                                  設定點選意圖

其它常用方法:

setTicker(CharSequence tickerText)                                                       設定簡要內容(剛出現時在狀態列上動畫顯示的文字) 

setContentInfo(CharSequence info)                                                        設定內容資訊(一般出現在右邊時間下方的資訊)

setLargeIcon(Bitmap icon)                                                                        設定大圖示(MIUI不起作用)

setColor(int argb)                                                                                        設定小圖示背景色(APILevel >= 21)

setWhen(long when)                                                                                   設定通知時間,預設當前系統時間

setShowWhen(boolean show)                                                                  設定是否顯示通知時間(API Level >= 17)

setAutoCancel(boolean autoCancel)                                                      設定是否點選後自動取消   

setOngoing(boolean ongoing)                                                                  設定是否是正在執行的任務通知(是否常駐)

setPriority(int pri)                                                                                         設定該通知優先順序,優先順序越高通知出現的位置越往前,預設:Notification.PRIORITY_DEFAULT

setNumber(number)                                                                                   設定通知集合的數量

setDefaults(int defaults)                                                                            設定通知(聲音、振動和閃燈)效果,可多個組合使用,值如:Notification.DEFAULT_VIBRATE(預設震動)、Notification.DEFAULT_SOUND(預設聲音)、Notification.DEFAULT_LIGHTS(預設三色燈)Notification.DEFAULT_ALL(預設以上3種全部),等

setSound(Uri sound)                                                                                 設定聲音效果。如:setSound(Uri.withAppendedPath(Audio.Media.INTERNAL_CONTENT_URI,"1")) 或 setSound(Uri.parse("file:///sdcard/XX.mp3"))  

setVibrate(long[] pattern)                                                                     設定單獨振動效果。如setVibrate(new long[] {100, 250, 100, 500}),表示延遲100ms,然後振250ms,在延遲100ms,接著在振動500ms

setLights(int argb, int onMs, int offMs)                                                 設定閃燈效果。其中引數argb表示燈光顏色、onMs 亮持續時間、offMs 暗的時間。具體是支援還要看裝置硬體。注意:只有在設定了Notification的標誌符flags為Notification.FLAG_SHOW_LIGHTS的時候,才支援閃燈提醒

setProgress(int max, int progress, booleanindeterminate)           設定通知欄帶進度條(API Level >= 14)。其中引數max表示進度條最大值、progress表示當前進度、indeterminate從翻譯來看是:是否不確定,其實可以理解為當正在顯示前面兩個值設定的正常進度時,此值為false,當要顯示一種無法評估的進度但要一直等待的狀態就應該為true。常見的情況有:下載APP時可看到下載進度,要為false;當下載完後要安裝時,無法評論安裝進度就為true

NotificationManager管理通知

新建/更新通知

我們在發出通知時,最終會使用到方法:NotificationManager.notify()。notify()方法接收一個ID和Notification物件(NotificationCompat.Builder呼叫build()返回),ID就是本次通知欄的ID。如果執行兩次notify()方法使用了相同的ID,則只會出現一條通知欄,後面彈出的通知欄會替換先彈出來的通知欄。我們一般使用這種情況來更新通知欄資訊。例如,簡訊APP,通過增加未讀訊息計數並將每封簡訊的摘要新增到通知,這種“堆疊”通知就可能這樣使用。

刪除通知

除非發生以下情況之一,否則通知仍然可見:

使用者單獨或通過使用“全部清除”清除了該通知(如果通知可以清除)。

使用者點選通知,且您在建立通知時呼叫了setAutoCancel()。

您針對特定的通知 ID 呼叫了 NotificationManager.cancel()。此方法還會刪除當前通知。

您呼叫了 NotificationManager.cancelAll() 方法,該方法將刪除之前發出的所有通知。

PendingIntent

PendingIntent表示處於一種待定、即將發生狀態的Intent(意圖)。PendingIntent是在將來某個不確定的時刻發生,而Intent是立刻發生。PendingIntent的典型使用場景是給通知欄新增單擊事件。PendingIntent能過send和cancel方法來發送和取消特定的Intent。我們將會在後續再詳細介紹PendingIntent,這裡先知道怎樣用即可。

通知欄示例

不同的Android版本和不同的廠商所顯示出來的通知欄會不一樣,我們將上面的程式碼加入一些跟介面相關的設定方法來看下出來的通知欄顯示樣式看看顯示情況。程式碼:

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
    Button btn= (Button) findViewById(R.id.btn);
   btn.setOnClickListener(new View.OnClickListener() {
       @Override
        publicvoid onClick(View v) {
           showNotification();
        }
    });
}
private void showNotification() {
   PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, newIntent(this, Main2Activity.class), PendingIntent.FLAG_UPDATE_CURRENT);
   NotificationCompat.Builder builder = newNotificationCompat.Builder(this);
   builder.setContentTitle("標題");
   builder.setContentText("內容");
   builder.setSmallIcon(R.mipmap.ic_launcher);
   builder.setContentIntent(pendingIntent);
   builder.setTicker("簡要內容");
   builder.setContentInfo("內容資訊");
   builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.aa));
    builder.setColor(Color.parseColor("#ff0000"));
   Notification notification = builder.build();
   NotificationManager manager =(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
   manager.notify(1, notification);
}
<?xml version="1.0"encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
    <Button
       android:id="@+id/btn"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="按鈕"/>
</RelativeLayout>

展示效果




注意

第一個是在Android原生系統6.0、第二個是在MIUI8安卓5.0 和第三個是在VIVO2.0安卓4.4中顯示出來的效果。這裡要重點說幾點:

1、setLargeIcon()方法設定大圖示在MIUI中是不生效的,通知欄左邊圖片位置始終顯示小圖示;

2、原生系統中大圖示是顯示在左邊圖片位置,而小圖示是以角標的形式顯示在大圖的右下方,而預設背景色是灰色(示例中是設定了紅點);

3、Android從5.0系統開始,通知欄小圖示(無論是狀態列上還是角標上)只使用alpha圖層來進行繪製(即變白了);

4、在Android4.4中小圖示是顯示在右邊 和 簡要內容生效了

自定義的通知欄使用

自定義通知欄只要稍修改一下上面的程式碼,把setContentTitle()和setContentText()方法換成setContent()方法即可。

示例

private void showNotification() {
   PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, newIntent(this, Main2Activity.class), PendingIntent.FLAG_UPDATE_CURRENT);
   PendingIntent pendingIntent2 = PendingIntent.getActivity(this, 0, newIntent(this, Main2Activity.class), PendingIntent.FLAG_UPDATE_CURRENT);
    RemoteViewsremoteViews = new RemoteViews(getPackageName(), R.layout.layout_notification);
   remoteViews.setTextViewText(R.id.title, "我是自定義標題");
   remoteViews.setImageViewResource(R.id.icon, R.drawable.aa);
   remoteViews.setOnClickPendingIntent(R.id.btn, pendingIntent2);
   NotificationCompat.Builder builder = newNotificationCompat.Builder(this);
   builder.setContent(remoteViews);
    //builder.setContentTitle("標題");
    // builder.setContentText("內容");
   builder.setSmallIcon(R.mipmap.ic_launcher);
   builder.setContentIntent(pendingIntent);
   Notification notification = builder.build();
   NotificationManager manager =(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
   manager.notify(1, notification);
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_toRightOf="@+id/icon"
        android:gravity="center"/>

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:layout_centerVertical="true"
        android:layout_alignParentRight="true"
        android:textColor="@color/btn_txt"
        android:background="@color/btn_bg"
        android:text="我是按鈕" />
</RelativeLayout>

展示效果


可以看出,三種系統樣式上相差還不算很大,但原生系統中有一個致命的錯誤,就是title的字型顏色。示例中是沒有去設定TextView的字型顏色,那是不是把TextView的字型顏色設定成黑色就可以了?當然不能這樣做,因為那樣的話,在像VIVO系統那樣通知欄預設背景不是白色的情況下也是非常的難看的。我們來思考一下,如果可以獲得到系統通知欄預設的文字樣式,這樣是不是就可以顯示出跟系統預設通知欄一樣的效果了?朝著這個思路,我們來看看實現的辦法。

自適應通知欄樣式

在Android5.0以下中提供了通知欄樣式:TextAppearance.StatusBar.EventContent.XXX,而5.0以後的版本中,不知出於什麼考慮又修改了新的通知欄樣式:TextAppearance.Material.Notification.XXX(所以說通知欄版本迭代一直都是一件令開發者頭痛的事情)。然而我們的程式想要同時適配這兩種情況,得在資原始檔夾中新建layout-v21資料夾,同時在裡頭拷貝一份跟原來layout裡同名的layout_notification.xml檔案。我們來看看修改後的XML檔案:

layout\layout_notification.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_toRightOf="@+id/icon"
        android:gravity="center"
        android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Title"/>

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:layout_centerVertical="true"
        android:layout_alignParentRight="true"
        android:textColor="@color/btn_txt"
        android:background="@color/btn_bg"
        android:text="我是按鈕" />
</RelativeLayout>

layout-v21\layout_notification.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_toRightOf="@+id/icon"
        android:gravity="center"
        android:textAppearance="@android:style/TextAppearance.Material.Notification.Title"/>

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:layout_centerVertical="true"
        android:layout_alignParentRight="true"
        android:textColor="@color/btn_txt"
        android:background="@color/btn_bg"
        android:text="我是按鈕" />
</RelativeLayout>

展示效果


自適應通知欄樣式方法二

在網上找到了一種大神的解決通知欄樣式的方案,原理大概是:通過獲取系統通知欄預設的標題顏色,然後比對此顏色來判斷些色值是接近黑色還是白色,從而在彈出通知欄時設定以其樣式相應的顏色。(參考自:http://www.jianshu.com/p/426d85f34561)程式碼:

public class NoficationBar {

    private static final double COLOR_THRESHOLD = 180.0;

    private String DUMMY_TITLE = "DUMMY_TITLE";
    private int titleColor;

    //判斷是否Notification背景是否為黑色
    public boolean isDarkNotificationBar(Context context) {
        return !isColorSimilar(Color.BLACK, getNotificationTitleColor(context));
    }

    //獲取Notification 標題的顏色
    private int getNotificationTitleColor(Context context) {
        int color = 0;
        if (context instanceof AppCompatActivity) {
            color = getNotificationColorCompat(context);
        } else {
            color = getNotificationColorInternal(context);
        }
        return color;
    }

    //判斷顏色是否相似
    public boolean isColorSimilar(int baseColor, int color) {
        int simpleBaseColor = baseColor | 0xff000000;
        int simpleColor = color | 0xff000000;
        int baseRed = Color.red(simpleBaseColor) - Color.red(simpleColor);
        int baseGreen = Color.green(simpleBaseColor) - Color.green(simpleColor);
        int baseBlue = Color.blue(simpleBaseColor) - Color.blue(simpleColor);

        double value = Math.sqrt(baseRed * baseRed + baseGreen * baseGreen + baseBlue * baseBlue);
        return value < COLOR_THRESHOLD;

    }

    //獲取標題顏色
    private int getNotificationColorInternal(Context context) {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
        builder.setContentTitle(DUMMY_TITLE);
        Notification notification = builder.build();

        ViewGroup notificationRoot = (ViewGroup) notification.contentView.apply(context, new FrameLayout(context));
        TextView title = (TextView) notificationRoot.findViewById(android.R.id.title);
        if (title == null) { //如果ROM廠商更改了預設的id
            iteratorView(notificationRoot, new Filter() {
                @Override
                public void filter(View view) {
                    if (view instanceof TextView) {
                        TextView textView = (TextView) view;
                        if (DUMMY_TITLE.equals(textView.getText().toString())) {
                            titleColor = textView.getCurrentTextColor();
                        }
                    }
                }
            });
            return titleColor;
        } else {
            return title.getCurrentTextColor();
        }
    }

    private int getNotificationColorCompat(Context context) {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
        Notification notification = builder.build();
        int layoutId = notification.contentView.getLayoutId();
        ViewGroup notificationRoot = (ViewGroup) LayoutInflater.from(context).inflate(layoutId, null);
        TextView title = (TextView) notificationRoot.findViewById(android.R.id.title);
        if (title == null) {
            final List<TextView> textViews = new ArrayList<>();
            iteratorView(notificationRoot, new Filter() {
                @Override
                public void filter(View view) {
                    if (view instanceof TextView) {
                        textViews.add((TextView) view);
                    }
                }
            });
            float minTextSize = Integer.MIN_VALUE;
            int index = 0;
            for (int i = 0, j = textViews.size(); i < j; i++) {
                float currentSize = textViews.get(i).getTextSize();
                if (currentSize > minTextSize) {
                    minTextSize = currentSize;
                    index = i;
                }
            }
            textViews.get(index).setText(DUMMY_TITLE);
            return textViews.get(index).getCurrentTextColor();
        } else {
            return title.getCurrentTextColor();
        }

    }

    private void iteratorView(View view, Filter filter) {
        if (view == null || filter == null) {
            return;
        }
        filter.filter(view);
        if (view instanceof ViewGroup) {
            ViewGroup container = (ViewGroup) view;
            for (int i = 0, j = container.getChildCount(); i < j; i++) {
                View child = container.getChildAt(i);
                iteratorView(child, filter);
            }
        }

    }

    interface Filter {
        void filter(View view);
    }
}

有了此類後,我們來修改彈出通知欄的程式碼:

private void showNotification() {
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, Main2Activity.class), PendingIntent.FLAG_UPDATE_CURRENT);
    PendingIntent pendingIntent2 = PendingIntent.getActivity(this, 0, new Intent(this, Main2Activity.class), PendingIntent.FLAG_UPDATE_CURRENT);
    RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_notification);
    remoteViews.setTextViewText(R.id.title, "我是自適應顏色的標題");
    remoteViews.setImageViewResource(R.id.icon, R.drawable.aa);
    remoteViews.setOnClickPendingIntent(R.id.btn, pendingIntent2);
    // 新加程式碼---------------------------------------------------
    NoficationBar noficationBar = new NoficationBar();
    boolean isDark = noficationBar.isDarkNotificationBar(this);
    if (isDark) {
        remoteViews.setTextColor(R.id.title, Color.parseColor("#ffffff"));
    } else {
        remoteViews.setTextColor(R.id.title, Color.parseColor("#000000"));
    }
    // 新加程式碼---------------------------------------------------
    NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
    // builder.setContentTitle("標題");
    // builder.setContentText("內容");
    builder.setContent(remoteViews);
    builder.setSmallIcon(R.mipmap.ic_launcher);
    builder.setContentIntent(pendingIntent);
    Notification notification = builder.build();
    NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
    manager.notify(1, notification);
}

為了驗證效果,記得把原來layout\layout_notification.xml和layout-v21\layout_notification.xml在TextView加入的textAppearance去除哦。

展示效果


結論

從這三臺手機上驗證可知,無論方法一還是方法二都是可行的。不過由於國內手機廠商太多,而且各家對Android訂製得都是非常個性的,筆者手頭上資源有限,已故無論我們在用方法一還是方法二,在實際開發過程中都有可能出現某某手機不相容般的不可預知的情況。在使用自定義通知欄時,大家可根據實際情況實際開發。