拒絕無用功,封裝一個通用的 PopupWindow

夏至的稻穗發表於2017-07-09

作者: 夏至,歡迎轉載,但請保留這段申明,謝謝
juejin.im/post/5961e0…

為了避免重複造輪子,我們一般都會封裝一個通用的控制元件,比如這次,專案中需要用到比較多的 popupwindow ,如果需要一個個寫,那麼依舊會累死人,而且還是無用功,無意義,所以,封裝一個通用的,除了讓同事看了直刷666之外,自己還省了很多事情。
先上效果圖:

1、如何使用

那麼,一般我們配置一個 PopupWindow 正常步驟需要多少程式碼呢?如下:

PopupWindow popupWindow = new PopupWindow(this);
        View contentview = LayoutInflater.from(this).inflate(R.layout.popup_calendar,null);
        popupWindow =
                new PopupWindow(contentview,
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        true);
        //設定取消
        popupWindow.setOutsideTouchable(true);
        popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));


        //設定位置
        View rootview = LayoutInflater.from(this).inflate(R.layout.activity_main,null);
        popupWindow.showAtLocation(rootview,Gravity.CENTER,0,0);複製程式碼

一般我們需要實現上面的基本程式碼,PopupWindow 才能跑起來,然後我們還需要新增動畫,監聽back鍵等等,然後,另外一個需要用到的時候,又得重複寫,真的讓人很絕望,這個時候,封裝的思想就從腦袋冒出來了,那麼,封裝之後,怎麼樣的呢?如下:

CustomPopupWindow popupWindow = new CustomPopupWindow.Builder()
                        .setContext(this) //設定 context
                        .setContentView(R.layout.popup_calendar) //設定佈局檔案
                        .setwidth(LinearLayout.LayoutParams.WRAP_CONTENT) //設定寬度,由於我已經在佈局寫好,這裡就用 wrap_content就好了
                        .setheight(LinearLayout.LayoutParams.WRAP_CONTENT) //設定高度
                        .setFouse(true)  //設定popupwindow 是否可以獲取焦點
                        .setOutSideCancel(true) //設定點選外部取消
                        .setAnimationStyle(R.style.popup_anim_style) //設定popupwindow動畫
                      //  .setBackGroudAlpha(mActivity,0.7f) //是否設定背景色,原理為調節 alpha
                        .builder() //
                        .showAtLocation(R.layout.activity_calendar, Gravity.CENTER,0,0); //設定popupwindow居中顯示複製程式碼

注意上面的 showAtLocation 是在 builder 之後的,表示顯示正中間;如果想讓它顯示在某個 view 的相應位置,也可以使用 showAsLocation() 來實現。
至於為什麼在 builder() 的後面呢?因為不太確定在用的時候,是顯示在父佈局的位置,還是顯示在某個控制元件的相應位置,所以,我把程式碼封裝成下面這樣:

   /**
     * 根據父佈局,顯示位置
     * @param rootviewid
     * @param gravity
     * @param x
     * @param y
     * @return
     */
    public CustomPopupWindow showAtLocation(int rootviewid,int gravity,int x,int y){
        if (mPopupWindow != null){
            View rootview = LayoutInflater.from(mContext).inflate(rootviewid,null);
            mPopupWindow.showAtLocation(rootview,gravity,x,y);
        }
        return this;
    }複製程式碼

當然,你要把它抽出來也可以的;

還有一種常見的情況,我們常用 popupwindow 作用 dialog,那麼裡面有 button 處理相應的邏輯。那如何想獲取 PopupWindow 裡面的控制元件怎麼辦?為了方便呼叫,這裡我也採用用 id 的形式,所以,呼叫只要這樣即可:

mMonthPicker = (PickerView) popupWindow.getItemView(R.id.picker_month);複製程式碼

然後就可以用 mMonthPicker 這個 view 搞事情了。

這樣就把 contentview 中的控制元件取出來使用了,只要知道 id 就可以了,是不是方便了很多,都挺簡單的,大家自己封裝一邊就ok全明白了。

封裝思路

相比封裝 listview 和 recyclerview ,這個算是比較簡單的,就是觀察最原始的程式碼,提取最核心不變的;無非就是 PopupWindow 的最要佈局

  • cnotentview ,為了避免每次都來個 layoutinflate ,我們封裝成一個 id
  • 大小,我們都知道 PopupWindow 沒有自己的佈局,上面在給了 contentview 之後,大小也要給
  • 顯示位置,顯示就兩個函式 ,showAtLocation 和 showAsLocation ,為了方便,我們也寫成 id 的方式,當然也可以傳入 view

基本就可以了,至於其他附加項,比如動畫,點選外部取消,監聽back鍵,或者簡單 contentview 控制元件的事件,都是變動的,所以,用 Builder 的模式構建比較舒服一些。具體就這些了。如果你對 Builder 這中模式不熟悉,可以看我以前文章:

模仿常用框架Builder初始化資料,如何優雅地裝逼

3、CustomPopupWindow 完成程式碼

以下是我現在用的程式碼,大家可以參考一下,根據自己的需求新增或者刪除。

(編輯器竟然無法更新程式碼,我這邊用圖片來吧,坑爹、、、、)

動畫部分:

menushow :

menudiss :

佈局就不貼出來,由於用到自定義控制元件,貼出來反而不好,大家根據自己的需求,編寫即可。我找個時間把這個上下滾動自定義的 pickerview 也寫一下好了。。。。。

