android原始碼解析--Dialog

傲慢的上校發表於2012-11-25

   在學習設計模式建造者模式時,發現AlertDialog和它的內部類Builder就是比較典型的建造者模式,所以先分析下基類Dialog,然後再看子類AlertDialog和它的內部類Builder



按照慣例,先看下類說明:

Base class for Dialogs.

Note: Activities provide a facility to manage the creation, saving and restoring of dialogs. See onCreateDialog(int), onPrepareDialog(int, Dialog), showDialog(int), and dismissDialog(int). If these methods are used, getOwnerActivity() will return the Activity that managed this dialog.

Often you will want to have a Dialog display on top of the current input method, because there is no reason for it to accept text. You can do this by setting the WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM window flag (assuming your Dialog takes input focus, as it the default) with the following code:

 getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
         WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);

Dialog是一系列Dialogs的基類。

注意:Activity提供了onCreateDialog(int)、onPrepareDialog(int, Dialog)和showDialog(int)這一些列方法用於管理dialog的建立、儲存和恢復(注:關於onCreateDialog的用法見於:onCreateDialog方法及示例)。如果使用了以上方法,Dialog的getOwnerActivity方法就會返回建立Dialog的Activity。

如果你的Dialog不需要輸入文字,想要顯示在當前顯示的輸入法的上面,可以像下面程式碼通過設定WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM來實現(如果想要獲取輸入,則不用設定,預設獲取焦點)

 getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
         WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);

使用WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM此標誌位表示隱藏輸入法。

看一下所用到的全域性變數:

public class Dialog implements DialogInterface, Window.Callback,
        KeyEvent.Callback, OnCreateContextMenuListener {
    private static final String TAG = "Dialog";
    private Activity mOwnerActivity;
    
    final Context mContext;
    final WindowManager mWindowManager;
    Window mWindow;
    View mDecor;
    private ActionBarImpl mActionBar;
    /**
     * This field should be made private, so it is hidden from the SDK.
     * {@hide}
     */
    protected boolean mCancelable = true;

    private String mCancelAndDismissTaken;
    private Message mCancelMessage;
    private Message mDismissMessage;
    private Message mShowMessage;

    private OnKeyListener mOnKeyListener;

    private boolean mCreated = false;
    private boolean mShowing = false;
    private boolean mCanceled = false;

    private final Thread mUiThread;
    private final Handler mHandler = new Handler();

    private static final int DISMISS = 0x43;
    private static final int CANCEL = 0x44;
    private static final int SHOW = 0x45;

    private Handler mListenersHandler;

    private ActionMode mActionMode;

    private final Runnable mDismissAction = new Runnable() {
        public void run() {
            dismissDialog();
        }
    };
變數都比較簡單,這裡不再詳述。
Dialog類實現了DialogInterface, Window.Callback,KeyEvent.Callback, OnCreateContextMenuListener這些介面,今天看下DialogInterface介面,其他介面在Activity原始碼和Menu原始碼的時候再看。

public interface DialogInterface {    
    /**
     * The identifier for the positive button.
     */
    public static final int BUTTON_POSITIVE = -1;

    /**
     * The identifier for the negative button. 
     */
    public static final int BUTTON_NEGATIVE = -2;

    /**
     * The identifier for the neutral button. 
     */
    public static final int BUTTON_NEUTRAL = -3;

    /**
     * @deprecated Use {@link #BUTTON_POSITIVE}
     */
    @Deprecated
    public static final int BUTTON1 = BUTTON_POSITIVE;

    /**
     * @deprecated Use {@link #BUTTON_NEGATIVE}
     */
    @Deprecated
    public static final int BUTTON2 = BUTTON_NEGATIVE;

    /**
     * @deprecated Use {@link #BUTTON_NEUTRAL}
     */
    @Deprecated
    public static final int BUTTON3 = BUTTON_NEUTRAL;
    
    public void cancel();

    public void dismiss();

    /**
     * Interface used to allow the creator of a dialog to run some code when the
     * dialog is canceled.
     * <p>
     * This will only be called when the dialog is canceled, if the creator
     * needs to know when it is dismissed in general, use
     * {@link DialogInterface.OnDismissListener}.
     */
    interface OnCancelListener {
        /**
         * This method will be invoked when the dialog is canceled.
         * 
         * @param dialog The dialog that was canceled will be passed into the
         *            method.
         */
        public void onCancel(DialogInterface dialog);
    }

    /**
     * Interface used to allow the creator of a dialog to run some code when the
     * dialog is dismissed.
     */
    interface OnDismissListener {
        /**
         * This method will be invoked when the dialog is dismissed.
         * 
         * @param dialog The dialog that was dismissed will be passed into the
         *            method.
         */
        public void onDismiss(DialogInterface dialog);
    }

    /**
     * Interface used to allow the creator of a dialog to run some code when the
     * dialog is shown.
     */
    interface OnShowListener {
        /**
         * This method will be invoked when the dialog is shown.
         *
         * @param dialog The dialog that was shown will be passed into the
         *            method.
         */
        public void onShow(DialogInterface dialog);
    }

    /**
     * Interface used to allow the creator of a dialog to run some code when an
     * item on the dialog is clicked..
     */
    interface OnClickListener {
        /**
         * This method will be invoked when a button in the dialog is clicked.
         * 
         * @param dialog The dialog that received the click.
         * @param which The button that was clicked (e.g.
         *            {@link DialogInterface#BUTTON1}) or the position
         *            of the item clicked.
         */
        /* TODO: Change to use BUTTON_POSITIVE after API council */
        public void onClick(DialogInterface dialog, int which);
    }
    
    /**
     * Interface used to allow the creator of a dialog to run some code when an
     * item in a multi-choice dialog is clicked.
     */
    interface OnMultiChoiceClickListener {
        /**
         * This method will be invoked when an item in the dialog is clicked.
         * 
         * @param dialog The dialog where the selection was made.
         * @param which The position of the item in the list that was clicked.
         * @param isChecked True if the click checked the item, else false.
         */
        public void onClick(DialogInterface dialog, int which, boolean isChecked);
    }
    
    /**
     * Interface definition for a callback to be invoked when a key event is
     * dispatched to this dialog. The callback will be invoked before the key
     * event is given to the dialog.
     */
    interface OnKeyListener {
        /**
         * Called when a key is dispatched to a dialog. This allows listeners to
         * get a chance to respond before the dialog.
         * 
         * @param dialog The dialog the key has been dispatched to.
         * @param keyCode The code for the physical key that was pressed
         * @param event The KeyEvent object containing full information about
         *            the event.
         * @return True if the listener has consumed the event, false otherwise.
         */
        public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event);
    }
}

