安卓开发popupWindow的使用

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通过控制可见性模拟阴影,可以在我们任意想要的地方设置阴影。这里只是简单实现一下。最后完成的效果就是下面这样。

相关推荐
robotx2 小时前
安卓线程相关
android
消失的旧时光-19432 小时前
Android 面试高频:JSON 文件、大数据存储与断电安全(从原理到工程实践)
android·面试·json
dalancon3 小时前
VSYNC 信号流程分析 (Android 14)
android
dalancon3 小时前
VSYNC 信号完整流程2
android
dalancon3 小时前
SurfaceFlinger 上帧后 releaseBuffer 完整流程分析
android
用户69371750013844 小时前
不卷AI速度,我卷自己的从容——北京程序员手记
android·前端·人工智能
程序员Android5 小时前
Android 刷新一帧流程trace拆解
android
墨狂之逸才5 小时前
解决 Android/Gradle 编译报错:Comparison method violates its general contract!
android
阿明的小蝴蝶6 小时前
记一次Gradle环境的编译问题与解决
android·前端·gradle
汪海游龙6 小时前
开源项目 Trending AI 招募 Google Play 内测人员(12 名)
android·github