Lectura de código Xpopup de la biblioteca de código abierto de Github

Prefacio

No he escrito nada desde hace mucho tiempo. Hice el examen de licencia de conducir en casa. Me llevó un mes y medio. El examen de conducir en China es realmente decepcionante. El día que obtuve mi licencia de conducir, estaba encerrado en En casa debido a la epidemia. Estuve encerrado durante más de un mes. Todo el mundo estaba. Estoy tan entumecido que probablemente celebraré el Año Nuevo otra vez. Acabo de empezar a trabajar un poco recientemente.
Xpopup es una biblioteca de código abierto de Github que me gusta mucho. La he estado usando todo el tiempo. Leí su código una vez cuando estaba en Xpopup2. Después de leerlo nuevamente, el código de Xpopup sigue siendo muy fácil de leer. Si Si estás interesado, puedes seguirme para comprender rápidamente su código.

1. Método de entrada

Uso básico de Xpopup

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

La clase Xpopup es una clase de nivel de entrada. Como puede ver por el nombre, Builder es un modo de construcción, que se utiliza para configurar varios parámetros de la ventana emergente. Todos los parámetros se resumirán en un objeto PopupInfo. Luego pase el objeto PopupInfo al objeto de instancia de BasePopupView a través del método asXXX () y finalmente llame al método show () de BasePopupView para ejecutar la lógica de visualización de la ventana emergente.

El código aproximado del Builder es el siguiente, omitiendo la mayoría de los métodos de configuración:

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. Proceso de ejecución de BasePopupView

2.1 Atributos importantes de la clase BasePopupView

La clase base de todas las ventanas emergentes en Xpopup es BasePopupView. Puede ver que BasePopupView es un FrameLayout personalizado. A continuación se enumeran varios atributos importantes.

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

Análisis de atributos:
PopupInfo popupInfo: la clase de configuración de atributos de la ventana emergente. Todos los parámetros que configuramos a través del método de la clase XPopup se resumirán en un objeto PopupInfo, como el ancho y alto de la ventana emergente, el fondo. , si el clic desaparece, información de la barra de navegación de la barra de estado, configuración del método de entrada, estilos de animación, etc.

PopupStatus popupStatus: el estado de la ventana emergente, que incluye Mostrar visualización, Mostrar, ejecutar animación de visualización, Descartar ocultación, Descartar ejecutar animación que desaparece.

Cuadro de diálogo FullScreenDialog: se utiliza para mostrar el cuadro de diálogo de PopupView. FullScreenDialog y BasePopupView contienen los objetos de cada uno. FullScreenDialog configura BasePopupView para mostrar contenido a través de setContentView(contentView);.
En FullScreenDialog, la pantalla completa del cuadro de diálogo, la barra de estado de la ventana, el estilo de la barra de navegación y otra información se configuran de acuerdo con popupInfo de BasePopupView.

2.2 Método de construcción

Después de leer las propiedades, echemos un vistazo al método de construcción de BasePopupView, de la siguiente manera:

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

El método de construcción de BasePopupView carga un diseño interno contentView. getInnerLayoutId() es un método abstracto. Otras clases principales heredadas de BasePopupView implementarán este método. Por ejemplo, los archivos de diseño CenterPopupView y FrameLayout tienen un FrameLayout, y el archivo de diseño BottomPopupView tiene un SmartDragLayout., ¿para qué sirve este contentView? Debido a que se puede decir que es una ventana emergente que muestra la Vista raíz, se puede cambiar su tamaño, desplazarla, animarla y se pueden realizar innumerables otras operaciones en ella.

2.3Flujo del método Show()

Echemos un vistazo al código del método 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;
    }

El método Show() inicia principalmente un Runnable y llama al método adjuntoToHost(). Revisemos este método:

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

Puede ver que este método es principalmente: crear un FullScreenDialog, asociar BasePopupView y FullScreenDialog, y luego llamar a dialog.show () para mostrar la ventana emergente.
Finalmente, se llama al método init (), continúe viendo el método 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() {
    
     }

Método init(): inicializa la animación de fondo, llama al método initPopupContent(), que se utiliza para la subclase principal de BasePopupView, realiza algunas operaciones de inicialización y llama al método onCreate(). Este método lo personalizamos heredando BasePopupView o su subclase principal.Se utiliza para la inicialización cuando aparece la ventana emergente. Finalmente, se ejecuta una operación de controlador initTask. Revisemos esta tarea de inicio.

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 también es relativamente sencillo, es decir, iniciar la animación. Tiene tres métodos: initAnimator(); inicializar la animación, doShowAnimation(); ejecutar la animación; doAfterShow(); completar la ejecución de la animación.

