代碼地址:
文章索引
- List item
序言
任何封裝都不是一蹴而就的,需要有一定的時間和經驗的積累,去挖掘深層次的需求。我看過很多人的封裝,有一種風格是在一個大的封裝裏集成了各種各樣的彈窗,是或否彈窗-進度條彈窗-加載彈窗-等等等,但後來我發現,面對日益多變的設計需求,任何指定UI的封裝都是活不不長久的,所以我從一開始做,就是打算讓每一個彈窗的創建都能自由化、簡單化,能真正對得起Base角色。
彈窗需求設計
- 簡單、快捷、一目瞭然的調用邏輯
- 支持自定義頁面
- 支持彈窗動畫自定義。
- 支持自動響應關閉、取消、確認等通用事件
- 支持統一化、能面對任何UI的事件回調機制
- 支持內存回收,處理內存泄漏問題
彈窗開做
構造方法
這裏的設計是,全程通過佈局文件來初始化彈窗,需要什麼UI,直接新建一個佈局就行了。至於怎麼自動響應關閉、取消、確認等通用事件,我這裏用的是通過在佈局內指定特定的id,即可添加相關的功能。裏入你需要確認按鈕和取消按鈕,只需要分別設置fw_dialog_win_bt_continue和fw_dialog_win_bt_cancel爲其id即可。
public TJDialog(@NonNull Context context, @LayoutRes int layoutId)
public TJDialog(@NonNull Context context, @LayoutRes int layoutId,@StyleRes int styleId)
public TJDialog(@NonNull Context context, @LayoutRes int layoutId, int width, int height)
public TJDialog(@NonNull Context context, @LayoutRes int layoutId, int width, int height,int styleId)
{
super(context,styleId);
this.width = width;//支持WRAP_CONTENT、MATCH_PARENT、自定義大小,用於後面設置彈窗的整體寬高
this.height = height;
//通過制定的id來尋找對應的組件,所以如果需要一個關閉按鈕,則只需要
VIEW_WIN_BG_ID = getViewWinBgId();//通過方法獲取id,如有需要可以重寫該方法二次封裝,改變其中默認的id。
VIEW_WIN_BOX_ID = getViewWinBoxId();
VIEW_WIN_TITLE_ID = getViewWinTitleId();
VIEW_WIN_BT_CONTINUE_ID = getViewWinBtContinue();
VIEW_WIN_BT_CANCEL_ID = getViewWinBtCancel();
VIEW_WIN_BT_CLOSE_ID = getViewWinBtClose();
baseView = LayoutInflater.from(context).inflate(layoutId, null);
setContentView(baseView);
initView();//初始化組件,設置點擊事件
}
private void initView()
{
//攔截兩個監聽事件,然後做中轉
super.setOnShowListener(dialog -> {
if(onShowListener!=null) {
onShowListener.onShow(dialog);
}
//統一事件監聽器,支持若干個常用事件監聽,和所有自定義按鈕的點擊事件監聽
if(onTJDialogListener!=null) {
onTJDialogListener.onShow(dialog,state);
}
});
super.setOnDismissListener(dialog -> {
if(onDismissListener!=null) {
onDismissListener.onDismiss(dialog);
}
//統一事件監聽器,支持若干個常用事件監聽,和所有自定義按鈕的點擊事件監聽
if(onTJDialogListener!=null) {
onTJDialogListener.onDismiss(dialog,state);
}
});
//初始化組件
winBgView = baseView.findViewById(VIEW_WIN_BG_ID);
winBoxView = baseView.findViewById(VIEW_WIN_BOX_ID);
winTitleView = baseView.findViewById(VIEW_WIN_TITLE_ID);
winBtContinue = baseView.findViewById(VIEW_WIN_BT_CONTINUE_ID);
winBtCancel = baseView.findViewById(VIEW_WIN_BT_CANCEL_ID);
winBtClose = baseView.findViewById(VIEW_WIN_BT_CLOSE_ID);
//初始化點擊事件
setOnClickListener(winBgView);
setOnClickListener(winBoxView);
setOnClickListener(winBtContinue);
setOnClickListener(winBtCancel);
setOnClickListener(winBtClose);
//這個方法是抽象方法,需要重寫設置你想要監聽其點擊事件的組件
int[] ids = onInitClick();
if(ids!=null)
{
for(int id:ids)
{
View view = baseView.findViewById(id);
setOnClickListener(view);
}
}
//抽象方法,用戶可以重寫初始化自己的組件
onInitView(baseView);
}
show()和dismiss()
@Override
public void show() {
if(!isShow)//防止重複顯示
{
baseView.setVisibility(View.VISIBLE);
if(winBgView!=null&&isShowWinBgAnim)
{
//顯示背景動畫,可自定義,當然默認的動畫也很美了
if(animationWinBgEnter==null) {
animationWinBgEnter = AnimationUtils.loadAnimation(getContext(), windowAnimEnterId);
} else {
animationWinBgEnter.reset();
}
winBgView.startAnimation(animationWinBgEnter);
}
if (winBoxView != null&&isShowWinBoxAnim) {
//顯示窗體動畫,可自定義,當然默認的動畫也很美了
if(animationWinBoxEnter==null) {
animationWinBoxEnter = AnimationUtils.loadAnimation(getContext(), contentAnimEnterId);
} else {
animationWinBoxEnter.reset();
}
winBoxView.startAnimation(animationWinBoxEnter);
}
Window window = getWindow();
if(window!=null)
{
window.setWindowAnimations(-1);
}
//攔截一些意外的報錯(popupWindows繼承過來的做法)
try {
super.show();
isShow = true;
}catch (Exception e){
LogUtil.exception(e);
}
//設置彈窗動畫
WindowManager.LayoutParams layoutParams = getWindow().getAttributes();
if (layoutParams != null) {
layoutParams.width = width;
layoutParams.height = height;
}
this.getWindow().setAttributes(layoutParams);
}
}
@Override
public void dismiss() {
if(isShow)
{
isShow = false;
if(winBoxView!=null&&isShowWinBoxAnim)
{
//執行窗體關閉動畫-默認的是漸變消失
if(animationWinBoxExit==null) {
animationWinBoxExit = AnimationUtils.loadAnimation(getContext(), contentAnimExitId);
} else {
animationWinBoxExit.reset();
}
winBoxView.startAnimation(animationWinBoxExit);
}
if(winBgView!=null)
{
//執行背景關閉動畫-默認的是漸變消失
if(animationWinBgExit==null) {
animationWinBgExit = AnimationUtils.loadAnimation(getContext(), windowAnimExitId);
} else {
animationWinBgExit.reset();
}
winBgView.startAnimation(animationWinBgExit);
animationWinBgExit.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
//在動畫執行完成之後dismiss彈窗。請放心,並不會造成內存泄漏。
TJDialog.super.dismiss();
baseView.setVisibility(View.INVISIBLE);
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
else
{
super.dismiss();
}
}
}
destroy()
這裏回收了所有的對象,完全不存在任何內存泄漏的可能。
@Override
public void destroy() {
setOnDismissListener(null);
setOnShowListener(null);
setOnTJDialogListener(null);
super.setOnDismissListener(null);
super.setOnShowListener(null);
onShowListener = null;
onDismissListener = null;
baseHandler.removeMessages(DISMISS);
baseHandler.removeCallbacksAndMessages(null);
baseView = null;
winBgView = null;
winBoxView= null;
winTitleView= null;
winBtContinue= null;
winBtCancel= null;
winBtClose= null;
}
整個類492行代碼,但主體代碼並不多,就上面這些。其實整個封裝的邏輯是很清晰的,並不複雜,但這樣之後確實爲我去掉了很多彈窗調用的煩惱。而且如果有需要的話,二次封裝一些通用的彈窗也是可以的。
個人知識有限,不敢託大,如有遺漏缺陷的地方,歡迎大家指正。