裡面有代表三個button的變數,還有cancel()、dismiss()、還有OnCancelListener、OnDismissListener、OnShowListener、OnClickListener、OnMultiChoiceClickListener、OnKeyListener這些回撥介面,會在AlertDialog原始碼解析中來說明這些方法。

下面是它的構造方法:

/**
     * Create a Dialog window that uses the default dialog frame style.
     * 
     * @param context The Context the Dialog is to run it.  In particular, it
     *                uses the window manager and theme in this context to
     *                present its UI.
     */
    public Dialog(Context context) {
        this(context, 0, true);
    }

    /**
     * Create a Dialog window that uses a custom dialog style.
     * 
     * @param context The Context in which the Dialog should run. In particular, it
     *                uses the window manager and theme from this context to
     *                present its UI.
     * @param theme A style resource describing the theme to use for the 
     * window. See <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">Style 
     * and Theme Resources</a> for more information about defining and using 
     * styles.  This theme is applied on top of the current theme in 
     * <var>context</var>.  If 0, the default dialog theme will be used.
     */
    public Dialog(Context context, int theme) {
        this(context, theme, true);
    }

    Dialog(Context context, int theme, boolean createContextWrapper) {
        if (theme == 0) {
            TypedValue outValue = new TypedValue();
            context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme,
                    outValue, true);
            theme = outValue.resourceId;
        }

        mContext = createContextWrapper ? new ContextThemeWrapper(context, theme) : context;
        mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
        Window w = PolicyManager.makeNewWindow(mContext);
        mWindow = w;
        w.setCallback(this);
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);
        mUiThread = Thread.currentThread();
        mListenersHandler = new ListenersHandler(this);
    }
    
    /**
     * @deprecated
     * @hide
     */
    @Deprecated
    protected Dialog(Context context, boolean cancelable,
            Message cancelCallback) {
        this(context);
        mCancelable = cancelable;
        mCancelMessage = cancelCallback;
    }

    protected Dialog(Context context, boolean cancelable,
            OnCancelListener cancelListener) {
        this(context);
        mCancelable = cancelable;
        setOnCancelListener(cancelListener);
    }
第一個構造方法是使用對話方塊風格,第二個構造方法使用自定義的對話方塊風格,這兩個構造方法都是呼叫的下面的Dialog方法,我們注意到他的許可權是預設,就是我我們在外部不能直接使用該構造方法。

Dialog(Context context, int theme, boolean createContextWrapper) {
        if (theme == 0) {
            TypedValue outValue = new TypedValue();
            context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme,
                    outValue, true);
            theme = outValue.resourceId;
        }

        mContext = createContextWrapper ? new ContextThemeWrapper(context, theme) : context;
        mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
        Window w = PolicyManager.makeNewWindow(mContext);
        mWindow = w;
        w.setCallback(this);
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);
        mUiThread = Thread.currentThread();
        mListenersHandler = new ListenersHandler(this);
    }

如果theme為0,則使用系統定義的風格,然後初始化mWindow。

第三個構造方法被Deprecated掉,不看,第四個可以實現取消回撥。

再看下面的兩個方法:

/**
     * Retrieve the Context this Dialog is running in.
     * 
     * @return Context The Context used by the Dialog.
     */
    public final Context getContext() {
        return mContext;
    }

    /**
     * Retrieve the {@link ActionBar} attached to this dialog, if present.
     *
     * @return The ActionBar attached to the dialog or null if no ActionBar is present.
     */
    public ActionBar getActionBar() {
        return mActionBar;
    }

獲取Dialog執行的山下文環境和ActionBar。

