代码地址:
文章索引
- 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()
{
//初始化组件
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)//防止重复显示
{
super.setOnShowListener(dialog -> {
super.setOnShowListener(null);
if(onShowListener!=null) {
onShowListener.onShow(dialog);
}
//统一事件监听器,支持若干个常用事件监听,和所有自定义按钮的点击事件监听
if(onTJDialogListener!=null) {
onTJDialogListener.onShow(dialog,state);
}
});
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)
{
super.setOnDismissListener(dialog -> {
super.setOnDismissListener(null);
if(onDismissListener!=null) {
onDismissListener.onDismiss(dialog);
}
//统一事件监听器,支持若干个常用事件监听,和所有自定义按钮的点击事件监听
if(onTJDialogListener!=null) {
onTJDialogListener.onDismiss(dialog,state);
}
});
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行代码,但主体代码并不多,就上面这些。其实整个封装的逻辑是很清晰的,并不复杂,但这样之后确实为我去掉了很多弹窗调用的烦恼。而且如果有需要的话,二次封装一些通用的弹窗也是可以的。
个人知识有限,不敢托大,如有遗漏缺陷的地方,欢迎大家指正。