CustomPopupWindow 完整程式碼:

public  class CustomPopupWindow implements PopupWindow.OnDismissListener {
    private static final String TAG = "CustomPopupWindow";
    private PopupWindow mPopupWindow;
    private View contentview;
    private Context mContext;
    private Activity mActivity;
    public CustomPopupWindow(Builder builder) {
        mContext = builder.context;
        contentview = LayoutInflater.from(mContext).inflate(builder.contentviewid,null);
        if (builder.width ==0 || builder.height == 0) {
            builder.width = ViewGroup.LayoutParams.WRAP_CONTENT;
            builder.height = ViewGroup.LayoutParams.WRAP_CONTENT;
        }
        mPopupWindow =
                new PopupWindow(contentview, builder.width, builder.height, builder.fouse);
        //需要跟 setBackGroundDrawable 結合
        mPopupWindow.setOutsideTouchable(builder.outsidecancel);
        mPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        mPopupWindow.setAnimationStyle(builder.animstyle);

        //設定背景色
        if (builder.alpha > 0 && builder.alpha < 1){
            mActivity = builder.activity;
            WindowManager.LayoutParams params = builder.activity.getWindow().getAttributes();
            params.alpha = builder.alpha;
            builder.activity.getWindow().setAttributes(params);
        }

        mPopupWindow.setOnDismissListener(this);
    }
    /**
     * popup 消失
     */
    public void dismiss(){
        if (mPopupWindow != null){
            mPopupWindow.dismiss();
            if (mActivity != null){
                WindowManager.LayoutParams params = mActivity.getWindow().getAttributes();
                params.alpha = 1.0f;
                mActivity.getWindow().setAttributes(params); //回覆背景色
            }
        }
    }

    /**
     * 根據id獲取view
     * @param viewid
     * @return
     */
    public View getItemView(int viewid){
        if (mPopupWindow != null){
            return this.contentview.findViewById(viewid);
        }
        return null;
    }
    /**
     * 根據父佈局,顯示位置
     * @param rootviewid
     * @param gravity
     * @param x
     * @param y
     * @return
     */
    public CustomPopupWindow showAtLocation(int rootviewid,int gravity,int x,int y){
        if (mPopupWindow != null){
            View rootview = LayoutInflater.from(mContext).inflate(rootviewid,null);
            mPopupWindow.showAtLocation(rootview,gravity,x,y);
        }
        return this;
    }
    /**
     * 根據id獲取view ,並顯示在該view的位置
     * @param targetviewId
     * @param gravity
     * @param offx
     * @param offy
     * @return
     */
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    public CustomPopupWindow showAsLaction(int targetviewId, int gravity, int offx, int offy){
        if (mPopupWindow != null){
            View targetview = LayoutInflater.from(mContext).inflate(targetviewId,null);
            mPopupWindow.showAsDropDown(targetview,offx,offy,gravity);
        }
        return this;
    }
    /**
     * 顯示在 targetview 的不同位置
     * @param targetview
     * @param gravity
     * @param offx
     * @param offy
     * @return
     */
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    public CustomPopupWindow showAsLaction(View targetview, int gravity, int offx, int offy){
        if (mPopupWindow != null){
            mPopupWindow.showAsDropDown(targetview,offx,offy,gravity);
        }
        return this;
    }
    /**
     * 根據id設定焦點監聽
     * @param viewid
     * @param listener
     */
    public void setOnFocusListener(int viewid,View.OnFocusChangeListener listener){
        View view = getItemView(viewid);
        view.setOnFocusChangeListener(listener);
    }

    /**
     * 根據id設定點選事件監聽
     * @param viewid
     * @param listener
     */
    public void setOnClickListener(int viewid, View.OnClickListener listener){
        getItemView(viewid).setOnClickListener(listener);
    }

    /**
     * 監聽 dismiss,還原背景色
     */
    @Override
    public void onDismiss() {
        Log.d(TAG, "onDismiss: ");
        if (mActivity != null){
            WindowManager.LayoutParams params = mActivity.getWindow().getAttributes();
            params.alpha = 1.0f;
            mActivity.getWindow().setAttributes(params);
        }
    }

    /**
     * builder 類
     */
    public static class Builder{
        private int contentviewid;
        private int width;
        private int height;
        private boolean fouse;
        private boolean outsidecancel;
        private int animstyle;
        private Context context;
        private Activity activity;
        private float alpha;
       public Builder setContext(Context context){
           this.context = context;
           return this;
       }
        public Builder setContentView(int contentviewid){
            this.contentviewid = contentviewid;
            return this;
        }
        public Builder setwidth(int width){
            this.width = width;
            return this;
        }
        public Builder setheight(int height){
            this.height = height;
            return this;
        }
        public Builder setFouse(boolean fouse){
            this.fouse = fouse;
            return this;
        }
        public Builder setOutSideCancel(boolean outsidecancel){
            this.outsidecancel = outsidecancel;
            return this;
        }
        public Builder setAnimationStyle(int animstyle){
            this.animstyle = animstyle;
            return this;
        }
        public Builder setBackGroudAlpha(Activity activity,float alpha){
            this.activity = activity;
            this.alpha = alpha;
            return this;
        }
        public CustomPopupWindow builder(){
           return new CustomPopupWindow(this);
        }
    }
}複製程式碼

相關文章