封裝一個通用的PopupWindow

上篇文章是關於建造者設計模式的,今天順便封裝一個通用的 PopupWindow 來實踐一下, 同時也方便之後使用 PopupWindow,本文將從下面幾個方面來介紹 PopupWindow 及其封裝,具體以下:java

  1. 概述
  2. 經常使用方法
  3. 基本使用
  4. 封裝 PopupWindow
  5. 使用封裝後的PopupWindow
  6. 顯示效果

概述

PopupWindow 表示一個彈窗,相似於 AlertDialog,相較 AlertDialog 來講 PopupWindow 使用起來更靈活,可有任意指定要顯示的位置,固然可以靈活的使用必然在某一層面有所犧牲,如 PopupWindow 相較 AlertDialog 沒有默認的佈局,每次都得專門建立彈窗的佈局,這一點來講 AlertDialog 就比較方便了,因此在開發中沒有最好的解決方案,要根據具體的需求選擇最合適的解決方案。設計模式

經常使用設置

PopupWindow 的建立,具體以下:微信

//構造方法
 public PopupWindow (Context context) public PopupWindow(View contentView) public PopupWindow(View contentView, int width, int height) public PopupWindow(View contentView, int width, int height, boolean focusable) 複製代碼

PopupWindow 的經常使用屬性設置,具體以下:ide

//設置View(必須)
 window.setContentView(contentView);
 //設置寬(必須)
 window.setWidth(WindowManager.LayoutParams.MATCH_PARENT);
 //設置高(必須)
 window.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
 //設置背景
 window.setBackgroundDrawable(new ColorDrawable(Color.GRAY));
 //設置PopupWindow以外的觸摸事件
 window.setOutsideTouchable(true);
 //設置PopupWindow消失的監聽器
 window.setOnDismissListener(this);
 //設置PopupWindow上的觸摸事件
 window.setTouchable(true);
 //設置PopupWindow彈出動畫
 window.setAnimationStyle(R.style.PopupWindowTranslateTheme);
複製代碼

PopupWindow 的顯示有兩種設置方式,一種是基於座標,另外一種是基於某個 View ,具體以下:佈局

//基於座標,參數(當前窗口的某個 View,位置,起始座標x, 起始座標y)
void showAtLocation (View parent, int gravity, int x, int y) //基於某個View,參數(附着的View,x 方向的偏移量,y 方向的偏移量) void showAsDropDown (View anchor, int xoff, int yoff, int gravity) void showAsDropDown (View anchor, int xoff, int yoff) void showAsDropDown (View anchor) 複製代碼

基本使用

PopupWindow 的主要內容基本如上,下面使用原生的 PopupWindow 實現一個彈窗,下面是關鍵代碼,具體以下:學習

//建立PopupWindow
PopupWindow window = new PopupWindow(this);
//設置顯示View
window.setContentView(contentView);
//設置寬高
window.setWidth(WindowManager.LayoutParams.MATCH_PARENT);
window.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
//設置背景
window.setBackgroundDrawable(new ColorDrawable(Color.GRAY));
//設置PopupWindow以外的觸摸事件
window.setOutsideTouchable(true);
//設置PopupWindow消失的監聽器
window.setOnDismissListener(new PopupWindow.OnDismissListener() {
    @Override
    public void onDismiss() {
        //監聽PopupWindow的消失
    }
});
//設置PopupWindow上的觸摸事件
window.setTouchable(true);
//設置PopupWindow彈出動畫
window.setAnimationStyle(R.style.PopupWindowTranslateTheme);
window.showAtLocation(btnTarget, Gravity.BOTTOM | Gravity.CENTER, 0, 0);
複製代碼

下面是顯示效果,具體以下:測試

PopupWindow.gif

封裝 PopupWindow

這裏對 PopupWindow 的封裝主要是對 PopupWindow 經常使用擺放位置作進一步封裝,使 PopupWindow 的調用更加靈活、簡潔。動畫