/**
     * Sets the Activity that owns this dialog. An example use: This Dialog will
     * use the suggested volume control stream of the Activity.
     * 
     * @param activity The Activity that owns this dialog.
     */
    public final void setOwnerActivity(Activity activity) {
        mOwnerActivity = activity;
        
        getWindow().setVolumeControlStream(mOwnerActivity.getVolumeControlStream());
    }

    /**
     * Returns the Activity that owns this Dialog. For example, if
     * {@link Activity#showDialog(int)} is used to show this Dialog, that
     * Activity will be the owner (by default). Depending on how this dialog was
     * created, this may return null.
     * 
     * @return The Activity that owns this Dialog.
     */
    public final Activity getOwnerActivity() {
        return mOwnerActivity;
    }

設定和獲取Dialog所屬的Activity和獲取(如果在onCreate()之外建立dialog,它不會附屬於任何的Activity,可以使用dialog的setOwnerActivity(Activity)來設定),呼叫showDialog方法的Activity預設情況下就是要返回的Activity。

    /**
     * @return Whether the dialog is currently showing.
     */
    public boolean isShowing() {
        return mShowing;
    }

判斷當前Dialog是否顯示。

/**
     * Start the dialog and display it on screen.  The window is placed in the
     * application layer and opaque.  Note that you should not override this
     * method to do initialization when the dialog is shown, instead implement
     * that in {@link #onStart}.
     */
    public void show() {
        if (mShowing) {
            if (mDecor != null) {
                if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
                    mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
                }
                mDecor.setVisibility(View.VISIBLE);
            }
            return;
        }

        mCanceled = false;
        
        if (!mCreated) {
            dispatchOnCreate(null);
        }

        onStart();
        mDecor = mWindow.getDecorView();

        if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
            mActionBar = new ActionBarImpl(this);
        }

        WindowManager.LayoutParams l = mWindow.getAttributes();
        if ((l.softInputMode
                & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
            WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
            nl.copyFrom(l);
            nl.softInputMode |=
                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
            l = nl;
        }

        try {
            mWindowManager.addView(mDecor, l);
            mShowing = true;
    
            sendShowMessage();
        } finally {
        }
    }

顯示建立的dialog,它是不透明的並且你不能在它顯示的時候過載show方法,可以在onStart方法裡實現。當mCreate為false的時候呼叫了dispatchOnCreate方法同時裡面呼叫onCreate方法確保Dialog被初始化:

// internal method to make sure mcreated is set properly without requiring
    // users to call through to super in onCreate
    void dispatchOnCreate(Bundle savedInstanceState) {
        if (!mCreated) {
            onCreate(savedInstanceState);
            mCreated = true;
        }
    }

    /**
     * Similar to {@link Activity#onCreate}, you should initialize your dialog
     * in this method, including calling {@link #setContentView}.
     * @param savedInstanceState If this dialog is being reinitalized after a
     *     the hosting activity was previously shut down, holds the result from
     *     the most recent call to {@link #onSaveInstanceState}, or null if this
     *     is the first time.
     */
    protected void onCreate(Bundle savedInstanceState) {
    }

/**
     * Called when the dialog is starting.
     */
    protected void onStart() {
        if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true);
    }


一切都建立好以後:呼叫sendShowMessage方法向UI執行緒傳送一個訊息:

private void sendShowMessage() {
        if (mShowMessage != null) {
            // Obtain a new message so this dialog can be re-used
            Message.obtain(mShowMessage).sendToTarget();
        }
    }

隱藏Dialog(但不是去除Dialog):

/**
     * Hide the dialog, but do not dismiss it.
     */
    public void hide() {
        if (mDecor != null) {
            mDecor.setVisibility(View.GONE);
        }
    }

去除Dialog:

    /**
     * Dismiss this dialog, removing it from the screen. This method can be
     * invoked safely from any thread.  Note that you should not override this
     * method to do cleanup when the dialog is dismissed, instead implement
     * that in {@link #onStop}.
     */
    public void dismiss() {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(mDismissAction);
        } else {
            mHandler.removeCallbacks(mDismissAction);
            mDismissAction.run();
        }
    }

從螢幕上去除dialog,這個方法在任何執行緒中都是安全的,注意當dialog被取消,不要重寫這個方法。可以在onStop方法裡面呼叫dismiss方法。

裡面執行了mDismissAction這個runnable:

private final Runnable mDismissAction = new Runnable() {
        public void run() {
            dismissDialog();
        }
    };

 主要執行dismissDialog方法:

 void dismissDialog() {
        if (mDecor == null || !mShowing) {
            return;
        }

        if (mWindow.isDestroyed()) {
            Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
            return;
        }

        try {
            mWindowManager.removeView(mDecor);
        } finally {
            if (mActionMode != null) {
                mActionMode.finish();
            }
            mDecor = null;
            mWindow.closeAllPanels();
            onStop();
            mShowing = false;
            
            sendDismissMessage();
        }
    }

裡面主要任務就是銷燬一切資源,然後裡面呼叫了onStop方法:

    /**
     * Called to tell you that you're stopping.
     */
    protected void onStop() {
        if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
    }

把ActionBar功能設為false。上面還呼叫了senDismissMessage方法:

private void sendDismissMessage() {
        if (mDismissMessage != null) {
            // Obtain a new message so this dialog can be re-used
            Message.obtain(mDismissMessage).sendToTarget();
        }
    }

