初學android-Toast的源碼分析

  在剛開始學習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方法可以使其消失。






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