一塊兒來封裝一個BasePopupWindow吧

本項目GitHub:github.com/razerdp/Bas…html

BasePopup 2.x更新思路分享連接:juejin.im/post/5c199c…

很是歡迎PR(dev分支)哦

本文首發於CSDN,次發於泡網在簡書這裏發佈,算是第三次修改了,這個項目也算是初步完成了,若是說要加些什麼,轉屏保持顯示算不算一個。。。java

固然,今天寫這個文章的目的是爲了方便朋友圈那邊文章的排版,畢竟我們朋友圈系列只要搞朋友圈相關的好了,其餘的控件一概封裝到別的文集裏面。android


介紹(超級簡單版)

在安卓系統,咱們常常會接觸到彈窗,說到彈窗,咱們常常接觸到的也就dialog或者popupWindow了。而這二者的區別,簡單的說就是「一大小二蒙層三阻塞」,若是再簡單點說,就是對話框與懸浮框的區別吧。。。具體仍是谷歌咯- -這裏就不詳細敘述了。git

問題

若是咱們度娘過popupWindow,咱們會知道,要是用一個popup,基本要如下幾個步驟:程序員

  1. 弄個佈局
  2. new 一個popup(傳入大小)
  3. 這個popup對象一大堆setxxxxx(特別是setBackgroundDrawable)
  4. 若是還須要動畫,那麼你一般會搜到的方法是。。。。xml弄出動畫, style裏面設定android:windowEnterAnimation和android:windowExitAnimation,而後執行第三步setXXXXX
  5. showAtLocation或者showAsDropDown什麼的

OMG!!!做爲一個程序員,我想要的只是跟TextView同樣,new一個對象,setText,完。作這麼多東東,又是style什麼的,真心想哭。github

因而,對此解決方法就是,封裝吧,親。


封裝

首先,我們要針對以上的問題提出一個指望的目標,很簡單,new一個popup,show,完- -。ide

那麼爲了之後的擴展,咱們須要咱們的popup最基本都要實現如下的功能:   - 自由的定義樣式佈局

  • 便利的動畫實現   - 可擴展   - 代碼簡潔易懂

在開工前,咱們先說說popup吧,popup支持咱們添加view來將其浮在當前層上,說到底,還不是windowManger.addView,將view給弄到decorView(注意,此decorView指popup的內部類PopupDecorView,是一個FrameLayout)上,那就懸浮了嘛。。。post

popup源碼

既然如此,在安卓裏面,萬(可見)物基於view嘛~因此咱們何不弄個ViewGroup進popup,而後咱們把它當成activity的佈局同樣,完成各類好玩的,好比點擊事件,好比動畫什麼的。動畫

因而咱們的工做流程就很清楚了:

  1. 提供設置view的接口
  2. 提供設置動畫方法
  3. 提供額外的輔助方法,好比點擊事件什麼的
  4. 統一管理showAtLocation方法

OK,大體流程肯定,接下來咱們一步一步的實現它。

Step 1 - 接口定義

首先,定義一個interface:

public interface BasePopup {
     View getPopupView();
     View getAnimaView();
}
複製代碼

該接口提供兩個功能:

  • 獲得popup的view(即咱們須要inflate的xml)
  • 獲得須要播放動畫的view

這裏還有一個能夠考慮,爲了更加簡便,咱們能夠考慮再添加一個方法:int getPopupViewById(),這樣咱們就不用在實現的時候寫那麼多的LayoutInflate.xxxxx了

Step 2 - BasePopup抽象

能夠確定的是,咱們要實現各類各樣的popup,那麼咱們確定不能是具體類,由於具體類限制一定不少,因此咱們抽象起來,至於具體的實現扔給子類完成就行了。

public abstract class BasePopupWindow implements BasePopup {
    private static final String TAG = "BasePopupWindow";
    //元素定義
    protected PopupWindow mPopupWindow;
    //popup視圖
    protected View mPopupView;
    protected View mAnimaView;
    protected View mDismissView;
    protected Activity mContext;
    //是否自動彈出輸入框(default:false)
    private boolean autoShowInputMethod = false;
    private OnDismissListener mOnDismissListener;
    //anima
    protected Animation curExitAnima;
    protected Animator curExitAnimator;
    protected Animation curAnima;
    protected Animator curAnimator;

    public BasePopupWindow(Activity context) {
        initView(context, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
    }

    public BasePopupWindow(Activity context, int w, int h) {
        initView(context, w, h);
    }
}
複製代碼

這裏解釋一下:由於是抽象,咱們大多數的權限都給protected,在咱們的變量,能夠看到彷佛重複了挺多的,從命名上看,咱們能夠分紅這麼幾類:

