中文字幕日韩精品一区二区免费_精品一区二区三区国产精品无卡在_国精品无码专区一区二区三区_国产αv三级中文在线

PopupWindow源碼分析

目錄介紹
  • 1.最簡單的創(chuàng)建方法
    • 1.1 PopupWindow構(gòu)造方法
    • 1.2 顯示PopupWindow
    • 1.3 最簡單的創(chuàng)建
    • 1.4 注意問題寬和高屬性
  • 2.源碼分析
    • 2.1 setContentView(View contentView)
    • 2.2 showAsDropDown()源碼
    • 2.3 dismiss()源碼分析
    • 2.4 PopupDecorView源碼分析
  • 3.經(jīng)典總結(jié)
    • 3.1 PopupWindow和Dialog有什么區(qū)別?
    • 3.2 創(chuàng)建和銷毀的大概流程
    • 3.3 為何彈窗點(diǎn)擊一下就dismiss呢?
  • 4.PopupWindow封裝庫介紹

好消息

  • 博客筆記大匯總【16年3月到至今】,包括Java基礎(chǔ)及深入知識(shí)點(diǎn),Android技術(shù)博客,Python學(xué)習(xí)筆記等等,還包括平時(shí)開發(fā)中遇到的bug匯總,當(dāng)然也在工作之余收集了大量的面試題,長期更新維護(hù)并且修正,持續(xù)完善……開源的文件是markdown格式的!同時(shí)也開源了生活博客,從12年起,積累共計(jì)47篇[近20萬字],轉(zhuǎn)載請注明出處,謝謝!
  • 鏈接地址:https://github.com/yangchong211/YCBlogs
  • 如果覺得好,可以star一下,謝謝!當(dāng)然也歡迎提出建議,萬事起于忽微,量變引起質(zhì)變!
  • PopupWindow封裝庫項(xiàng)目地址:https://github.com/yangchong211/YCDialog
  • 02.Toast源碼深度分析
    • 最簡單的創(chuàng)建,簡單改造避免重復(fù)創(chuàng)建,show()方法源碼分析,scheduleTimeoutLocked吐司如何自動(dòng)銷毀的,TN類中的消息機(jī)制是如何執(zhí)行的,普通應(yīng)用的Toast顯示數(shù)量是有限制的,用代碼解釋為何Activity銷毀后Toast仍會(huì)顯示,Toast偶爾報(bào)錯(cuò)Unable to add window是如何產(chǎn)生的,Toast運(yùn)行在子線程問題,Toast如何添加系統(tǒng)窗口的權(quán)限等等
  • 03.DialogFragment源碼分析
    • 最簡單的使用方法,onCreate(@Nullable Bundle savedInstanceState)源碼分析,重點(diǎn)分析彈窗展示和銷毀源碼,使用中show()方法遇到的IllegalStateException分析
  • 05.PopupWindow源碼分析
    • 顯示PopupWindow,注意問題寬和高屬性,showAsDropDown()源碼,dismiss()源碼分析,PopupWindow和Dialog有什么區(qū)別?為何彈窗點(diǎn)擊一下就dismiss呢?
  • 06.Snackbar源碼分析
    • 最簡單的創(chuàng)建,Snackbar的make方法源碼分析,Snackbar的show顯示與點(diǎn)擊消失源碼分析,顯示和隱藏中動(dòng)畫源碼分析,Snackbar的設(shè)計(jì)思路,為什么Snackbar總是顯示在最下面
  • 07.彈窗常見問題
    • DialogFragment使用中show()方法遇到的IllegalStateException,什么常見產(chǎn)生的?Toast偶爾報(bào)錯(cuò)Unable to add window,Toast運(yùn)行在子線程導(dǎo)致崩潰如何解決?

1.最簡單的創(chuàng)建方法

1.1 PopupWindow構(gòu)造方法
  • 如下所示
    public PopupWindow (Context context)
    public PopupWindow(View contentView)
    public PopupWindow(int width, int height)
    public PopupWindow(View contentView, int width, int height)
    public PopupWindow(View contentView, int width, int height, boolean focusable)
1.2 顯示PopupWindow
  • 如下所示
    showAsDropDown(View anchor):相對某個(gè)控件的位置(正左下方),無偏移
    showAsDropDown(View anchor, int xoff, int yoff):相對某個(gè)控件的位置,有偏移
    showAtLocation(View parent, int gravity, int x, int y):相對于父控件的位置(例如正中央Gravity.CENTER,下方Gravity.BOTTOM等),可以設(shè)置偏移或無偏移
1.3 最簡單的創(chuàng)建
  • 具體如下所示

    讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來自于我們對這個(gè)行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價(jià)值的長期合作伙伴,公司提供的服務(wù)項(xiàng)目有:國際域名空間、虛擬主機(jī)、營銷軟件、網(wǎng)站建設(shè)、惠陽網(wǎng)站維護(hù)、網(wǎng)站推廣。

    //創(chuàng)建對象
    PopupWindow popupWindow = new PopupWindow(this);
    View inflate = LayoutInflater.from(this).inflate(R.layout.view_pop_custom, null);
    //設(shè)置view布局
    popupWindow.setContentView(inflate);
    popupWindow.setWidth(LinearLayout.LayoutParams.WRAP_CONTENT);
    popupWindow.setHeight(LinearLayout.LayoutParams.WRAP_CONTENT);
    //設(shè)置動(dòng)畫的方法
    popupWindow.setAnimationStyle(R.style.BottomDialog);
    //設(shè)置PopUpWindow的焦點(diǎn),設(shè)置為true之后,PopupWindow內(nèi)容區(qū)域,才可以響應(yīng)點(diǎn)擊事件
    popupWindow.setTouchable(true);
    //設(shè)置背景透明
    popupWindow.setBackgroundDrawable(new ColorDrawable(0x00000000));
    //點(diǎn)擊空白處的時(shí)候讓PopupWindow消失
    popupWindow.setOutsideTouchable(true);
    // true時(shí),點(diǎn)擊返回鍵先消失 PopupWindow
    // 但是設(shè)置為true時(shí)setOutsideTouchable,setTouchable方法就失效了(點(diǎn)擊外部不消失,內(nèi)容區(qū)域也不響應(yīng)事件)
    // false時(shí)PopupWindow不處理返回鍵,默認(rèn)是false
    popupWindow.setFocusable(false);
    //設(shè)置dismiss事件
    popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
        @Override
        public void onDismiss() {
    
        }
    });
    boolean showing = popupWindow.isShowing();
    if (!showing){
        //show,并且可以設(shè)置位置
        popupWindow.showAsDropDown(mTv1);
    }
1.4 注意問題寬和高屬性
  • 先看問題代碼,下面這個(gè)不會(huì)出現(xiàn)彈窗,思考:為什么?

    PopupWindow popupWindow = new PopupWindow(this);
    View inflate = LayoutInflater.from(this).inflate(R.layout.view_pop_custom, null);
    popupWindow.setContentView(inflate);
    popupWindow.setAnimationStyle(R.style.BottomDialog);
    popupWindow.showAsDropDown(mTv1);
  • 注意:必須設(shè)置寬和高,否則不顯示任何東西
    • 這里的WRAP_CONTENT可以換成fill_parent 也可以是具體的數(shù)值,它是指PopupWindow的大小,也就是contentview的大小,注意popupwindow根據(jù)這個(gè)大小顯示你的View,如果你的View本身是從xml得到的,那么xml的第一層view的大小屬性將被忽略。相當(dāng)于popupWindow的width和height屬性直接和第一層View相對應(yīng)。

2.源碼分析

