Github开源库Xpopup代码阅读

这篇具有很好参考价值的文章主要介绍了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()方法初始化,执行业务逻辑)文章来源地址https://www.toymoban.com/news/detail-462567.html

到了这里,关于Github开源库Xpopup代码阅读的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 首个接入 GPT-4,曾经比 GitHub Copilot 还好用的代码编辑器,开源了!

    首个接入 GPT-4,曾经比 GitHub Copilot 还好用的代码编辑器,开源了!

    公众号关注 “GitHubDaily” 设为 “星标”,每天带你逛 GitHub! 上周四,GitHub Copilot X 震撼发布,将一众老牌代码编辑器打得措手不及。 但其实,Copilot X 不是第一个集成 GPT-4 模型的 AI 智能编程工具。 在它发布的一两周前,一款名为 Cursor 的代码编辑器便已开始在技术圈内被

    2024年02月09日
    浏览(16)
  • 【通义千问】大模型Qwen GitHub开源工程学习笔记(2)--使用Qwen进行推理的示例代码解析,及transformers的使用

    【通义千问】大模型Qwen GitHub开源工程学习笔记(2)--使用Qwen进行推理的示例代码解析,及transformers的使用

    如希望使用Qwen-chat进行推理,所需要写的只是如下所示的数行代码。 请确保你使用的是最新代码,并指定正确的模型名称和路径,如 Qwen/Qwen-7B-Chat 和 Qwen/Qwen-14B-Chat 这里给出了一段代码

    2024年02月08日
    浏览(14)
  • 安卓弹出popup之XPopup

    安卓弹出popup之XPopup

    弹窗自己写的话。虽然很简单。但不够丝滑。如果要优雅点的。又要添加动画。但是。。。如果用上了XPopup,动画别人帮你写。爽不爽?丝滑不丝滑。。? 丝滑第一步。先引入依赖 如果没有这些依赖库。也要添加 xpopup依赖了subsampling-scale-image-view, 如果你也依赖了这个库并且

    2024年02月13日
    浏览(9)
  • 【Github】玩转Github系列之四——阅读github上源码的利器

    【Github】玩转Github系列之四——阅读github上源码的利器

    🐚作者简介:花神庙码农(专注于Linux、WLAN、TCP/IP、Python等技术方向) 🐳博客主页:花神庙码农 ,地址:https://blog.csdn.net/qxhgd 🌐系列专栏:GitHub开源 📰如觉得博主文章写的不错或对你有所帮助的话,还望大家三连支持一下呀!!! 👉关注✨、点赞👍、收藏📂、评论。

    2024年02月16日
    浏览(32)
  • 写点东西《Docker入门(上)》

    写点东西《Docker入门(上)》

    Docker 是一个工具,允许开发人员将他们的应用程序及其所有依赖项打包到一个容器中。然后,此容器可以轻松传输并在安装了 Docker 的任何机器上运行,而无需担心环境差异。这是一种标准化的软件打包和运行方式。 什么是容器? 容器就像一个小包,其中包含程序运行所需

    2024年02月22日
    浏览(15)
  • 记录-new Date() 我忍你很久了!

    记录-new Date() 我忍你很久了!

    大家平时在开发的时候有没被new Date()折磨过?就是它的诸多怪异的设定让你每每用的时候,都可能不小心踩坑。造成程序意外出错,却一下子找不到问题出处,那叫一个烦透了…… 下面,我就列举它的“四宗罪”及应用思考 这行代码无论在Macbook中还是iPhone中的Safari浏览器,

    2023年04月19日
    浏览(23)
  • 【论文阅读】ControlNet、文章作者 github 上的 discussions

    【论文阅读】ControlNet、文章作者 github 上的 discussions

    提出 ControlNet ,通过引入该结构微调预训练文生图扩散模型,可以给模型增加空间定位条件. 在 Stable Diffusion 上使用 ControlNet 微调,使模型能接受 Canny edges, Hough lines, user scribbles, human key points, segmentation maps, shape normals, depths, cartoon line drawings 图像作为输入条件. 消融实验、定量

    2024年01月20日
    浏览(46)
  • 写点东西《检查和更新NPM包》

    写点东西《检查和更新NPM包》

    关于如何检查和更新 npm 包的快速概述。 用于检查和更新包的 npm 命令。 命令 说明 npm outdated 检查过时的软件包 npm update --save 更新软件包并保存到 package.json npm update --save-dev --save 更新开发软件包并保存到 package.json npm update -g 更新全局软件包 或者,您可以使用 npm-check-update

    2024年01月25日
    浏览(14)
  • 写点东西《渐进式网络应用入门》

    PWA 是一种渐进式网络应用程序,它结合了应用程序的功能和网络技术。 您可以说它们是使用网络技术构建的应用程序,但感觉和功能都像原生应用程序。 网络应用程序似乎变得有限,因为大多数人更喜欢构建移动应用程序,以便用户可以将它们保存在手机上,而不是构建网

    2024年01月19日
    浏览(14)
  • IOS看书最终选择|源阅读转换|开源阅读|IOS自签

    IOS看书最终选择|源阅读转换|开源阅读|IOS自签

    环境:IOS想使用 换源阅读 问题:换新手机,源阅读下架后,没有好的APP阅读小说 解决办法:自签APP + 转换源仓库书源 最终预览 :https://rc.real9.cn/ 背景:自从我换了新iPhone手机,就无法使换源阅读了,于是我自用上,结果发现现在的书源发展的很快,旧版本的源阅读APP部分

    2024年02月14日
    浏览(11)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包