最近在实现PopupWindow的时候发现一个很好用的开源库----BasePopupWindow
没有使用封装的开源库之前,我们定义一个PopupWindowe正常的步骤是
private var popWindow = PopupWindow()
popView = LayoutInflater.from(mContext).inflate(R.layout.gis_select_pop_layout,null)
popWindow.apply {
contentView = popView
isOutsideTouchable = true//必须设置背景
isTouchable = true
setBackgroundDrawable(BitmapDrawable())
}
popView?.lists?.setOnItemClickListener { parent, view, position, id ->
val currentSelectedInfo = mShowList[position]
if(currentSelectedInfo.key == selectedInfo?.key){
//当前选择的项和上次选择的项重复,则变为取消操作
if(!mCancel){
popWindow.dismiss()
mSelectListener?.onSelected(selectedInfo)
return@setOnItemClickListener
}
currentSelectedInfo.isSelected = false
selectResult.text = ""
selectedInfo = null
} else{
//选择当前项
currentSelectedInfo.isSelected = true
if(selectedInfo!=null){
selectedInfo?.isSelected = false
}
selectedInfo = currentSelectedInfo
selectResult.text = currentSelectedInfo.title
for (item in mAdapter?.list!!){
(item as SelectInfo).isSelected = item.key == selectedInfo?.key
}
}
所有的配置项都在activity中实现,额外增加了activity的代码量,所以很早就有大神就封装了关于PopupWindow实现的开源库----BasePopupWindow
首先看一下BasePopupWindow的构造函数,可以看到构造器可以传入的参数是很全面的,注释也解释的很清楚,传入什么参数就和该参数的window层级一致,无法显示在其他Window上方。
public BasePopupWindow(Context context) {
this(context, 0, 0);
}
public BasePopupWindow(Context context, int width, int height) {
this(context, width, height, 0);
}
public BasePopupWindow(Fragment fragment) {
this(fragment, 0, 0);
}
public BasePopupWindow(Fragment fragment, int width, int height) {
this(fragment, width, height, 0);
}
public BasePopupWindow(Dialog dialog) {
this(dialog, 0, 0);
}
public BasePopupWindow(Dialog dialog, int width, int height) {
this(dialog, width, height, 0);
}
示例:
在fragment创建点击事件弹出一个dialog,在dialog内部创建点击事件弹出PopupWindow,传入的参数为Context,运行后的效果是弹出的PopupWindow在dialog后面一层级,而且dialog销毁了该PopupWindow还没有销毁
修改传入的参数为Dialog,运行后的效果是弹出的PopupWindow和dialog同层级
然后我们看下BasePopupWindow初始化做的工作,可以看到根据传入的参数不同在BasePopupHelper里面返回其activity,如果传入的参数为空就返回其顶层的activity,根据返回的activity绑定其生命周期(防止内存泄露);初始化BasePopupHelper对象;初始化View
BasePopupWindow(Object ownerAnchorParent, int width, int height, int flag) {
this.ownerAnchorParent = ownerAnchorParent;
Activity act = BasePopupHelper.findActivity(ownerAnchorParent);
//double check
if (act == null) {
throw new NullPointerException("无法从context处获得Activity,请确保您的Context是否为Activity");
}
if (act instanceof LifecycleOwner) {
bindLifecycleOwner((LifecycleOwner) act);
} else {
listenForLifeCycle(act);
}
onCreateConstructor(ownerAnchorParent, width, height);
mContext = act;
mHelper = new BasePopupHelper(this);
initView(width, height);
}
@Nullable
static Activity findActivity(Object parent) {
return findActivity(parent, true);
}
@Nullable
static Activity findActivity(Object parent, boolean returnTopIfNull) {
Activity act = null;
if (parent instanceof Context) {
act = PopupUtils.getActivity((Context) parent);
} else if (parent instanceof Fragment) {
act = ((Fragment) parent).getActivity();
} else if (parent instanceof Dialog) {
act = PopupUtils.getActivity(((Dialog) parent).getContext());
}
if (act == null && returnTopIfNull) {
act = BasePopupSDK.getInstance().getTopActivity();
}
return act;
}
再进入到初始化view的方法中,可以看到调用了onCreateContentView用来获取ContentView,如果自定义了宽高设置popupWindow的宽高,这里初始化PopupWindowProxy,PopupWindowProxy传入的参数是BasePopupContextWrapper,我们都知道ContextWrapper是Context的装饰类,这里将context和BasePopupHelper对象传进去组成PopupWindow的装饰类(装饰器模式用来动态给对象增加额外的功能),BasePopupContextWrapper是为了构建PopupWindow的WindowManager代理类WindowManagerProxy,最后将初始化的BasePopupContextWrapper对象作为参数传入PopupWindow的代理类PopupWindowProxy,BasePopupHelper类用来处理BasePopupWindow的绘制和动画的逻辑, 最后当ContentView创建的时候回调onViewCreated方法。
void initView(int width, int height) {
mContentView = onCreateContentView();
mHelper.setContentRootId(mContentView);
mDisplayAnimateView = onCreateAnimateView();
if (mDisplayAnimateView == null) {
mDisplayAnimateView = mContentView;
}
setWidth(width);
setHeight(height);
//默认占满全屏
mPopupWindowProxy = new PopupWindowProxy(new PopupWindowProxy.BasePopupContextWrapper(getContext(), mHelper));
mPopupWindowProxy.setContentView(mContentView);
mPopupWindowProxy.setOnDismissListener(this);
setPopupAnimationStyle(0);
mHelper.preMeasurePopupView(mContentView, width, height);
if (mContentView != null) {
onViewCreated(mContentView);
}
}
static class BasePopupContextWrapper extends ContextWrapper implements ClearMemoryObject {
BasePopupHelper helper;
WindowManagerProxy mWindowManagerProxy;
public BasePopupContextWrapper(Context base, BasePopupHelper helper) {
super(base);
this.helper = helper;
}
@Override
public Object getSystemService(String name) {
if (TextUtils.equals(name, Context.WINDOW_SERVICE)) {
if (mWindowManagerProxy != null) {
return mWindowManagerProxy;
}
WindowManager windowManager = (WindowManager) super.getSystemService(name);
mWindowManagerProxy = new WindowManagerProxy(windowManager, helper);
return mWindowManagerProxy;
}
return super.getSystemService(name);
}
@Override
public void clear(boolean destroy) {
if (mWindowManagerProxy != null) {
mWindowManagerProxy.clear(destroy);
}
if (destroy) {
helper = null;
mWindowManagerProxy = null;
}
}
}
最后看下BasePopupWindow代理类PopupWindowProxy里面实现了哪些方法,设置弹出窗口获取焦点、点击外部消失、设置背景、设置输入法模式等
public PopupWindowProxy(BasePopupContextWrapper context) {
super(context);
mBasePopupContextWrapper = context;
setFocusable(true);
setOutsideTouchable(true);
setBackgroundDrawable(new ColorDrawable());
setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
}
关于BasePopupWindow的具体使用以及开放的Api参考: www.yuque.com/razerdp/bas…