在剛開始學習android的時候,我對android的疑惑簡直快令我放棄,我感覺自己像是漂浮在空中,因爲我在敲代碼的時候,總感覺我在抄襲代碼,並沒有自己的思想;而且我抄襲的時候還沒有理解這些代碼的深層次的東西,比如爲什麼我能通過new一個ListView,給他傳入一個適配器就能在顯示屏上顯示出一格一格的東西,我甚至很想知道他底層到底是怎麼實現的,每一步我都想搞清楚,查閱資料發現並沒有寫這個的文章,這令我更加疑惑,感覺自己飄在空中。
慶幸我沒有放棄,我還是硬着頭皮學下去,雖然會用一些控件,組件的簡單搭配,但是這些沒有“說服”我心中的疑惑,就在昨天,我終於有點小小的明白,只能說小小的,在看到視頻裏說的自定義toast的時候,看着他進行一步一步的源碼分析,我心中似有感悟,於是我也拿着源碼分析起來,果然感覺自己開朗了許多,雖然還是有許多疑問。
1.首先打開Toast源碼,先查看構造函數
public Toast(Context context) {
//這裏初始化了上下文 mContext = context; mTN = new TN();
//這裏通過得到toast_y_offset.xml和config_toastDefaultGravity這個資源文件賦值給不知道什麼
東東 mTN.mY = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.toast_y_offset);
mTN.mGravity = context.getResources().getInteger( com.android.internal.R.integer.config_toastDefaultGravity); }
2.在這裏我不知道TN是什麼,於是我找到TN的源碼,發現他是一個內部類,這個類幾乎就是實現toast的重
要類,真的好長的代碼啊,但是也要硬着頭皮看,抓住其中的主幹,一步一步來
private static class TN extends ITransientNotification.Stub {
final Runnable mShow = new Runnable() { @Override public void run() { handleShow(); } }; final Runnable mHide = new Runnable() { @Override public void run() { handleHide(); // Don't do this in handleHide() because it is also invoked by handleShow() mNextView = null; } }; private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(); final Handler mHandler = new Handler(); int mGravity; int mX, mY; float mHorizontalMargin; float mVerticalMargin; View mView; View mNextView; WindowManager mWM; TN() { // XXX This should be changed to use a Dialog, with a Theme.Toast // defined that sets up the layout params appropriately. final WindowManager.LayoutParams params = mParams; params.height = WindowManager.LayoutParams.WRAP_CONTENT; params.width = WindowManager.LayoutParams.WRAP_CONTENT; params.format = PixelFormat.TRANSLUCENT; params.windowAnimations = com.android.internal.R.style.Animation_Toast; params.type = WindowManager.LayoutParams.TYPE_TOAST; params.setTitle("Toast"); params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; } /** * schedule handleShow into the right thread */ @Override public void show() { if (localLOGV) Log.v(TAG, "SHOW: " + this); mHandler.post(mShow); } /** * schedule handleHide into the right thread */ @Override public void hide() { if (localLOGV) Log.v(TAG, "HIDE: " + this); mHandler.post(mHide); } public void handleShow() { if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView + " mNextView=" + mNextView); if (mView != mNextView) { // remove the old view if necessary handleHide(); mView = mNextView; Context context = mView.getContext().getApplicationContext(); String packageName = mView.getContext().getOpPackageName(); if (context == null) { context = mView.getContext(); } mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); // We can resolve the Gravity here by using the Locale for getting // the layout direction final Configuration config = mView.getContext().getResources().getConfiguration(); final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection()); mParams.gravity = gravity; if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { mParams.horizontalWeight = 1.0f; } if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) { mParams.verticalWeight = 1.0f; } mParams.x = mX; mParams.y = mY; mParams.verticalMargin = mVerticalMargin; mParams.horizontalMargin = mHorizontalMargin; mParams.packageName = packageName; if (mView.getParent() != null) { if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this); mWM.removeView(mView); } if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this); mWM.addView(mView, mParams); trySendAccessibilityEvent(); } } private void trySendAccessibilityEvent() { AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mView.getContext()); if (!accessibilityManager.isEnabled()) { return; } // treat toasts as notifications since they are used to // announce a transient piece of information to the user AccessibilityEvent event = AccessibilityEvent.obtain( AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); event.setClassName(getClass().getName()); event.setPackageName(mView.getContext().getPackageName()); mView.dispatchPopulateAccessibilityEvent(event); accessibilityManager.sendAccessibilityEvent(event); } public void handleHide() { if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView); if (mView != null) { // note: checking parent() just to make sure the view has // been added... i have seen cases where we get here when // the view isn't yet added, so let's try not to crash. if (mView.getParent() != null) { if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this); mWM.removeView(mView); } mView = null; } } } }
(1)先從構造函數看起
TN() { // XXX This should be changed to use a Dialog, with a Theme.Toast // defined that sets up the layout params appropriately.
//這裏的mParams是TN的一個成員變量,是WindowManager.LayoutParams類型的,我並不知道這是什麼類型
final WindowManager.LayoutParams params = mParams;
//看到這裏,我似乎明白上面的params是什麼了,應該是佈局的一些參數,這裏給高度賦值包裹類型 params.height = WindowManager.LayoutParams.WRAP_CONTENT;
//這裏給寬度賦值包裹類型 params.width = WindowManager.LayoutParams.WRAP_CONTENT; params.format = PixelFormat.TRANSLUCENT;
//這裏大概就是toast出現,消失的那種效果 params.windowAnimations = com.android.internal.R.style.Animation_Toast; params.type = WindowManager.LayoutParams.TYPE_TOAST; params.setTitle("Toast");
//這裏表示在toast創建的時候保持屏幕亮,無焦點,不可點擊 params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; }
總結一下:這裏的構造函數是用來初始化即將出現的toast的基本參數。
3.看到這裏,我們彷彿瞭解了一下toast構造出來的原理,但是還是不能知道toast從
出現到消失的整個過程,我們要的是過程。那麼我又發現了Toast的裏面的方法,是我
最常見的一個方法---Toast.makeText();這個方法是最常見的了,他只要傳入三個參
數,就能出現我們想要的結果。那麼這裏面肯定有我們想要的過程。上源碼:
public static Toast makeText(Context context, CharSequence text, @Duration int duration) { //這是上面說過的,在這裏會初始化一些數據包括Toast的成員變量mTN,mContext和mTN裏的成員變量
Toast result = new Toast(context); //這裏得到一個系統的加載佈局的服務 LayoutInflater inflate = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//加載系統自帶的佈局文件,這是一個帶有TextView的LinearLayout的佈局文件 View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
//這裏就有我們想要的東西了,這個就是toast出來的文字
TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message); tv.setText(text); //View v裏面已經裝載好了信息,返回給Toast並賦值給成員變量mNextView result.mNextView = v; result.mDuration = duration; //返回這個Toast,一會要用 return result; }總結一下:這裏其實是構造出來了一個View,View裏面可以包含各種信息,我覺得這裏面應該可以加載圖片,回頭試試
4.就這樣完了?注意後面還有show(),我們才能將toast顯示出來,那我們看看show方法
public void show() {
//判斷有沒有View,沒有拋異常
if (mNextView == null) { throw new RuntimeException("setView must have been called"); }
//這裏不太清楚 INotificationManager service = getService(); String pkg = mContext.getOpPackageName();
//將構造函數裏初始化的mTN賦值給tn
TN tn = mTN;
//tn裏的mNextView賦值 tn.mNextView = mNextView; try { service.enqueueToast(pkg, tn, mDuration); } catch (RemoteException e) { // Empty } }
5.toast的顯示
這個是TN類中的show方法
public void show() { if (localLOGV) Log.v(TAG, "SHOW: " + this);
//這裏開啓了mShow線程 mHandler.post(mShow); }
final Runnable mShow = new Runnable() { @Override public void run() { handleShow(); } };
這裏可以看到handleShow()方法
public void handleShow() { if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView + " mNextView=" + mNextView); if (mView != mNextView) { // remove the old view if necessary handleHide(); mView = mNextView; Context context = mView.getContext().getApplicationContext(); String packageName = mView.getContext().getOpPackageName(); if (context == null) { context = mView.getContext(); } mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); // We can resolve the Gravity here by using the Locale for getting // the layout direction final Configuration config = mView.getContext().getResources().getConfiguration(); final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection()); mParams.gravity = gravity; if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { mParams.horizontalWeight = 1.0f; } if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) { mParams.verticalWeight = 1.0f; } mParams.x = mX; mParams.y = mY; mParams.verticalMargin = mVerticalMargin; mParams.horizontalMargin = mHorizontalMargin; mParams.packageName = packageName; if (mView.getParent() != null) { if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this); mWM.removeView(mView); } if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this); mWM.addView(mView, mParams); trySendAccessibilityEvent(); } }其實這裏面的代碼抓住要點的就是一行mVM.addView(mView,mParams).
將上面已經準備好的mView,mParams整合起來,並通過WindowsManager顯示到屏幕上
6.toast的消失
public void hide() { if (localLOGV) Log.v(TAG, "HIDE: " + this); mHandler.post(mHide); }
final Runnable mHide = new Runnable() { @Override public void run() { handleHide(); // Don't do this in handleHide() because it is also invoked by handleShow() mNextView = null; } };handleHide方法
public void handleHide() { if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView); if (mView != null) { // note: checking parent() just to make sure the view has // been added... i have seen cases where we get here when // the view isn't yet added, so let's try not to crash. if (mView.getParent() != null) { if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this); mWM.removeView(mView); } mView = null; } }其實核心的代碼就是mVM.removeView(mView)。
7.最後總結一下:要想自定義toast,那麼我們只需要構造出合適的View並且初始化
LayoutParams的一些參數,然後通過WindowsManager的add方法就可以顯示了,用
removeView方法可以使其消失。