下一個就是保持狀態的onSaveInstanceState和從儲存狀態中恢復onRestoreInstanceState:

    /**
     * Saves the state of the dialog into a bundle.
     *
     * The default implementation saves the state of its view hierarchy, so you'll
     * likely want to call through to super if you override this to save additional
     * state.
     * @return A bundle with the state of the dialog.
     */
    public Bundle onSaveInstanceState() {
        Bundle bundle = new Bundle();
        bundle.putBoolean(DIALOG_SHOWING_TAG, mShowing);
        if (mCreated) {
            bundle.putBundle(DIALOG_HIERARCHY_TAG, mWindow.saveHierarchyState());
        }
        return bundle;
    }

    /**
     * Restore the state of the dialog from a previously saved bundle.
     *
     * The default implementation restores the state of the dialog's view
     * hierarchy that was saved in the default implementation of {@link #onSaveInstanceState()},
     * so be sure to call through to super when overriding unless you want to
     * do all restoring of state yourself.
     * @param savedInstanceState The state of the dialog previously saved by
     *     {@link #onSaveInstanceState()}.
     */
    public void onRestoreInstanceState(Bundle savedInstanceState) {
        final Bundle dialogHierarchyState = savedInstanceState.getBundle(DIALOG_HIERARCHY_TAG);
        if (dialogHierarchyState == null) {
            // dialog has never been shown, or onCreated, nothing to restore.
            return;
        }
        dispatchOnCreate(savedInstanceState);
        mWindow.restoreHierarchyState(dialogHierarchyState);
        if (savedInstanceState.getBoolean(DIALOG_SHOWING_TAG)) {
            show();
        }
    }
下一個方法是獲取當前的Window用於直接訪問其API而不是通過Activity或者Screen:

    /**
     * Retrieve the current Window for the activity.  This can be used to
     * directly access parts of the Window API that are not available
     * through Activity/Screen.
     * 
     * @return Window The current window, or null if the activity is not
     *         visual.
     */
    public Window getWindow() {
        return mWindow;
    }

再獲取當前擁有焦點的View:

   /**
     * Call {@link android.view.Window#getCurrentFocus} on the
     * Window if this Activity to return the currently focused view.
     * 
     * @return View The current View with focus or null.
     * 
     * @see #getWindow
     * @see android.view.Window#getCurrentFocus
     */
    public View getCurrentFocus() {
        return mWindow != null ? mWindow.getCurrentFocus() : null;
    }

還可以在onStart方法裡面呼叫findViewById來通過ID獲取xml檔案中的View:

/**
     * Finds a view that was identified by the id attribute from the XML that
     * was processed in {@link #onStart}.
     *
     * @param id the identifier of the view to find
     * @return The view if found or null otherwise.
     */
    public View findViewById(int id) {
        return mWindow.findViewById(id);
    }

也可以通過ID和View設定Dialog的View:

    /**
     * Set the screen content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the screen.
     * 
     * @param layoutResID Resource ID to be inflated.
     */
    public void setContentView(int layoutResID) {
        mWindow.setContentView(layoutResID);
    }

    /**
     * Set the screen content to an explicit view.  This view is placed
     * directly into the screen's view hierarchy.  It can itself be a complex
     * view hierarhcy.
     * 
     * @param view The desired content to display.
     */
    public void setContentView(View view) {
        mWindow.setContentView(view);
    }

還可以新增View的屬性:

/**
     * Set the screen content to an explicit view.  This view is placed
     * directly into the screen's view hierarchy.  It can itself be a complex
     * view hierarhcy.
     * 
     * @param view The desired content to display.
     * @param params Layout parameters for the view.
     */
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        mWindow.setContentView(view, params);
    }

在Dialog已存在的View的基礎上,再新增一個View:

   /**
     * Add an additional content view to the screen.  Added after any existing
     * ones in the screen -- existing views are NOT removed.
     * 
     * @param view The desired content to display.
     * @param params Layout parameters for the view.
     */
    public void addContentView(View view, ViewGroup.LayoutParams params) {
        mWindow.addContentView(view, params);
    }

先前的View並不會被去除。

為Dialog設定標題:

    /**
     * Set the title text for this dialog's window.
     * 
     * @param title The new text to display in the title.
     */
    public void setTitle(CharSequence title) {
        mWindow.setTitle(title);
        mWindow.getAttributes().setTitle(title);
    }

    /**
     * Set the title text for this dialog's window. The text is retrieved
     * from the resources with the supplied identifier.
     *
     * @param titleId the title's text resource identifier
     */
    public void setTitle(int titleId) {
        setTitle(mContext.getText(titleId));
    }

onKeyDown只處理了back鍵:

/**
     * A key was pressed down.
     * 
     * <p>If the focused view didn't want this event, this method is called.
     *
     * <p>The default implementation consumed the KEYCODE_BACK to later
     * handle it in {@link #onKeyUp}.
     *
     * @see #onKeyUp
     * @see android.view.KeyEvent
     */
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            event.startTracking();
            return true;
        }

        return false;
    

長按則不做任何處理:

    /**
     * Default implementation of {@link KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
     * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle
     * the event).
     */
    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
        return false;
    }

按鍵鬆開:

    /**
     * A key was released.
     * 
     * <p>The default implementation handles KEYCODE_BACK to close the
     * dialog.
     *
     * @see #onKeyDown
     * @see KeyEvent
     */
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
                && !event.isCanceled()) {
            onBackPressed();
            return true;
        }
        return false;
    }