La clase base de animación de Xpopup es PopupAnimator, que es una clase abstracta. Las animaciones específicas se implementan mediante subclases y se representan mediante tres métodos abstractos: initAnimator();
inicializar la operación de animación
animateShow(); mostrar animación
animateDismiss(); desaparecer animación
. El código es el siguiente:

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 tiene muchas implementaciones comunes de animación de ventanas emergentes, todas las cuales se encuentran en com.lxj.xpopup.animator.
Veamos una implementación: ScaleAlphaAnimator escala y cambia la animación de transparencia. En el método de animación de inicialización, se configuran setAlpha (0) y PivotX, PivotY, y luego se ejecuta la animación de cambio de transparencia y escala a 1 en la animación de visualización, y la animación finaliza. , realiza la animación opuesta.

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

}

Volvamos atrás y veamos los tres métodos de animación:
Animación de inicialización: el objeto de ejecución de la animación es la primera subvista de BasePopupView, que es el diseño getInnerLayoutId() que cargamos anteriormente. Obtenga la información de animación que configuramos a partir de la información del parámetro PopupInfo e inicialícela.

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(): Nada que decir, solo ejecutar la animación

/**
 * 执行显示动画:动画由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 (): ejecuta una operación de controlador doAfterShowTask, modifica el estado de la ventana emergente, configúrala en el estado de visualización y vuelve a llamar a los métodos e interfaces del ciclo de vida personalizados. Por supuesto, se vuelve a llamar a todo el proceso, como onCreate ( ), onShow() y algunas otras operaciones de finalización.

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

Lo anterior es el flujo del método show(). Al finalizar la ventana emergente, se puede imaginar que el método dismiss() realiza algunas operaciones de reciclaje, finaliza animaciones, devuelve funciones del ciclo de vida, interfaces y otras operaciones. No entraré en detalles aquí.

3. Varios lugares interesantes en el código.

3.1 Haga clic en la operación para desaparecer la ventana emergente externa

Debido a que BasePopupView es un Framelayout, para eventos de clic, el método onTouchEvent debe reescribirse para determinar si la posición del clic está dentro del rango de contentView, si se transmite de forma transparente y el área donde el clic no desaparece. El código se muestra a continuación:

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 Enfoque forzado

Si configura la ventana emergente para que se enfoque, recorrerá todos los EditTexts y permitirá que el primero que se enfoque aparezca en el teclado virtual.
Recuerdo que cuando usaba la versión anterior, había una operación de enfoque bastante problemática, porque era muy doloroso tener el enfoque incautado.

 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 Ajustar el tamaño de la ventana emergente

El tamaño de la ventana emergente se controla en tres lugares: uno es la configuración del propio ViewGroup; otro es el ancho y alto máximos de PopupInfo (maxWidth, maxHeight) o la configuración de la subclase BasePopupView (getMaxWidth(), getMaxHeight() ); uno es PopupInfo (popupWidth, popupHeight) o el ancho y alto especificados establecidos por la subclase BasePopupView (getPopupWidth(), getPopupHeight()).
Estos tres tamaños se ajustan uniformemente mediante el método applyPopupSize.
Por supuesto, hay ajustes correspondientes en algunas subclases de BasePopupView, como AttachPopupView.
AttachPopupView hará que la ventana emergente se adjunte a un punto o componente. De forma predeterminada, se mostrará debajo del objetivo del vínculo. Si no se muestra, se mostrará arriba. Si no se muestra, se mostrará arriba. , seleccione el que tenga el espacio más amplio entre la parte superior e inferior, y luego ajuste el tamaño de la ventana emergente para que la ventana emergente La ventana se puede mostrar sin ser invisible más allá de la pantalla.
El código se muestra a continuación:

 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. Mire una implementación de BasePopupView

Finalmente, echemos un vistazo a la implementación principal de PopupView: CenterPopupView muestra la ventana emergente en el centro.

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

El código es simple: cargue un diseño en el método de inicialización initPopupContent() y luego céntrelo. La implementación de animación predeterminada también se establece mediante getPopupAnimator().

getInnerLayoutId() se usa para las ventanas emergentes principales. A menos que reescribamos la ventana emergente principal, generalmente no se usará. Nuestro diseño de visualización se configura mediante el método getImplLayoutId().

Entonces, una ventana emergente de Xpopup en su conjunto es FrameLayout (BasePopupView, configurado en diálogo, mostrado en diálogo). Se agrega un diseño getInnerLayoutId() (inicializado usando el método initPopupContent() para realizar operaciones principales como animación, desplazamiento y configuración del tamaño de visualización), y luego getInnerLayoutId ()El usuario agrega el diseño getImplLayoutId() en el diseño (inicializado usando el método onCreate() para ejecutar la lógica empresarial)

Supongo que te gusta

Origin blog.csdn.net/weixin_43864176/article/details/128120967
Recomendado
Clasificación