PopupWindow的用法
PopupWindow 是 Android 系统提供的弹窗容器组件,核心作用是在当前页面的任意位置弹出一个悬浮视图,用于展示额外内容、提供操作选项或临时交互,不占用固定布局位置。popupWindow常用来做筛选栏,我们就以下面的效果为例,学习popupWindow的基本用法

下面先写一个xml文件,先写出筛选栏
xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="5dp"
android:background="#F9F9F9F9"
android:layout_margin="5dp">
<com.google.android.material.card.MaterialCardView
android:id="@+id/gerenxianzhi_layout"
android:layout_marginStart="3dp"
android:layout_width="wrap_content"
android:layout_height="26.2dp"
app:layout_constraintTop_toTopOf="@id/xinyongyouxiu_layout"
app:cardCornerRadius="3dp"
android:padding="2dp"
app:layout_constraintStart_toStartOf="parent"
android:backgroundTint="@color/system_gray">
<TextView
android:id="@+id/gerenxianzhi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="个人闲置"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
android:textSize="10sp"/>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/xinyongyouxiu_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
app:cardCornerRadius="3dp"
app:layout_constraintTop_toTopOf="@id/gerenxianzhi_layout"
app:layout_constraintStart_toEndOf="@id/gerenxianzhi_layout"
android:backgroundTint="@color/system_gray"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/xinyongyouxiu_small_layout"
android:layout_width="wrap_content"
android:padding="2dp"
android:layout_height="26dp">
<TextView
android:id="@+id/xinyongyouxiu"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:text="卖家信用"
android:gravity="center"
android:textSize="10sp"/>
<ImageView
android:layout_width="10dp"
android:layout_height="10dp"
android:src="@drawable/arrow_down"
android:layout_marginStart="2dp"
app:layout_constraintStart_toEndOf="@id/xinyongyouxiu"
app:layout_constraintTop_toTopOf="@id/xinyongyouxiu"
app:layout_constraintBottom_toBottomOf="@id/xinyongyouxiu"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/fabushijian_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="3dp"
android:layout_marginStart="5dp"
app:layout_constraintTop_toTopOf="@id/gerenxianzhi_layout"
app:layout_constraintStart_toEndOf="@id/xinyongyouxiu_layout"
android:backgroundTint="@color/system_gray"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/fabushijian_small_layout"
android:layout_width="wrap_content"
android:padding="2dp"
android:layout_height="26dp">
<TextView
android:id="@+id/fabushijian"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:text="发布时间"
android:textSize="10sp"/>
<ImageView
android:layout_width="10dp"
android:layout_height="10dp"
android:src="@drawable/arrow_down"
android:layout_marginStart="2dp"
app:layout_constraintStart_toEndOf="@id/fabushijian"
app:layout_constraintTop_toTopOf="@id/fabushijian"
app:layout_constraintBottom_toBottomOf="@id/fabushijian"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/gengduo_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="3dp"
android:layout_marginStart="5dp"
app:layout_constraintTop_toTopOf="@id/gerenxianzhi_layout"
app:layout_constraintStart_toEndOf="@id/fabushijian_layout"
android:backgroundTint="@color/system_gray"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/gengduo_small_layout"
android:layout_width="wrap_content"
android:padding="2dp"
android:layout_height="26dp">
<TextView
android:id="@+id/gengduo"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="更多"
android:gravity="center"
android:textSize="10sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<ImageView
android:layout_width="10dp"
android:layout_height="10dp"
android:src="@drawable/arrow_down"
android:layout_marginStart="2dp"
app:layout_constraintStart_toEndOf="@id/gengduo"
app:layout_constraintTop_toTopOf="@id/gengduo"
app:layout_constraintBottom_toBottomOf="@id/gengduo"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
接下来定义每个筛选栏的下拉菜单,这里简单用一个ListView展示即可
xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent">
<ListView
android:id="@+id/filter_condition"
android:layout_height="wrap_content"
android:divider="@null"
android:dividerHeight="0dp"
android:layout_width="match_parent"/>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
下面直接抛对应活动的代码
kotlin
class NewIssueFragment : Fragment() {
private lateinit var binding: FragmentNewissueBinding
private lateinit var popupWindow: PopupWindow
private lateinit var listView: ListView
private lateinit var popupView: View
private val creditOptions = listOf("信用优秀", "信用极好")
private val timeOptions = listOf("12小时内", "24小时内", "2天内", "3天内")
private val moreOptions = listOf("包邮", "全新", "七天无理由")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentNewissueBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
popupView = layoutInflater.inflate(R.layout.xinfa_filter_list, null)
popupWindow = PopupWindow(popupView, LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT,
true)
listView = popupView.findViewById<ListView>(R.id.filter_condition)
popupWindow.isOutsideTouchable = true
popupWindow.setOnDismissListener {
dimBackground(1.0f)
}
binding.newissueFilter.gengduoLayout.setOnClickListener { showMenu(it, moreOptions, "更多") }
binding.newissueFilter.xinyongyouxiuLayout.setOnClickListener { showMenu(it, creditOptions, "卖家信用") }
binding.newissueFilter.fabushijianLayout.setOnClickListener { showMenu(it, timeOptions, "发布时间") }
}
private fun showMenu(anchor: View, options: List<String>, type: String) {
dimBackground(0.8f) // 背景半透明
val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_list_item_1, options)
listView.adapter = adapter
binding.newissueRecyclerView.itemAnimator = ScaleInAnimator()
listView.setOnItemClickListener { _, _, position, _ ->
//执行对应点击逻辑
}
// popupWindow 显示逻辑
if (popupWindow.isShowing) {
popupWindow.dismiss()
return
}
popupWindow.showAsDropDown(anchor)
// 显示时高亮颜色
when (type) {
"个人闲置" -> binding.newissueFilter.gerenxianzhi.setBackgroundColor(Color.parseColor("#FFFF00"))
"更多" -> binding.newissueFilter.gengduoSmallLayout.setBackgroundColor(Color.parseColor("#FFFF00"))
"卖家信用" -> binding.newissueFilter.xinyongyouxiuSmallLayout.setBackgroundColor(Color.parseColor("#FFFF00"))
"发布时间" -> binding.newissueFilter.fabushijianSmallLayout.setBackgroundColor(Color.parseColor("#FFFF00"))
}
// 关闭时自动恢复颜色
popupWindow.setOnDismissListener {
when (type) {
"个人闲置" -> binding.newissueFilter.gerenxianzhi.setBackgroundColor(Color.WHITE)
"更多" -> binding.newissueFilter.gengduoSmallLayout.setBackgroundColor(Color.WHITE)
"卖家信用" -> binding.newissueFilter.xinyongyouxiuSmallLayout.setBackgroundColor(Color.WHITE)
"发布时间" -> binding.newissueFilter.fabushijianSmallLayout.setBackgroundColor(Color.WHITE)
}
dimBackground(1f)
}
}
private fun dimBackground(alpha: Float) {
val lp = requireActivity().window.attributes
lp.alpha = alpha
requireActivity().window.attributes = lp
}
}
代码整体比较多,我们一点点来拆解。
首先加载出popupWindow的菜单布局popupView然后进行初始化,popupWinodw的四个参数分别是 对应实例,菜单宽,菜单高,是否可以获取焦点。
kotlin
popupView = layoutInflater.inflate(R.layout.xinfa_filter_list, null)
popupWindow = PopupWindow(popupView, LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT,
true)
然后对popupWindow的关键属性进行设置。一般我们点击筛选栏以外的区域都是可以关闭筛选菜单,这个通过下面两行代码进行设置,其中isOutsideTouchable必须设置,对于setBackgroundDrawable也应该设置,否则部分机型可能出现点击外部无法关闭弹窗的问题,(系统依赖背景判断触摸区域)。
kotlin
popupWindow.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
// 设置点击窗口外部区域是否可以关闭窗口
popupWindow.isOutsideTouchable = true
再设置popupWindow.isTouchable = true来响应用户的点击事件。
接着我们就可以对选项的点击进行监听,然后调用showMenu展开对应的菜单。
kotlin
binding.newissueFilter.gengduoLayout.setOnClickListener { showMenu(it, moreOptions, "更多") }
注意showMenu()方法的传进的第一个参数anchor: View,这个参数在popupWindow.showAsDropDown(anchor)中调用。通过 showAsDropDown(anchor) 让弹窗紧跟锚点 View 下方。若需要微调弹窗与锚点的距离,可使用重载方法 showAsDropDown(anchor, xOffset, yOffset)。最后既然是筛选栏,我们肯定要监听对应的点击事件,菜单栏对应的点击事件的监听取决于绑定的组件,我们菜单用的是ListView,因此使用LIstView监听点击的方法即可。
kotlin
listView.setOnItemClickListener { _, _, position, _ ->
//执行对应点击逻辑
}
这些设置好后筛选栏就基本完成了。不过代码中还有一个dimBackground()方法,这个是设置阴影的,因为一般当我们下拉筛选菜单时,为了突出当前的筛选事件,屏幕其他区域会变暗,这里就模拟了这个效果,点击筛选项后除了popupWindow外其他区域都变暗了,popupWindow没有变暗是因为它是一个独立的悬浮窗口,它相对于Activity属于更高层级。我们在shouMenu()中调用dimBackground(0.8f) 就可以让屏幕其他区域变暗,不过这个控制的比较粗糙,除了菜单栏以外的区域都变暗了,为了达到更好的效果我们可以设置一个View通过控制可见性模拟阴影,可以在我们任意想要的地方设置阴影。这里只是简单实现一下。最后完成的效果就是下面这样。
