FrameWork層源碼分析之popupWindow

上文講述了dialog的創(chuàng)建流程之后,接下來(lái)講一下popwindow的創(chuàng)建以及和dialog的不同之處
首先看任何代碼都要帶著疑問(wèn)去看览露,不然很容易一頭霧水觉义,先說(shuō)幾個(gè)問(wèn)題
1.popwindow的顯示分2種黑毅,一種是showAsDropDown和showAtLocation
其各自實(shí)現(xiàn)的原理
2.popwindow的動(dòng)畫(huà)是和dialog一樣是window的動(dòng)畫(huà)么
3.popwindow的點(diǎn)擊是如何處理的暑中?
4.popwindow中和dialog一樣新建了一個(gè)phonewindow,同時(shí)又用附屬的activity的windowManager添加的么其障?
首先簡(jiǎn)單看下顯示的代碼

View mPopView = getLayoutInflater().inflate(R.layout.popwindow_layout, null);
                // 將轉(zhuǎn)換的View放置到 新建一個(gè)popuwindow對(duì)象中
                mPopupWindow = new PopupWindow(mPopView,
                        WindowManager.LayoutParams.WRAP_CONTENT,
                        WindowManager.LayoutParams.WRAP_CONTENT);
                // 點(diǎn)擊popuwindow外讓其消失
                mPopupWindow.setOutsideTouchable(true);

                 mPopupWindow.showAsDropDown(mCenterTx, Gravity.CENTER, 0, 0);

可以看到用法和dialog如出一轍银室,主要看下其構(gòu)造和show的邏輯吧

  public PopupWindow(View contentView, int width, int height, boolean focusable) {
        if (contentView != null) {
            mContext = contentView.getContext();
            mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        }

        setContentView(contentView);
        setWidth(width);
        setHeight(height);
        setFocusable(focusable);
    }

可以看到用的還是依附的activity的windowManager,然后設(shè)置了幾個(gè)屬性励翼。
接下來(lái)看下關(guān)鍵的showAsDropDown方法

public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
        if (isShowing() || mContentView == null) {
            return;
        }

        TransitionManager.endTransitions(mDecorView);

        registerForScrollChanged(anchor, xoff, yoff, gravity);

        mIsShowing = true;
        mIsDropdown = true;
//關(guān)鍵點(diǎn)一
        final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken());
        preparePopup(p);關(guān)鍵點(diǎn)一

        final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff, gravity);
        updateAboveAnchor(aboveAnchor);
//關(guān)鍵點(diǎn)二
        invokePopup(p);
    }

關(guān)鍵點(diǎn)一:

 public IBinder getWindowToken() {
        return mAttachInfo != null ? mAttachInfo.mWindowToken : null;
    }

無(wú)論是popwindow還是dialog時(shí)蜈敢,show的時(shí)候都要綁定所屬activity的token,而mAttachInfo這個(gè)對(duì)象是viewRootImp初始化的時(shí)候新建的
而初始化的時(shí)機(jī)就是WindowManagerGlobal.addview時(shí)候所創(chuàng)建的
所以這個(gè)token是為null的汽抚,在onCreate時(shí)候
接下來(lái)看下createPopupLayoutParams方法

private WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
        final WindowManager.LayoutParams p = new WindowManager.LayoutParams();

        // These gravity settings put the view at the top left corner of the
        // screen. The view is then positioned to the appropriate location by
        // setting the x and y offsets to match the anchor's bottom-left
        // corner.
        p.gravity = Gravity.START | Gravity.TOP;
        p.flags = computeFlags(p.flags);
        p.type = mWindowLayoutType;
        p.token = token;
        p.softInputMode = mSoftInputMode;
        p.windowAnimations = computeAnimationResource();

        if (mBackground != null) {
            p.format = mBackground.getOpacity();
        } else {
            p.format = PixelFormat.TRANSLUCENT;
        }

        if (mHeightMode < 0) {
            p.height = mLastHeight = mHeightMode;
        } else {
            p.height = mLastHeight = mHeight;
        }

        if (mWidthMode < 0) {
            p.width = mLastWidth = mWidthMode;
        } else {
            p.width = mLastWidth = mWidth;
        }

        // Used for debugging.
        p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));

        return p;
    }

