前言
很久没写点东西了,在家闲着考了个驾照,花了一个半月,中国的驾考真的是没眼看,刚拿到驾照当天就被疫情封闭在家,直接封了一个多月,人都麻了,再来一次估计直接过年了,最近刚开始干点活。
Xpopup是我非常喜欢的一个Github开源库,一直在用,我在Xpopup2.x版本的时候看过一遍它的代码,现在已经更新到3.x版本了,这两天也没啥事,又重新看了一遍,Xpopup的代码还是很容易阅读的,有兴趣的话可以跟着我一起快速的了解一下他的代码。
一、入口方法
Xpopup的基本用法
new XPopup.Builder(getContext())
.xxx() 参数设置方法
.asXXX(传入一个BasePopupView 的实例对象)
.show();
Xpopup类是一个使用入口类,Builder听名字就知道是一个建造者模式,用来配置弹窗的各种参数,所有参数都会汇总成一个PopupInfo 对象。然后通过asXXX()方法将这个PopupInfo 对象传给BasePopupView 的实例对象,最后调用BasePopupView 的show()方法执行弹窗的显示逻辑。
Builder的大致代码如下,省略大部分设置方法:
public static class Builder {
private final PopupInfo popupInfo = new PopupInfo();
private Context context;
public Builder(Context context) {
this.context = context;
}
/**
* 设置按下返回键是否关闭弹窗,默认为true
*
* @param isDismissOnBackPressed
* @return
*/
public Builder dismissOnBackPressed(Boolean isDismissOnBackPressed) {
this.popupInfo.isDismissOnBackPressed = isDismissOnBackPressed;
return this;
}
...省略其他参数设置方法
/**
* 显示确认和取消对话框
*
* @param title 对话框标题,传空串会隐藏标题
* @param content 对话框内容
* @param cancelBtnText 取消按钮的文字内容
* @param confirmBtnText 确认按钮的文字内容
* @param confirmListener 点击确认的监听器
* @param cancelListener 点击取消的监听器
* @param isHideCancel 是否隐藏取消按钮
* @param bindLayoutId 自定义的布局Id,没有则传0;要求自定义布局中必须包含的TextView以及id有:tv_title,tv_content,tv_cancel,tv_confirm
* @return
*/
public ConfirmPopupView asConfirm(CharSequence title, CharSequence content, CharSequence cancelBtnText, CharSequence confirmBtnText, OnConfirmListener confirmListener, OnCancelListener cancelListener, boolean isHideCancel,
int bindLayoutId) {
ConfirmPopupView popupView = new ConfirmPopupView(this.context, bindLayoutId);
popupView.setTitleContent(title, content, null);
popupView.setCancelText(cancelBtnText);
popupView.setConfirmText(confirmBtnText);
popupView.setListener(confirmListener, cancelListener);
popupView.isHideCancel = isHideCancel;
popupView.popupInfo = this.popupInfo;
return popupView;
}
...省略其他默认弹窗方法
}
二、BasePopupView 的执行流程
2.1 BasePopupView类的重要属性
Xpopup中所有弹窗的基类是BasePopupView ,可以看到BasePopupView 是一个自定义的FrameLayout.下面列出了几个比较重要的属性。
//BasePopupView重要的属性
public abstract class BasePopupView extends FrameLayout{
public PopupInfo popupInfo;
public PopupStatus popupStatus = PopupStatus.Dismiss;
public FullScreenDialog dialog;
}
//FullScreenDialog ,省略大部分代码
public class FullScreenDialog extends Dialog {
BasePopupView contentView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(contentView);
}
}
属性解析:
PopupInfo popupInfo:弹窗的属性配置类,我们通过XPopup类的方法设置的所有参数都会汇总成一个PopupInfo 对象,像弹窗的宽高、背景、点击是否消失、状态栏导航栏信息、输入法设置、动画样式等等。
PopupStatus popupStatus:弹窗的状态,包括Show 显示、Showing, 正在执行显示动画、Dismiss 隐藏、Dismissing 正在执行消失动画。
FullScreenDialog dialog:用于显示PopupView的dialog,FullScreenDialog和BasePopupView 互相持有对方的对象。FullScreenDialog通过setContentView(contentView);将BasePopupView 设置为了显示内容。
FullScreenDialog 里面按照BasePopupView 的popupInfo进行设置dialog全屏、以及窗口状态栏、导航栏样式等信息。
2.2 构造方法
看完属性,再看一下BasePopupView 的构造方法,如下:
public BasePopupView(@NonNull Context context) {
super(context);
...省略其他代码
setId(View.generateViewId());
View contentView = LayoutInflater.from(context).inflate(getInnerLayoutId(), this, false);
contentView.setAlpha(0);
addView(contentView);
}
/**
* 内部使用,自定义弹窗的时候不要重新这个方法
* @return
*/
protected abstract int getInnerLayoutId();
BasePopupView的构造方法里加载了一个内部布局contentView ,getInnerLayoutId()是一个抽象方法,其他继承自BasePopupView的核心类都会实现这个方法,比如CenterPopupView和FrameLayout布局文件里是一个FrameLayout,BottomPopupView布局文件里是一个SmartDragLayout,这个contentView 有什么用呢?因为它可以说是弹窗显示根View,所以可以对它进行大小调整、位移、动画等无数的操作。
2.3Show()方法的流程
我们查看一下show()方法的代码:
public BasePopupView show() {
...省略其他代码,主要是判断弹窗是否正在显示或者已经显示过
View cv = activity.getWindow().getDecorView().findViewById(android.R.id.content);
cv.post(new Runnable() {
@Override
public void run() {
attachToHost();
}
});
return this;
}
Show()方法主要启动了一个Runnable,调用了attachToHost()方法,我们查看该方法:
private void attachToHost() {
...省略其他代码,主要是设置生命周期监听
if (popupInfo.isViewMode) {
//view实现
ViewGroup decorView = (ViewGroup) getActivity().getWindow().getDecorView();
if(getParent()!=null) ((ViewGroup)getParent()).removeView(this);
decorView.addView(this, getLayoutParams());
} else {
//dialog实现
if (dialog == null) {
dialog = new FullScreenDialog(getContext()).setContent(this);
}
Activity activity = getActivity();
if(activity!=null && !activity.isFinishing() && !dialog.isShowing()) dialog.show();
}
...省略其他代码,主要是设置软键盘的监听。
init();
}
可以看到该方法主要是:创建FullScreenDialog,并且关联BasePopupView和FullScreenDialog,然后调用 dialog.show()显示弹窗。
最后调用了init()方法,继续查看init()方法。
/**
* 执行初始化
*/
protected void init() {
if (shadowBgAnimator == null)
shadowBgAnimator = new ShadowBgAnimator(this, getAnimationDuration(), getShadowBgColor());
if (popupInfo.hasBlurBg) {
blurAnimator = new BlurAnimator(this, getShadowBgColor());
blurAnimator.hasShadowBg = popupInfo.hasShadowBg;
blurAnimator.decorBitmap = XPopupUtils.view2Bitmap((getActivity()).getWindow().getDecorView());
}
//1. 初始化Popup
if (this instanceof AttachPopupView || this instanceof BubbleAttachPopupView
|| this instanceof PartShadowPopupView || this instanceof PositionPopupView) {
initPopupContent();
} else if (!isCreated) {
initPopupContent();
}
if (!isCreated) {
isCreated = true;
onCreate();
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
if (popupInfo.xPopupCallback != null) popupInfo.xPopupCallback.onCreated(this);
}
handler.post(initTask);
}
/**
* 请使用onCreate,主要给弹窗内部用,不要去重写。
*/
protected void initPopupContent() { }
Init()方法:初始化一下背景动画,调用 initPopupContent()方法,给BasePopupView的核心子类用的,做一些初始化操作,调用onCreate()方法,这个方法是我们继承BasePopupView或者它的核心子类自定义弹窗时初始化用的。最后执行了一个handler操作initTask。我们查看这个initTask。
private final Runnable initTask = new Runnable() {
@Override
public void run() {
if (getHostWindow() == null) return;
if (popupInfo!=null && popupInfo.xPopupCallback != null)
popupInfo.xPopupCallback.beforeShow(BasePopupView.this);
beforeShow();
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
if (!(BasePopupView.this instanceof FullScreenPopupView)) focusAndProcessBackPress();
//由于部分弹窗有个位置设置过程,需要在位置设置完毕自己开启动画
if (!(BasePopupView.this instanceof AttachPopupView) && !(BasePopupView.this instanceof BubbleAttachPopupView)
&& !(BasePopupView.this instanceof PositionPopupView)
&& !(BasePopupView.this instanceof PartShadowPopupView)) {
initAnimator();
doShowAnimation();
doAfterShow();
}
}
};
initTask也比较直接,就是开启动画的,三个方法initAnimator();初始化动画,doShowAnimation();执行动画;doAfterShow();动画执行完毕。
Xpopup的动画基类为PopupAnimator ,是一个抽象类,具体动画由子类实现,三个抽象方法代表:
initAnimator();初始化动画操作
animateShow();显示动画
animateDismiss();消失动画
代码如下:
public abstract class PopupAnimator {
protected boolean animating = false;
public boolean hasInit = false;
public View targetView; //执行动画的view
public int animationDuration = 0; //动画时间
public PopupAnimation popupAnimation; // 内置的动画
public PopupAnimator(){}
public PopupAnimator(View target, int animationDuration){
this(target, animationDuration, null);
}
public PopupAnimator(View target, int animationDuration, PopupAnimation popupAnimation){
this.targetView = target;
this.animationDuration = animationDuration;
this.popupAnimation = popupAnimation;
}
public abstract void initAnimator();
public abstract void animateShow();
public abstract void animateDismiss();
public int getDuration(){
return animationDuration;
}
...省略其他代码
}
Xpopup有许多常见的弹窗动画实现,都在com.lxj.xpopup.animator中。
我们看一个实现:ScaleAlphaAnimator 缩放加透明度变化的动画,在初始化动画方法中设置了setAlpha(0)和PivotX、PivotY,然后再显示动画中执行了将透明度和缩放都变为1的动画,在结束动画中,执行相反的动画。
/**
* Description: 缩放透明
* Create by dance, at 2018/12/9
*/
public class ScaleAlphaAnimator extends PopupAnimator {
public ScaleAlphaAnimator(View target, int animationDuration, PopupAnimation popupAnimation) {
super(target, animationDuration, popupAnimation);
}
float startScale = .95f;
@Override
public void initAnimator() {
targetView.setScaleX(startScale);
targetView.setScaleY(startScale);
targetView.setAlpha(0);
// 设置动画参考点
targetView.post(new Runnable() {
@Override
public void run() {
applyPivot();
}
});
}
/**
* 根据不同的PopupAnimation来设定对应的pivot
*/
private void applyPivot() {
switch (popupAnimation) {
case ScaleAlphaFromCenter:
targetView.setPivotX(targetView.getMeasuredWidth() / 2f);
targetView.setPivotY(targetView.getMeasuredHeight() / 2f);
break;
case ScaleAlphaFromLeftTop:
targetView.setPivotX(0);
targetView.setPivotY(0);
break;
case ScaleAlphaFromRightTop:
targetView.setPivotX(targetView.getMeasuredWidth());
targetView.setPivotY(0f);
break;
case ScaleAlphaFromLeftBottom:
targetView.setPivotX(0f);
targetView.setPivotY(targetView.getMeasuredHeight());
break;
case ScaleAlphaFromRightBottom:
targetView.setPivotX(targetView.getMeasuredWidth());
targetView.setPivotY(targetView.getMeasuredHeight());
break;
}
}
@Override
public void animateShow() {
targetView.post(new Runnable() {
@Override
public void run() {
targetView.animate().scaleX(1f).scaleY(1f).alpha(1f)
.setDuration(animationDuration)
.setInterpolator(new OvershootInterpolator(1f))
// .withLayer() 在部分6.0系统会引起crash
.start();
}
});
}
@Override
public void animateDismiss() {
if(animating)return;
observerAnimator(targetView.animate().scaleX(startScale).scaleY(startScale).alpha(0f).setDuration(animationDuration)
.setInterpolator(new FastOutSlowInInterpolator()))
// .withLayer() 在部分6.0系统会引起crash
.start();
}
}
我们回过头继续看三个动画方法:
初始化动画:动画的执行对象是BasePopupView的第一个字view,也就是我们前面加载的getInnerLayoutId()布局。从参数信息PopupInfo中获取我们设置的动画信息,初始化。
protected void initAnimator() {
getPopupContentView().setAlpha(1f);
// 优先使用自定义的动画器
if (popupInfo!=null && popupInfo.customAnimator != null) {
popupContentAnimator = popupInfo.customAnimator;
if(popupContentAnimator.targetView==null) popupContentAnimator.targetView = getPopupContentView();
} else {
// 根据PopupInfo的popupAnimation字段来生成对应的动画执行器,如果popupAnimation字段为null,则返回null
popupContentAnimator = genAnimatorByPopupType();
if (popupContentAnimator == null) {
popupContentAnimator = getPopupAnimator();
}
}
//3. 初始化动画执行器
if (popupInfo!=null && popupInfo.hasShadowBg) {
shadowBgAnimator.initAnimator();
}
if (popupInfo!=null && popupInfo.hasBlurBg && blurAnimator != null) {
blurAnimator.initAnimator();
}
if (popupContentAnimator != null) {
popupContentAnimator.initAnimator();
}
}
public View getPopupContentView() {
return getChildAt(0);
}
doShowAnimation() :没啥好说的,就是执行动画
/**
* 执行显示动画:动画由2部分组成,一个是背景渐变动画,一个是Content的动画;
* 背景动画由父类实现,Content由子类实现
*/
protected void doShowAnimation() {
if (popupInfo == null) return;
if (popupInfo.hasShadowBg && !popupInfo.hasBlurBg && shadowBgAnimator!=null) {
shadowBgAnimator.animateShow();
} else if (popupInfo.hasBlurBg && blurAnimator != null) {
blurAnimator.animateShow();
}
if (popupContentAnimator != null)
popupContentAnimator.animateShow();
}
doAfterShow():执行了一个handler操作doAfterShowTask,修改一下弹窗状态,设置为显示状态,回调自定义的生命周期方法、接口,当然整个过程都在回调,比如onCreate()、 onShow(),一些其他的收尾操作。
protected void doAfterShow() {
handler.removeCallbacks(doAfterShowTask);
handler.postDelayed(doAfterShowTask, getAnimationDuration());
}
protected Runnable doAfterShowTask = new Runnable() {
@Override
public void run() {
popupStatus = PopupStatus.Show;
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
onShow();
if (BasePopupView.this instanceof FullScreenPopupView) focusAndProcessBackPress();
if (popupInfo != null && popupInfo.xPopupCallback != null)
popupInfo.xPopupCallback.onShow(BasePopupView.this);
//再次检测移动距离
if (getHostWindow() != null && XPopupUtils.getDecorViewInvisibleHeight(getHostWindow()) > 0 && !hasMoveUp) {
XPopupUtils.moveUpToKeyboard(XPopupUtils.getDecorViewInvisibleHeight(getHostWindow()), BasePopupView.this);
}
}
};
上面就是show()方法的流程了,结束弹窗时dismiss()方法,可以想象,就是做一些回收操作、结束动画、回调一下生命周期函数、接口等操作,这里就不啰嗦了。
三、代码中比较有意思的几个地方
3.1、点击外部弹窗消失的操作
因为BasePopupView是个Framelayout,对于点击事件肯定是重写onTouchEvent方法,判断点击位置是不是在contentView范围内,还有判断是否透传、点击不消失的区域。代码如下:
private float x, y;
@Override
public boolean onTouchEvent(MotionEvent event) {
// 如果自己接触到了点击,并且不在PopupContentView范围内点击,则进行判断是否是点击事件,如果是,则dismiss
Rect rect = new Rect();
getPopupImplView().getGlobalVisibleRect(rect);
if (!XPopupUtils.isInRect(event.getX(), event.getY(), rect)) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x = event.getX();
y = event.getY();
if(popupInfo!=null && popupInfo.xPopupCallback!=null){
popupInfo.xPopupCallback.onClickOutside(this);
}
passTouchThrough(event);
break;
case MotionEvent.ACTION_MOVE:
if(popupInfo != null){
if(popupInfo.isDismissOnTouchOutside){
checkDismissArea(event);
}
if(popupInfo.isTouchThrough)passTouchThrough(event);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
float dx = event.getX() - x;
float dy = event.getY() - y;
float distance = (float) Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
passTouchThrough(event);
if (distance < touchSlop && popupInfo != null && popupInfo.isDismissOnTouchOutside) {
checkDismissArea(event);
}
x = 0;
y = 0;
break;
}
}
return true;
}
public void passTouchThrough(MotionEvent event) {
if (popupInfo != null && (popupInfo.isClickThrough || popupInfo.isTouchThrough) ) {
if (popupInfo.isViewMode) {
//需要从DecorView分发,并且要排除自己,否则死循环
ViewGroup decorView = (ViewGroup) getActivity().getWindow().getDecorView();
for (int i = 0; i < decorView.getChildCount(); i++) {
View view = decorView.getChildAt(i);
//自己和兄弟弹窗都不互相分发,否则死循环
if (!(view instanceof BasePopupView)) view.dispatchTouchEvent(event);
}
} else {
getActivity().dispatchTouchEvent(event);
}
}
}
private void checkDismissArea(MotionEvent event){
//查看是否在排除区域外
ArrayList<Rect> rects = popupInfo.notDismissWhenTouchInArea;
if(rects!=null && rects.size()>0){
boolean inRect = false;
for (Rect r : rects) {
if(XPopupUtils.isInRect(event.getX(), event.getY(), r)){
inRect = true;
break;
}
}
if(!inRect){
dismiss();
}
}else {
dismiss();
}
}
3.2、强制获取焦点
如果设置弹窗获取焦点,就会循环遍历所有的EditText,并且让第一个获取焦点弹出软键盘。
我记得用老版本的时候,当时有一个比较麻烦的焦点操作,因为被抢占了焦点搞的很痛苦。
public void focusAndProcessBackPress() {
if (popupInfo != null && popupInfo.isRequestFocus) {
setFocusableInTouchMode(true);
setFocusable(true);
// 此处焦点可能被内部的EditText抢走,也需要给EditText也设置返回按下监听
if (Build.VERSION.SDK_INT >= 28) {
addOnUnhandledKeyListener(this);
} else {
setOnKeyListener(new BackPressListener());
}
//let all EditText can process back pressed.
ArrayList<EditText> list = new ArrayList<>();
XPopupUtils.findAllEditText(list, (ViewGroup) getPopupContentView());
if (list.size() > 0) {
preSoftMode = getHostWindow().getAttributes().softInputMode;
if (popupInfo.isViewMode) {
getHostWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
hasModifySoftMode = true;
}
for (int i = 0; i < list.size(); i++) {
final EditText et = list.get(i);
// addOnUnhandledKeyListener(et);
if (Build.VERSION.SDK_INT >= 28) {
addOnUnhandledKeyListener(et);
}else {
boolean hasSetKeyListener = XPopupUtils.hasSetKeyListener(et);
if(!hasSetKeyListener) et.setOnKeyListener(new BackPressListener());
}
if (i == 0) {
if (popupInfo.autoFocusEditText) {
et.setFocusable(true);
et.setFocusableInTouchMode(true);
et.requestFocus();
if (popupInfo.autoOpenSoftInput) showSoftInput(et);
} else {
if (popupInfo.autoOpenSoftInput) showSoftInput(this);
}
}
}
} else {
if (popupInfo.autoOpenSoftInput) showSoftInput(this);
}
}
}
3.3、调整弹窗大小
弹窗显示的大小由三个地方控制:一个是ViewGroup 本身的设置;一个是PopupInfo(maxWidth、maxHeight)或者BasePopupView子类设置(getMaxWidth()、getMaxHeight() )的最大宽高;一个是PopupInfo(popupWidth、popupHeight)或者BasePopupView子类设置(getPopupWidth()、getPopupHeight())的指定宽高。
这三个大小通过applyPopupSize方法进行统一的调整。
当然,在一些BasePopupView子类中也有对应的调整,比如AttachPopupView,
AttachPopupView会使弹窗依附于一个点或者一个组件,默认显示在依附目标的下方,如果显示不开会显示在上方,如果都显示不开,会上方下方中选择一个空间最充裕的,然后调整弹窗大小,是弹窗能够显示的开不至于超出屏幕看不见。
代码如下:
public static void applyPopupSize(final ViewGroup content, final int maxWidth, final int maxHeight,
final int popupWidth, final int popupHeight, final Runnable afterApplySize) {
content.post(() -> {
ViewGroup.LayoutParams params = content.getLayoutParams();
View implView = content.getChildAt(0);
ViewGroup.LayoutParams implParams = implView.getLayoutParams();
// 假设默认Content宽是match,高是wrap
int w = content.getMeasuredWidth();
// response impl view wrap_content params.
if (maxWidth > 0) {
//指定了最大宽度,就限制最大宽度
if(w > maxWidth) params.width = Math.min(w, maxWidth);
if (implParams.width == ViewGroup.LayoutParams.MATCH_PARENT) {
implParams.width = Math.min(w, maxWidth);
if (implParams instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams mp = ((ViewGroup.MarginLayoutParams) implParams);
implParams.width = implParams.width - mp.leftMargin - mp.rightMargin;
}
}
if (popupWidth > 0) {
params.width = Math.min(popupWidth, maxWidth);
implParams.width = Math.min(popupWidth, maxWidth);
}
} else if (popupWidth > 0) {
params.width = popupWidth;
implParams.width = popupWidth;
}
if (maxHeight > 0) {
int h = content.getMeasuredHeight();
if(h > maxHeight) params.height = Math.min(h, maxHeight);
if (popupHeight > 0) {
params.height = Math.min(popupHeight, maxHeight);
implParams.height = Math.min(popupHeight, maxHeight);
}
} else if (popupHeight > 0) {
params.height = popupHeight;
implParams.height = popupHeight;
} else {
// params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
// implParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
}
implView.setLayoutParams(implParams);
content.setLayoutParams(params);
content.post(() -> {
if (afterApplySize != null) {
afterApplySize.run();
}
});
});
}
四、看一个BasePopupView实现
最后我们看一下核心PopupView实现:CenterPopupView 居中显示弹窗。
/**
* Description: 在中间显示的Popup
* Create by dance, at 2018/12/8
*/
public class CenterPopupView extends BasePopupView {
protected FrameLayout centerPopupContainer;
protected int bindLayoutId;
protected int bindItemLayoutId;
protected View contentView;
public CenterPopupView(@NonNull Context context) {
super(context);
centerPopupContainer = findViewById(R.id.centerPopupContainer);
}
protected void addInnerContent(){
contentView = LayoutInflater.from(getContext()).inflate(getImplLayoutId(), centerPopupContainer, false);
LayoutParams params = (LayoutParams) contentView.getLayoutParams();
params.gravity = Gravity.CENTER;
centerPopupContainer.addView(contentView, params);
}
@Override
final protected int getInnerLayoutId() {
return R.layout._xpopup_center_popup_view;
}
@Override
protected void initPopupContent() {
super.initPopupContent();
if(centerPopupContainer.getChildCount()==0)addInnerContent();
getPopupContentView().setTranslationX(popupInfo.offsetX);
getPopupContentView().setTranslationY(popupInfo.offsetY);
XPopupUtils.applyPopupSize((ViewGroup) getPopupContentView(), getMaxWidth(), getMaxHeight(),
getPopupWidth(), getPopupHeight(),null);
}
@Override
protected void doMeasure() {
super.doMeasure();
XPopupUtils.applyPopupSize((ViewGroup) getPopupContentView(), getMaxWidth(), getMaxHeight(),
getPopupWidth(), getPopupHeight(),null);
}
protected void applyTheme(){
if(bindLayoutId==0) {
if(popupInfo.isDarkTheme){
applyDarkTheme();
}else {
applyLightTheme();
}
}
}
@Override
protected void applyDarkTheme() {
super.applyDarkTheme();
centerPopupContainer.setBackground(XPopupUtils.createDrawable(getResources().getColor(R.color._xpopup_dark_color),
popupInfo.borderRadius));
}
@Override
protected void applyLightTheme() {
super.applyLightTheme();
centerPopupContainer.setBackground(XPopupUtils.createDrawable(getResources().getColor(R.color._xpopup_light_color),
popupInfo.borderRadius));
}
/**
* 具体实现的类的布局
*
* @return
*/
protected int getImplLayoutId() {
return 0;
}
protected int getMaxWidth() {
if(popupInfo==null) return 0;
return popupInfo.maxWidth==0 ? (int) (XPopupUtils.getAppWidth(getContext()) * 0.72f)
: popupInfo.maxWidth;
}
@Override
protected PopupAnimator getPopupAnimator() {
return new ScaleAlphaAnimator(getPopupContentView(), getAnimationDuration(), ScaleAlphaFromCenter);
}
}
代码很简单:在initPopupContent()初始化方法中加载一个布局,然后居中。也通过getPopupAnimator()设置了一下默认的动画实现。
getInnerLayoutId()是给核心弹窗用的,除非我们重写核心弹窗,一般不会用到。我们的显示布局是使用getImplLayoutId()方法设置的。
所以一个Xpopup弹窗整体就是 FrameLayout(BasePopupView,设置给dialog,在dialog中显示) 添加了一个getInnerLayoutId()布局(使用initPopupContent()方法初始化,执行动画、位移、设置显示大小等核心操作),然后getInnerLayoutId()布局中由使用者添加getImplLayoutId()布局(使用onCreate()方法初始化,执行业务逻辑)