Github开源库Xpopup代码阅读

前言

很久没写点东西了,在家闲着考了个驾照,花了一个半月,中国的驾考真的是没眼看,刚拿到驾照当天就被疫情封闭在家,直接封了一个多月,人都麻了,再来一次估计直接过年了,最近刚开始干点活。
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()方法初始化,执行业务逻辑)

猜你喜欢

转载自blog.csdn.net/weixin_43864176/article/details/128120967