诶?小赫赫为什么很少在常用的 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 仓库中。

参考了以下: