1. 程式人生 > >自定義Notification和Toast

自定義Notification和Toast

一、自定義Notification
在Android開發中,我們經常會使用Notification,首先來看看我們使用系統預設的Notification的通常做法。

CharSequence title = "I am Notification";
int icon = R.drawable.ic_launcher;
// 1、定義一個Notification物件
Notification noti = new Notification(icon, title, System.currentTimeMillis());

// 2、設定狀態列的顯示型別,可選
noti.flags = Notification.FLAG_INSISTENT;

// 3、設定提示型別,可選
noti.defaults |= Notification.DEFAULT_SOUND; // 4、定義點選通知觸發的Intent跳轉,可選 PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent("android.settings.SETTINGS"), 0); // 5、設定預設的RemoteViews物件 noti.setLatestEventInfo(MainActivity.this, "標題", "內容",contentIntent); // 6、得到NotificationManager服務 NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); // 7、觸發notify
mNotificationManager.notify(2, noti);

完整的使用流程就是上面的這個,下面我們對上面的幾步進行分析分析。

1、Notification.flags

// 螢幕LED開啟
public static final int FLAG_SHOW_LIGHTS        = 0x00000001;
// 通知放置在正在執行
public static final int FLAG_ONGOING_EVENT      = 0x00000002;
// 提示聲音一直重複直到通知被取消或者被開啟
public static final int FLAG_INSISTENT          = 0x00000004;
// 每次發生通知都會有聲音或者振動提示
public static final int FLAG_ONLY_ALERT_ONCE = 0x00000008; // 該通知能被清除掉 public static final int FLAG_AUTO_CANCEL = 0x00000010; // 該通知不能被直接清除掉 public static final int FLAG_NO_CLEAR = 0x00000020; // 代表當前正在執行的服務 public static final int FLAG_FOREGROUND_SERVICE = 0x00000040;

2、Notification.defaults

// 預設所有
public static final int DEFAULT_ALL = ~0;
// 預設聲音
public static final int DEFAULT_SOUND = 1;
// 預設振動
public static final int DEFAULT_VIBRATE = 2;
// 預設閃光
public static final int DEFAULT_LIGHTS = 4;

3、setLatestEventInfo到底做了哪些工作?
這個對我們自定義Notification相當的重要,所以我們需要把裡面的程式碼進行分析分析。

直接進入setLatestEventInfo函式:

public void setLatestEventInfo(Context context,
        CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
    Notification.Builder builder = new Notification.Builder(context);

    // First, ensure that key pieces of information that may have been set directly
    // are preserved
    // 顯示時間
    builder.setWhen(this.when);
    // 小圖示
    builder.setSmallIcon(this.icon);
    // 優先順序 
    builder.setPriority(this.priority);
    // 提示資訊
    builder.setTicker(this.tickerText);
    // 設定資訊最大數目
    builder.setNumber(this.number);
    // 設定Flags
    builder.mFlags = this.flags;
    // 設定提示聲音
    builder.setSound(this.sound, this.audioStreamType);
    // 設定Default
    builder.setDefaults(this.defaults);
    // 設定振動模式
    builder.setVibrate(this.vibrate);

    // now apply the latestEventInfo fields
    // 設定內容標題
    if (contentTitle != null) {
        builder.setContentTitle(contentTitle);
    }
    // 設定內容文字
    if (contentText != null) {
        builder.setContentText(contentText);
    }
    //設定PendingIntent
    builder.setContentIntent(contentIntent);

    builder.buildInto(this);
}

它使用了一個Builder構建者來對Notification的各個選項進行設定,這個Builder物件儲存了各個設定項,這些設定項跟Notification裡面的選項是對應的。

下面我們來看看buildInto函式:

public Notification buildInto(Notification n) {
    build().cloneInto(n, true);
    return n;
}

可以看到它首先執行了build()函式,接著執行了cloneInto函式:

public Notification build() {
    Notification n = buildUnstyled();

    if (mStyle != null) {
        n = mStyle.buildStyled(n);
    }

    n.extras = mExtras != null ? new Bundle(mExtras) : new Bundle();

    addExtras(n.extras);
    if (mStyle != null) {
        mStyle.addExtras(n.extras);
    }

    return n;
}

這裡住要看看buildUnstyled函式:

public Notification buildUnstyled() {
    Notification n = new Notification();
    n.when = mWhen;
    n.icon = mSmallIcon;
    n.iconLevel = mSmallIconLevel;
    n.number = mNumber;
    n.contentView = makeContentView();
    n.contentIntent = mContentIntent;
    n.deleteIntent = mDeleteIntent;
    n.fullScreenIntent = mFullScreenIntent;
    n.tickerText = mTickerText;
    n.tickerView = makeTickerView();
    n.largeIcon = mLargeIcon;
    n.sound = mSound;
    n.audioStreamType = mAudioStreamType;
    n.vibrate = mVibrate;
    n.ledARGB = mLedArgb;
    n.ledOnMS = mLedOnMs;
    n.ledOffMS = mLedOffMs;
    n.defaults = mDefaults;
    n.flags = mFlags;
    n.bigContentView = makeBigContentView();
    if (mLedOnMs != 0 || mLedOffMs != 0) {
        n.flags |= FLAG_SHOW_LIGHTS;
    }
    if ((mDefaults & DEFAULT_LIGHTS) != 0) {
        n.flags |= FLAG_SHOW_LIGHTS;
    }
    if (mKindList.size() > 0) {
        n.kind = new String[mKindList.size()];
        mKindList.toArray(n.kind);
    } else {
        n.kind = null;
    }
    n.priority = mPriority;
    if (mActions.size() > 0) {
        n.actions = new Action[mActions.size()];
        mActions.toArray(n.actions);
    }

    return n;
}