還是隻處理back鍵,呼叫onBackPressed:

    /**
     * Called when the dialog has detected the user's press of the back
     * key.  The default implementation simply cancels the dialog (only if
     * it is cancelable), but you can override this to do whatever you want.
     */
    public void onBackPressed() {
        if (mCancelable) {
            cancel();
        }
    }

這個方法預設實現當你按返回鍵的時候簡單的取消dialog,如果你想實現其他功能,可以重寫它。

/**
     * Called when an key shortcut event is not handled by any of the views in the Dialog.
     * Override this method to implement global key shortcuts for the Dialog.
     * Key shortcuts can also be implemented by setting the
     * {@link MenuItem#setShortcut(char, char) shortcut} property of menu items.
     *
     * @param keyCode The value in event.getKeyCode().
     * @param event Description of the key event.
     * @return True if the key shortcut was handled.
     */
    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
        return false;
    

對多次重複按鍵不做任何響應:

   /**
     * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
     * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle
     * the event).
     */
    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
        return false;
    }


對於快捷鍵,不做任何處理。

/**
     * Called when a touch screen event was not handled by any of the views
     * under it. This is most useful to process touch events that happen outside
     * of your window bounds, where there is no view to receive it.
     * 
     * @param event The touch screen event being processed.
     * @return Return true if you have consumed the event, false if you haven't.
     *         The default implementation will cancel the dialog when a touch
     *         happens outside of the window bounds.
     */
    public boolean onTouchEvent(MotionEvent event) {
        if (mCancelable && mShowing && mWindow.shouldCloseOnTouch(mContext, event)) {
            cancel();
            return true;
        }
        
        return false;
    }

onTouch事件,當發生在dialog區域內,不做任何事,發生在區域外,去除Dialog。

/**
     * Cancel the dialog.  This is essentially the same as calling {@link #dismiss()}, but it will
     * also call your {@link DialogInterface.OnCancelListener} (if registered).
     */
    public void cancel() {
        if (!mCanceled && mCancelMessage != null) {
            mCanceled = true;
            // Obtain a new message so this dialog can be re-used
            Message.obtain(mCancelMessage).sendToTarget();
        }
        dismiss();
    }

取消dialog。

好吧,下面還有軌跡球:

   /**
     * Called when the trackball was moved and not handled by any of the
     * views inside of the activity.  So, for example, if the trackball moves
     * while focus is on a button, you will receive a call here because
     * buttons do not normally do anything with trackball events.  The call
     * here happens <em>before</em> trackball movements are converted to
     * DPAD key events, which then get sent back to the view hierarchy, and
     * will be processed at the point for things like focus navigation.
     * 
     * @param event The trackball event being processed.
     * 
     * @return Return true if you have consumed the event, false if you haven't.
     * The default implementation always returns false.
     */
    public boolean onTrackballEvent(MotionEvent event) {
        return false;
    }

實現該方法來處理觸屏事件. 一般的動作事件,描述操縱桿移動、滑鼠懸停、觸控板事件、滾輪移動以及其他輸入事件. 動作 源指定了接收到事件的型別.實現該方法必須 必須在處理事件之前檢測動作源的標誌位.下面是如果處理的示例程式碼. 發生源為 SOURCE_CLASS_POINTER 的動作事件傳送到游標下的檢視. 所有其他發生源的動作時間傳送到擁有焦點的檢視.這裡還是不做任何處理。(眾所周知,現在軌跡球已經基本消失)

當Window的屬性發生改變時,會重新設定屬性:

public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
        if (mDecor != null) {
            mWindowManager.updateViewLayout(mDecor, params);
        }
    }
當內容、焦點、Window新增或移除View都沒有處理。
 public void onContentChanged() {
    }
    
    public void onWindowFocusChanged(boolean hasFocus) {
    }

    public void onAttachedToWindow() {
    }
    
    public void onDetachedFromWindow() {
    }