很顯然抓狭,用的動(dòng)畫(huà)也是windowManger里屬性的動(dòng)畫(huà)

來(lái)看下關(guān)鍵點(diǎn)二

private void preparePopup(WindowManager.LayoutParams p) {
        if (mContentView == null || mContext == null || mWindowManager == null) {
            throw new IllegalStateException("You must specify a valid content view by "
                    + "calling setContentView() before attempting to show the popup.");
        }

        // The old decor view may be transitioning out. Make sure it finishes
        // and cleans up before we try to create another one.
        if (mDecorView != null) {
            mDecorView.cancelTransitions();
        }

        // When a background is available, we embed the content view within
        // another view that owns the background drawable.
        if (mBackground != null) {
            mBackgroundView = createBackgroundView(mContentView);
            mBackgroundView.setBackground(mBackground);
        } else {
            mBackgroundView = mContentView;
        }

        mDecorView = createDecorView(mBackgroundView);

        // The background owner should be elevated so that it casts a shadow.
        mBackgroundView.setElevation(mElevation);

        // We may wrap that in another view, so we'll need to manually specify
        // the surface insets.
        final int surfaceInset = (int) Math.ceil(mBackgroundView.getZ() * 2);
        p.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);
        p.hasManualSurfaceInsets = true;

        mPopupViewInitialLayoutDirectionInherited =
                (mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
        mPopupWidth = p.width;
        mPopupHeight = p.height;
    }

可以看到popwindow并沒(méi)有像dialog一樣,新建了一個(gè)phonewindow,那它是window么造烁?肯定是辐宾,只不過(guò)它的windowManager.layoutParams的屬性要自己配置狱从,而且它也沒(méi)有常規(guī)setContentView的這么多的嵌套
那既然沒(méi)有新建phonewindow,那它的點(diǎn)擊事件的回掉又從那里來(lái)呢膨蛮?

 private class PopupDecorView extends FrameLayout {
        private TransitionListenerAdapter mPendingExitListener;

        public PopupDecorView(Context context) {
            super(context);
        }

        @Override
        public boolean dispatchKeyEvent(KeyEvent event) {
            if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
                if (getKeyDispatcherState() == null) {
                    return super.dispatchKeyEvent(event);
                }

                if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
                    final KeyEvent.DispatcherState state = getKeyDispatcherState();
                    if (state != null) {
                        state.startTracking(event, this);
                    }
                    return true;
                } else if (event.getAction() == KeyEvent.ACTION_UP) {
                    final KeyEvent.DispatcherState state = getKeyDispatcherState();
                    if (state != null && state.isTracking(event) && !event.isCanceled()) {
                        dismiss();
                        return true;
                    }
                }
                return super.dispatchKeyEvent(event);
            } else {
                return super.dispatchKeyEvent(event);
            }
        }
....

可以看到它的點(diǎn)擊事件全部有自己的PopupDecorView重寫(xiě)了叠纹,并沒(méi)有遵循傳統(tǒng)的decorview的事件的傳遞方式,所以也沒(méi)有了回掉
關(guān)鍵點(diǎn)三

 private void invokePopup(WindowManager.LayoutParams p) {
        if (mContext != null) {
            p.packageName = mContext.getPackageName();
        }

        final PopupDecorView decorView = mDecorView;
        decorView.setFitsSystemWindows(mLayoutInsetDecor);

        setLayoutDirectionFromAnchor();

        mWindowManager.addView(decorView, p);

        if (mEnterTransition != null) {
            decorView.requestEnterTransition(mEnterTransition);
        }
    }

這里還是用activity的windowManager添加的敞葛,后續(xù)的操作和dialog的顯示是一樣的誉察,這里就不再特別分析了,有個(gè)關(guān)于token的地方要著重說(shuō)下
window的adjustLayoutParamsForSubWindow方法