從上面的程式碼可以看到,其實真正的通知不是我們自己new的那個Notification,而是這裡幫我們new的Notification,上面我們自己new的Notification主要乾了一件事,就是儲存設定項,在setLatestEventInfo函式裡面,就會把這些設定項統一的用一個builder儲存起來,而上面程式碼的工作就是建立一個真正需要傳送的通知n,然後把builder裡面儲存的設定項設定給這個通知n,最終會把這個通知返回回去,cloneInto的工作就是把我們返回回去的這個通知裡面的所有內容全部拷貝到我們自己new的那個Notificaton裡面去,這種最終就可以通過NotificationManager把這個通知傳送出去。

從上面我們就可以知道自定義Notification的過程了,系統搞來搞去其實真正的工作就是上面的程式碼,它已經很好的展現了自定義Notification的過程,主要分為4步:

// 1、建立一個Notification物件
Notification n = new Notification();

// 2、設定Notification內容項
n.when = mWhen;
n.icon = mSmallIcon;
n.iconLevel = mSmallIconLevel;
n.number = mNumber;
n.contentView = makeContentView();
n.contentIntent = mContentIntent;
n.deleteIntent = mDeleteIntent;
n.fullScreenIntent = mFullScreenIntent;
n.tickerText = mTickerText;
n.tickerView = makeTickerView();
n.largeIcon = mLargeIcon;
n.sound = mSound;
n.audioStreamType = mAudioStreamType;
n.vibrate = mVibrate;
n.ledARGB = mLedArgb;
n.ledOnMS = mLedOnMs;
n.ledOffMS = mLedOffMs;
n.defaults = mDefaults;
n.flags = mFlags;
n.bigContentView = makeBigContentView();

// 3、得到NotificationManager服務
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

// 4、傳送通知
mNotificationManager.notify(2, n);

對於上面的過程,我們重點需要看看第二步裡面的contentView的設定,它是Notification裡面的顯示檢視,我們自定義Notification的時候,往往需要設計裡面的顯示內容,下面來看看makeContentView函式:

private RemoteViews makeContentView() {
    if (mContentView != null) {
        return mContentView;
    } else {
        return applyStandardTemplate(R.layout.notification_template_base, true); // no more special large_icon flavor
    }
}

從這個裡面我們就可以看到,其實contentView是一個RemoteViews物件,下面需要看看applyStandardTemplate函式:

private RemoteViews applyStandardTemplate(int resId, boolean fitIn1U) {
    //建立一個RemoteViews物件,這個物件傳入了一個佈局檔案resId
    RemoteViews contentView = new RemoteViews(mContext.getPackageName(), resId);
    boolean showLine3 = false;
    boolean showLine2 = false;
    int smallIconImageViewId = R.id.icon;
    // 下面的工作就是為這個佈局檔案View裡面的各個物件賦值,主要是通過id來進行識別
    if (mLargeIcon != null) {
        contentView.setImageViewBitmap(R.id.icon, mLargeIcon);
        smallIconImageViewId = R.id.right_icon;
    }
    if (mPriority < PRIORITY_LOW) {
        contentView.setInt(R.id.icon,
                "setBackgroundResource", R.drawable.notification_template_icon_low_bg);
        contentView.setInt(R.id.status_bar_latest_event_content,
                "setBackgroundResource", R.drawable.notification_bg_low);
    }
    if (mSmallIcon != 0) {
        contentView.setImageViewResource(smallIconImageViewId, mSmallIcon);
        contentView.setViewVisibility(smallIconImageViewId, View.VISIBLE);
    } else {
        contentView.setViewVisibility(smallIconImageViewId, View.GONE);
    }
    if (mContentTitle != null) {
        contentView.setTextViewText(R.id.title, mContentTitle);
    }
    if (mContentText != null) {
        contentView.setTextViewText(R.id.text, mContentText);
        showLine3 = true;
    }
    if (mContentInfo != null) {
        contentView.setTextViewText(R.id.info, mContentInfo);
        contentView.setViewVisibility(R.id.info, View.VISIBLE);
        showLine3 = true;
    } else if (mNumber > 0) {
        final int tooBig = mContext.getResources().getInteger(
                R.integer.status_bar_notification_info_maxnum);
        if (mNumber > tooBig) {
            contentView.setTextViewText(R.id.info, mContext.getResources().getString(
                        R.string.status_bar_notification_info_overflow));
        } else {
            NumberFormat f = NumberFormat.getIntegerInstance();
            contentView.setTextViewText(R.id.info, f.format(mNumber));
        }
        contentView.setViewVisibility(R.id.info, View.VISIBLE);
        showLine3 = true;
    } else {
        contentView.setViewVisibility(R.id.info, View.GONE);
    }

    // Need to show three lines?
    if (mSubText != null) {
        contentView.setTextViewText(R.id.text, mSubText);
        if (mContentText != null) {
            contentView.setTextViewText(R.id.text2, mContentText);
            contentView.setViewVisibility(R.id.text2, View.VISIBLE);
            showLine2 = true;
        } else {
            contentView.setViewVisibility(R.id.text2, View.GONE);
        }
    } else {
        contentView.setViewVisibility(R.id.text2, View.GONE);
        if (mProgressMax != 0 || mProgressIndeterminate) {
            contentView.setProgressBar(
                    R.id.progress, mProgressMax, mProgress, mProgressIndeterminate);
            contentView.setViewVisibility(R.id.progress, View.VISIBLE);
            showLine2 = true;
        } else {
            contentView.setViewVisibility(R.id.progress, View.GONE);
        }
    }
    if (showLine2) {
        if (fitIn1U) {
            // need to shrink all the type to make sure everything fits
            final Resources res = mContext.getResources();
            final float subTextSize = res.getDimensionPixelSize(
                    R.dimen.notification_subtext_size);
            contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, subTextSize);
        }
        // vertical centering
        contentView.setViewPadding(R.id.line1, 0, 0, 0, 0);
    }

    if (mWhen != 0 && mShowWhen) {
        if (mUseChronometer) {
            contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
            contentView.setLong(R.id.chronometer, "setBase",
                    mWhen + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
            contentView.setBoolean(R.id.chronometer, "setStarted", true);
        } else {
            contentView.setViewVisibility(R.id.time, View.VISIBLE);
            contentView.setLong(R.id.time, "setTime", mWhen);
        }
    } else {
        contentView.setViewVisibility(R.id.time, View.GONE);
    }

    contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE);
    contentView.setViewVisibility(R.id.overflow_divider, showLine3 ? View.VISIBLE : View.GONE);
    return contentView;
}

上面的工作就是建立了一個RemoteViews物件,併為它傳入了一個佈局檔案,這個佈局檔案就是我們定義通知要顯示的佈局檔案,所以我們如果需要自定義Notificaion,我們可以把我們需要顯示的RemoteViews傳遞給contentView就可以了。

弄清楚了上面的過程,下面我們就可以來自定義一個Notification了。

CharSequence title = "i am new";
int icon = R.drawable.ic_launcher;
long when = System.currentTimeMillis();

// 1、建立一個Notification物件,這裡直接通過建構函式初始了三個內容項,icon,title,when
Notification noti = new Notification(icon, title, when);

// 2、設定Notification內容項
noti.flags = Notification.FLAG_INSISTENT;

RemoteViews remoteView = new RemoteViews(this.getPackageName(),R.layout.notification);
remoteView.setImageViewResource(R.id.image, R.drawable.ic_launcher);
remoteView.setTextViewText(R.id.text , "通知型別為:自定義View");
noti.contentView = remoteView;

//這兒點選後簡單啟動Settings模組
PendingIntent contentIntent = PendingIntent.getActivity
        (MainActivity.this, 0,new Intent("android.settings.SETTINGS"), 0);
noti.contentIntent = contentIntent;

// 3、得到NotificationManager服務
NotificationManager mnotiManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// 4、傳送通知
mnotiManager.notify(0, noti);

二、自定義Toast

這個比較簡單,我們平時在使用系統的Toast的時候,只需要一行程式碼就可以搞定;

Toast.makeText(this, "我的Toast", Toast.LENGTH_LONG).show();

下面我們來看看這行程式碼到底為我們做了哪些事情。

首先重點來看看makeText函式:

public static Toast makeText(Context context, CharSequence text, int duration) {
    Toast result = new Toast(context);

    LayoutInflater inflate = (LayoutInflater)
            context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
    TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
    tv.setText(text);

    result.mNextView = v;
    result.mDuration = duration;

    return result;
}

從這個裡面我們就可以看到自定義Toast的整個過程:

// 1、建立Toast
Toast result = new Toast(context);

// 2、自定義的佈局
LayoutInflater inflate = (LayoutInflater)
        context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
tv.setText(text);

// 3、設定內容項              
result.mNextView = v;
result.mDuration = duration;

// 4、顯示
result.show();

下面我們通過上面的過程來自定義一個Toast:

// 1、建立Toast
Toast toast = new Toast(context);

// 2、自定義的佈局
LayoutInflater inflater = context.getLayoutInflater();
View view=inflater.inflate(R.layout.toast_info, null);
TextView txt=(TextView) view.findViewById(R.id.txt_tips);
txt.setText(info);

 // 3、設定內容項               
toast.setGravity(Gravity.CENTER_HORIZONTAL|Gravity.BOTTOM, 0, 80);
toast.setDuration(time);
toast.setView(view);

// 4、顯示
toast.show();

參考文章: