背景

基本上每个APP中都会使用到Toast和Dialog,但多数时候我们有自定义样式、功能的需求,例如Toast需要做单例形式、Dialog需要调整样式等。本文中涉及的自定义效果演示如下:

android 对话框设置值 安卓自定义对话框样式_android

思路

Toast

系统中的Toast在使用时无法做到单例的效果,如果用户连续多次点击某个触发操作,则Toast会做出类似堆栈调用,即使退出应用,也会一次次的相继弹出,直到调用次数响应完为止,在自定义的过程中,除了样式意外,着重将Toast做成单例类。

Dialog

利用AlertDialoggetWindow().setContentView方法,通过设置Dialog内的布局和定义各类响应事件,实现多样化的弹窗需求。本次自定义的Dialog主要包括:

  • 加载提示(loading)
  • 普通警示框(文字提示)
  • 询问对话框(确认操作)
  • 输入对话框
  • 选项对话框(单选)

实现

1. Toast调用优化

Android系统的Toast开放了setView功能,可以自定义Toast中的界面和样式,通过使用Java中的静态方法和静态对象,实现类似于单例的控制效果。代码实现如下:

public class NoticeToast {
    private static View view; // notice界面
    private static TextView tvMessage; // 提示文本
    private static Toast toast;

    /**
     * 显示Toast提示
     *
     * @param context
     * @param message 提示内容
     */
    public static void show(Context context, String message) {
        initViews(context);
        tvMessage.setText(message);
        toast.show();
    }

    /**
     * 显示Toast提示
     *
     * @param context
     * @param message 提示内容
     */
    public static void showLong(Context context, String message) {
        initViews(context);
        tvMessage.setText(message);
        toast.setDuration(Toast.LENGTH_LONG);
        toast.show();
    }

    /**
     * 在底部显示Toast提示
     *
     * @param context
     * @param message 提示内容
     */
    public static void showAtBottom(Context context, String message) {
        initViews(context);
        tvMessage.setText(message);
        toast.show();
    }

    /**
     * 显示Toast提示
     *
     * @param context
     * @param resId   string资源ID
     */
    public static void show(Context context, int resId) {
        initViews(context);
        tvMessage.setText(resId);
        toast.show();
    }

    private static void initViews(Context context) {
        if (toast == null) {
            toast = new Toast(context);
        }
        if (view == null) {
            View view = LayoutInflater.from(context).inflate(R.layout.toast_notice, null);
            tvMessage = (TextView) view.findViewById(R.id.txt_toast_message);
            // 设置Toast的位置
            toast.setDuration(Toast.LENGTH_SHORT);
            // 让Toast显示为我们自定义的窗体
            toast.setView(view);
        }
    }
}

2. Dialog-加载提示(loading)

加载提示主要用于一些未知时间的耗时操作上,用以锁定操作界面,给予用户等待提醒等。

首先,自定义加载提示的界面,主要防止一个原型的进度条,和提醒文字。代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg_dialog"
    android:gravity="center"
    android:orientation="horizontal"
    android:padding="18dp">

    <ProgressBar
        android:layout_width="30dip"
        android:layout_height="30dip"
        android:indeterminateTint="@color/purple_700" />

    <TextView
        android:id="@+id/txt_dialog_message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dip"
        android:gravity="center"
        android:textColor="@color/gray_text" />
</LinearLayout>

在功能实现上,替换原来AlertDialog的view即可:

public class LoadingDialog {
    private static View view;
    private static TextView tvMessage;
    private static AlertDialog dialog;

    public static void show(Context context) {
        initViews(context);
        tvMessage.setText(R.string.note_processing);
        dialog.show();
    }

    public static void show(Context context, String message) {
        initViews(context);
        tvMessage.setText(message);
        dialog.show();
    }

    public static void show(Context context, int resId) {
        initViews(context);
        tvMessage.setText(resId);
        dialog.show();
    }

    public static void close() {
        if (dialog != null) {
            dialog.dismiss();
        }
    }

    private static void initViews(Context context) {
        if (view == null) {
            View view = LayoutInflater.from(context).inflate(R.layout.dialog_loading, null);
            tvMessage = (TextView) view.findViewById(R.id.txt_dialog_message);

            AlertDialog.Builder builder = new AlertDialog.Builder(context);
            builder.setView(view);

            dialog = builder.create();
            dialog.setCancelable(false);
        }
    }

}

3. Dialog-普通警示框(文字提示)

普通警示框主要用于提醒,并没有实质的交互反应,与Toast不同的是警示框一般自己不消失,需要用户主动点击OK(确认)等,以保证用户看到有效信息。警示框的页面可以单独定义,也可以复用后续的询问对话框(确认操作),由于功能类似,此处不单独赘述,见下一节。

4. Dialog-询问对话框(确认操作)

询问对话框一般要求用户进行确认操作,多用于数据删除、确认危险动作等。界面自定义为:

<?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"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/white"
    android:orientation="vertical">

    <TextView
        android:id="@+id/txt_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dip"
        android:layout_marginTop="20dip"
        android:gravity="center_vertical"
        android:text="title"
        android:textColor="@color/gray_text"
        android:textSize="20sp"
        android:visibility="visible" />

    <TextView
        android:id="@+id/txt_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dip"
        android:layout_marginTop="20dip"
        android:layout_marginRight="30dip"
        android:layout_marginBottom="20dip"
        android:lineSpacingMultiplier="1.2"
        android:text="content"
        android:textColor="@color/gray_text"
        android:textSize="16sp" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:layout_marginBottom="10dip">

        <Button
            android:id="@+id/btn_cancel"
            style="?attr/borderlessButtonStyle"
            android:layout_width="80dip"
            android:layout_height="40dip"
            android:layout_gravity="center_horizontal"
            android:background="?attr/selectableItemBackground"
            android:text="@string/layout_cancel"
            android:textAllCaps="false"
            android:textColor="@color/gray_text_light"
            android:textSize="14sp" />

        <Button
            android:id="@+id/btn_confirm"
            style="?attr/borderlessButtonStyle"
            android:layout_width="80dip"
            android:layout_height="40dip"
            android:layout_gravity="center_horizontal"
            android:background="?attr/selectableItemBackground"
            android:text="@string/layout_confirm"
            android:textAllCaps="false"
            android:textColor="@color/purple_700"
            android:textSize="14sp" />
    </LinearLayout>
</LinearLayout>

功能代码部分,需要自定义点击确认之后的事件响应动作,对外部暴露onConfirm()接口:

public class ConfirmDialog {
    private OnClickListener listener;

    private Dialog dialog;
    private TextView tvTitle;
    private TextView tvContent;
    private Button btnCancel;
    private Button btnConfirm;

    public ConfirmDialog(Context context, OnClickListener listener) {
        this.listener = listener;
        LayoutInflater inflater = LayoutInflater.from(context);
        View layout = inflater.inflate(R.layout.dialog_confirm, null);

        initView(layout);

        dialog = new AlertDialog.Builder(context).create();
        dialog.setCanceledOnTouchOutside(false);
        dialog.show();
        dialog.getWindow().setContentView(layout);
    }

    private void initView(View layout) {
        tvTitle = (TextView) layout.findViewById(R.id.txt_title);
        tvContent = (TextView) layout.findViewById(R.id.txt_content);

        btnCancel = (Button) layout.findViewById(R.id.btn_cancel);
        btnCancel.setOnClickListener(view -> dialog.dismiss());
        btnConfirm = (Button) layout.findViewById(R.id.btn_confirm);
        btnConfirm.setOnClickListener(view -> {
            if (listener != null) {
                listener.onConfirm();
            }
            dialog.dismiss();
        });
    }

    public void setTitle(String text) {
        tvTitle.setText(text);
        tvTitle.setVisibility(View.VISIBLE);
    }

    public void setTitle(int resid) {
        tvTitle.setText(resid);
        tvTitle.setVisibility(View.VISIBLE);
    }

    public void setContent(String text) {
        tvContent.setText(text);
    }

    public void setContent(int resid) {
        tvContent.setText(resid);
    }

    public void close() {
        dialog.dismiss();
    }