void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
        CharSequence curTitle = wp.getTitle();
        if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
            wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
            if (wp.token == null) {
                View decor = peekDecorView();
                if (decor != null) {
                    wp.token = decor.getWindowToken();
                }
            }
            if (curTitle == null || curTitle.length() == 0) {
                String title;
                if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA) {
                    title="Media";
                } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY) {
                    title="MediaOvr";
                } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
                    title="Panel";
                } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL) {
                    title="SubPanel";
                } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL) {
                    title="AboveSubPanel";
                } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG) {
                    title="AtchDlg";
                } else {
                    title=Integer.toString(wp.type);
                }
                if (mAppName != null) {
                    title += ":" + mAppName;
                }
                wp.setTitle(title);
            }
        } else {
            if (wp.token == null) {
                wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
            }
            if ((curTitle == null || curTitle.length() == 0)
                    && mAppName != null) {
                wp.setTitle(mAppName);
            }
        }
        if (wp.packageName == null) {
            wp.packageName = mContext.getPackageName();
        }
        if (mHardwareAccelerated) {
            wp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
        }
    }

可以看到由于popwindow是子窗口類(lèi)型惹谐,token為null的情況下會(huì)調(diào)用activity的decorview的token持偏,當(dāng)然在oncreate時(shí)也為null了,而dialog就不同了氨肌,默認(rèn)給到的就是null鸿秆,走了else的邏輯直接把mApptoken也就是activity的token給到了,這也是為何popwindow不能再oncreate顯示而dialog可以的最根本原因

總結(jié)

先來(lái)說(shuō)下幾個(gè)問(wèn)題
1.顯示的位置其實(shí)和WindowManager.layoutParams有關(guān)showAsDropDown其實(shí)就是算出錨地的位置然后放到其下方怎囚,而showAtLocation沒(méi)有此段邏輯
2.動(dòng)畫(huà)的顯示卿叽,其實(shí)也是依靠WindowManager.layoutParam的屬性,可以說(shuō)一樣的
3.點(diǎn)擊區(qū)域的處理恳守,這個(gè)和dialog有所不同考婴,由于不是系統(tǒng)的decorview,所以重寫(xiě)了分發(fā)的方法催烘。
4.源碼中表明了都是windowManager添加的沥阱,但是popwindow并沒(méi)有新建phonewinow,其實(shí)新建一個(gè)子window只要windowManager和decorview就能新建,新建了phonewindow默認(rèn)的屬性就是應(yīng)用類(lèi)型的window而已伊群,這也是兩者最大的不同之處
很多人說(shuō)popwindow比dialog都要“輕”考杉,但是又說(shuō)不出個(gè)所以然來(lái),其實(shí)無(wú)非就是子window上不能新建子window舰始,而dialog上可以添加子window罷了

注意點(diǎn):

1.popwindow和dialog都會(huì)存在activity的token不存在了崇棠,而不能顯示的情況.要做好基類(lèi)的統(tǒng)一處理
2.popwinow一般用在依靠某個(gè)view顯示的位置情況,dialog則不然

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蔽午,一起剝皮案震驚了整個(gè)濱河市易茬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌及老,老刑警劉巖抽莱,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異骄恶,居然都是意外死亡食铐,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)僧鲁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)虐呻,“玉大人象泵,你說(shuō)我怎么就攤上這事≌宓穑” “怎么了偶惠?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)朗涩。 經(jīng)常有香客問(wèn)我忽孽,道長(zhǎng),這世上最難降的妖魔是什么谢床? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任兄一,我火速辦了婚禮,結(jié)果婚禮上识腿,老公的妹妹穿的比我還像新娘出革。我一直安慰自己,他們只是感情好渡讼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布骂束。 她就那樣靜靜地躺著,像睡著了一般硝全。 火紅的嫁衣襯著肌膚如雪栖雾。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,554評(píng)論 1 305
  • 那天伟众,我揣著相機(jī)與錄音析藕,去河邊找鬼。 笑死凳厢,一個(gè)胖子當(dāng)著我的面吹牛账胧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播先紫,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼治泥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了遮精?” 一聲冷哼從身側(cè)響起居夹,我...
    開(kāi)封第一講書(shū)人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎本冲,沒(méi)想到半個(gè)月后准脂,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡檬洞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年狸膏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片添怔。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡湾戳,死狀恐怖贤旷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情砾脑,我是刑警寧澤幼驶,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站拦止,受9級(jí)特大地震影響县遣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜汹族,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望其兴。 院中可真熱鬧顶瞒,春花似錦、人聲如沸元旬。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)匀归。三九已至坑资,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間穆端,已是汗流浹背袱贮。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留体啰,地道東北人攒巍。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像荒勇,于是被迫代替她去往敵國(guó)和親柒莉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容