【Android】PopupWindow实现长按菜单

发布于:2025-08-02 ⋅ 阅读:(17) ⋅ 点赞:(0)

在这里插入图片描述

三三要成为安卓糕手
高能预警,又是炸裂的一局

一:PopupWindow实现长按菜单

需求分析:长按消息,显示复制翻译转发

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

需要两个xml布局

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".popupwindow.PopupWindowActivity">


    <Button
        android:id="@+id/btn_show_bottom_menu"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="显示一个底部弹窗"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="10dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/btn_show_bottom_menu">

        <TextView
            android:id="@+id/tv_message"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:background="@drawable/bg_chat_message"
            android:padding="20dp"
            android:textColor="@color/my_blue"
            android:text="hello,my name is lao sun,i'm from Chinasdadasdsadsadasdsadassadsad!"
            android:textSize="28sp" />

        <ImageView
            android:id="@+id/iv_avatar"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_marginLeft="5dp"
            android:layout_marginTop="30dp"
            android:src="@drawable/icon_face1" />

    </LinearLayout>



</androidx.constraintlayout.widget.ConstraintLayout>

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

需要注意的一个点:因为我们使用的是.9图片,当看不到文字的时候,添加一个内边距能解决

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/parent"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/bg_msg_opreation"
    android:paddingLeft="12dp"
    android:paddingRight="12dp"
    android:paddingBottom="16dp">

    <TextView
        android:id="@+id/tv_copy"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/black"
        android:padding="8dp"
        android:text="复制"
        android:textColor="@color/white"
        android:textSize="18sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_translate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/black"
        android:padding="8dp"
        android:text="翻译"
        android:textColor="@color/white"
        android:textSize="18sp"
        app:layout_constraintLeft_toRightOf="@+id/tv_copy"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_report"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/black"
        android:padding="8dp"
        android:text="转发"
        android:textColor="@color/white"
        android:textSize="18sp"
        app:layout_constraintLeft_toRightOf="@+id/tv_translate"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

public class PopupWindowActivity extends AppCompatActivity {
    private static final String TAG = "PopupWindowActivity";

    private PopupWindow popupWindow;
    private PopupWindow msgOpetationPopupWindow;
    private TextView tvMessage;
    private View msgOperationParent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_popup_window);

        ConstraintLayout parent = findViewById(R.id.main);

        findViewById(R.id.btn_show_bottom_menu).setOnClickListener(view ->{
            //显示在哪个根布局,位置,xy坐标
            popupWindow.showAtLocation(parent, Gravity.BOTTOM,0,0);
            setBackgroundAlpha(0.3f);
        });


        /**
         * 处理msgOperationPopupWindow弹窗的显示逻辑
         * 估算高度
         */
//        tvMessage = findViewById(R.id.tv_message);
//        tvMessage.setOnLongClickListener(new View.OnLongClickListener() {
//            @Override
//            public boolean onLongClick(View v) {
//                showMsgLongMenuBySimple();
//                return false;
//            }
//        });



        /**
         * 处理msgOperationPopupWindow弹窗的显示逻辑
         * 精确高度
         */
        tvMessage = findViewById(R.id.tv_message);
        tvMessage.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                //先让PopupWindow显示一次
                msgOpetationPopupWindow.showAsDropDown(tvMessage,0,0);


                //当msgOperationParent控件完全绘制完毕后,执行润里面的代码
                msgOperationParent.post(new Runnable() {
                    @Override
                    public void run() {
                        if(msgOpetationPopupWindow.isShowing()){

                            msgOpetationPopupWindow.dismiss();
                            int tvMessageWidth = tvMessage.getWidth()/3;
                            int tvMessageHeight = tvMessage.getHeight();
//                            int popupWindowHeight = msgOpetationPopupWindow.getHeight();
                            int msgOperationParentHeight = msgOperationParent.getHeight();
                            int x = tvMessageWidth;
                            int y = -(tvMessageHeight + msgOperationParentHeight);
                            Log.i(TAG, "onLongClick: popupWindowHeight = " + msgOperationParentHeight);
                            msgOpetationPopupWindow.showAsDropDown(tvMessage,x,y);
                        }
                    }
                });

                return false;
            }
        });


        createBottomPopupWindow();
        createMsgPopupWindow();

    }

    /**
     * 估算高度展示PopupWindow
     */
    private void showMsgLongMenuBySimple() {
        int width = tvMessage.getWidth()/2;
        //把PopupWindow显示在某个控件的底部,xy偏移量
        int height = tvMessage.getHeight();
        int autoHeight = 180;
        int popupWindowHeight = msgOpetationPopupWindow.getHeight();
        Log.i(TAG, "onLongClick: PopupWindowHeight = " + popupWindowHeight);
        msgOpetationPopupWindow.showAsDropDown(tvMessage,width,-(height + autoHeight));
    }


    private void createBottomPopupWindow(){
        View view = getLayoutInflater().inflate(R.layout.layout_bottom_menu_popupwindow, null);
        popupWindow = new PopupWindow(view,
                ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);

        popupWindow.setOutsideTouchable(true);
        //如果弹窗消失,就把透明度修改回来
        popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
            @Override
            public void onDismiss() {
                setBackgroundAlpha(1.0f);
            }
        });
    }

    /**
     * 设置透明度
     * @param f
     */
    private void setBackgroundAlpha(float f){
        WindowManager.LayoutParams layoutParams = getWindow().getAttributes();
        layoutParams.alpha = f;
        getWindow().setAttributes(layoutParams);
    }


    /**
     * 创建消息弹窗
     */
    private void createMsgPopupWindow(){
        View view = getLayoutInflater().inflate(R.layout.layout_msg_operation,null);
        msgOpetationPopupWindow = new PopupWindow(view,
                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        msgOpetationPopupWindow.setOutsideTouchable(true);

        msgOperationParent = view.findViewById(R.id.parent);
    }
}

二:粗略定义弹窗位置

1:创建PopupWindow

上一章已经细讲过,这里尽量不啰嗦

简述:把复制粘贴的布局转为视图,转载到PopupWindow上创建,设置外部可触摸后关闭,最后在主函数中调用这个方法

    /**
     * 创建消息弹窗
     */
    private void createMsgPopupWindow(){
        View view = getLayoutInflater().inflate(R.layout.layout_msg_operation,null);
        msgOpetationPopupWindow = new PopupWindow(view,
                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        msgOpetationPopupWindow.setOutsideTouchable(true);

		//这行代码后面会解释
        msgOperationParent = view.findViewById(R.id.parent);
    }

2:弹窗显示位置关联控件

        /**
         * 处理msgOperationPopupWindow弹窗的显示逻辑
         */
        TextView tvMessage = findViewById(R.id.tv_message);
        tvMessage.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                int width = tvMessage.getWidth()/2;
                //把PopupWindow显示在某个控件的底部,xy偏移量
                int height = tvMessage.getHeight();
                int autoHeight = 180;
                
//                int popupWindowHeight = msgOpetationPopupWindow.getHeight();
//                Log.i(TAG, "onLongClick: PopupWindowHeight = " + popupWindowHeight);
                msgOpetationPopupWindow.showAsDropDown(tvMessage,width,-(height + autoHeight));
                return false;
            }
        });

(1)showAsDropDown

将PopupWindow显示在某个控件的底部

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这里我们让复制粘贴显示在消息框下面,再去设置偏移量

x轴偏移消息框宽度的一半,y轴偏移消息框高度+估算 (使用估算这种方法一般可以覆盖90%的场景,精确等会展示)

提问:这里msgOpetationPopupWindow.getHeight()能获取到弹窗的高度吗?

不能,因为它没有被真实的显示,所以获取不到

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(2)方法效果展示

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(3)setOnLongClickListener

设置长按这个操作的监听器,new View.OnLongClickListener()

三:精确估计弹窗位置

对弹窗显示的位置要求比较精细的情况下,估算法就不管用了。

因为PopupWindow没有真正的加入到当前的页面,如何获取它的高度??

方案:在弹窗显示之前先让它显示一遍,第一次显示后就可以获取到它的高度了

1:获取PopupWindow高度

声明为成员变量,方便下面访问它的高度和宽度

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

        /**
         * 处理msgOperationPopupWindow弹窗的显示逻辑
         * 精确高度
         */
        tvMessage = findViewById(R.id.tv_message);
        tvMessage.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                //先让PopupWindow显示一次
                msgOpetationPopupWindow.showAsDropDown(tvMessage,0,0);


                //当msgOperationParent控件完全绘制完毕后,执行润里面的代码
                msgOperationParent.post(new Runnable() {
                    @Override
                    public void run() {
                        if(msgOpetationPopupWindow.isShowing()){

                            msgOpetationPopupWindow.dismiss();
                            int tvMessageWidth = tvMessage.getWidth()/3;
                            int tvMessageHeight = tvMessage.getHeight();
//                            int popupWindowHeight = msgOpetationPopupWindow.getHeight();
                            int msgOperationParentHeight = msgOperationParent.getHeight();
                            int x = tvMessageWidth;
                            int y = -(tvMessageHeight + msgOperationParentHeight);
                            Log.i(TAG, "onLongClick: popupWindowHeight = " + msgOperationParentHeight);
                            msgOpetationPopupWindow.showAsDropDown(tvMessage,x,y);
                        }
                    }
                });

                return false;
            }
        });

2:post方法的作用

为毛弹窗高度还是-2,因为第一次显示后,立刻就关闭弹窗,此时弹窗视图来不及被创建,需要等第一次的PopupWindow真的显示出来了,才能获取到它的高度

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

解决方案:我们去通过监听它的根布局加载成功后,通过根布局获取弹窗的高度

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这句代码的作用就展示出来了兄弟,声明为成员变量,便于使用

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

调post方法,+改名

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

获取PopupWindow根布局的高度,其实就是PopupWindow本身的高度

3:isShowing

弹窗是否已经显示

4:dismiss

关闭弹窗,你看这不就用上了这个方法,需要我们手动关闭

四:收获

总结:对弹窗偏移量的精准度要求不高,就选第一种估算法;高的话就选第二种

遇到问题了,重要的是有解决方案,也就是解决问题的思路,至于解决问题的工具先学都来的及