本專案GitHub:github.com/razerdp/Bas…
BasePopup 2.x更新思路分享連結:juejin.im/post/5c199c…
非常歡迎PR(dev分支)哦
本文首發於CSDN,次發於泡網在簡書這裡釋出,算是第三次修改了,這個專案也算是初步完成了,如果說要加些什麼,轉屏保持顯示算不算一個。。。
當然,今天寫這個文章的目的是為了方便朋友圈那邊文章的排版,畢竟我們們朋友圈系列只要搞朋友圈相關的好了,其他的控制元件一律封裝到別的文集裡面。
介紹(超級簡單版)
在安卓系統,我們經常會接觸到彈窗,說到彈窗,我們經常接觸到的也就dialog或者popupWindow了。而這兩者的區別,簡單的說就是“一大小二蒙層三阻塞”
,如果再簡單點說,就是對話方塊與懸浮框的區別吧。。。具體還是谷歌咯- -這裡就不詳細敘述了。
問題
如果我們度娘過popupWindow,我們會知道,要是用一個popup,基本要以下幾個步驟:
- 弄個佈局
- new 一個popup(傳入大小)
- 這個popup物件一大堆setxxxxx(特別是setBackgroundDrawable)
- 如果還需要動畫,那麼你通常會搜到的方法是。。。。xml弄出動畫, style裡面設定android:windowEnterAnimation和android:windowExitAnimation,然後執行第三步setXXXXX
- showAtLocation或者showAsDropDown什麼的
OMG!!!作為一個程式設計師,我想要的只是跟TextView一樣,new一個物件,setText,完。做這麼多東東,又是style什麼的,真心想哭。
於是,對此解決方法就是,封裝吧,親。
封裝
首先,我們們要針對以上的問題提出一個期望的目標,很簡單,new一個popup,show,完- -。
那麼為了以後的擴充套件,我們需要我們的popup最基本都要實現以下的功能: - 自由的定義樣式
- 便利的動畫實現 - 可擴充套件 - 程式碼簡潔易懂
在開工前,我們先說說popup吧,popup支援我們新增view來將其浮在當前層上,說到底,還不是windowManger.addView,將view給弄到decorView(注意,此decorView指popup的內部類PopupDecorView,是一個FrameLayout
)上,那就懸浮了嘛。。。
既然如此,在安卓裡面,萬(可見)物基於view嘛~所以我們何不弄個ViewGroup進popup,然後我們把它當成activity的佈局一樣,完成各種好玩的,比如點選事件,比如動畫什麼的。
於是我們的工作流程就很清楚了:
- 提供設定view的介面
- 提供設定動畫方法
- 提供額外的輔助方法,比如點選事件什麼的
- 統一管理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
這三個方法都指向於同一個方法: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看,而圖一,將會是接下來為朋友圈點贊控制元件實現的效果):