Android:弹出对话框方式梳理一览(一)

ops/2024/10/19 2:18:56/

Android:弹出对话框方式梳理一览(一)

在这里插入图片描述

Guide|导言

在Android开发中,对话框可能是我们与用户交互的非常常用的方式,包括弹出一个小界面,可能在实际场景中都非常实用。本篇文章主要就是对Android弹出对话框的一些方式的梳理,同时也帮助我自己巩固,避免遗忘。

本文主要还是参考Google的官方文档,详见:对话框|Android开发者;

Dialog|对话框的基类

在Android中,Dialog类是对话框的基类,它负责实现对话框的一些共有属性,不过我们一般不直接使用Dialog类,而是使用它的衍生类,比如AlertDialog(可显示标题、最多三个按钮、可选项目列表或自定义布局的对话框),DatePickerDialog 或 TimePickerDialog(一个对话框,带有可让用户选择日期或时间的预定义界面)。

之前的文章中我们已经简单介绍过了Android中的Window相关机制,实际上Dialog也是通过Window机制显示出来的,我们可以简单看一眼源码(此处跳过也不影响后边内容):

Dialog(@UiContext @NonNull Context context, @StyleRes int themeResId,boolean createContextThemeWrapper) {......//获取WindowManager服务mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);//新创建一个Window用来显示对话框final Window w = new PhoneWindow(mContext);mWindow = w;w.setCallback(this);w.setOnWindowDismissedCallback(this);w.setOnWindowSwipeDismissedCallback(() -> {if (mCancelable) {cancel();}});//将新创建出来的Window与WindowManager关联w.setWindowManager(mWindowManager, null, null);//设置弹出的位置为中心w.setGravity(Gravity.CENTER);mListenersHandler = new ListenersHandler(this);}

一些重要的点已经注释在了代码中,可以看到,在Dialog的构造方法中就创建出来了一个Window来显示需要弹出的内容,所以说它本质上也是使用到了Window机制来进行内容的显示。接下来继续看它的show方法的部分逻辑:

    public void show() {......mWindowManager.addView(mDecor, l);if (restoreSoftInputMode) {l.softInputMode &=~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;}mShowing = true;sendShowMessage();}

这个show方法就是最终显示对话框的方法,可以看到,这里显然是使用到了windowManager的addView方法,所以到这里我们就可以明确其显示原理了,就是使用到了windowManagerService的功能。

Basic|基础使用

介绍完了Dialog的基本原理,接下来我们来了解Dialog的基础使用方法。使用Dialog大致可以分为五步:

  1. 创建Dialog的构造器
  2. 配置Dialog的内容
  3. [可选]进行一些生命周期配置(比如onStart,onStop等,此处可能在自定义Dialog中使用到)
  4. 显示Dialog
  5. 注销/隐藏Dialog

如果只是简单的使用,其实两步就可以概括:

  • 创建Dialog
  • 显示Dialog

比如一个最简单的示例如下所示:

//1.创建构造器
val dialogBuilder = AlertDialog.Builder(this)
//2.配置dialog内容
dialogBuilder.apply {//配置正文内容setMessage("This is the Message!")//配置标题setTitle("This is a Dialog!")
}
//3.创建并显示dialog
dialogBuilder.create().show()

这里注意似乎语法糖有一些问题,比如这样写:

 //1.创建构造器val dialogBuilder = AlertDialog.Builder(this)//2.配置dialog内容dialogBuilder.apply {//配置正文内容setMessage("This is the Message!")//配置标题 -- 使用语法糖将会导致失效title = "This is a Dialog!"}//3.创建并显示dialogdialogBuilder.create().show()

配置出来的标题就会失效,因为这个语法糖关联的方法的是Activity的方法,目前建议还是先别用语法糖。如果需要添加按钮,也按照上面这个方式来添加即可。

添加按钮&Message

对于AlertDialog来说,最多可以添加三个按钮,性质分别是Positive,Negative,Neutral,官方意义上可以理解成确认,取消,中立三个意思。比如说申请权限时的授权拒绝授权仅在使用中允许就差不多可以对应上前面的三个意思。

对于Positive性质的按钮,我们可以调用setPositiveButton(CharSequence text, final OnClickListener listener) 方法来设置,比如:

setPositiveButton("确认",object :DialogInterface.OnClickListener {override fun onClick(dialog: DialogInterface?, which: Int) {Log.d(TAG, "onClick: 确认")}})

不过,我们当然也可以替换成Lambda表达式:

setNegativeButton("取消") {dialog,which->Log.d(TAG, "onCreate: 取消")}

我们来改进上面这段代码,最终为:

//1.创建构造器
val dialogBuilder = AlertDialog.Builder(this)
//2.配置dialog内容
dialogBuilder.apply {//配置正文内容setMessage("This is the Message!")//配置标题setTitle("This is a Dialog!")//设置按钮//确认setPositiveButton("确认",object :DialogInterface.OnClickListener{override fun onClick(dialog: DialogInterface?, which: Int) {Log.d(TAG, "onClick: 确认")}})//取消setNegativeButton("取消"){dialog,which->Log.d(TAG, "onCreate: 取消")}//中立setNeutralButton("中立"){dialog,which ->Log.d(TAG, "onCreate: 中立")}}
//3.创建并显示dialog
dialogBuilder.create().show()

最终显示的效果:
在这里插入图片描述

添加一般列表

除了一般的按钮和文本之外,我们还可以向对话框内填入列表,不过由于列表显示在对话框的内容区域中,因此对话框无法同时显示消息和列表

添加列表项的最简单的方法通过setItems方法:

setItems(arrayOf("列表一","列表二","列表三"),object :DialogInterface.OnClickListener{override fun onClick(dialog: DialogInterface?, which: Int) {Log.d(TAG, "index:${which} ,dialog:${dialog}")}})

其中dialog参数为弹出的Dialog对象,which参数为之前传入的String数组的索引值,需要说明的是这个Dialog应该是不会被复用的,是一个非永久的Dialog,我们可以通过打印出来的日志看出来:
在这里插入图片描述

理解了上边的代码后,可以进一步简化为:

setItems(arrayOf("列表一","列表二","列表三"),{dialog, which ->Log.d(TAG, "index:${which} ,dialog:${dialog}")
})

实现的效果如下:

在这里插入图片描述
另外,也可以使用实现了ListAdapter接口的Adapter来设置列表项,比如说:

setAdapter(ArrayAdapter(context,com.google.android.material.R.layout.support_simple_spinner_dropdown_item,arrayOf("1","2")
), object :DialogInterface.OnClickListener{override fun onClick(dialog: DialogInterface?, which: Int) {Log.d(TAG, "index:${which} ,dialog:${dialog}")}})

添加永久性列表

前边提到过我们添加的DIalog不是一个可复用的,而是每次弹出都会创建一个新的Dialog,接下来我们来介绍可以添加永久性列表的方法。

添加永久性复选框☑️

所谓复选框,就是可以同时选择多个列表项的Dialog,它的添加方法和之前的也类似,我们可以调用setMultiChoiceItems来设置,比如:

setMultiChoiceItems(arrayOf("Item1","Item2","Item3"), booleanArrayOf(true,false,true),object :DialogInterface.OnMultiChoiceClickListener {override fun onClick(dialog: DialogInterface?,which: Int,isChecked: Boolean) {Log.d(TAG, "dialog is ${dialog},index is ${which},is checked? ${isChecked}")}}
)

这个方法相比之前添加列表的参数中多了一个 BooleanArray 类型,该参数指定的是第一次弹出复选框时的列表选择状态,比如说这里我们传入的是 true,false,true的参数,最终第一次弹框的效果就是:
在这里插入图片描述
如果想一开始什么都不选中,那传入一个null值即可。

最后我们来验证一下该Dialog是否是一个永久性的,分别点击列表项,我们可以发现每次打印的Dialog都是同一项:
在这里插入图片描述
说明这确实是一个永久性的Dialog。

添加永久性的单选框

最后就是添加一个永久性的单选框,这个其实和一开始的添加一个非永久性的单选框的方法很类似,唯一的一点就是多了一个参数来指定第一次弹出时选中的列表项,跟之前的类似:

setSingleChoiceItems(arrayOf("item1","item2","item3"),1,object :DialogInterface.OnClickListener {override fun onClick(dialog: DialogInterface?, which: Int) {Log.d(TAG, "index:${which} ,dialog:${dialog}")}}
)

为Dialog添加自定义内容

到之前为止其实都是Dialog的很基础的应用,实际上在使用过程中我们可能需要弹出一个Dialog来展示我们自己的定制化的内容,比如说一个登录页面,这个时候我们就不能用之前的简单的方法了,取而代之,我们可以调用setView方法来为我们的Dialog填充自定义的内容,这个过程实际上类似于动态添加View的过程。

举例来说,我们可以先设计一个布局来描述我们需要填充的具体内容:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><ImageViewandroid:id="@+id/header_img"android:layout_width="match_parent"android:layout_height="wrap_content"android:scaleType="centerCrop"app:srcCompat="@drawable/images" /><com.google.android.material.textfield.TextInputLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><com.google.android.material.textfield.TextInputEditTextandroid:id="@+id/password_input"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="password" /></com.google.android.material.textfield.TextInputLayout><com.google.android.material.textfield.TextInputLayoutandroid:id="@+id/textInputLayout"android:layout_width="match_parent"android:layout_height="wrap_content"><com.google.android.material.textfield.TextInputEditTextandroid:id="@+id/user_input"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="user" /></com.google.android.material.textfield.TextInputLayout>
</LinearLayout>

这里用了一个线性布局来描述,至于为什么不用更好用的约束布局,因为我测试过发现直接丢一个约束布局可能会带来显示异常的问题,所以如果想使用约束布局的话可能需要在约束布局外边包一层线性布局。

然后我们来配置这个自定义的Dialog:

//1.创建构造器
val dialogBuilder = AlertDialog.Builder(this)
//2.配置dialog内容
dialogBuilder.apply {//添加自定义的布局setView(R.layout.dialog_layout)setNegativeButton("Cancel",object :DialogInterface.OnClickListener {override fun onClick(dialog: DialogInterface?, which: Int) {}})setPositiveButton("Confirm",object : DialogInterface.OnClickListener {override fun onClick(dialog: DialogInterface?, which: Int) {}})
}
//3.创建并显示dialog
dialogBuilder.create().show()

最终显示的效果就是这样的:
在这里插入图片描述
我们可以和官方文档上实现的效果进行对比:
在这里插入图片描述
可以发现我们的按钮是比较丑的,实际上这部分我们也可以直接做进我们的自定义布局里:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><ImageViewandroid:id="@+id/header_img"android:layout_width="match_parent"android:layout_height="wrap_content"android:scaleType="centerCrop"app:srcCompat="@drawable/images" /><com.google.android.material.textfield.TextInputLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><com.google.android.material.textfield.TextInputEditTextandroid:id="@+id/password_input"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="password" /></com.google.android.material.textfield.TextInputLayout><com.google.android.material.textfield.TextInputLayoutandroid:id="@+id/textInputLayout"android:layout_width="match_parent"android:layout_height="wrap_content"><com.google.android.material.textfield.TextInputEditTextandroid:id="@+id/user_input"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="user" /></com.google.android.material.textfield.TextInputLayout><LinearLayoutandroid:orientation="horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"><androidx.appcompat.widget.AppCompatButtonandroid:id="@+id/positivebtn"android:layout_width="0dp"android:layout_height="60dp"android:layout_margin="0dp"android:layout_weight="1"android:background="#C8E6C9"android:elevation="0dp"android:text="Confirm" /><androidx.appcompat.widget.AppCompatButtonandroid:id="@+id/negbtn"android:layout_width="0dp"android:layout_height="60dp"android:layout_margin="0dp"android:layout_weight="1"android:background="#FFCDD2"android:elevation="0dp"android:text="Cancel" /></LinearLayout>
</LinearLayout>

最终的效果就好上很多:
在这里插入图片描述
至于我们具体的点击事件的设置,则可以通过布局膨胀器获取具体的View来为按钮设置点击事件监听 :

//2.配置dialog内容
dialogBuilder.apply {val customView = layoutInflater.inflate(R.layout.dialog_layout,null,false)setView(customView)customView.findViewById<Button>(R.id.negbtn).setOnClickListener {Toast.makeText(context, "cancel!", Toast.LENGTH_SHORT).show()}customView.findViewById<Button>(R.id.positivebtn).setOnClickListener {Toast.makeText(context, "load!", Toast.LENGTH_SHORT).show()}}
//3.创建并显示dialog
dialogBuilder.create().show()

为Dialog添加进出场动画

在使用其他App时,我们往往会发现弹出的Dialog并不都是直接闪现出在屏幕中间的,经常会有一个从底部或者是从左右弹出的动画效果,实现该效果的步骤也很简单,不过我们需要有一些Android动画的基础,我们在anim文件夹下新建弹入和弹出动画:

//弹入动画

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"><translate android:fromYDelta="100%p" android:toYDelta="0"android:duration="@android:integer/config_mediumAnimTime"/>
</set>

//弹出动画

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"><translate android:fromYDelta="0" android:toYDelta="100%p"android:duration="@android:integer/config_mediumAnimTime"/>
</set>

之后我们在styles文件下新建style属性:

    <style name="BottomAni"><item name="android:windowEnterAnimation">@anim/pop_from_bottom</item><item name="android:windowExitAnimation">@anim/exit_from_bottom</item></style>

最后在创建完Dialog时指定window动画属性:

        viewBinding.button.setOnClickListener {//1.创建构造器val dialogBuilder = AlertDialog.Builder(this)//2.配置dialog内容dialogBuilder.apply {val customView = layoutInflater.inflate(R.layout.dialog_layout,null,false)setView(customView)customView.findViewById<Button>(R.id.negbtn).setOnClickListener {Toast.makeText(context, "cancel!", Toast.LENGTH_SHORT).show()}customView.findViewById<Button>(R.id.positivebtn).setOnClickListener {Toast.makeText(context, "load!", Toast.LENGTH_SHORT).show()}}//3.创建并显示dialogdialogBuilder.create().apply {//指定窗口动画window?.attributes?.windowAnimations = R.style.BottomAniwindow?.setGravity(Gravity.BOTTOM)}.show()

最终效果:

在这里插入图片描述


http://www.ppmy.cn/ops/36862.html

相关文章

MySQL学习笔记

一、数据库的操作 SHOW DATABASE;-- 显示所有数据库 CREATE DATABASE;-- 创建数据库 DROP DATABASE test1;-- 删除数据库 二、数据库常用数据类型 整数型&#xff0c;浮点型&#xff0c;定点型&#xff0c;字符串类型&#xff0c;时间型 三、表的基本操作 use demo1-- 选中…

001 定期同步mysql数据到es 删除数据库记录同时删除es记录 es全文搜索分词和高亮

文章目录 ProductController.javaProduct.javaElasticsearchSyncListener.javaProductElasticSearchMapper.javaProductMapper.javaProductDeletedEvent.javaProductServiceImpl.javaSyncProductService.javaIProductService.javaElasticSearchSpringDemoApplication.javaServl…

Windows系统和unbtun系统连接usb 3.0海康可见MVS和红外艾睿相机

一.海康可见USB3.0工业面阵相机 海康usb相机需要去海康官网上下载对应系统的MVS客户端及SDK开发包 海康机器人-机器视觉-下载中心 选择Windows系统和unbtun&#xff08;我是linux aarch64,所以选择了对应压缩包解压&#xff09; Windows系统 1.双击安装包进入安装界面&…

Laravel Sail

先创建一个laravel应用&#xff0c;就先用他取的默认名字 example.com ./vendor/bin/sail up -d [] Running 6/6 ✔ Container example-app-mailpit-1 Running …

【二叉树算法题记录】110. 平衡二叉树

题目描述 给定一个二叉树&#xff0c;判断它是否是平衡二叉树。 什么是平衡二叉树&#xff1f;一个二叉树每个节点的左右两个子树的高度差的绝对值不超过1。 题目分析 其实平衡二叉树的定义就给出了递归的思路&#xff1a;每个节点的左右两个子树的高度差的绝对值不超过1。…

今日头条,抖音,西瓜视频你不知道的秘密?

西瓜视频和抖音这两款产品是一家&#xff0c;都是由今日头条孵化。 抖音是由今日头条孵化的一款音乐创意短视频社交软件&#xff0c;该软件于2016年9月20日上线&#xff0c;是一个面向全年龄的音乐短视频社区平台。用户可以通过这款软件选择歌曲&#xff0c;拍摄音乐短视频&am…

什么是虚拟货币?

随着科技的进步&#xff0c;虚拟货币逐渐进入公众视野&#xff0c;其影响深远且复杂。本文将从专业角度分析虚拟货币的发展现状、未来趋势&#xff0c;以及面临的挑战&#xff0c;并尝试提出一些思考。 一、虚拟货币的定义与现状 虚拟货币是一种基于区块链技术的数字资产&…

《Effective Java》如果说我需要一本Java编程的书,那就是它了

《Effective Java》 编写出更优雅的Java代码作者简介斩获Jolt图书大奖本文福利 编写出更优雅的Java代码 《Effective Java》是Java编程领域的一本经典之作&#xff0c;由Joshua Bloch撰写&#xff0c;旨在帮助Java程序员提高编码技巧&#xff0c;写出更加健壮、高效和易于维护的…