    public void show() {
        dialog.show();
    }

    public interface OnClickListener {
        public void onConfirm();
    }

    public void setConfirmText(int resId) {
        btnConfirm.setText(resId);
    }

    public void setCancelText(int resId) {
        btnCancel.setText(resId);
    }
}

5. Dialog-输入对话框

输入对话框主要是在弹出层中让用户输入少量的文字内容,需要注意的是是否需要将默认弹出的键盘隐藏。界面自定义:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/white"
    android:orientation="vertical">

    <TextView
        android:id="@+id/txt_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dip"
        android:layout_marginTop="20dip"
        android:gravity="center_vertical"
        android:text="title"
        android:textColor="@color/gray_text"
        android:textSize="20sp"
        android:visibility="visible" />

    <EditText
        android:id="@+id/et_input"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dip"
        android:layout_marginTop="20dip"
        android:layout_marginRight="30dip"
        android:layout_marginBottom="20dip"
        android:inputType="text"
        android:lines="1"
        android:text="hint text"
        android:textColor="@color/gray_text"
        android:textSize="16sp" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:layout_marginBottom="10dip">

        <Button
            android:id="@+id/btn_cancel"
            style="?attr/borderlessButtonStyle"
            android:layout_width="80dip"
            android:layout_height="40dip"
            android:layout_gravity="center_horizontal"
            android:background="?attr/selectableItemBackground"
            android:text="@string/layout_cancel"
            android:textAllCaps="false"
            android:textColor="@color/gray_text_light"
            android:textSize="14sp" />

        <Button
            android:id="@+id/btn_confirm"
            style="?attr/borderlessButtonStyle"
            android:layout_width="80dip"
            android:layout_height="40dip"
            android:layout_gravity="center_horizontal"
            android:background="?attr/selectableItemBackground"
            android:text="@string/layout_confirm"
            android:textAllCaps="false"
            android:textColor="@color/purple_700"
            android:textSize="14sp" />
    </LinearLayout>
</LinearLayout>

在处理确定按钮回调时,应将输入内容作为回调参数传回调用者。功能代码(输入键盘默认为隐藏状态):

public class InputDialog {
    private Context context;
    private OnClickListener listener;

    private Dialog dialog;
    private TextView tvTitle;
    private EditText etInput;
    private Button btnCancel;
    private Button btnConfirm;

    public InputDialog(Context context, OnClickListener listener) {
        this.context = context;
        this.listener = listener;
        LayoutInflater inflater = LayoutInflater.from(context);
        View layout = inflater.inflate(R.layout.dialog_input, null);

        initView(layout);

        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setView(layout);
        builder.setCancelable(false);
        dialog = builder.create();
    }

    private void initView(View layout) {
        tvTitle = (TextView) layout.findViewById(R.id.txt_title);
        etInput = (EditText) layout.findViewById(R.id.et_input);

        btnCancel = (Button) layout.findViewById(R.id.btn_cancel);
        btnCancel.setOnClickListener(view -> dialog.dismiss());
        btnConfirm = (Button) layout.findViewById(R.id.btn_confirm);
        btnConfirm.setOnClickListener(view -> {
            if (listener != null) {
                hideKeyBoard();
                listener.onConfirm(etInput.getText().toString());
            }
        });
    }

    public void close() {
        dialog.dismiss();
    }

    public void setTitle(String text) {
        tvTitle.setText(text);
        tvTitle.setVisibility(View.VISIBLE);
    }

    public void setTitle(int resid) {
        tvTitle.setText(resid);
        tvTitle.setVisibility(View.VISIBLE);
    }

    public void show() {
        etInput.setText("");
        dialog.show();
    }

    private void hideKeyBoard() {
        InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(etInput.getWindowToken(), 0);
    }

    public interface OnClickListener {
        void onConfirm(String text);
    }
}

6. Dialog-选项对话框(单选)

选项对话框基本与输入对话框的定义方式一致,区别主要在于确定按钮点击之后的回调处理略有不同,不多做赘述。

上述未尽内容,详见源码。

源码

https://github.com/ahuyangdong/DialogCustom