【Android】PopupWindow实现长按菜单
三三要成为安卓糕手
高能预警,又是炸裂的一局
一: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"><Buttonandroid: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" /><LinearLayoutandroid: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"><TextViewandroid: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" /><ImageViewandroid: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"><TextViewandroid: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" /><TextViewandroid: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" /><TextViewandroid: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;@Overrideprotected 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() {@Overridepublic boolean onLongClick(View v) {//先让PopupWindow显示一次msgOpetationPopupWindow.showAsDropDown(tvMessage,0,0);//当msgOperationParent控件完全绘制完毕后,执行润里面的代码msgOperationParent.post(new Runnable() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic boolean onLongClick(View v) {//先让PopupWindow显示一次msgOpetationPopupWindow.showAsDropDown(tvMessage,0,0);//当msgOperationParent控件完全绘制完毕后,执行润里面的代码msgOperationParent.post(new Runnable() {@Overridepublic 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
关闭弹窗,你看这不就用上了这个方法,需要我们手动关闭
四:收获
总结:对弹窗偏移量的精准度要求不高,就选第一种估算法;高的话就选第二种
遇到问题了,重要的是有解决方案,也就是解决问题的思路,至于解决问题的工具先学都来的及