當使用者在使用Android App時,可以讓App跳出一些浮動式、會自動關閉的的訊息提示使用者應該要做哪些動作,或者是App已經完成了哪些動作,讓使用者清楚知道目前App的執行狀況。要如何在Android實作出氣泡訊息呢?很簡單,在Android SDK中就有內建「Toast」類別可以做到這樣的功能。



Toast的使用方法

簡易作法

最簡單顯示出氣泡訊息的作法,就是直接使用Toast類別下的makeText方法來產生出一個Toast物件,再用show方法將這個Toast物件顯示出來。如以下程式碼:

Toast.makeText(context, text, duration).show();

其中需要注意的是duration這個參數,duration顧名思義就是Toast訊息顯示的持續時間,只有兩個整數數值有作用,分別是短時間的「Toast.LENGTH_SHORT」和長時間的「Toast.LENGTH_LONG」。在預設的情況下,短時間大約是2秒、而長時間大約是3.5秒。

直接使用makeText產生出來的Toast,樣式和位置由系統來決定,如下圖是直接使用Toast的makeText方法和show方法顯示出來的氣泡訊息:

用這種方式來顯示出氣泡訊息的話,會有一個很大的缺點,那就是如果在氣泡訊息尚未消失前,又使用了一次makeText方法和show方法,新的氣泡訊息就會被安排在舊的氣泡訊息消失後才會顯示,而不是立刻將舊的氣泡訊息蓋過(墊在下面),也不是直接改變舊氣泡訊息的文字。當氣泡訊息一多,可是要等很久才可以把所有的訊息顯示完哦!

進階作法

為了避免發生上述使用簡易作法產生的問題,我們必須要管理makeText之後所得到的Toast物件。可以使用一個物件變數或是類別變數來儲存Toast物件的參考,以供重複使用。如以下程式:

...
private static Toast toast;
...
private static void makeTextAndShow(final Context context, final String text, final int duration) {
	if (toast == null) {
		//如果還沒有用過makeText方法,才使用
		toast = android.widget.Toast.makeText(context, text, duration);
	} else {
		toast.setText(text);
		toast.setDuration(duration);
	}
	toast.show();
}
...

有了上面的程式後,只要呼叫makeTextAndShow方法就可以顯示出不會排隊延遲的氣泡訊息了!

自訂Toast的樣式

直接使用makeText產生出來的Toast物件,是使用系統內建的樣式,因此可能會因App執行在不同的Android裝置上而有所改變。如果想要自訂Toast的樣式,就不該使用makeText方法,應該直接用new實體化一個新的Toast物件,接著再指定一個View給他,可以將上面的makeTextAndShow方法改寫如下:

...
private static Toast toast;
private static TextView toastText;
...
private static void makeTextAndShow(final Context context, final String text, final int duration) {
	if (toast == null) {
		//如果還沒有建立過Toast,才建立
		final ViewGroup toastView = new FrameLayout(context); // 用來裝toastText的容器
		final FrameLayout.LayoutParams flp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
		final GradientDrawable background = new GradientDrawable();
		toastText = new TextView(context);
		toastText.setLayoutParams(flp);
		toastText.setSingleLine(false);
		toastText.setTextSize(18);
		toastText.setTextColor(Color.argb(0xAA, 0xFF, 0xFF, 0xFF)); // 設定文字顏色為有點透明的白色
		background.setColor(Color.argb(0xAA, 0xFF, 0x00, 0x00)); // 設定氣泡訊息顏色為有點透明的紅色
		background.setCornerRadius(20); // 設定氣泡訊息的圓角程度

		toastView.setPadding(30, 30, 30, 30); // 設定文字和邊界的距離
		toastView.addView(toastText);
		toastView.setBackgroundDrawable(background);

		toast = new Toast(context);
		toast.setView(toastView);
	}
	toastText.setText(text);
	toast.setDuration(duration);
	toast.show();
}
...

執行結果:

自製Toast類別

由於Toast訊息算是Android App常用到的功能,因此可以將它另寫成一個類別來處理:

/**
 * Android 氣泡訊息
 * 
 * @author magiclen
 *
 */
public class Toast {

	// -----類別常數-----
	/**
	 * 短時間訊息
	 */
	public static final int SHORT = android.widget.Toast.LENGTH_SHORT;

	/**
	 * 長時間訊息
	 */
	public static final int LONG = android.widget.Toast.LENGTH_LONG;

	/**
	 * 類別位置
	 * 
	 * @author magiclen
	 *
	 */
	public static enum Position {
		CENTER, CENTER_BOTTOM, CENTER_TOP, CENTER_LEFT, CENTER_RIGHT, LEFT_TOP, RIGHT_TOP, LEFT_BOTTOM, RIGHT_BOTTOM;
	}

	/**
	 * 調整Toast與螢幕邊界的距離,數值愈大距離愈遠
	 */
	private static final int offsetFactor = 12;

	// -----類別變數-----
	/**
	 * 儲存Context的參考
	 */
	private static Context context;

