
三三要成为安卓糕手
高能预警,又是炸裂的一局
一:PopupWindow实现长按菜单
需求分析:长按消息,显示复制翻译转发

需要两个xml布局
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
<?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>

java
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上创建,设置外部可触摸后关闭,最后在主函数中调用这个方法
java
/**
* 创建消息弹窗
*/
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:弹窗显示位置关联控件
java
/**
* 处理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高度
声明为成员变量,方便下面访问它的高度和宽度

java
/**
* 处理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
关闭弹窗,你看这不就用上了这个方法,需要我们手动关闭
四:收获
总结:对弹窗偏移量的精准度要求不高,就选第一种估算法;高的话就选第二种
遇到问题了,重要的是有解决方案,也就是解决问题的思路,至于解决问题的工具先学都来的及