下面幾個方法就是把一系列點選、觸屏、滑動軌跡球等事件往上分發傳遞否則就呼叫本類中方法處理,你也可以在重寫這些方法使它在向上分發傳遞事件之前做一些自己的事。

    /**
     * Called to process key events.  You can override this to intercept all 
     * key events before they are dispatched to the window.  Be sure to call 
     * this implementation for key events that should be handled normally.
     * 
     * @param event The key event.
     * 
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchKeyEvent(KeyEvent event) {
        if ((mOnKeyListener != null) && (mOnKeyListener.onKey(this, event.getKeyCode(), event))) {
            return true;
        }
        if (mWindow.superDispatchKeyEvent(event)) {
            return true;
        }
        return event.dispatch(this, mDecor != null
                ? mDecor.getKeyDispatcherState() : null, this);
    }

    /**
     * Called to process a key shortcut event.
     * You can override this to intercept all key shortcut events before they are
     * dispatched to the window.  Be sure to call this implementation for key shortcut
     * events that should be handled normally.
     *
     * @param event The key shortcut event.
     * @return True if this event was consumed.
     */
    public boolean dispatchKeyShortcutEvent(KeyEvent event) {
        if (mWindow.superDispatchKeyShortcutEvent(event)) {
            return true;
        }
        return onKeyShortcut(event.getKeyCode(), event);
    }

    /**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     * 
     * @param ev The touch screen event.
     * 
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mWindow.superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
    
    /**
     * Called to process trackball events.  You can override this to
     * intercept all trackball events before they are dispatched to the
     * window.  Be sure to call this implementation for trackball events
     * that should be handled normally.
     * 
     * @param ev The trackball event.
     * 
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTrackballEvent(MotionEvent ev) {
        if (mWindow.superDispatchTrackballEvent(ev)) {
            return true;
        }
        return onTrackballEvent(ev);
    }

    /**
     * Called to process generic motion events.  You can override this to
     * intercept all generic motion events before they are dispatched to the
     * window.  Be sure to call this implementation for generic motion events
     * that should be handled normally.
     *
     * @param ev The generic motion event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchGenericMotionEvent(MotionEvent ev) {
        if (mWindow.superDispatchGenericMotionEvent(ev)) {
            return true;
        }
        return onGenericMotionEvent(ev);
    }

分發傳遞dispatchPopulateAccessibilityEvent:

public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
        event.setClassName(getClass().getName());
        event.setPackageName(mContext.getPackageName());

        LayoutParams params = getWindow().getAttributes();
        boolean isFullScreen = (params.width == LayoutParams.MATCH_PARENT) &&
            (params.height == LayoutParams.MATCH_PARENT);
        event.setFullScreen(isFullScreen);

        return false;
    }

/**
     * @see Activity#onCreatePanelView(int)
     */
    public View onCreatePanelView(int featureId) {
        return null;
    }
實現Window.Callback介面的onCreatePanelView方法,簡單的實現就是返回null。

/**
     * @see Activity#onCreatePanelMenu(int, Menu)
     */
    public boolean onCreatePanelMenu(int featureId, Menu menu) {
        if (featureId == Window.FEATURE_OPTIONS_PANEL) {
            return onCreateOptionsMenu(menu);
        }
        
        return false;
    }

建立一個選單,實現Window.Callback介面的onCreatePanelMenu方法。

    /**
     * @see Activity#onPreparePanel(int, View, Menu)
     */
    public boolean onPreparePanel(int featureId, View view, Menu menu) {
        if (featureId == Window.FEATURE_OPTIONS_PANEL && menu != null) {
            boolean goforit = onPrepareOptionsMenu(menu);
            return goforit && menu.hasVisibleItems();
        }
        return true;
    }

實現Window.Callback介面的onPreparePanel方法。每次當panel視窗顯示前,都會呼叫該函式,返回true顯示,false則不顯示。

    /**
     * @see Activity#onMenuOpened(int, Menu)
     */
    public boolean onMenuOpened(int featureId, Menu menu) {
        if (featureId == Window.FEATURE_ACTION_BAR) {
            mActionBar.dispatchMenuVisibilityChanged(true);
        }
        return true;
    }

實現Window.Callback介面的onMenuOpened方法。當選單開啟時,呼叫該方法。

    /**
     * @see Activity#onMenuItemSelected(int, MenuItem)
     */
    public boolean onMenuItemSelected(int featureId, MenuItem item) {
        return false;
    }

實現Window.Callback介面的onMenuItemSelected方法。不做任何處理。

    /**
     * @see Activity#onPanelClosed(int, Menu)
     */
    public void onPanelClosed(int featureId, Menu menu) {
        if (featureId == Window.FEATURE_ACTION_BAR) {
            mActionBar.dispatchMenuVisibilityChanged(false);
        }
    }
實現Window.Callback介面的onPanelClosed方法。當featureId為Window.FEATURE_ACTION_BAR時,修改ActionBar的分發事件。

    /**
     * It is usually safe to proxy this call to the owner activity's
     * {@link Activity#onCreateOptionsMenu(Menu)} if the client desires the same
     * menu for this Dialog.
     * 
     * @see Activity#onCreateOptionsMenu(Menu)
     * @see #getOwnerActivity()
     */
    public boolean onCreateOptionsMenu(Menu menu) {
        return true;
    }
呼叫建立該Dialog的Activity的onCreateOptionMenu方法。

/**
     * It is usually safe to proxy this call to the owner activity's
     * {@link Activity#onPrepareOptionsMenu(Menu)} if the client desires the
     * same menu for this Dialog.
     * 
     * @see Activity#onPrepareOptionsMenu(Menu)
     * @see #getOwnerActivity()
     */
    public boolean onPrepareOptionsMenu(Menu menu) {
        return true;
    }

呼叫建立Activity的onCreatePrepareOptionsMenu方法。

    /**
     * @see Activity#onOptionsItemSelected(MenuItem)
     */
    public boolean onOptionsItemSelected(MenuItem item) {
        return false;
    }

    /**
     * @see Activity#onOptionsMenuClosed(Menu)
     */
    public void onOptionsMenuClosed(Menu menu) {
    }

對選項選單的操作,兩個都未做處理。

    /**
     * @see Activity#openOptionsMenu()
     */
    public void openOptionsMenu() {
        mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
    }
    
    /**
     * @see Activity#closeOptionsMenu()
     */
    public void closeOptionsMenu() {
        mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL);
    }

    /**
     * @see Activity#invalidateOptionsMenu()
     */
    public void invalidateOptionsMenu() {
        mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL);
    }

對選項選單的三個操作,均是呼叫的Window類裡面的方法。