2.1 setContentView(View contentView)源碼分析
  • 首先先來看看源碼

    • 可以看出,先判斷是否show,如果沒有showing的話,則進(jìn)行contentView賦值,如果mWindowManager為null,則取獲取mWindowManager,這個(gè)很重要。最后便是根據(jù)SDK版本而不是在構(gòu)造函數(shù)中設(shè)置附加InDecor的默認(rèn)設(shè)置,因?yàn)闃?gòu)造函數(shù)中可能沒有上下文對象。我們只想在這里設(shè)置默認(rèn),如果應(yīng)用程序尚未設(shè)置附加InDecor。

      
      public void setContentView(View contentView) {
      //判斷是否show,如果已經(jīng)show,則返回
      if (isShowing()) {
          return;
      }
      //賦值
      mContentView = contentView;
      
      if (mContext == null && mContentView != null) {
          mContext = mContentView.getContext();
      }
      
      if (mWindowManager == null && mContentView != null) {
          mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
      }
      
      //在這里根據(jù)SDK版本而不是在構(gòu)造函數(shù)中設(shè)置附加InDecor的默認(rèn)設(shè)置,因?yàn)闃?gòu)造函數(shù)中可能沒有上下文對象。我們只想在這里設(shè)置默認(rèn),如果應(yīng)用程序尚未設(shè)置附加InDecor。
      if (mContext != null && !mAttachedInDecorSet) {
          setAttachedInDecor(mContext.getApplicationInfo().targetSdkVersion
                  >= Build.VERSION_CODES.LOLLIPOP_MR1);
      }

    }

  • 接著來看一下setAttachedInDecor源碼部分
    • 執(zhí)行setAttachedInDecor給一個(gè)變量賦值為true,表示已經(jīng)在decor里注冊了(注意:現(xiàn)在還沒有使用WindowManager把PopupWindow添加到DecorView上)
      public void setAttachedInDecor(boolean enabled) {
      mAttachedInDecor = enabled;
      mAttachedInDecorSet = true;
      }
2.2 showAsDropDown()源碼
  • 先來看一下showAsDropDown(View anchor)部分代碼

    • 可以看出,調(diào)用這個(gè)方法,默認(rèn)偏移值都是0;關(guān)于這個(gè)attachToAnchor(anchor, xoff, yoff, gravity)方法作用,下面再說。之后通過createPopupLayoutParams方法創(chuàng)建和初始化LayoutParams,然后把這個(gè)LayoutParams傳過去,把PopupWindow真正的樣子,也就是view創(chuàng)建出來。
      
      public void showAsDropDown(View anchor) {
      showAsDropDown(anchor, 0, 0);
      }

    //主要看這個(gè)方法
    //注意啦:關(guān)于更多內(nèi)容,可以參考我的博客大匯總:https://github.com/yangchong211/YCBlogs
    public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
    if (isShowing() || mContentView == null) {
    return;
    }

    TransitionManager.endTransitions(mDecorView);
    
    //下面單獨(dú)講
    //https://github.com/yangchong211/YCBlogs
    attachToAnchor(anchor, xoff, yoff, gravity);
    
    mIsShowing = true;
    mIsDropdown = true;
    
    //通過createPopupLayoutParams方法創(chuàng)建和初始化LayoutParams
    final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken());
    preparePopup(p);
    
    final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
            p.width, p.height, gravity);
    updateAboveAnchor(aboveAnchor);
    p.accessibilityIdOfAnchor = (anchor != null) ? anchor.getAccessibilityViewId() : -1;
    
    invokePopup(p);

    }

  • 接著來看看attachToAnchor(anchor, xoff, yoff, gravity)源碼

    • 執(zhí)行了一個(gè)attachToAnchor,意思是PopupWindow類似一個(gè)錨掛在目標(biāo)view的下面,這個(gè)函數(shù)主要講xoff、yoff(x軸、y軸偏移值)、gravity(比如Gravity.BOTTOM之類,指的是PopupWindow放在目標(biāo)view哪個(gè)方向邊緣的位置)這個(gè)attachToAnchor有點(diǎn)意思,通過弱引用保存目標(biāo)view和目標(biāo)view的rootView(我們都知道:通過弱引用和軟引用可以防止內(nèi)存泄漏)、這個(gè)rootview是否依附在window、還有保存偏差值、gravity
    • 關(guān)于四種引用的深入介紹可以參考我的這邊文章:01.四種引用比較與源碼分析

      private void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
      detachFromAnchor();
      
      final ViewTreeObserver vto = anchor.getViewTreeObserver();
      if (vto != null) {
          vto.addOnScrollChangedListener(mOnScrollChangedListener);
      }
      
      final View anchorRoot = anchor.getRootView();
      anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
      
      mAnchor = new WeakReference<>(anchor);
      mAnchorRoot = new WeakReference<>(anchorRoot);
      mIsAnchorRootAttached = anchorRoot.isAttachedToWindow();
      
      mAnchorXoff = xoff;
      mAnchorYoff = yoff;
      mAnchoredGravity = gravity;
      }
  • 接著再來看看preparePopup(p)這個(gè)方法源碼
    • 把這個(gè)LayoutParams傳過去,把PopupWindow真正的樣子,也就是view創(chuàng)建出來,在這個(gè)preparePopup函數(shù)里,一開始準(zhǔn)備backgroundView,因?yàn)橐话鉳BackgroundView是null,所以把之前setContentView設(shè)置的contentView作為mBackgroundView。
    • PopupWindow源碼分析
  • 接著看看createDecorView(mBackgroundView)這個(gè)方法源碼
    • 把PopupWindow的根view創(chuàng)建出來,并把contentView通過addView方法添加進(jìn)去。PopupDecorView繼承FrameLayout,其中沒有繪畫什么,只是復(fù)寫了dispatchKeyEvent和onTouchEvent之類的事件分發(fā)的函數(shù),還有實(shí)現(xiàn)進(jìn)場退場動(dòng)畫的執(zhí)行函數(shù)
    • PopupWindow源碼分析
    • PopupWindow源碼分析
  • 最后看看invokePopup(WindowManager.LayoutParams p)源碼
    • 執(zhí)行invokePopup(p),這個(gè)函數(shù)主要將popupView添加到應(yīng)用DecorView的相應(yīng)位置,通過之前創(chuàng)建WindowManager完成這個(gè)步驟,現(xiàn)在PopupWIndow可以看得到。
    • 并且請求在下一次布局傳遞之后運(yùn)行Enter轉(zhuǎn)換。
    • PopupWindow源碼分析
2.3 dismiss()源碼分析
  • 通過對象調(diào)用該方法可以達(dá)到銷毀彈窗的目的。
    • 重點(diǎn)看一下這個(gè)兩個(gè)方法。移除view和清除錨視圖
    • PopupWindow源碼分析
  • 接著看看dismissImmediate(View decorView, ViewGroup contentHolder, View contentView)源碼

    • 第一步,通過WindowManager注銷PopupView
    • 第二步,PopupView移除contentView
    • 第三步,講mDecorView,mBackgroundView置為null

      private void dismissImmediate(View decorView, ViewGroup contentHolder, View contentView) {
      // If this method gets called and the decor view doesn't have a parent,
      // then it was either never added or was already removed. That should
      // never happen, but it's worth checking to avoid potential crashes.
      if (decorView.getParent() != null) {
          mWindowManager.removeViewImmediate(decorView);
      }
      
      if (contentHolder != null) {
          contentHolder.removeView(contentView);
      }
      
      // This needs to stay until after all transitions have ended since we
      // need the reference to cancel transitions in preparePopup().
      mDecorView = null;
      mBackgroundView = null;
      mIsTransitioningToDismiss = false;
      }
2.4 PopupDecorView源碼分析
  • 通過createDecorView(View contentView)方法可以知道,是PopupDecorView直接new出來的布局對象decorView,外面包裹了一層PopupDecorView,這里的PopupDecorView也是我們自定義的FrameLayout的子類,然后看一下里面的代碼:

    • 可以發(fā)現(xiàn)其重寫了onTouchEvent時(shí)間,這樣我們在點(diǎn)擊popupWindow外面的時(shí)候就會(huì)執(zhí)行pupopWindow的dismiss方法,取消PopupWindow。
    private class PopupDecorView extends FrameLayout {
        private TransitionListenerAdapter mPendingExitListener;
    
        public PopupDecorView(Context context) {
            super(context);
        }
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            final int x = (int) event.getX();
            final int y = (int) event.getY();
    
            if ((event.getAction() == MotionEvent.ACTION_DOWN)
                    && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
                dismiss();
                return true;
            } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
                dismiss();
                return true;
            } else {
                return super.onTouchEvent(event);
            }
        }
    }

3.經(jīng)典總結(jié)

3.1 PopupWindow和Dialog有什么區(qū)別?
  • 兩者最根本的區(qū)別在于有沒有新建一個(gè)window,PopupWindow沒有新建,而是將view加到DecorView;Dialog是新建了一個(gè)window,相當(dāng)于走了一遍Activity中創(chuàng)建window的流程
  • 從源碼中可以看出,PopupWindow最終是執(zhí)行了mWindowManager.addView方法,全程沒有新建window
