Github open source library Xpopup code reading

Preface

I haven’t written anything for a long time. I took the driver’s license test at home. It took me a month and a half. The driving test in China is really disappointing. The day I got my driver’s license, I was locked at home due to the epidemic. I was locked down for more than a month. Everyone was I'm so numb, I'll probably celebrate the New Year again. I just started doing some work recently.
Xpopup is a Github open source library that I like very much. I have been using it all the time. I read its code once when it was in the Xpopup2. After reading it again, Xpopup's code is still very easy to read. If you are interested, you can follow me to quickly understand its code.

1. Entry method

Basic usage of Xpopup

new XPopup.Builder(getContext())
        .xxx() 参数设置方法
        .asXXX(传入一个BasePopupView 的实例对象)
        .show();

The Xpopup class is an entry-level class. As you can tell from the name, Builder is a builder mode, which is used to configure various parameters of the pop-up window. All parameters will be summarized into a PopupInfo object. Then pass the PopupInfo object to the instance object of BasePopupView through the asXXX() method, and finally call the show() method of BasePopupView to execute the display logic of the pop-up window.

The approximate code of the Builder is as follows, omitting most of the setting methods:

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;
}
 ...省略其他默认弹窗方法
}

2. Execution process of BasePopupView

2.1 Important attributes of the BasePopupView class

The base class of all pop-up windows in Xpopup is BasePopupView. You can see that BasePopupView is a customized FrameLayout. Several important attributes are listed below.

//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);
}
}

Attribute analysis:
PopupInfo popupInfo: the attribute configuration class of the pop-up window. All the parameters we set through the XPopup class method will be summarized into a PopupInfo object, such as the width and height of the pop-up window, background, whether the click disappears, status bar navigation bar information, input Method settings, animation styles, etc.

PopupStatus popupStatus: The status of the popup window, including Show display, Showing, executing display animation, Dismiss hiding, Dismissing executing disappearing animation.

FullScreenDialog dialog: Used to display the dialog of PopupView. FullScreenDialog and BasePopupView hold each other's objects. FullScreenDialog sets BasePopupView to display content through setContentView(contentView);.
In FullScreenDialog, the dialog full screen, window status bar, navigation bar style and other information are set according to the popupInfo of BasePopupView.

2.2 Construction method

After reading the properties, let’s take a look at the construction method of BasePopupView, as follows:

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();

The construction method of BasePopupView loads an internal layout contentView. getInnerLayoutId() is an abstract method. Other core classes inherited from BasePopupView will implement this method. For example, the CenterPopupView and FrameLayout layout files have a FrameLayout, and the BottomPopupView layout file has a SmartDragLayout. , what is the use of this contentView? Because it can be said to be a pop-up window displaying the root View, it can be resized, displaced, animated, and countless other operations can be performed on it.

2.3Flow of Show() method

Let's take a look at the code of the show() method:

    public BasePopupView show() {
    
    
        ...省略其他代码,主要是判断弹窗是否正在显示或者已经显示过
        View cv = activity.getWindow().getDecorView().findViewById(android.R.id.content);
        cv.post(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                attachToHost();
            }
        });
        return this;
    }

The Show() method mainly starts a Runnable and calls the attachToHost() method. Let's check this method:

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();
}

You can see that this method is mainly: create a FullScreenDialog, associate BasePopupView and FullScreenDialog, and then call dialog.show() to display the pop-up window.
Finally, the init() method is called, continue to view the init() method.

/**
 * 执行初始化
 */
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() method: Initialize the background animation, call the initPopupContent() method, for the core subclass of BasePopupView, do some initialization operations, call the onCreate() method, this method is customized by us inheriting BasePopupView or its core subclass Used for initialization when pop-up window. Finally, a handler operation initTask is executed. Let's check this 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 is also relatively straightforward, which is to start animation. It has three methods: initAnimator(); initialize animation, doShowAnimation(); execute animation; doAfterShow(); animation execution is completed.

