诶?小赫赫为什么很少在常用的 app 中看到有使用系统自带的对话框呢?而且,自定义的对话框又好神奇。自从看了天哥的自定义的 Dialog 对话框之后……
前情概要
之前在学习的时候,学到了使用 Android 内置的 AlertDialog 和 ProgressDialog 对话框。然后我见到某一些 app 中都没有见到使用系统自带的 Dialog ,都是去进行自定义的。bilibili 有自己的对话框,微信和 QQ 也有自定义的对话框。
后面看了天哥的视频发现,如果需要来自定义对话框。需要继承自 Dialog 这个类(我想,继承 AlertDialog 也可以吧,重写它的一些方法应该就行了吧),然后再自己写一个对话框的布局就可以了。
不过,这次想整一个 iPhone 的对话框。话不多说,现在开干。
开始整活
首先,我们要……
当然是新建一个Activity啦。
继承类、重写方法
戳啦,先要建立一个类。类名随便起一个,然后直接继承自 Dialog。直接继承完成之后,Android Studio 肯定会有画红色的下划线,需要你来重写构造方法。重写构造方法也简单,直接给个提示,重写一个就好了,不过我这里重写了两个。
public class CustomDialog extends Dialog{
// 传入当前的 Activity
public CustomDialog(@NonNull Context context) {
super(context);
}
// 传入一个 Activity 还有主题
public CustomDialog(@NonNull Context context, int themeResId) {
super(context, themeResId);
}
}
诶?重写完之后,我自定义的对话框肯定是需要有 xml 布局文件才能够真正的实现布局吧。重写 Dialog 类中的 onCreate
方法就好咯。
在里面调用一个 setContentView
方法,这个方法特别熟悉,就是在我们创建一个 Activity 之后,默认重写的 onCreate
方法中有调用这个方法,目的就是加载布局文件咯。
public class CustomDialog extends Dialog{
public CustomDialog(@NonNull Context context) {
super(context);
}
public CustomDialog(@NonNull Context context, int themeResId) {
super(context, themeResId);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.custom_dialog_layout);
}
}
既然这样,重写完需要之后,就可以自己来布局好 iPhone 的提示框了。
自定义布局
布局出来的 iPhone 界面需要花一些时间,对话框的制作还是简单的,不会特别难。不过细节还是需要注意一下的
害,其实我在这里跳了好多坑,比如在设置 corners 属性时,要设置完 radius 再设置每一个角的 radius ,这样就不会有报错的现象。之所以会报错,是因为我是仅设置了单个角的 radius。也就是说属性 radius 是必须存在的
吃一堑,长一智。
下亿次还会犯错错误的写法:
<corners android:topLeftRadius="0dp" android:topRightRadius="0dp" android:bottomLeftRadius="20dp" android:bottomRightRadius="0dp"/>
正确的写法:
<corners android:radius="0dp" android:topLeftRadius="0dp" android:topRightRadius="0dp" android:bottomLeftRadius="20dp" android:bottomRightRadius="0dp"/>
对话框布局
对话框布局的代码:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@drawable/bg_custom_dialog">
<!-- 文本框的标题部分 -->
<TextView
android:id="@+id/dialog_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TextView"
android:textSize="20sp"
android:textColor="@color/colorBlack"
android:gravity="center"
android:layout_marginTop="10dp"
android:textStyle="bold"/>
<!-- 文本框的内容部分 -->
<TextView
android:id="@+id/dialog_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12sp"
android:text="TextView"
android:gravity="center"
android:textColor="@color/colorBlack"
android:textSize="15sp"
android:layout_marginBottom="20dp"/>
<!-- 用它来画横线,我是不是很机智,嘻嘻 -->
<View
android:layout_width="wrap_content"
android:layout_height="0.5dp"
android:background="#ccc"/>
<!-- 水平线性布局 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="45dp">
<!-- 退出按钮 -->
<TextView
android:id="@+id/dialog_cancel"
android:layout_width="0dp"
android:layout_height="match_parent"
android:text="cancel"
android:textSize="16sp"
android:gravity="center"
android:layout_weight="1"
android:background="@drawable/bg_dialog_left_btn"
android:textColor="#4E90F3"/>
<!-- 用它来画竖线,我是不是很机智,嘻嘻 -->
<View
android:layout_width="0.5dp"
android:layout_height="match_parent"
android:background="#ccc"/>
<!-- 设置确认按钮的样式 -->
<TextView
android:id="@+id/dialog_ok"
android:layout_width="0dp"
android:layout_height="match_parent"
android:text="ok"
android:textSize="16sp"
android:gravity="center"
android:layout_weight="1"
android:textColor="#4E90F3"
android:background="@drawable/bg_dialog_right_btn" />
</LinearLayout>
</LinearLayout>
对话框样式
对话框的背景样式如下:
设置了对话框的边框颜色,四个角,和背景颜色
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke
android:color="@color/colorGray" />
<corners
android:radius="20dp"/>
<solid
android:color="#ccffffff"/>
</shape>
按钮的样式
由于两个按钮的样式,只有一边出现圆角。退出按钮的左下方出现圆角,确认按钮的右下方出现圆角。所以需要编写两种不同的样式分别对两个按钮使用
左下角按钮样式
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 使用selector来改变其元素的状态 -->
<!-- 按下去的状态 -->
<item android:state_pressed="true">
<shape>
<solid android:color="#cccccccc"/>
<!-- 圆角 -->
<corners
android:radius="0dp"
android:topLeftRadius="0dp"
android:topRightRadius="0dp"
android:bottomLeftRadius="20dp"
android:bottomRightRadius="0dp"/>
</shape>
</item>
<!-- 未按下的状态 -->
<item android:state_pressed="false">
<shape>
<!-- 圆角 -->
<corners
android:radius="0dp"
android:topLeftRadius="0dp"
android:topRightRadius="0dp"
android:bottomLeftRadius="0dp"
android:bottomRightRadius="20dp"/>
</shape>
</item>
</selector>
右下角按钮样式
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 使用selector来改变其元素的状态 -->
<!-- 按下去的状态 -->
<item android:state_pressed="true">
<shape>
<solid android:color="#cccccccc"/>
<!-- 圆角 -->
<corners
android:radius="0dp"
android:topLeftRadius="0dp"
android:topRightRadius="0dp"
android:bottomLeftRadius="0dp"
android:bottomRightRadius="20dp"/>
</shape>
</item>
<!-- 未按下的状态 -->
<item android:state_pressed="false">
<shape>
<!-- 圆角 -->
<corners
android:radius="0dp"
android:topLeftRadius="0dp"
android:topRightRadius="0dp"
android:bottomLeftRadius="0dp"
android:bottomRightRadius="20dp"/>
</shape>
</item>
</selector>
更改对话框的默认样式
最后,就是更改对话框的默认样式了,也就是 styles.xml 的配置了,虽然这样子的配置宾不是特别懂。但是总有能解决的办法的。
我记得在使用 RGB 来表示透明度的时候,是用 rgba(0, 0, 0, 0.2),也就是最后一项是透明的。但是在这种 16 进制下面。比如 #00000000 这种的,那么它的前两位 00 表示的是透明度。和上面的小数点一样,数值越小,就越透明,否则就越不透明。
这个也是后面使用调色板才发现的。
对话框样式如下
默认的一个对话框是白色,为了让其消失,只好将背景进行完全的透明即可
<style name="CustomDialog" parent="@android:style/Theme.Dialog">
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowBackground">#00000000</item>
</style>
见证奇迹的时刻
这些样式写完了,那我们就放出效果图
这么会这么难看😨😨😨,这不就是那个浏览器的那个恶意广告😂😂
不好意思,放错图了,这个才是真的。已经比较接近 iPhone 的那个对话框
不过还没完,接下来还要获取按钮的点击事件,以及上面的文字都能设置的那种。这个对话框,才算是真正可以用的对话框
文字设置和点击事件
要设置其中的文字,以及获取控件,需要在里面声明一些变量。
public class CustomDialog extends Dialog{
// 获取文本控件
private TextView mTitle, mMessage, mCancel, mOk;
// 字体设置
private String title, message, cancel, ok;
......
}
声明两个按钮的接口,方便外边实现这个接口,然后对接口进行回调。所谓的接口回调,其实就是运用了面向对象的多态性而已啦。
public class CustomDialog extends Dialog{
// 退出事件接口
private OnCancelListener cancelListener;
// 确定事件接口
private OnOkListener okListener;
......
public interface OnCancelListener {
void onClick(CustomDialog dialog);
}
public interface OnOkListener {
void onClick(CustomDialog dialog);
}
}
然后呢,就是设置一些文字和监听事件
public class CustomDialog extends Dialog{
......
/**
* 设置弹窗标题
*
* @param title 标题
*/
public void setTitle(String title) {
this.title = title;
}
/**
* 设置弹窗消息
*
* @param message 消息
*/
public void setMessage(String message) {
this.message = message;
}
/**
* 设置退出按钮以及监听事件
*
* @param cancel 退出按钮文字
* @param cancelListener 按钮点击事件
*/
public void setCancel(String cancel, OnCancelListener cancelListener) {
this.cancel = cancel;
this.cancelListener = cancelListener;
}
/**
* 设置 OK 按钮以及监听事件
*
* @param ok 确定按钮文字
* @param okListener 确定按钮事件
*/
public void setOk(String ok, OnOkListener okListener) {
this.ok = ok;
this.okListener = okListener;
}
}
最后就是找到控件,然后设置点击事件和相关的文字就行了
public class CustomDialog extends Dialog{
......
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 自适应宽高
OtherUtils.adjustDialog(Objects.requireNonNull(getWindow()));
// 载入自定义的对话框样式
this.setContentView(R.layout.custom_dialog_layout);
// 获取
mTitle = findViewById(R.id.dialog_title);
mMessage = findViewById(R.id.dialog_content);
mCancel = findViewById(R.id.dialog_cancel);
mOk = findViewById(R.id.dialog_ok);
// 设置相关文字,判断设置的字符串是否为空
if(!TextUtils.isEmpty(title)){
mTitle.setText(title);
}
if(!TextUtils.isEmpty(message)){
mMessage.setText(message);
}
if(!TextUtils.isEmpty(cancel)){
mCancel.setText(cancel);
}
if(!TextUtils.isEmpty(ok)){
mOk.setText(ok);
}
// 设置退出监听事件
mCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(cancelListener != null){
cancelListener.onClick(CustomDialog.this);
}
dismiss();
}
});
// 设置确定监听事件
mOk.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(okListener != null){
okListener.onClick(CustomDialog.this);
}
dismiss();
}
});
}
}
最后的效果如下
可以点击出来的效果
如何调用
调用的方式非常简单,就像是创建 AlertDialog.Builder 一样,调用方法去设置就好了。以下是调用示例
// 这里把 styles.xml 中自定义的样式调入进来
CustomDialog customDialog = new CustomDialog(AlertDialogActivity.this, R.style.CustomDialog);
customDialog.setTitle("警告");
customDialog.setMessage("你的手机电量过低,请充电");
customDialog.setCancel("取消", new CustomDialog.OnCancelListener() {
@Override
public void onClick(CustomDialog dialog) {
Toast.makeText(AlertDialogActivity.this, "点击了取消按钮", Toast.LENGTH_SHORT).show();
}
});
customDialog.setOk("确定", new CustomDialog.OnOkListener() {
@Override
public void onClick(CustomDialog dialog) {
Toast.makeText(AlertDialogActivity.this, "点击了确定按钮", Toast.LENGTH_SHORT).show();
}
});
customDialog.show();
后记
项目的代码都在 AndroidStudy 的 github 仓库中。
参考了以下:
请勿发布违反中国大陆地区法律的言论,请勿人身攻击、谩骂、侮辱和煽动式的语言。