背景
基本上每个APP中都会使用到Toast和Dialog,但多数时候我们有自定义样式、功能的需求,例如Toast需要做单例形式、Dialog需要调整样式等。本文中涉及的自定义效果演示如下:
思路
Toast
系统中的Toast在使用时无法做到单例的效果,如果用户连续多次点击某个触发操作,则Toast会做出类似堆栈调用,即使退出应用,也会一次次的相继弹出,直到调用次数响应完为止,在自定义的过程中,除了样式意外,着重将Toast做成单例类。
Dialog
利用AlertDialog
的getWindow().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