├── InputDialog.java ├── README.md ├── dialog_bottom_line.9.png └── dialog_input_layout.xml /InputDialog.java: -------------------------------------------------------------------------------- 1 | import android.app.Dialog; 2 | import android.content.Context; 3 | import android.content.DialogInterface; 4 | import android.graphics.drawable.Drawable; 5 | import android.os.Handler; 6 | import android.os.Message; 7 | import android.support.v7.app.AlertDialog; 8 | import android.text.Editable; 9 | import android.text.InputFilter; 10 | import android.text.TextUtils; 11 | import android.view.View; 12 | import android.view.Window; 13 | import android.view.inputmethod.InputMethodManager; 14 | import android.widget.Button; 15 | import android.widget.EditText; 16 | 17 | import java.lang.ref.WeakReference; 18 | import java.lang.reflect.Field; 19 | 20 | /** 21 | * A proxy class of AlertDialog that provides an input box. 22 | * Some specified interfaces and listeners are designed for you 23 | * to simplify you work of controlling input settings or validation 24 | * responses. 25 | * Created by Cookizz 26 | */ 27 | public class InputDialog { 28 | 29 | private AlertDialog dialog; 30 | private EditText editText; 31 | 32 | private InputDialog(AlertDialog dialog, EditText editText) { 33 | this.dialog = dialog; 34 | this.editText = editText; 35 | } 36 | 37 | public void show() { 38 | dialog.show(); 39 | } 40 | 41 | /** 42 | * Dismiss this dialog, removing it from the screen. This method can be 43 | * invoked safely from any thread. 44 | */ 45 | public void dismiss() { 46 | dialog.dismiss(); 47 | } 48 | 49 | /** 50 | * Hide the dialog, but not dismiss it. 51 | */ 52 | public void hide() { 53 | dialog.hide(); 54 | } 55 | 56 | /** 57 | * Cancel the dialog. This is essentially the same as calling {@link #dismiss()}, but it will 58 | * also call your {@link DialogInterface.OnCancelListener} (if registered). 59 | */ 60 | public void cancel() { 61 | dialog.cancel(); 62 | } 63 | 64 | /** 65 | * Retrieve the current Window for the activity. This can be used to 66 | * directly access parts of the Window API that are not available 67 | * through Activity/Screen. 68 | * 69 | * @return Window The current window, or null if the activity is not 70 | * visual. 71 | */ 72 | public Window getWindow() { 73 | return dialog.getWindow(); 74 | } 75 | 76 | /** 77 | * Retrieve the Context this Dialog is running in. 78 | * 79 | * @return Context The Context used by the Dialog. 80 | */ 81 | public Context getContext() { 82 | return dialog.getContext(); 83 | } 84 | 85 | /** 86 | * Sets a listener to be invoked when the dialog is shown. 87 | * @param listener The {@link DialogInterface.OnShowListener} to use. 88 | */ 89 | public void setOnShowListener(DialogInterface.OnShowListener listener) { 90 | dialog.setOnShowListener(listener); 91 | } 92 | 93 | /** 94 | * Set resId to 0 if you don't want an icon. 95 | * 96 | * @param resId the resourceId of the drawable to use as the icon or 0 97 | * if you don't want an icon. 98 | */ 99 | public void setIcon(int resId) { 100 | dialog.setIcon(resId); 101 | } 102 | 103 | public void setIcon(Drawable res) { 104 | dialog.setIcon(res); 105 | } 106 | 107 | /** 108 | * Set an icon as supplied by a theme attribute. e.g. android.R.attr.alertDialogIcon 109 | * 110 | * @param attrId ID of a theme attribute that points to a drawable resource. 111 | */ 112 | public void setIconAttributes(int attrId) { 113 | dialog.setIconAttribute(attrId); 114 | } 115 | 116 | /** 117 | * Set the title text for this dialog's window. 118 | * 119 | * @param title The new text to display in the title. 120 | */ 121 | public void setTitle(CharSequence title) { 122 | dialog.setTitle(title); 123 | } 124 | 125 | /** 126 | * Set the title text for this dialog's window. The text is retrieved 127 | * from the resources with the supplied identifier. 128 | * 129 | * @param titleId the title's text resource identifier 130 | */ 131 | public void setTitle(int titleId) { 132 | dialog.setTitle(titleId); 133 | } 134 | 135 | /** 136 | * @see Builder#setCustomTitle(View) 137 | */ 138 | public void setCustomTitle(View customTitleView) { 139 | dialog.setCustomTitle(customTitleView); 140 | } 141 | 142 | /** 143 | * Gets one of the buttons used in the dialog. Returns null if the specified 144 | * button does not exist or the dialog has not yet been fully created (for 145 | * example, via {@link #show()} 146 | * 147 | * @param whichButton The identifier of the button that should be returned. 148 | * For example, this can be 149 | * {@link DialogInterface#BUTTON_POSITIVE}. 150 | * @return The button from the dialog, or null if a button does not exist. 151 | */ 152 | public Button getButton(int whichButton) { 153 | return dialog.getButton(whichButton); 154 | } 155 | 156 | /** 157 | * @return Whether the dialog is currently showing. 158 | */ 159 | public boolean isShowing() { 160 | return dialog.isShowing(); 161 | } 162 | 163 | /** 164 | * Finds a child view with the given identifier. Returns null if the 165 | * specified child view does not exist or the dialog has not yet been fully 166 | * created (for example, via {@link #show()} 167 | * 168 | * @param id the identifier of the view to find 169 | * @return The view with the given id or null. 170 | */ 171 | public View findViewById(int id) { 172 | return dialog.findViewById(id); 173 | } 174 | 175 | /** 176 | * Retrieve the current text content in the input box of dialog. 177 | * @return The text content. 178 | */ 179 | public CharSequence getInputText() { 180 | Editable editable = editText.getText(); 181 | return editable.toString(); 182 | } 183 | 184 | public static class Builder { 185 | 186 | private InputDialog inputDialog; // the proxy dialog 187 | private AlertDialog.Builder builder; 188 | private AlertDialog alertDialog; 189 | 190 | private ButtonActionListener positiveButtonActionListener; 191 | private ButtonActionListener negativeButtonActionListener; 192 | private ButtonActionListener neutralButtonActionListener; 193 | private OnCancelListener onCancelListener; 194 | private OnDismissListener onDismissListener; 195 | private ButtonActionIntercepter buttonActionIntercepter; 196 | 197 | private int customizedLayoutResId; 198 | 199 | private int editTextId; 200 | private CharSequence inputDefaultText; 201 | 202 | private CharSequence inputHint; 203 | private int inputMaxWords = -1; 204 | private boolean interceptAutoPopupInputMethod; 205 | 206 | private ButtonHandler mHandler; 207 | 208 | public Builder(Context context) { 209 | builder = new AlertDialog.Builder(context); 210 | } 211 | 212 | public Builder(Context context, int style) { 213 | builder = new AlertDialog.Builder(context, style); 214 | } 215 | 216 | public Builder setTitle(CharSequence title) { 217 | builder.setTitle(title); 218 | return this; 219 | } 220 | 221 | public Builder setInputDefaultText(CharSequence text) { 222 | inputDefaultText = text; 223 | return this; 224 | } 225 | 226 | public Builder setInputHint(CharSequence text) { 227 | inputHint = text; 228 | return this; 229 | } 230 | 231 | /** 232 | * Set max words limitation of the input box. 233 | */ 234 | public Builder setInputMaxWords(int maxWords) { 235 | this.inputMaxWords = maxWords; 236 | return this; 237 | } 238 | 239 | /** 240 | * Set a listener to be invoked when the positive button of the dialog is pressed. 241 | * 242 | * @param textId The resource id of the text to display in the positive button 243 | * @param listener The {@link InputDialog.ButtonActionListener} to use. 244 | * @return This Builder object to allow for chaining of calls to set methods 245 | */ 246 | public Builder setPositiveButton(final int textId, final ButtonActionListener listener) { 247 | positiveButtonActionListener = listener; 248 | builder.setPositiveButton(textId, new DialogInterface.OnClickListener() { 249 | @Override 250 | public void onClick(DialogInterface dialog, int which) { 251 | boolean intercept = onInterceptButtonAction(which, inputDialog.getInputText()); 252 | if (!intercept) { 253 | if (positiveButtonActionListener != null) { 254 | positiveButtonActionListener.onClick(inputDialog.getInputText()); 255 | } 256 | } 257 | } 258 | }); 259 | return this; 260 | } 261 | 262 | /** 263 | * Set a listener to be invoked when the positive button of the dialog is pressed. 264 | * 265 | * @param text The text to display in the positive button 266 | * @param listener The {@link InputDialog.ButtonActionListener} to use. 267 | * @return This Builder object to allow for chaining of calls to set methods 268 | */ 269 | public Builder setPositiveButton(final CharSequence text, final ButtonActionListener listener) { 270 | positiveButtonActionListener = listener; 271 | builder.setPositiveButton(text, new DialogInterface.OnClickListener() { 272 | @Override 273 | public void onClick(DialogInterface dialog, int which) { 274 | boolean intercept = onInterceptButtonAction(which, inputDialog.getInputText()); 275 | if (!intercept) { 276 | if (positiveButtonActionListener != null) { 277 | positiveButtonActionListener.onClick(inputDialog.getInputText()); 278 | } 279 | } 280 | } 281 | }); 282 | return this; 283 | } 284 | 285 | /** 286 | * Set a listener to be invoked when the negative button of the dialog is pressed. 287 | * 288 | * @param textId The resource id of the text to display in the negative button 289 | * @param listener The {@link InputDialog.ButtonActionListener} to use. 290 | * @return This Builder object to allow for chaining of calls to set methods 291 | */ 292 | public Builder setNegativeButton(final int textId, final ButtonActionListener listener) { 293 | negativeButtonActionListener = listener; 294 | builder.setNegativeButton(textId, new DialogInterface.OnClickListener() { 295 | @Override 296 | public void onClick(DialogInterface dialog, int which) { 297 | boolean intercept = onInterceptButtonAction(which, inputDialog.getInputText()); 298 | if (!intercept) { 299 | if (negativeButtonActionListener != null) { 300 | negativeButtonActionListener.onClick(inputDialog.getInputText()); 301 | } 302 | } 303 | } 304 | }); 305 | return this; 306 | } 307 | 308 | /** 309 | * Set a listener to be invoked when the negative button of the dialog is pressed. 310 | * 311 | * @param text The text to display in the negative button 312 | * @param listener The {@link InputDialog.ButtonActionListener} to use. 313 | * @return This Builder object to allow for chaining of calls to set methods 314 | */ 315 | public Builder setNegativeButton(final CharSequence text, final ButtonActionListener listener) { 316 | negativeButtonActionListener = listener; 317 | builder.setNegativeButton(text, new DialogInterface.OnClickListener() { 318 | @Override 319 | public void onClick(DialogInterface dialog, int which) { 320 | boolean intercept = onInterceptButtonAction(which, inputDialog.getInputText()); 321 | if (!intercept) { 322 | if (negativeButtonActionListener != null) { 323 | negativeButtonActionListener.onClick(inputDialog.getInputText()); 324 | } 325 | } 326 | } 327 | }); 328 | return this; 329 | } 330 | 331 | /** 332 | * Set a listener to be invoked when the neutral button of the dialog is pressed. 333 | * 334 | * @param textId The resource id of the text to display in the neutral button 335 | * @param listener The {@link DialogInterface.OnClickListener} to use. 336 | * @return This Builder object to allow for chaining of calls to set methods 337 | */ 338 | public Builder setNeutralButton(final int textId, final ButtonActionListener listener) { 339 | neutralButtonActionListener = listener; 340 | builder.setNeutralButton(textId, new DialogInterface.OnClickListener() { 341 | @Override 342 | public void onClick(DialogInterface dialog, int which) { 343 | boolean intercept = onInterceptButtonAction(which, inputDialog.getInputText()); 344 | if (!intercept) { 345 | if (neutralButtonActionListener != null) { 346 | neutralButtonActionListener.onClick(inputDialog.getInputText()); 347 | } 348 | } 349 | } 350 | }); 351 | return this; 352 | } 353 | 354 | /** 355 | * Set a listener to be invoked when the neutral button of the dialog is pressed. 356 | * 357 | * @param text The text to display in the neutral button 358 | * @param listener The {@link DialogInterface.OnClickListener} to use. 359 | * @return This Builder object to allow for chaining of calls to set methods 360 | */ 361 | public Builder setNeutralButton(final CharSequence text, final ButtonActionListener listener) { 362 | neutralButtonActionListener = listener; 363 | builder.setNeutralButton(text, new DialogInterface.OnClickListener() { 364 | @Override 365 | public void onClick(DialogInterface dialog, int which) { 366 | boolean intercept = onInterceptButtonAction(which, inputDialog.getInputText()); 367 | if (!intercept) { 368 | if (neutralButtonActionListener != null) { 369 | neutralButtonActionListener.onClick(inputDialog.getInputText()); 370 | } 371 | } 372 | } 373 | }); 374 | return this; 375 | } 376 | 377 | /** 378 | * Sets whether the dialog is cancelable or not. Default is true. 379 | * 380 | * @return This Builder object to allow for chaining of calls to set methods 381 | */ 382 | public Builder setCancelable(boolean cancelable) { 383 | alertDialog.setCancelable(cancelable); 384 | return this; 385 | } 386 | 387 | /** 388 | * Sets the callback that will be called if the dialog is canceled. 389 | * 390 | *
Even in a cancelable dialog, the dialog may be dismissed for reasons other than 391 | * being canceled or one of the supplied choices being selected. 392 | * If you are interested in listening for all cases where the dialog is dismissed 393 | * and not just when it is canceled, see 394 | * {@link #setOnDismissListener(InputDialog.OnDismissListener) 395 | * setOnDismissListener}.
396 | * 397 | * @return This Builder object to allow for chaining of calls to set methods 398 | * @see #setCancelable(boolean) 399 | * @see #setOnDismissListener(InputDialog.OnDismissListener) 400 | */ 401 | public Builder setOnCancelListener(OnCancelListener listener) { 402 | onCancelListener = listener; 403 | builder.setOnCancelListener(new DialogInterface.OnCancelListener() { 404 | @Override 405 | public void onCancel(DialogInterface d) { 406 | if (onCancelListener != null) { 407 | onCancelListener.onCancel(inputDialog.getInputText()); 408 | } 409 | } 410 | }); 411 | return this; 412 | } 413 | 414 | /** 415 | * Sets the callback that will be called when the dialog is dismissed for any reason. 416 | * 417 | * @return This Builder object to allow for chaining of calls to set methods 418 | */ 419 | public Builder setOnDismissListener(OnDismissListener listener) { 420 | onDismissListener = listener; 421 | builder.setOnDismissListener(new DialogInterface.OnDismissListener() { 422 | @Override 423 | public void onDismiss(DialogInterface d) { 424 | hideSoftInput(alertDialog); 425 | if (onDismissListener != null) { 426 | onDismissListener.onDismiss(inputDialog.getInputText()); 427 | } 428 | } 429 | }); 430 | return this; 431 | } 432 | 433 | /** 434 | * Used to intercept the inherent behaviors of AlertDialog 435 | * after a button is clicked. 436 | * The behaviors include dismiss-after-button-clicked and 437 | * the #onClick(CharSequence) callback. 438 | * 439 | * @param intercepter The {@link InputDialog.ButtonActionIntercepter} to use. 440 | * @return This Builder object to allow for chaining of calls to set methods 441 | */ 442 | public Builder interceptButtonAction(ButtonActionIntercepter intercepter) { 443 | buttonActionIntercepter = intercepter; 444 | return this; 445 | } 446 | 447 | /** 448 | * Used to disable the automatically-popup-soft-input-keyboard behavior 449 | * when the dialog shows. 450 | * @return This Builder object to allow for chaining of calls to set methods 451 | */ 452 | public Builder disableAutoPopupSoftInput() { 453 | interceptAutoPopupInputMethod = true; 454 | return this; 455 | } 456 | 457 | /** 458 | * Set a custom view resource to be the contents of the Dialog. The 459 | * resource will be inflated, adding all top-level views to the screen. 460 | * 461 | * @param layoutResId Resource ID to be inflated. 462 | * @return This Builder object to allow for chaining of calls to set 463 | * methods 464 | */ 465 | public Builder setView(int layoutResId, int editTextId) { 466 | customizedLayoutResId = layoutResId; 467 | this.editTextId = editTextId; 468 | return this; 469 | } 470 | 471 | /** 472 | * Creates a {@link InputDialog} with the arguments supplied to this builder and 473 | * {@link Dialog#show()}'s the dialog. 474 | */ 475 | public InputDialog show() { 476 | initLayout(); 477 | alertDialog = builder.show(); 478 | initEditText(alertDialog); 479 | popupSoftInput(alertDialog); 480 | replaceHandler(alertDialog); 481 | return createInputDialog(alertDialog); 482 | } 483 | 484 | /** 485 | * Creates a {@link InputDialog} with the arguments supplied to this builder. It does not 486 | * {@link Dialog#show()} the dialog. This allows the user to do any extra processing 487 | * before displaying the dialog. Use {@link #show()} if you don't have any other processing 488 | * to do and want this to be created and displayed. 489 | */ 490 | public InputDialog create() { 491 | initLayout(); 492 | alertDialog = builder.create(); 493 | initEditText(alertDialog); 494 | popupSoftInput(alertDialog); 495 | replaceHandler(alertDialog); 496 | return createInputDialog(alertDialog); 497 | } 498 | 499 | private boolean onInterceptButtonAction(int buttonType, CharSequence text) { 500 | if (buttonActionIntercepter != null) { 501 | boolean intercept = buttonActionIntercepter.onInterceptButtonAction(buttonType, text); 502 | if (intercept) { 503 | mHandler.interceptButtonAction(); 504 | return true; 505 | } 506 | } 507 | return false; 508 | } 509 | 510 | private void initLayout() { 511 | if (customizedLayoutResId == 0) { 512 | builder.setView(R.layout.dialog_input_layout); 513 | } else { 514 | builder.setView(customizedLayoutResId); 515 | } 516 | } 517 | 518 | private void initEditText(AlertDialog dialog) { 519 | EditText input = obtainEditText(dialog); 520 | if (inputMaxWords >= 0) { 521 | input.setFilters(new InputFilter[] {new InputFilter.LengthFilter(inputMaxWords)}); 522 | } 523 | if (!TextUtils.isEmpty(inputDefaultText)) { 524 | input.setText(inputDefaultText); 525 | // Move text cursor to the end if there are default texts. 526 | int len = inputMaxWords >= 0 ? Math.min(inputMaxWords, inputDefaultText.length()) : inputDefaultText.length(); 527 | input.setSelection(len); 528 | } 529 | if (!TextUtils.isEmpty(inputHint)) { 530 | input.setHint(inputHint); 531 | } 532 | } 533 | 534 | private void popupSoftInput(final AlertDialog dialog) { 535 | if (interceptAutoPopupInputMethod) { 536 | return; 537 | } 538 | Handler handler = new Handler(); 539 | handler.postDelayed(new Runnable() { 540 | @Override 541 | public void run() { 542 | EditText input = obtainEditText(dialog); 543 | InputMethodManager imm = (InputMethodManager) dialog.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 544 | imm.showSoftInput(input, 0); 545 | } 546 | }, 100); 547 | } 548 | 549 | private void hideSoftInput(final AlertDialog dialog) { 550 | if (interceptAutoPopupInputMethod) { 551 | return; 552 | } 553 | InputMethodManager im = (InputMethodManager) dialog.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 554 | EditText input = obtainEditText(dialog); 555 | im.hideSoftInputFromWindow(input.getWindowToken(), 0); 556 | } 557 | 558 | /** 559 | * Replace {@link AlertDialog}'s inner handler by {@link ButtonHandler} via java reflection. 560 | * Used to intercept the inherent behavior when a button is clicked. 561 | */ 562 | private void replaceHandler(AlertDialog dialog) { 563 | mHandler = new ButtonHandler(dialog); 564 | try { 565 | Field fController = dialog.getClass().getDeclaredField("mAlert"); 566 | fController.setAccessible(true); 567 | Object controller = fController.get(dialog); 568 | Field fHandler = controller.getClass().getDeclaredField("mHandler"); 569 | fHandler.setAccessible(true); 570 | fHandler.set(controller, mHandler); 571 | } catch (Exception e) { 572 | e.printStackTrace(); 573 | } 574 | } 575 | 576 | private InputDialog createInputDialog(AlertDialog dialog) { 577 | inputDialog = new InputDialog(dialog, obtainEditText(dialog)); 578 | return inputDialog; 579 | } 580 | 581 | private EditText obtainEditText(AlertDialog dialog) { 582 | if (editTextId == 0) { 583 | return (EditText) dialog.findViewById(R.id.input); 584 | } else { 585 | return (EditText) dialog.findViewById(editTextId); 586 | } 587 | } 588 | 589 | /** 590 | * ButtonHandler substitute for intercepting 591 | */ 592 | private static final class ButtonHandler extends Handler { 593 | // Button clicks have Message.what as the BUTTON{1,2,3} constant 594 | private static final int MSG_DISMISS_DIALOG = 1; 595 | 596 | private WeakReference