弹窗在安卓开发中是必不可缺的一部分,通知,余额不足提示,第三方SDK的跳转,一些条件筛选的弹窗等等,在Android中,最常用的一般是Dialog和PopWindow这俩个控件。

自己平时也用得多,今天就把这个基础的东西总结下。

其实这俩个控件有一些实现和展示上的区别,不过也可以通过代码使得展示上大差不差

  • PopupWindow为非模态,可以继续操作弹出界面之下的控件;
  • Dialog为模态,必须先取消Dialog才能操作Dialog之下的控件;
  • 两者最根本的区别在于有没有新建一个 Window,PopupWindow 没有新建,而是通过 WMS 将 View 加到 DecorView;Dialog 是新建了一个 window (PhoneWindow),相当于走了一遍 Activity 中创建 window 的流程。

一、Dialog

1、AlertDialog

AlertDialog是Dialog的子类,所以它包含了Dialog类的很多属性和方法。

他的实现非常简单:


val dialog1: AlertDialog.Builder = AlertDialog.Builder(this);
dialog1.setTitle("提示")
    .setMessage("你的金额不足!")
    .setNegativeButton("关闭", null)
    .create()
    .show();


展示为:


Android中某个弹窗一天只弹出一次 安卓弹窗控件_Android中某个弹窗一天只弹出一次


具体可以设置其属性的方法:

    setTitle :为对话框设置标题
    setIcon :为对话框设置图标
    setMessage:为对话框设置内容
    setView : 给对话框设置自定义样式
    setItems :设置对话框要显示的一个list,一般用于显示几个命令时
    setMultiChoiceItems :用来设置对话框显示一系列的复选框

    setPositiveButton  :给对话框添加"Yes"按钮
    setNegativeButton :对话框添加"No"按钮
    create : 创建对话框
    show :显示对话框

2、Dialog

直接上代码: 

1.首先new 一个Dialog,第二个属性是为其配置一些dialog属性,例如背景,show()/dismiss()动画,是否有标题等等

2.inflate一个布局文件,再通过  dialog.setContentView(localView) 设置其展示的自定义布局

3.获取到dialog的window,通过window设置其布局的属性,长宽自适应,展示的位置等等

4.为弹窗布局里的控件赋值啊,添加点击事件等等,我这儿是一个弹窗展示下载进度,进度懒得写,就为图片和按钮增加了点击事件。

5.dialog.show()展示就完事


val dialog = Dialog(this, R.style.jz_style_dialog_progress) //设置一些属性
            val localView = LayoutInflater.from(this).inflate(R.layout.common_global_volume_dialog, null) //设置自定义的弹窗UI
            dialog.setContentView(localView)
            val window = dialog.window
            window?.setLayout(-2, -2) //-2 其实就是WRAP_CONTENT
            val localLayoutParams = window?.attributes
            localLayoutParams?.gravity = Gravity.BOTTOM
            window?.attributes = localLayoutParams

            val imageV: AppCompatImageView = dialog.findViewById(R.id.volume_image_tip)
            val cancelTv = dialog.findViewById<AppCompatTextView>(R.id.volume_tv_cancel)
            val confirmTv = dialog.findViewById<AppCompatTextView>(R.id.volume_tv_confirm)
            imageV.setOnClickListener {
                dialog.dismiss()
            }
            cancelTv.setOnClickListener {
                Toast.makeText(it.context, "Cancel", Toast.LENGTH_SHORT).show()
                dialog.dismiss()
            }
            confirmTv.setOnClickListener {
                Toast.makeText(it.context, "Confirm", Toast.LENGTH_LONG).show()
                dialog.dismiss()
            }
            dialog.show() //展示弹窗


展示效果:

Android中某个弹窗一天只弹出一次 安卓弹窗控件_控件_02

 
     1、当参数值包含Gravity.LEFT时,对话框出现在左边,所以lp.x就表示相对左边的偏移,负值忽略.
     2、当参数值包含Gravity.RIGHT时,对话框出现在右边,所以lp.x就表示相对右边的偏移,负值忽略.
     3、当参数值包含Gravity.TOP时,对话框出现在上边,所以lp.y就表示相对上边的偏移,负值忽略.
     4、当参数值包含Gravity.BOTTOM时,对话框出现在下边,所以lp.y就表示相对下边的偏移,负值忽略.
     5、当参数值包含Gravity.CENTER_HORIZONTAL时
     对话框水平居中,所以lp.x就表示在水平居中的位置移动lp.x像素,正值向右移动,负值向左移动.
     6、当参数值包含Gravity.CENTER_VERTICAL时
     ,对话框垂直居中,所以lp.y就表示在垂直居中的位置移动lp.y像素,正值向右移动,负值向左移动.
     7、gravity的默认值为Gravity.CENTER,即Gravity.CENTER_HORIZONTAL |
     Gravity.CENTER_VERTICAL.
     8、Dialogh距离边界都有一小段距离
 

二、popWindow

这是popwindow实例

package com.example.lxview.base.pop

import android.annotation.SuppressLint
import android.graphics.drawable.ColorDrawable
import android.view.*
import android.widget.PopupWindow
import androidx.appcompat.widget.AppCompatTextView
import com.example.lxview.R

@SuppressLint("InflateParams")
class PickHistorySelectPop private constructor(v: View, val listener: (Int?) -> Unit?, private val focusAble: Boolean = true) {

    var popWindow: PopupWindow? = null

    companion object {
        fun show(v: View, focusAble: Boolean? = true, listener: (Int?) -> Unit?): PickHistorySelectPop {
            return PickHistorySelectPop(v, listener, focusAble ?: true)
        }
    }

    private lateinit var replied: AppCompatTextView
    private lateinit var reply: AppCompatTextView
    private lateinit var refused: AppCompatTextView
    private lateinit var all: AppCompatTextView

    init {
        show(v, listener)
    }

    enum class SelectType(val type: Int) {
        REPLIED(1), NO_REPLY(0), REFUSED(3), ALL(-1)
    }

    private fun show(v: View, onResult: (Int?) -> Unit?) {
        popWindow = PopupWindow(v.context)
        val view = View.inflate(v.context, R.layout.select_pop_content, null)
        popWindow?.contentView = view
        replied = view.findViewById(R.id.pick_history_filter_replied)
        reply = view.findViewById(R.id.pick_history_filter_no_reply)
        refused = view.findViewById(R.id.pick_history_filter_refused)
        all = view.findViewById(R.id.pick_history_filter_all)
        popWindow?.setBackgroundDrawable(ColorDrawable())
        popWindow?.isOutsideTouchable = true
        popWindow?.isTouchable = true
        popWindow?.isFocusable = true
        replied.setOnClickListener {
            onResult(SelectType.REPLIED.type)
            popWindow?.dismiss()
        }
        reply.setOnClickListener {
            onResult(SelectType.NO_REPLY.type)
            popWindow?.dismiss()
        }
        refused.setOnClickListener {
            onResult(SelectType.REFUSED.type)
            popWindow?.dismiss()
        }
        all.setOnClickListener {
            onResult(SelectType.ALL.type)
            popWindow?.dismiss()
        }
        popWindow?.showAtLocation(v, Gravity.END or Gravity.TOP, 50, 200)
    }

}

调用的地方:

easyPopTv?.setOnClickListener {
            PickHistorySelectPop.show(it, true) { type ->
                when (type) {
                    0 -> {
                        Toast.makeText(it.context, "只展示未回复的", Toast.LENGTH_LONG).show()
                    }
                    1 -> {
                        Toast.makeText(it.context, "只展示已回复的", Toast.LENGTH_LONG).show()
                    }
                    3 -> {
                        Toast.makeText(it.context, "只展示拒绝的", Toast.LENGTH_LONG).show()
                    }
                    else -> {
                        Toast.makeText(it.context, "展示所有问答", Toast.LENGTH_LONG).show()
                    }
                }
            }
        }

展示效果:

 

Android中某个弹窗一天只弹出一次 安卓弹窗控件_java_03

 这个弹窗,因为要实现多语言,所以宽度不定,我用的是ninePatch来实现的宽度拉伸,这个用于项目里右上角点击图标对展示内容进行筛选。

  • showAsDropDown(View anchor):相对某个控件的位置(正左下方),无偏移。
  • showAsDropDown(View anchor, int xoff, int yoff):相对某个控件的位置,有偏移。
  • showAtLocation(View parent, int gravity, int x, int y):相对于父控件的位置(例如正中央Gravity.CENTER,下方Gravity.BOTTOM等),可以设置偏移或无偏移。

三、细节上的区别

(1)Popupwindow在显示之前一定要设置宽高,Dialog无此限制。

(2)Popupwindow默认不会响应物理键盘的back,除非显示设置了popup.setFocusable(true);而在点击back的时候,Dialog会消失。

(3)Popupwindow不会给页面其他的部分添加蒙层,而Dialog会。

(4)Popupwindow没有标题,Dialog默认有标题,可以通过dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);取消标题,第二个dialog实例我就是通过在style里面设置其没得标题。

(5)二者显示的时候都要设置Gravity。如果不设置,Dialog默认是Gravity.CENTER。

(6)二者都有默认的背景,都可以通过setBackgroundDrawable(new ColorDrawable(android.R.color.transparent));去掉。

通过Dialog和popWindow,可以实现各种各样的效果,什么全局加载loading,五花八门的分享弹窗等等,具体选择实现可以根据需求而定。如果用的地方比较多,可以进行简单的封装,只需要简单的传入布局文件,点击事件,内容,通过几行代码便可以实现好看的弹窗功能。

当然,更简单且不需要交互的提示只需要用Toast就可以展示了。

因为想着简单明了的说明dialog和popwindow,所以几个样例都是很简单的实现。还有些自定义的弹窗在下面的Demo里