2.2.3版本中,我们增加了BasePopup与AnchorView的位置回调,在该回调中我们可以调整BasePopup中的View位置或其他属性,因此实现一个带箭头的BasePopup将会变得十分简单。
下面跟随本例一起实现一个简单的带箭头的BasePopup吧~

编写xml布局

我们简单地写一个布局,该布局只是简单的包含了一个TextView和一个ImageView,其中TextView相当于内容区,ImageView相当于箭头。

  • 在本例中因为图片存在边距,因此主内容的margin略小于图片的大小,以保证两者之间不存在间隙

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    3. xmlns:tools="http://schemas.android.com/tools"
    4. android:layout_width="wrap_content"
    5. android:layout_height="wrap_content">
    6. <ImageView
    7. android:id="@+id/iv_arrow"
    8. android:layout_width="16dp"
    9. android:layout_height="16dp"
    10. android:src="@drawable/ic_popup_arrow"
    11. tools:rotation="180"
    12. tools:translationX="75dp" />
    13. <TextView
    14. android:id="@+id/tv_desc"
    15. android:layout_width="150dp"
    16. android:layout_height="150dp"
    17. android:layout_margin="12dp"
    18. android:background="@drawable/bg_round_white_8dp"
    19. android:gravity="center"
    20. android:padding="@dimen/default_padding"
    21. android:text="带箭头的Popup"
    22. android:textColor="@color/text_black2"
    23. android:textSize="@dimen/text_large" />
    24. </FrameLayout>

TIM截图20200512173619.png

覆写onPopupLayout()回调

在写BasePopup的Java代码时,我们需要override onPopupLayout()方法,该方法包含两个参数:

  • Rect popupRect:Popup在屏幕中的大小位置
  • Rect anchorRect:AnchorView在屏幕中的大小位置

在该方法中我们可以直接使用新提供的computeGravity()方法计算出BasePopup中心点与AnchorView中心的方位,返回值是Gravity的int值,然后我们可以根据BasePopup显示在AnchorView的位置来调整我们的箭头:

@Override
    public void onPopupLayout(@NonNull Rect popupRect, @NonNull Rect anchorRect) {
        //计算basepopup中心与anchorview中心方位
        //e.g:算出gravity == Gravity.Left,意味着Popup显示在anchorView的左侧
        int gravity = computeGravity(popupRect, anchorRect); 
        boolean verticalCenter = false;
        //计算垂直位置
        switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
            case Gravity.TOP:
                mIvArrow.setVisibility(View.VISIBLE);
                //设置箭头水平位置为相对于basepopup居中
                mIvArrow.setTranslationX((popupRect.width() - mIvArrow.getWidth()) >> 1);
                //设置箭头垂直位置为相对于basepopup底部
                mIvArrow.setTranslationY(popupRect.height() - mIvArrow.getHeight());
                //设置旋转角度0度(即箭头朝下,具体根据您的初始切图而定)
                mIvArrow.setRotation(0f);
                break;
            case Gravity.BOTTOM:
                mIvArrow.setVisibility(View.VISIBLE);
                mIvArrow.setTranslationX((popupRect.width() - mIvArrow.getWidth()) >> 1);
                mIvArrow.setTranslationY(0);
                mIvArrow.setRotation(180f);
                break;
            case Gravity.CENTER_VERTICAL:
                verticalCenter = true;
                break;
        }
        switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
            case Gravity.LEFT:
                mIvArrow.setVisibility(View.VISIBLE);
                mIvArrow.setTranslationX(popupRect.width() - mIvArrow.getWidth());
                mIvArrow.setTranslationY((popupRect.height() - mIvArrow.getHeight()) >> 1);
                mIvArrow.setRotation(270f);
                break;
            case Gravity.RIGHT:
                mIvArrow.setVisibility(View.VISIBLE);
                mIvArrow.setTranslationX(0);
                mIvArrow.setTranslationY((popupRect.height() - mIvArrow.getHeight()) >> 1);
                mIvArrow.setRotation(90f);
                break;
            case Gravity.CENTER_HORIZONTAL:
                //如果basepopup与anchorview中心对齐,则隐藏箭头
                mIvArrow.setVisibility(verticalCenter ? View.INVISIBLE : View.VISIBLE);
                break;
        }
    }

效果图

ezgif-6-4661669c768b.gif

写在后面

有人吐槽为什么带箭头的BasePopup没有做成“自适应”或者是“一句话完成”,其实是因为一旦我们做成了这种形式,您的使用就必定会受到我们api的限制,对于库和对于使用者来说,并不是使用越简单就越好。
举个例子

我希望在Popup显示在右边的时候,箭头在左边居中显示。

对于这种情况,我们完全可以跟PopupGravity一样提供【Gravity】api,但是某天遇到这种情况:

我希望箭头不要居中,在靠上(或者靠下)显示

对于这种情况,我们在【Gravity】的基础上添加【offset】,但是,我们又会遇到这种情况:

我希望在屏幕上方靠下,屏幕下方靠上

对于这种情况,我们可以在layout的时候进行判断,然而,还可能会遇到这种情况:

我的箭头图片默认是不是向下的,我希望BasePopup能够自适应箭头旋转

对于这种情况,我们可以给定初始旋转角度,但是,还有这种情况:

我希望箭头能够离ContentView一定距离(哪怕是负距离)

对于这种情况,我们可以单独允许用户设置一个View,专门用于箭头,然而,还会有。。。。。
无数种情况
是的,需求是无止境的。
如您所见,就如上面几种需求,我们为了完成【一句话配置】,就不得不加很多api,越是复杂的逻辑,就包含着隐藏越深的bug,而且越是具体的实现,对开发者来说就越是一种使用上的限制,对于库的作者来说,是一种永远都在迭代具体需求的限制。
因此,就如BasePopup的开发初衷一样,我们尽可能提供【能让您完成绘画的画笔】,而不是提供【能让你一眼看完的画作】。
所以,我们只提供了最基本的也是最关键的两个数据:【位置】【大小】,有了这两个数据,您想怎么呈现出您的画作,都由您来实现。