XPopup icon indicating copy to clipboard operation
XPopup copied to clipboard

气泡弹窗的位置可以优化一下吗

Open davidgerka opened this issue 5 months ago • 1 comments

XPopup版本 最新版本

手机系统和型号 所有手机型号

描述你的问题 如果定位的View的宽度是未知的,当它的宽度要占满全屏时,然后气泡只有比较小的宽度时,气泡的位置不太友好。这种情况,可以让气泡自适应吗。由于定位的View的宽度会跟进业务来变化,isCenterHorizontal(false)方法也不能确定。

Image

davidgerka avatar Jul 21 '25 01:07 davidgerka

新建了一个BaseBubbleAttachPopupView类,直接继承BubbleAttachPopupView,然后重写定位逻辑,先这样用了。

package com.lxj.xpopupdemo.custom;

import android.content.Context;
import android.graphics.Rect;
import android.view.ViewGroup;

import androidx.annotation.NonNull;

import com.lxj.xpopup.XPopup;
import com.lxj.xpopup.core.BubbleAttachPopupView;
import com.lxj.xpopup.util.XPopupUtils;
import com.lxj.xpopup.widget.BubbleLayout;

/**
 * 带气泡背景的Attach弹窗基类(由于XPopup库里面的[BubbleAttachPopupView]的弹窗定位体验不太好,这个类对定位进行优化)
 * 注意:由于BasePopupView基类里面的判断必须是[BubbleAttachPopupView]类才能定位成功,所以该类要继承它。
 */
public abstract class BaseBubbleAttachPopupView extends BubbleAttachPopupView {

    public BaseBubbleAttachPopupView(@NonNull Context context) {
        super(context);
    }

    /**
     * 执行倚靠逻辑
     */
    float translationX = 0, translationY = 0;
    // 弹窗显示的位置不能超越Window高度
    float maxY = XPopupUtils.getAppHeight(getContext());
    int overflow = XPopupUtils.dp2px(getContext(), 10);
    float centerY = 0;