  • View:
    • popup主體(即xml)
    • 須要播放動畫的view
    • 點擊執行dismiss的view
  • Anima,分爲兩種主要是由於有些特別點的效果用animator更好:
    • animation(enter/exit)
    • animator(enter/exit)
  • Other:一些配置和接口

構造器裏,咱們只給出兩種,一種是傳入context,一種是指定寬高,這樣就能夠適應絕大多數的使用場景了。

接下來咱們初始化咱們的view:

private void initView(Activity context, int w, int h) {
        mContext = context;

        mPopupView = getPopupView();
        mPopupView.setFocusableInTouchMode(true);
        //默認佔滿全屏
        mPopupWindow = new PopupWindow(mPopupView, w, h);
        //指定透明背景,back鍵相關
        mPopupWindow.setBackgroundDrawable(new ColorDrawable());
        mPopupWindow.setFocusable(true);
        mPopupWindow.setOutsideTouchable(true);
        //無需動畫
        mPopupWindow.setAnimationStyle(0);

        //=============================================================爲外層的view添加點擊事件,並設置點擊消失
        mAnimaView = getAnimaView();
        mDismissView = getClickToDismissView();
        if (mDismissView != null) {
            mDismissView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    dismiss();
                }
            });
            if (mAnimaView != null) {
                mAnimaView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {

                    }
                });
            }
        }
        //=============================================================元素獲取
        curAnima = getShowAnimation();
        curAnimator = getShowAnimator();
        curExitAnima = getExitAnimation();
        curExitAnimator = getExitAnimator();
    }
複製代碼

在初始化方法裏,咱們主要是初始化一些常見的配置參數,但要注意的是,咱們的view是在popup new出來以前就獲取好的,固然,是經過抽象方法給子類實現。至於爲何mAnimaView 要給個點擊事件但不實現呢,這裏主要是防止點擊事件被屏蔽了。

咱們能夠看到各類getXXXX,在以前的版本中我給定所有都是抽象方法,後來發現,沒這個必要,因而這些方法只保留了幾個抽象的,其餘的都是功用方法(應該改成protected?)

protected abstract Animation getShowAnimation();

    protected abstract View getClickToDismissView();

    public Animator getShowAnimator() { return null; }

    public View getInputView() { return null; }

    public Animation getExitAnimation() {
        return null;
    }

    public Animator getExitAnimator() {
        return null;
    }
複製代碼

接下來是showPopup,這裏提供三個方法,分別是無參/紫苑id/view

showPopup

這三個方法都指向於同一個方法:tryToShowPopup

private void tryToShowPopup(int res, View v) throws Exception {
        //傳遞了view
        if (res == 0 && v != null) {
            mPopupWindow.showAtLocation(v, Gravity.CENTER, 0, 0);
        }
        //傳遞了res
        if (res != 0 && v == null) {
            mPopupWindow.showAtLocation(mContext.findViewById(res), Gravity.CENTER, 0, 0);
        }
        //什麼都沒傳遞,取頂級view的id
        if (res == 0 && v == null) {
            mPopupWindow.showAtLocation(mContext.findViewById(android.R.id.content), Gravity.CENTER, 0, 0);
        }
        if (curAnima != null && mAnimaView != null) {
            mAnimaView.clearAnimation();
            mAnimaView.startAnimation(curAnima);
        }
        if (curAnima == null && curAnimator != null && mAnimaView != null) {
            curAnimator.start();
        }
        //自動彈出鍵盤
        if (autoShowInputMethod && getInputView() != null) {
            getInputView().requestFocus();
            InputMethodUtils.showInputMethod(getInputView(), 150);
        }
    }
複製代碼

相關的註釋也寫了,其中android.R.id.content是decorView的contnet的id,也就是咱們setContentView的父類id。

接下來咱們須要對一些狀態操做進行控制,好比dismiss:

public void dismiss() {
        try {
            if (curExitAnima != null) {
                curExitAnima.setAnimationListener(mAnimationListener);
                mAnimaView.clearAnimation();
                mAnimaView.startAnimation(curExitAnima);
            }
            else if (curExitAnimator != null) {
                curExitAnimator.removeListener(mAnimatorListener);
                curExitAnimator.addListener(mAnimatorListener);
                curExitAnimator.start();
            }
            else {
                mPopupWindow.dismiss();
            }
        } catch (Exception e) {
            Log.d(TAG, "dismiss error");
        }
    }
複製代碼

若是存在exit animation/animator,則在dismiss前播放,固然,咱們的anima須要給定監聽器:

private Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
    ...animatorstart

        @Override
        public void onAnimationEnd(Animator animation) {
            mPopupWindow.dismiss();
        }

  ...animator cancel
  ...animator repeat
    };

    private Animation.AnimationListener mAnimationListener = new Animation.AnimationListener() {
     ...animationstart
        @Override
        public void onAnimationEnd(Animation animation) {
            mPopupWindow.dismiss();
        }
    ...animation repeat
    };
複製代碼

這樣就能夠確保咱們在執行完動畫纔去dismiss

這樣,咱們的basepopup就封裝好了,之後子類繼承他僅僅須要實現四個方法,而後就能夠跟平時寫佈局同樣使用popup了(甚至getClickToDismissView也能夠不用管,若是不是須要點擊消失的話)

例子


下面是一些根據這個basepopup寫的例子(具體的能夠到github看,而圖一,將會是接下來爲朋友圈點贊控件實現的效果):

comment_popup_with_exitAnima.gif
dialog_popup.gif
input_popup.gif
list_popup.gif
menu_popup.gif
scale_popup.gif
slide_from_bottom_popup.gif
相關文章
相關標籤/搜索