	/**
	 * 儲存Toast的參考
	 */
	private static android.widget.Toast toast;

	/**
	 * 儲存Toast的位置
	 */
	private static Position toastPosition = Position.CENTER_BOTTOM;

	/**
	 * 儲存X邊界
	 */
	private static int offsetX;

	/**
	 * 儲存Y邊界
	 */
	private static int offsetY;

	/**
	 * 儲存使用者使用的ToastView
	 */
	private static ToastView toastView;

	/**
	 * 設定Context給Toast使用,建議在Activity onCreate的時候都指定一次,<br/>
	 * 避免參考到的Context已經被釋放掉,或是變數被清空
	 * 
	 * @param context
	 */
	public static void setContext(final Context context) {
		Toast.context = context;
		useDefaultOffset();
	}

	/**
	 * 指定ToastView,這個設定只有在重新呼叫showToast方法後才可以看到結果
	 * 
	 * @param toastView
	 *            傳入ToastView物件
	 */
	public static void setToastView(final ToastView toastView) {
		Toast.toastView = toastView;
		toast = null;
	}

	/**
	 * 設定Toast的位置,這個設定只有在重新呼叫showToast方法後才可以看到結果
	 * 
	 * @param position
	 *            傳入Toast的位置
	 */
	public static void setPosition(final Position position) {
		if (position != null) {
			Toast.toastPosition = position;
		} else {
			Toast.toastPosition = Position.CENTER_BOTTOM;
		}
		useDefaultOffset();
	}

	/**
	 * 設定Toast的位置,這個設定只有在重新呼叫showToast方法後才可以看到結果
	 * 
	 * @param position
	 *            傳入Toast的位置
	 * @param offsetX
	 *            傳入X位移量
	 * @param offsetY
	 *            傳入Y位移量
	 */
	public static void setPosition(final Position position, final int offsetX, final int offsetY) {
		if (position != null) {
			Toast.toastPosition = position;
		} else {
			Toast.toastPosition = Position.CENTER_BOTTOM;
		}
		setOffset(offsetX, offsetY);
	}

	/**
	 * 使用預設的位移
	 */
	private static void useDefaultOffset() {
		final DisplayMetrics dm = context.getResources().getDisplayMetrics();
		setOffset(dm.widthPixels / offsetFactor, dm.heightPixels / offsetFactor);
	}

	/**
	 * 設定氣泡訊息的位移
	 * 
	 * @param offsetX
	 *            傳入X位移量
	 * @param offsetY
	 *            傳入Y位移量
	 */
	private static void setOffset(final int offsetX, final int offsetY) {
		Toast.offsetX = offsetX;
		Toast.offsetY = offsetY;
	}

	/**
	 * 顯示氣泡訊息(短時間)
	 * 
	 * @param text
	 *            傳入訊息內容
	 */
	public static void showToast(final String text) {
		showToast(text, android.widget.Toast.LENGTH_SHORT);
	}

	/**
	 * 建立Toast物件
	 * 
	 * @param text
	 *            傳入訊息內容
	 * @param duration
	 *            傳入訊息維持的時間
	 * @return 傳回Toast物件
	 */
	private static android.widget.Toast makeText(final String text, final int duration) {
		if (toastView != null) {
			final android.widget.Toast toast = new android.widget.Toast(context);
			toastView.setText(text);
			toast.setDuration(duration);
			toast.setView(toastView);
			return toast;
		} else {
			return android.widget.Toast.makeText(context, text,android.widget.Toast.LENGTH_SHORT);
		}
	}

	/**
	 * 顯示氣泡訊息
	 * 
	 * @param text
	 *            傳入訊息內容
	 * @param duration
	 *            傳入訊息維持的時間
	 */
	public static void showToast(final String text, final int duration) {
		if (toast == null) {
			// 如果Toast物件不存在,就用makeText方法建立一個新的Toast
			toast = makeText(text, duration);
		} else {
			// 如果Toast物件存在,就重設它的持續時間和訊息文字
			toast.setDuration(duration);
			if (toastView == null) {
				toast.setText(text);
			} else {
				toastView.setText(text);
			}
		}

		// 設定氣泡訊息的位置
		int gravity = 0, offsetX = 0, offsetY = 0;
		switch (toastPosition) {
		case CENTER:
			gravity = Gravity.CENTER;
			break;
		case CENTER_BOTTOM:
			gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
			offsetY = Toast.offsetY;
			break;
		case CENTER_TOP:
			gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
			offsetY = Toast.offsetY;
			break;
		case CENTER_LEFT:
			gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL;
			offsetX = Toast.offsetX;
			break;
		case CENTER_RIGHT:
			gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL;
			offsetX = Toast.offsetX;
			break;
		case LEFT_TOP:
			gravity = Gravity.LEFT | Gravity.TOP;
			offsetX = Toast.offsetX;
			offsetY = Toast.offsetY;
			break;
		case LEFT_BOTTOM:
			gravity = Gravity.LEFT | Gravity.BOTTOM;
			offsetX = Toast.offsetX;
			offsetY = Toast.offsetY;
			break;
		case RIGHT_TOP:
			gravity = Gravity.RIGHT | Gravity.TOP;
			offsetX = Toast.offsetX;
			offsetY = Toast.offsetY;
			break;
		case RIGHT_BOTTOM:
			gravity = Gravity.RIGHT | Gravity.BOTTOM;
			offsetX = Toast.offsetX;
			offsetY = Toast.offsetY;
			break;
		}
		toast.setGravity(gravity, offsetX, offsetY);
		// 顯示出氣泡訊息
		toast.show();
	}