    public void doAttach() {
        if (popupInfo == null) return;
        maxY = XPopupUtils.getAppHeight(getContext()) - overflow;
        final boolean isRTL = XPopupUtils.isLayoutRtl(getContext());
        //0. 判断是依附于某个点还是某个View
        if (popupInfo.touchPoint != null) {
            if (XPopup.longClickPoint != null) popupInfo.touchPoint = XPopup.longClickPoint;
            popupInfo.touchPoint.x -= getActivityContentLeft();
            centerY = popupInfo.touchPoint.y;
            // 依附于指定点,尽量优先放在下方,当不够的时候在显示在上方
            //假设下方放不下,超出window高度
            boolean isTallerThanWindowHeight = (popupInfo.touchPoint.y + getPopupContentView().getMeasuredHeight()) > maxY;
            if (isTallerThanWindowHeight) {
                isShowUp = popupInfo.touchPoint.y > XPopupUtils.getScreenHeight(getContext()) / 2f;
            } else {
                isShowUp = false;
            }
            isShowLeft = popupInfo.touchPoint.x > XPopupUtils.getAppWidth(getContext()) / 2f;

            //限制最大宽高
            ViewGroup.LayoutParams params = getPopupContentView().getLayoutParams();
            int maxHeight = (int) (isShowUpToTarget() ? (popupInfo.touchPoint.y - getStatusBarHeight() - overflow)
                    : (XPopupUtils.getScreenHeight(getContext()) - popupInfo.touchPoint.y - overflow));
            int maxWidth = (int) (isShowLeft ? (popupInfo.touchPoint.x - overflow) : (XPopupUtils.getAppWidth(getContext())
                    - popupInfo.touchPoint.x - overflow));
            if (getPopupContentView().getMeasuredHeight() > maxHeight) {
                params.height = maxHeight;
            }
            if (getPopupContentView().getMeasuredWidth() > maxWidth) {
                params.width = maxWidth;
            }
            getPopupContentView().setLayoutParams(params);

            getPopupContentView().post(() -> {
                if (popupInfo == null) return;
                boolean isRealCenterHorizontal = false;
                //只要弹窗不超出左右边界,都让弹窗居中显示
                boolean crossLeft = (popupInfo.touchPoint.x - getPopupContentView().getMeasuredWidth() / 2f
                        - bubbleContainer.getShadowRadius()) < 0;
                boolean crossRight = (popupInfo.touchPoint.x + getPopupContentView().getMeasuredWidth() / 2f
                        + bubbleContainer.getShadowRadius()) > XPopupUtils.getAppWidth(getContext());
                if (!crossLeft && !crossRight) {
                    isRealCenterHorizontal = true;
                }
                if (isRealCenterHorizontal) {
                    if (isRTL) {
                        translationX = -(XPopupUtils.getAppWidth(getContext()) -
                                popupInfo.touchPoint.x + defaultOffsetX - getPopupContentView().getMeasuredWidth() / 2f);
                    } else {
                        translationX = popupInfo.touchPoint.x + defaultOffsetX - getPopupContentView().getMeasuredWidth() / 2f;
                    }
                } else {
                    if (isRTL) {    //未完成
                        if (isShowLeft) {
                            if (crossRight) {
                                translationX = -bubbleContainer.getShadowRadius() - defaultOffsetX;
                            } else {
                                translationX = XPopupUtils.getAppWidth(getContext()) - getPopupContentView().getMeasuredWidth()
                                        + defaultOffsetX - bubbleContainer.getShadowRadius();
                            }
                        } else {
                            if (crossRight) {
                                translationX = defaultOffsetX + bubbleContainer.getShadowRadius();
                            } else {
                                translationX = -(XPopupUtils.getAppWidth(getContext()) - popupInfo.touchPoint.x - defaultOffsetX
                                        - getPopupContentView().getMeasuredWidth() / 2f);
                            }
                        }
                    } else {
                        if (isShowLeft) {
                            if (crossRight) {
                                translationX = XPopupUtils.getAppWidth(getContext()) - getPopupContentView().getMeasuredWidth()
                                        + defaultOffsetX - bubbleContainer.getShadowRadius();
                            } else {
                                translationX = defaultOffsetX + bubbleContainer.getShadowRadius();
                            }
                        } else {
                            if (crossLeft) {
                                translationX = defaultOffsetX + bubbleContainer.getShadowRadius();
                            } else {
                                translationX = XPopupUtils.getAppWidth(getContext()) - getPopupContentView().getMeasuredWidth()
                                        + defaultOffsetX - bubbleContainer.getShadowRadius();
                            }
                        }
                    }
                }

                if (isShowUpToTarget()) {
                    translationY = popupInfo.touchPoint.y - getPopupContentView().getMeasuredHeight() - defaultOffsetY;
                } else {
                    translationY = popupInfo.touchPoint.y + defaultOffsetY;
                }

                //设置气泡相关
                if (isShowUpToTarget()) {
                    bubbleContainer.setLook(BubbleLayout.Look.BOTTOM);
                } else {
                    bubbleContainer.setLook(BubbleLayout.Look.TOP);
                }
                //箭头对着目标位置
                if (isRealCenterHorizontal) {
                    bubbleContainer.setLookPositionCenter(true);
                } else {
                    bubbleContainer.setLookPosition(Math.max(0,
                            (int) (popupInfo.touchPoint.x - defaultOffsetX - translationX - (float) bubbleContainer.mLookWidth / 2)));
                }
                bubbleContainer.invalidate();

                getPopupContentView().setTranslationX(translationX);
                getPopupContentView().setTranslationY(translationY);
                initAndStartAnimation();
            });

        } else {
            // 依附于指定View
            //1. 获取atView在屏幕上的位置
            final Rect rect = popupInfo.getAtViewRect();
            rect.left -= getActivityContentLeft();
            rect.right -= getActivityContentLeft();
            final int centerX = (rect.left + rect.right) / 2;

            // 尽量优先放在下方,当不够的时候在显示在上方
            //假设下方放不下,超出window高度
            boolean isTallerThanWindowHeight = (rect.bottom + getPopupContentView().getMeasuredHeight()) > maxY;
            centerY = (rect.top + rect.bottom) / 2f;
            //超出可用大小就显示在上方
            isShowUp = isTallerThanWindowHeight;
            isShowLeft = centerX > XPopupUtils.getAppWidth(getContext()) / 2;

            //修正高度,弹窗的高有可能超出window区域
            ViewGroup.LayoutParams params = getPopupContentView().getLayoutParams();
            int maxHeight = isShowUpToTarget() ? (rect.top - getStatusBarHeight() - overflow)
                    : (XPopupUtils.getScreenHeight(getContext()) - rect.bottom - overflow);
            int maxWidth = isShowLeft ? (rect.right - overflow) : (XPopupUtils.getAppWidth(getContext()) - rect.left - overflow);
            if (getPopupContentView().getMeasuredHeight() > maxHeight) {
                params.height = maxHeight;
            }
            if (getPopupContentView().getMeasuredWidth() > maxWidth) {
                params.width = maxWidth;
            }
            getPopupContentView().setLayoutParams(params);

            getPopupContentView().post(() -> {
                if (popupInfo == null) return;
                boolean isRealCenterHorizontal = false;
                //只要弹窗不超出左右边界,都让弹窗居中显示
                boolean crossLeft = (centerX - getPopupContentView().getMeasuredWidth() / 2f
                        - bubbleContainer.getShadowRadius()) < 0;
                boolean crossRight = (centerX + getPopupContentView().getMeasuredWidth() / 2f
                        + bubbleContainer.getShadowRadius()) > XPopupUtils.getAppWidth(getContext());
                if (!crossLeft && !crossRight) {
                    isRealCenterHorizontal = true;
                }
                // translationX: 在左边就和atView左边对齐,在右边就和其右边对齐
                if (isRealCenterHorizontal) {
                    if (isRTL) {
                        translationX = -(XPopupUtils.getAppWidth(getContext()) -
                                centerX + defaultOffsetX - getPopupContentView().getMeasuredWidth() / 2f);
                    } else {
                        translationX = centerX + defaultOffsetX - getPopupContentView().getMeasuredWidth() / 2f;
                    }
                } else {
                    if (isRTL) {
                        if (isShowLeft) {
                            translationX = -(XPopupUtils.getAppWidth(getContext()) -
                                    rect.right + defaultOffsetX + bubbleContainer.getShadowRadius());
                        } else {
                            translationX = -(XPopupUtils.getAppWidth(getContext()) -
                                    rect.left + defaultOffsetX - bubbleContainer.getShadowRadius()
                                    - getPopupContentView().getMeasuredWidth());
                        }
                    } else {
                        if (isShowLeft) {
                            translationX = rect.right + defaultOffsetX -
                                    getPopupContentView().getMeasuredWidth() - bubbleContainer.getShadowRadius();
                        } else {
                            translationX = rect.left + defaultOffsetX + bubbleContainer.getShadowRadius();
                        }
                    }
                }

                if (isShowUpToTarget()) {
                    //说明上面的空间比较大,应显示在atView上方
                    translationY = rect.top - getPopupContentView().getMeasuredHeight() - defaultOffsetY;
                } else {
                    translationY = rect.bottom + defaultOffsetY;
                }

                //设置气泡相关
                if (isShowUpToTarget()) {
                    bubbleContainer.setLook(BubbleLayout.Look.BOTTOM);
                } else {
                    bubbleContainer.setLook(BubbleLayout.Look.TOP);
                }
                //箭头对着目标View的中心
                if (isRealCenterHorizontal) {
                    bubbleContainer.setLookPositionCenter(true);
                } else {
                    if (isRTL) {
                        if (isShowLeft) {
                            bubbleContainer.setLookPosition(getPopupContentView().getMeasuredWidth() / 2 +
                                    (getPopupContentView().getMeasuredWidth() / 2 - rect.width() / 2 - bubbleContainer.mLookWidth / 2)
                                    + bubbleContainer.getShadowRadius());
                        } else {
                            bubbleContainer.setLookPosition(rect.width() / 2 - bubbleContainer.getShadowRadius() - bubbleContainer.mLookWidth / 2);
                        }
                    } else {
                        bubbleContainer.setLookPosition(Math.max(0,
                                (int) (rect.right - (float) rect.width() / 2 - translationX - (float) bubbleContainer.mLookWidth / 2)));
                    }
                }
                bubbleContainer.invalidate();

                getPopupContentView().setTranslationX(translationX);
                getPopupContentView().setTranslationY(translationY);
                initAndStartAnimation();
            });
        }
    }

}

davidgerka avatar Jul 21 '25 08:07 davidgerka

我也看了半天,原来是里面强行加了一个overflow,我也不知道坐着为啥非要加一个这个,这也应该交给用户来自行设置啊,改也改不掉

molast avatar Dec 18 '25 08:12 molast