The animation base class of Xpopup is PopupAnimator, which is an abstract class. Specific animations are implemented by subclasses and are represented by three abstract methods: initAnimator();
initialize animation operation
animateShow(); display animation
animateDismiss(); disappear animation
. The code is as follows:

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 has many common pop-up window animation implementations, all of which are in com.lxj.xpopup.animator.
Let’s look at an implementation: ScaleAlphaAnimator scales and transparency changes animation. In the initialization animation method, setAlpha(0) and PivotX, PivotY are set, and then the animation of changing the transparency and scaling to 1 is executed in the display animation, and the animation is ended. , performs the opposite animation.

/**
 * 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();
    }

}

Let's go back and look at the three animation methods:
Initialization animation: The execution object of the animation is the first subview of BasePopupView, which is the getInnerLayoutId() layout we loaded earlier. Get the animation information we set from the parameter information PopupInfo and initialize it.

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(): Nothing to say, just execute animation

/**
 * 执行显示动画:动画由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(): Execute a handler operation doAfterShowTask, modify the pop-up window status, set it to the display status, and call back the custom life cycle methods and interfaces. Of course, the entire process is called back, such as onCreate(), onShow(), and some others. finishing operations.

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);
        }
    }
};

The above is the flow of the show() method. When ending the pop-up window, the dismiss() method can be imagined to do some recycling operations, end animations, call back life cycle functions, interfaces and other operations. I won’t go into details here.

3. Several interesting places in the code

3.1. Click the operation to disappear the external pop-up window

Because BasePopupView is a Framelayout, for click events, the onTouchEvent method must be rewritten to determine whether the click position is within the contentView range, and whether it is transparently transmitted and the area where the click does not disappear. code show as below:

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. Forced focus

If you set the pop-up window to get focus, it will loop through all EditTexts and let the first one that gets focus pop up the soft keyboard.
I remember that when using the old version, there was a rather troublesome focus operation, because it was very painful to have the focus seized.

 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. Adjust pop-up window size

The size of the pop-up window display is controlled in three places: one is the settings of the ViewGroup itself; one is the maximum width and height of PopupInfo (maxWidth, maxHeight) or BasePopupView subclass settings (getMaxWidth(), getMaxHeight()); one is PopupInfo (popupWidth , popupHeight) or the specified width and height set by the BasePopupView subclass (getPopupWidth(), getPopupHeight()).
These three sizes are adjusted uniformly through the applyPopupSize method.
Of course, there are corresponding adjustments in some BasePopupView subclasses, such as AttachPopupView.
AttachPopupView will make the pop-up window attach to a point or a component. By default, it will be displayed below the attachment target. If it is not displayed, it will be displayed above. If it is not displayed, it will be displayed above. , select the one with the most ample space between the top and bottom, and then adjust the size of the pop-up window so that the pop-up window can be displayed without being invisible beyond the screen.
code show as below:

 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();
                }
            });

        });
    }

4. Look at a BasePopupView implementation

Finally, let’s take a look at the core PopupView implementation: CenterPopupView displays the pop-up window in the center.

/**
 * 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);
    }
}

The code is simple: load a layout in the initPopupContent() initialization method, and then center it. The default animation implementation is also set through getPopupAnimator().

getInnerLayoutId() is used for core pop-up windows. Unless we rewrite the core pop-up window, it will generally not be used. Our display layout is set using the getImplLayoutId() method.

So an Xpopup pop-up window as a whole is FrameLayout (BasePopupView, set to dialog, displayed in dialog). A getInnerLayoutId() layout is added (initialized using the initPopupContent() method to perform core operations such as animation, displacement, and display size setting), and then getInnerLayoutId ()The user adds the getImplLayoutId() layout in the layout (initialized using the onCreate() method to execute business logic)

Guess you like

Origin blog.csdn.net/weixin_43864176/article/details/128120967