	/**
	 * 顯示氣泡訊息(短時間)
	 * 
	 * @param textId
	 *            傳入String Resoure的ID,或是整數數字
	 */
	public static void showToast(final int textId) {
		showToast(textId, android.widget.Toast.LENGTH_SHORT);
	}

	/**
	 * 顯示氣泡訊息
	 * 
	 * @param textId
	 *            傳入String Resoure的ID,或是整數數字
	 * @param duration
	 *            傳入訊息維持的時間
	 */
	public static void showToast(final int textId, final int duration) {
		String text;
		try {
			// 嘗試取得String Resource
			text = context.getResources().getString(textId);
		} catch (final Exception ex) {
			// 若String Resource取得失敗,則直接使用整數作為氣泡訊息
			text = String.valueOf(textId);
		}
		showToast(text, duration);
	}

	/**
	 * 顯示氣泡訊息(短時間)
	 * 
	 * @param decimalText
	 *            傳入整數或是浮點數數字作為訊息內容
	 */
	public static void showToast(final double decimalText) {
		showToast(decimalText, android.widget.Toast.LENGTH_SHORT);
	}

	/**
	 * 顯示氣泡訊息
	 * 
	 * @param decimalText
	 *            傳入整數或是浮點數數字作為訊息內容
	 * @param duration
	 */
	public static void showToast(final double decimalText, final int duration) {
		if (decimalText == Math.round(decimalText)) {
			// 如果傳入的數是整數
			showToast(String.valueOf((int) decimalText), duration);
		} else {
			// 如果傳入的數不是整數
			showToast(String.valueOf(decimalText), duration);
		}
	}
}
/**
 * Toast專用的View
 * 
 * @author magiclen
 *
 */
public class ToastView extends FrameLayout {

	private static int DEFAULT_TEXT_SIZE = 18;
	private static int DEFAULT_TEXT_COLOR = Color.argb(0xAA, 0xFF, 0xFF, 0xFF);
	private static int DEFAULT_PADDING = 30;
	private static int DEFAULT_BACKGROUND_COLOR = Color.argb(0xAA, 0xFF, 0x00, 0x00);
	private static int DEFAULT_RADIUS = 20;

	private TextView text;
	private GradientDrawable background = new GradientDrawable();

	public ToastView(final Context context) {
		super(context);
		init(context);
	}

	public ToastView(final Context context, final AttributeSet attrs) {
		super(context, attrs);
		init(context);
	}

	public ToastView(final Context context, final AttributeSet attrs, final int defStyle) {
		super(context, attrs, defStyle);
		init(context);
	}

	private final void init(final Context context) {
		text = new TextView(context);
		final FrameLayout.LayoutParams flp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
		text.setLayoutParams(flp);
		text.setSingleLine(false);
		text.setId(android.R.id.message);

		text.setTextSize(DEFAULT_TEXT_SIZE);
		text.setTextColor(DEFAULT_TEXT_COLOR);

		addView(text);

		setPadding(DEFAULT_PADDING, DEFAULT_PADDING, DEFAULT_PADDING, DEFAULT_PADDING);

		background.setColor(DEFAULT_BACKGROUND_COLOR);
		background.setCornerRadius(DEFAULT_RADIUS);
		setBackgroundDrawable(background);
	}

	public void setTextSize(final float size) {
		text.setTextSize(size);
	}

	public void setTextSize(final int unit, final float size) {
		text.setTextSize(unit, size);
	}

	public void setTextColor(final int color) {
		text.setTextColor(color);
	}

	public void setBackgroundColor(final int color) {
		background.setColor(color);
	}

	public void setRadius(final float radius) {
		background.setCornerRadius(radius);
	}

	public void setPadding(final int padding) {
		setPadding(padding, padding, padding, padding);
	}

	void setText(final String textString) {
		if (textString != null) {
			text.setText(textString);
		} else {
			text.setText("");
		}
	}
}

用法舉例

Toast.setContext(context);
Toast.setPosition(Toast.Position.RIGHT_BOTTOM);

ToastView tv = new ToastView(this);
tv.setRadius(50);
tv.setTextSize(50);
tv.setBackgroundColor(Color.argb(0xAA, 0x00, 0xAA, 0x00));

Toast.setToastView(tv);

Toast.showToast("氣泡訊息!");

執行結果: