中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

PopupWindow源碼分析

發布時間:2020-07-21 15:40:04 來源:網絡 閱讀:308 作者:楊充 欄目:移動開發
目錄介紹
  • 1.最簡單的創建方法
    • 1.1 PopupWindow構造方法
    • 1.2 顯示PopupWindow
    • 1.3 最簡單的創建
    • 1.4 注意問題寬和高屬性
  • 2.源碼分析
    • 2.1 setContentView(View contentView)
    • 2.2 showAsDropDown()源碼
    • 2.3 dismiss()源碼分析
    • 2.4 PopupDecorView源碼分析
  • 3.經典總結
    • 3.1 PopupWindow和Dialog有什么區別?
    • 3.2 創建和銷毀的大概流程
    • 3.3 為何彈窗點擊一下就dismiss呢?
  • 4.PopupWindow封裝庫介紹

好消息

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

1.最簡單的創建方法

1.1 PopupWindow構造方法
  • 如下所示
    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):相對某個控件的位置(正左下方),無偏移
    showAsDropDown(View anchor, int xoff, int yoff):相對某個控件的位置,有偏移
    showAtLocation(View parent, int gravity, int x, int y):相對于父控件的位置(例如正中央Gravity.CENTER,下方Gravity.BOTTOM等),可以設置偏移或無偏移
1.3 最簡單的創建
  • 具體如下所示

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

    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);
  • 注意:必須設置寬和高,否則不顯示任何東西
    • 這里的WRAP_CONTENT可以換成fill_parent 也可以是具體的數值,它是指PopupWindow的大小,也就是contentview的大小,注意popupwindow根據這個大小顯示你的View,如果你的View本身是從xml得到的,那么xml的第一層view的大小屬性將被忽略。相當于popupWindow的width和height屬性直接和第一層View相對應。

2.源碼分析

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

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

      
      public void setContentView(View contentView) {
      //判斷是否show,如果已經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);
      }
      
      //在這里根據SDK版本而不是在構造函數中設置附加InDecor的默認設置,因為構造函數中可能沒有上下文對象。我們只想在這里設置默認,如果應用程序尚未設置附加InDecor。
      if (mContext != null && !mAttachedInDecorSet) {
          setAttachedInDecor(mContext.getApplicationInfo().targetSdkVersion
                  >= Build.VERSION_CODES.LOLLIPOP_MR1);
      }

    }

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

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

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

    TransitionManager.endTransitions(mDecorView);
    
    //下面單獨講
    //https://github.com/yangchong211/YCBlogs
    attachToAnchor(anchor, xoff, yoff, gravity);
    
    mIsShowing = true;
    mIsDropdown = true;
    
    //通過createPopupLayoutParams方法創建和初始化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)源碼

    • 執行了一個attachToAnchor,意思是PopupWindow類似一個錨掛在目標view的下面,這個函數主要講xoff、yoff(x軸、y軸偏移值)、gravity(比如Gravity.BOTTOM之類,指的是PopupWindow放在目標view哪個方向邊緣的位置)這個attachToAnchor有點意思,通過弱引用保存目標view和目標view的rootView(我們都知道:通過弱引用和軟引用可以防止內存泄漏)、這個rootview是否依附在window、還有保存偏差值、gravity
    • 關于四種引用的深入介紹可以參考我的這邊文章: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)這個方法源碼
    • 把這個LayoutParams傳過去,把PopupWindow真正的樣子,也就是view創建出來,在這個preparePopup函數里,一開始準備backgroundView,因為一般mBackgroundView是null,所以把之前setContentView設置的contentView作為mBackgroundView。
    • PopupWindow源碼分析
  • 接著看看createDecorView(mBackgroundView)這個方法源碼
    • 把PopupWindow的根view創建出來,并把contentView通過addView方法添加進去。PopupDecorView繼承FrameLayout,其中沒有繪畫什么,只是復寫了dispatchKeyEvent和onTouchEvent之類的事件分發的函數,還有實現進場退場動畫的執行函數
    • PopupWindow源碼分析
    • PopupWindow源碼分析
  • 最后看看invokePopup(WindowManager.LayoutParams p)源碼
    • 執行invokePopup(p),這個函數主要將popupView添加到應用DecorView的相應位置,通過之前創建WindowManager完成這個步驟,現在PopupWIndow可以看得到。
    • 并且請求在下一次布局傳遞之后運行Enter轉換。
    • PopupWindow源碼分析
2.3 dismiss()源碼分析
  • 通過對象調用該方法可以達到銷毀彈窗的目的。
    • 重點看一下這個兩個方法。移除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的子類,然后看一下里面的代碼:

    • 可以發現其重寫了onTouchEvent時間,這樣我們在點擊popupWindow外面的時候就會執行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.經典總結

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

4.PopupWindow封裝庫介紹

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

關于其他內容介紹

01.關于博客匯總鏈接
  • 1.技術博客匯總
  • 2.開源項目匯總
  • 3.生活博客匯總
  • 4.喜馬拉雅音頻匯總
  • 5.其他匯總
02.關于我的博客
  • 我的個人站點: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
  • 泡在網上的日子: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
向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

温泉县| 正定县| 潜山县| 阿图什市| 酉阳| 神木县| 平南县| 舒城县| 双辽市| 理塘县| 丹阳市| 乃东县| 怀安县| 永兴县| 焉耆| 綦江县| 呼玛县| 华宁县| 高碑店市| 上高县| 宣汉县| 陇川县| 隆尧县| 萝北县| 漳州市| 永顺县| 东明县| 大庆市| 武安市| 灵台县| 株洲市| 仪征市| 房产| 安龙县| 鄢陵县| 盱眙县| 黎城县| 新晃| 新密市| 苗栗市| 闽清县|