在封裝過程當中遇到的問題是不能正確獲取到 PopupWindow 的寬高,正確獲取寬高的方法是先對 PopupWindow 進行測量,而後再獲取其寬高,具體以下:ui

//獲取PopupWindow的寬高
mPopupWindow.getContentView().measure(
    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
int popupWidth = mPopupWindow.getContentView().getMeasuredWidth();
int popupHeight = mPopupWindow.getContentView().getMeasuredHeight();
複製代碼

對 PopupWindow 的封裝使用了建造者設計模式,下面看一下 PopupWindow 的默認配置,具體以下:this

public Builder(Context context) {
    this.context = context;
    this.popupWindow = new PopupWindow(context);
    //默認PopupWindow響應觸摸事件
    this.outsideTouchable = true;
    //默認響應觸摸事件
    this.touchable = true;
    //默認背景透明
    this.backgroundDrawable = new ColorDrawable(Color.TRANSPARENT);
    //默認寬高爲WRAP_CONTENT
    this.width  = WindowManager.LayoutParams.WRAP_CONTENT;
    this.height = WindowManager.LayoutParams.WRAP_CONTENT;
    //默認Gravity爲Gravity.CENTER
    this.gravity = Gravity.CENTER;
    this.layoutId = -1;
    //默認偏移量爲0
    this.offsetX = 0;
    this.offsetY = 0;
    //...
}
複製代碼

因爲寬高、背景、是否可點擊等相關屬性已經設置了默認值,使用時根據本身的需求設置相關屬性,如 PopupWindow 的動畫等,因此這些設置確定是非必須的,那麼那些事建立時必須的呢。

下面是對 PopupWindow 封裝類 MPopupWindow 的初始化,具體以下:

private void setPopupWindowConfig(MPopupWindow window) {
        if (contentView != null && layoutId != -1){
            throw new MException("setContentView and setLayoutId can't be used together.", "0");
        }else if (contentView == null && layoutId == -1){
            throw new MException("contentView or layoutId can't be null.", "1");
        }

        if (context == null) {
            throw new MException("context can't be null.", "2");
        } else {
            window.mContext = this.context;
        }

        window.mWidth  = this.width;
        window.mHeight = this.height;
        window.mView = this.contentView;
        window.mLayoutId = layoutId;
        window.mPopupWindow = this.popupWindow;
        window.mOutsideTouchable   = this.outsideTouchable;
        window.mBackgroundDrawable = this.backgroundDrawable;
        window.mOnDismissListener  = this.onDismissListener;
        window.mAnimationStyle = this.animationStyle;
        window.mTouchable = this.touchable;
        window.mOffsetX = this.offsetX;
        window.mOffsetY = this.offsetY;
        window.mGravity = this.gravity;
    }
}
複製代碼

顯然,這裏能夠看出 context 和 contentView 或 layoutId 是必須須要設置的,若是沒有設置相應的會有錯誤提示,固然在封裝中也對 contentView 和 layoutId 不能同時使用作了限制和若是使用了二者的錯誤提示。

下面是對外提供的顯示 PopupWindow 的方法,根據不一樣的枚舉類型將 PopupWindow 顯示在不一樣的位置,具體以下:

public void showPopupWindow(View v, LocationType type) {
    if (mView!=null){
        mPopupWindow.setContentView(mView);
    }else if (mLayoutId != -1){
        View contentView = LayoutInflater.from(mContext).inflate(mLayoutId, null);
        mPopupWindow.setContentView(contentView);
    }
    mPopupWindow.setWidth(mWidth);
    mPopupWindow.setHeight(mHeight);
    mPopupWindow.setBackgroundDrawable(mBackgroundDrawable);
    mPopupWindow.setOutsideTouchable(mOutsideTouchable);
    mPopupWindow.setOnDismissListener(mOnDismissListener);
    mPopupWindow.setAnimationStyle(mAnimationStyle);
    mPopupWindow.setTouchable(mTouchable);
    //獲取目標View的座標
    int[] locations = new int[2];
    v.getLocationOnScreen(locations);
    int left = locations[0];
    int top  =  locations[1];
    //獲取PopupWindow的寬高
    mPopupWindow.getContentView().measure(
            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
    int popupWidth = mPopupWindow.getContentView().getMeasuredWidth();
    int popupHeight = mPopupWindow.getContentView().getMeasuredHeight();

    switch (type) {
        case TOP_LEFT:
            mPopupWindow.showAtLocation(v,Gravity.NO_GRAVITY,left - popupWidth + mOffsetX,top - popupHeight + mOffsetY);
            break;
        case TOP_CENTER:
            int offsetX = (v.getWidth() - popupWidth) / 2;
            mPopupWindow.showAtLocation(v,Gravity.NO_GRAVITY,left + offsetX + mOffsetX,top - popupHeight + mOffsetY);
            break;
        case TOP_RIGHT:
            mPopupWindow.showAtLocation(v,Gravity.NO_GRAVITY,left + v.getWidth() + mOffsetX,top - popupHeight + mOffsetY);
            break;

        case BOTTOM_LEFT:
            mPopupWindow.showAsDropDown(v, -popupWidth + mOffsetX,mOffsetY);
            break;
        case BOTTOM_CENTER:
            int offsetX1 = (v.getWidth() - popupWidth) / 2;
            mPopupWindow.showAsDropDown(v,offsetX1 + mOffsetX,mOffsetY);
            break;
        case BOTTOM_RIGHT:
            mPopupWindow.showAsDropDown(v, v.getWidth() + mOffsetX,mOffsetY);
            break;

        case LEFT_TOP:
            mPopupWindow.showAtLocation(v, Gravity.NO_GRAVITY, left - popupWidth + mOffsetX, top - popupHeight + mOffsetY);
            break;
        case LEFT_BOTTOM:
            mPopupWindow.showAtLocation(v, Gravity.NO_GRAVITY, left - popupWidth + mOffsetX, top + v.getHeight() + mOffsetY);
            break;
        case LEFT_CENTER:
            int offsetY = (v.getHeight() - popupHeight) / 2;
            mPopupWindow.showAtLocation(v, Gravity.NO_GRAVITY,left - popupWidth + mOffsetX,top + offsetY + mOffsetY);
            break;

        case RIGHT_TOP:
            mPopupWindow.showAtLocation(v, Gravity.NO_GRAVITY, left + v.getWidth() + mOffsetX,top - popupHeight + mOffsetY);
            break;
        case RIGHT_BOTTOM:
            mPopupWindow.showAtLocation(v, Gravity.NO_GRAVITY, left + v.getWidth() + mOffsetX,top + v.getHeight() + mOffsetY);
            break;
        case RIGHT_CENTER:
            int offsetY1 = (v.getHeight() - popupHeight) / 2;
            mPopupWindow.showAtLocation(v, Gravity.NO_GRAVITY,left + v.getWidth() + mOffsetX,top + offsetY1 + mOffsetY);
            break;
        case FROM_BOTTOM:
            mPopupWindow.showAtLocation(v,mGravity,mOffsetX,mOffsetY);
            break;
    }
}
複製代碼

使用封裝後的PopupWindow

下面是使用封裝後的 PopupWindow,只需四行代碼就能夠顯示一個默認的 PopupWindow 了,具體以下:

private void showPopupWindow(MPopupWindow.LocationType type) {
    MPopupWindow popupWindow = new MPopupWindow
            .Builder(this)
            .setLayoutId(R.layout.popup_window_layout)
            .build();
    popupWindow.showPopupWindow(btnTarget, type);
}
複製代碼

因爲默認 PopupWindow 背景是透明的,建議測試時設置背景。

顯示效果:

下面是 PopupWindow 在各個位置的顯示,具體以下:

MPopupWindow.gif

能夠選擇關注微信公衆號:jzman-blog 獲取最新更新,一塊兒交流學習,公衆號回覆 MPopupWindow 獲取源碼連接。

零點小築
相關文章
相關標籤/搜索