3.2 創(chuàng)建和銷毀的大概流程
  • 源碼比較少,比較容易懂,即使不太懂,只要借助有道詞典翻譯一下英文注釋,還是可以搞明白的。
  • 總結(jié)一下PopupWindow的創(chuàng)建出現(xiàn)、消失有哪些重要操作
    • 創(chuàng)建PopupWindow的時(shí)候,先創(chuàng)建WindowManager,因?yàn)閃IndowManager擁有控制view的添加和刪除、修改的能力。這一點(diǎn)關(guān)于任主席的藝術(shù)探索書上寫的很詳細(xì)……
    • 然后是setContentView,保存contentView,這個(gè)步驟就做了這個(gè)
    • 顯示PopupWindow,這個(gè)步驟稍微復(fù)雜點(diǎn),創(chuàng)建并初始化LayoutParams,設(shè)置相關(guān)參數(shù),作為以后PopupWindow在應(yīng)用DecorView里哪里顯示的憑據(jù)。然后創(chuàng)建PopupView,并且將contentView插入其中。最后使用WindowManager將PopupView添加到應(yīng)用DecorView里。
    • 銷毀PopupView,WindowManager把PopupView移除,PopupView再把contentView移除,最后把對象置為null
3.3 為何彈窗點(diǎn)擊一下就dismiss呢?
  • PopupWindow通過為傳入的View添加一層包裹的布局,并重寫該布局的點(diǎn)擊事件,實(shí)現(xiàn)點(diǎn)擊PopupWindow之外的區(qū)域PopupWindow消失的效果

4.PopupWindow封裝庫介紹

項(xiàng)目地址:https://github.com/yangchong211/YCDialog
  • 鏈?zhǔn)骄幊?,十分方便,更多?nèi)容可以直接參考我的開源demo
    new CustomPopupWindow.PopupWindowBuilder(this)
        //.setView(R.layout.pop_layout)
        .setView(contentView)
        .setFocusable(true)
        //彈出popWindow時(shí),背景是否變暗
        .enableBackgroundDark(true)
        //控制亮度
        .setBgDarkAlpha(0.7f)
        .setOutsideTouchable(true)
        .setAnimationStyle(R.style.popWindowStyle)
        .setOnDissmissListener(new PopupWindow.OnDismissListener() {
            @Override
            public void onDismiss() {
                //對話框銷毀時(shí)
            }
        })
        .create()
        .showAsDropDown(tv6,0,10);

關(guān)于其他內(nèi)容介紹

01.關(guān)于博客匯總鏈接
  • 1.技術(shù)博客匯總
  • 2.開源項(xiàng)目匯總
  • 3.生活博客匯總
  • 4.喜馬拉雅音頻匯總
  • 5.其他匯總
02.關(guān)于我的博客
  • 我的個(gè)人站點(diǎn):www.yczbj.org,www.ycbjie.cn
  • github:https://github.com/yangchong211
  • 知乎:https://www.zhihu.com/people/yang-chong-69-24/pins/posts
  • 簡書:http://www.jianshu.com/u/b7b2c6ed9284
  • csdn:http://my.csdn.net/m0_37700275
  • 喜馬拉雅聽書:http://www.ximalaya.com/zhubo/71989305/
  • 開源中國:https://my.oschina.net/zbj1618/blog
  • 泡在網(wǎng)上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
  • 郵箱:yangchong211@163.com
  • 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
  • segmentfault頭條:https://segmentfault.com/u/xiangjianyu/articles

本文名稱:PopupWindow源碼分析
當(dāng)前網(wǎng)址:http://www.rwnh.cn/article38/jipcsp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站策劃、網(wǎng)站設(shè)計(jì)公司虛擬主機(jī)、移動(dòng)網(wǎng)站建設(shè)云服務(wù)器、標(biāo)簽優(yōu)化

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

微信小程序開發(fā)
峨眉山市| 铁岭市| 报价| 黄浦区| 鸡西市| 洞头县| 宁晋县| 淳化县| 桓仁| 昆山市| 杂多县| 田林县| 吉首市| 甘孜县| 小金县| 衢州市| 望城县| 万宁市| 应用必备| 贞丰县| 钦州市| 万源市| 吴川市| 淅川县| 威远县| 汝城县| 城市| 阿拉善左旗| 咸丰县| 勃利县| 巩留县| 长春市| 枞阳县| 平和县| 收藏| 安庆市| 鸡泽县| 青岛市| 怀宁县| 安徽省| 防城港市|