/**
     * @see Activity#onCreateContextMenu(ContextMenu, View, ContextMenuInfo)
     */
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
    }

建立上下文選單,未處理。

為一個View註冊和取消註冊一個上下文選單:

    /**
     * @see Activity#registerForContextMenu(View)
     */
    public void registerForContextMenu(View view) {
        view.setOnCreateContextMenuListener(this);
    }
    
    /**
     * @see Activity#unregisterForContextMenu(View)
     */
    public void unregisterForContextMenu(View view) {
        view.setOnCreateContextMenuListener(null);
    }

開啟一個View的上下文選單:

   /**
     * @see Activity#openContextMenu(View)
     */
    public void openContextMenu(View view) {
        view.showContextMenu();
    }

    /**
     * @see Activity#onContextItemSelected(MenuItem)
     */
    public boolean onContextItemSelected(MenuItem item) {
        return false;
    }

上下文選單選項被處理,未做任何事情。

    /**
     * @see Activity#onContextMenuClosed(Menu)
     */
    public void onContextMenuClosed(Menu menu) {
    }

上下文選單關閉。

/**
     * This hook is called when the user signals the desire to start a search.
     */
    public boolean onSearchRequested() {
        final SearchManager searchManager = (SearchManager) mContext
                .getSystemService(Context.SEARCH_SERVICE);

        // associate search with owner activity
        final ComponentName appName = getAssociatedActivity();
        if (appName != null && searchManager.getSearchableInfo(appName) != null) {
            searchManager.startSearch(null, false, appName, null, false);
            dismiss();
            return true;
        } else {
            return false;
        }
    }

這個鉤子方法會在使用者想要搜尋的時候被呼叫。

 public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
        if (mActionBar != null) {
            return mActionBar.startActionMode(callback);
        }
        return null;
    }

通過系統給Activity一個控制UI操作模式(Give the Activity a chance to control the UI for an action mode requested by the system.)

/**
     * {@inheritDoc}
     *
     * Note that if you override this method you should always call through
     * to the superclass implementation by calling super.onActionModeStarted(mode).
     */
    public void onActionModeStarted(ActionMode mode) {
        mActionMode = mode;
    }

    /**
     * {@inheritDoc}
     *
     * Note that if you override this method you should always call through
     * to the superclass implementation by calling super.onActionModeFinished(mode).
     */
    public void onActionModeFinished(ActionMode mode) {
        if (mode == mActionMode) {
            mActionMode = null;
        }
    }

注意,如果你重寫這個方法,你一定要呼叫父類的super.onActionModeFinished(mode)。開始時候設定mode,結束時把mode設為空。

 /**
     * @return The activity associated with this dialog, or null if there is no associated activity.
     */
    private ComponentName getAssociatedActivity() {
        Activity activity = mOwnerActivity;
        Context context = getContext();
        while (activity == null && context != null) {
            if (context instanceof Activity) {
                activity = (Activity) context;  // found it!
            } else {
                context = (context instanceof ContextWrapper) ?
                        ((ContextWrapper) context).getBaseContext() : // unwrap one level
                        null;                                         // done
            }
        }
        return activity == null ? null : activity.getComponentName();
    }

返回相關聯activity的名字。

/**
     * Request that key events come to this dialog. Use this if your
     * dialog has no views with focus, but the dialog still wants
     * a chance to process key events.
     * 
     * @param get true if the dialog should receive key events, false otherwise
     * @see android.view.Window#takeKeyEvents
     */
    public void takeKeyEvents(boolean get) {
        mWindow.takeKeyEvents(get);
    }

請求來自Dialog的的按鍵事件,如果dialog上面沒有view獲取焦點,而且這個dialog想要處理按鍵事件,就呼叫該方法。

   /**
     * Enable extended window features.  This is a convenience for calling
     * {@link android.view.Window#requestFeature getWindow().requestFeature()}.
     * 
     * @param featureId The desired feature as defined in
     *                  {@link android.view.Window}.
     * @return Returns true if the requested feature is supported and now
     *         enabled.
     * 
     * @see android.view.Window#requestFeature
     */
    public final boolean requestWindowFeature(int featureId) {
        return getWindow().requestFeature(featureId);
    }

設定window的屬性。

/**
     * Convenience for calling
     * {@link android.view.Window#setFeatureDrawableResource}.
     */
    public final void setFeatureDrawableResource(int featureId, int resId) {
        getWindow().setFeatureDrawableResource(featureId, resId);
    }

    /**
     * Convenience for calling
     * {@link android.view.Window#setFeatureDrawableUri}.
     */
    public final void setFeatureDrawableUri(int featureId, Uri uri) {
        getWindow().setFeatureDrawableUri(featureId, uri);
    }

    /**
     * Convenience for calling
     * {@link android.view.Window#setFeatureDrawable(int, Drawable)}.
     */
    public final void setFeatureDrawable(int featureId, Drawable drawable) {
        getWindow().setFeatureDrawable(featureId, drawable);
    }

    /**
     * Convenience for calling
     * {@link android.view.Window#setFeatureDrawableAlpha}.
     */
    public final void setFeatureDrawableAlpha(int featureId, int alpha) {
        getWindow().setFeatureDrawableAlpha(featureId, alpha);
    }

