本項目GitHub:github.com/razerdp/Bas…html
本文首發於CSDN,次發於泡網在簡書這裏發佈,算是第三次修改了,這個項目也算是初步完成了,若是說要加些什麼,轉屏保持顯示算不算一個。。。java
固然,今天寫這個文章的目的是爲了方便朋友圈那邊文章的排版,畢竟我們朋友圈系列只要搞朋友圈相關的好了,其餘的控件一概封裝到別的文集裏面。android
在安卓系統,咱們常常會接觸到彈窗,說到彈窗,咱們常常接觸到的也就dialog或者popupWindow了。而這二者的區別,簡單的說就是「一大小二蒙層三阻塞」
,若是再簡單點說,就是對話框與懸浮框的區別吧。。。具體仍是谷歌咯- -這裏就不詳細敘述了。git
若是咱們度娘過popupWindow,咱們會知道,要是用一個popup,基本要如下幾個步驟:程序員
OMG!!!做爲一個程序員,我想要的只是跟TextView同樣,new一個對象,setText,完。作這麼多東東,又是style什麼的,真心想哭。github
首先,我們要針對以上的問題提出一個指望的目標,很簡單,new一個popup,show,完- -。ide
那麼爲了之後的擴展,咱們須要咱們的popup最基本都要實現如下的功能: - 自由的定義樣式佈局
在開工前,咱們先說說popup吧,popup支持咱們添加view來將其浮在當前層上,說到底,還不是windowManger.addView,將view給弄到decorView(注意,此decorView指popup的內部類PopupDecorView,是一個FrameLayout
)上,那就懸浮了嘛。。。post
既然如此,在安卓裏面,萬(可見)物基於view嘛~因此咱們何不弄個ViewGroup進popup,而後咱們把它當成activity的佈局同樣,完成各類好玩的,好比點擊事件,好比動畫什麼的。動畫
因而咱們的工做流程就很清楚了:
OK,大體流程肯定,接下來咱們一步一步的實現它。
首先,定義一個interface:
public interface BasePopup {
View getPopupView();
View getAnimaView();
}
複製代碼
該接口提供兩個功能:
這裏還有一個能夠考慮,爲了更加簡便,咱們能夠考慮再添加一個方法:int getPopupViewById(),這樣咱們就不用在實現的時候寫那麼多的LayoutInflate.xxxxx了
能夠確定的是,咱們要實現各類各樣的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,在咱們的變量,能夠看到彷佛重複了挺多的,從命名上看,咱們能夠分紅這麼幾類:
構造器裏,咱們只給出兩種,一種是傳入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
這三個方法都指向於同一個方法: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看,而圖一,將會是接下來爲朋友圈點贊控件實現的效果):