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

相关推荐
asfdsfgas4 小时前
从 SSP 配置到 Gradle 同步:Android SDK 开发中 Manifest 合并冲突的踩坑记录
android
zhaoyufei1334 小时前
RK3399 11.0关闭调试串口改为普通RS232通信串口
android·驱动开发
消失的旧时光-19434 小时前
Kotlin 协程最佳实践:用 CoroutineScope + SupervisorJob 替代 Timer,实现优雅周期任务调度
android·开发语言·kotlin
UWA5 小时前
为什么Android游戏画面在30帧运行时有抖动现象
android·游戏
锐湃6 小时前
Android车载多媒体开发MediaSession框架理解
android
yueqc16 小时前
Android 通信机制简析
android·binder·handle
qq_7174100110 小时前
FAQ05047:在进入camera或者在camera中切换场景时,出现“很抱歉,相机已停止运行”
android
KevinWang_11 小时前
Activity Result API 的缺点
android
奔跑中的蜗牛66611 小时前
直播APP架构升级和性能优化:WebView 容器化
android