設定一系列的drawable特性。

   /**
     * Sets whether this dialog is cancelable with the
     * {@link KeyEvent#KEYCODE_BACK BACK} key.
     */
    public void setCancelable(boolean flag) {
        mCancelable = flag;
    }

設定dialog是否可以取消。

    /**
     * Sets whether this dialog is canceled when touched outside the window's
     * bounds. If setting to true, the dialog is set to be cancelable if not
     * already set.
     * 
     * @param cancel Whether the dialog should be canceled when touched outside
     *            the window.
     */
    public void setCanceledOnTouchOutside(boolean cancel) {
        if (cancel && !mCancelable) {
            mCancelable = true;
        }
        
        mWindow.setCloseOnTouchOutside(cancel);
    }

設定當觸控dialog外部時,dialog是否可以取消。

 /**
     * Set a listener to be invoked when the dialog is canceled.
     * <p>
     * This will only be invoked when the dialog is canceled, if the creator
     * needs to know when it is dismissed in general, use
     * {@link #setOnDismissListener}.
     * 
     * @param listener The {@link DialogInterface.OnCancelListener} to use.
     */
    public void setOnCancelListener(final OnCancelListener listener) {
        if (mCancelAndDismissTaken != null) {
            throw new IllegalStateException(
                    "OnCancelListener is already taken by "
                    + mCancelAndDismissTaken + " and can not be replaced.");
        }
        if (listener != null) {
            mCancelMessage = mListenersHandler.obtainMessage(CANCEL, listener);
        } else {
            mCancelMessage = null;
        }
    }

設定一個當dialog被取消時,會被呼叫的監聽。

/**
     * Set a message to be sent when the dialog is canceled.
     * @param msg The msg to send when the dialog is canceled.
     * @see #setOnCancelListener(android.content.DialogInterface.OnCancelListener)
     */
    public void setCancelMessage(final Message msg) {
        mCancelMessage = msg;
    }

當dialog被取消時,設定傳送的資訊。

    /**
     * Set a listener to be invoked when the dialog is dismissed.
     * @param listener The {@link DialogInterface.OnDismissListener} to use.
     */
    public void setOnDismissListener(final OnDismissListener listener) {
        if (mCancelAndDismissTaken != null) {
            throw new IllegalStateException(
                    "OnDismissListener is already taken by "
                    + mCancelAndDismissTaken + " and can not be replaced.");
        }
        if (listener != null) {
            mDismissMessage = mListenersHandler.obtainMessage(DISMISS, listener);
        } else {
            mDismissMessage = null;
        }
    }

設定一個當dialog被銷燬時,會被呼叫的監聽

   /**
     * Sets a listener to be invoked when the dialog is shown.
     * @param listener The {@link DialogInterface.OnShowListener} to use.
     */
    public void setOnShowListener(OnShowListener listener) {
        if (listener != null) {
            mShowMessage = mListenersHandler.obtainMessage(SHOW, listener);
        } else {
            mShowMessage = null;
        }
    }
設定一個當dialog顯示時,會被呼叫的監聽

    /**
     * Set a message to be sent when the dialog is dismissed.
     * @param msg The msg to send when the dialog is dismissed.
     */
    public void setDismissMessage(final Message msg) {
        mDismissMessage = msg;
    }

設定dialog被銷燬時,傳送的資訊。

/** @hide */
    public boolean takeCancelAndDismissListeners(String msg, final OnCancelListener cancel,
            final OnDismissListener dismiss) {
        if (mCancelAndDismissTaken != null) {
            mCancelAndDismissTaken = null;
        } else if (mCancelMessage != null || mDismissMessage != null) {
            return false;
        }
        
        setOnCancelListener(cancel);
        setOnDismissListener(dismiss);
        mCancelAndDismissTaken = msg;
        
        return true;
    }

同時設定OnCancelListener和OnDismissListener。

    /**
     * By default, this will use the owner Activity's suggested stream type.
     * 
     * @see Activity#setVolumeControlStream(int)
     * @see #setOwnerActivity(Activity)
     */
    public final void setVolumeControlStream(int streamType) {
        getWindow().setVolumeControlStream(streamType);
    }

    /**
     * @see Activity#getVolumeControlStream()
     */
    public final int getVolumeControlStream() {
        return getWindow().getVolumeControlStream();
    }


設定和獲取Dialog所屬的Activity中音量控制鍵控制的音訊流。

    /**
     * Sets the callback that will be called if a key is dispatched to the dialog.
     */
    public void setOnKeyListener(final OnKeyListener onKeyListener) {
        mOnKeyListener = onKeyListener;
    }

設定一個回撥,當按鍵事件被分發到dialog 回撥函式會被呼叫。

private static final class ListenersHandler extends Handler {
        private WeakReference<DialogInterface> mDialog;

        public ListenersHandler(Dialog dialog) {
            mDialog = new WeakReference<DialogInterface>(dialog);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case DISMISS:
                    ((OnDismissListener) msg.obj).onDismiss(mDialog.get());
                    break;
                case CANCEL:
                    ((OnCancelListener) msg.obj).onCancel(mDialog.get());
                    break;
                case SHOW:
                    ((OnShowListener) msg.obj).onShow(mDialog.get());
                    break;
            }
        }
    }

ListenersHandler的定義。

將近3點了,有點困,想睡了。。。



檢視更多原始碼內容:Android原始碼解析



相關文章