MaterialDialog.java 65 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665
  1. package com.afollestad.materialdialogs;
  2. import android.annotation.SuppressLint;
  3. import android.content.Context;
  4. import android.content.DialogInterface;
  5. import android.graphics.Paint;
  6. import android.graphics.Typeface;
  7. import android.graphics.drawable.Drawable;
  8. import android.os.Build;
  9. import android.os.Handler;
  10. import android.support.annotation.ArrayRes;
  11. import android.support.annotation.AttrRes;
  12. import android.support.annotation.ColorInt;
  13. import android.support.annotation.ColorRes;
  14. import android.support.annotation.DimenRes;
  15. import android.support.annotation.DrawableRes;
  16. import android.support.annotation.LayoutRes;
  17. import android.support.annotation.NonNull;
  18. import android.support.annotation.Nullable;
  19. import android.support.annotation.StringRes;
  20. import android.support.annotation.UiThread;
  21. import android.support.v4.content.res.ResourcesCompat;
  22. import android.text.Editable;
  23. import android.text.TextUtils;
  24. import android.text.TextWatcher;
  25. import android.view.LayoutInflater;
  26. import android.view.View;
  27. import android.view.ViewTreeObserver;
  28. import android.view.WindowManager;
  29. import android.widget.AdapterView;
  30. import android.widget.CheckBox;
  31. import android.widget.EditText;
  32. import android.widget.FrameLayout;
  33. import android.widget.ImageView;
  34. import android.widget.ListAdapter;
  35. import android.widget.ListView;
  36. import android.widget.ProgressBar;
  37. import android.widget.RadioButton;
  38. import android.widget.TextView;
  39. import com.afollestad.materialdialogs.internal.MDButton;
  40. import com.afollestad.materialdialogs.internal.MDRootLayout;
  41. import com.afollestad.materialdialogs.internal.MDTintHelper;
  42. import com.afollestad.materialdialogs.util.DialogUtils;
  43. import com.afollestad.materialdialogs.util.TypefaceHelper;
  44. import java.text.NumberFormat;
  45. import java.util.ArrayList;
  46. import java.util.Arrays;
  47. import java.util.Collections;
  48. import java.util.List;
  49. /**
  50. * @author Aidan Follestad (afollestad)
  51. */
  52. public class MaterialDialog extends DialogBase implements
  53. View.OnClickListener, AdapterView.OnItemClickListener {
  54. protected final Builder mBuilder;
  55. protected ListView listView;
  56. protected ImageView icon;
  57. protected TextView title;
  58. protected View titleFrame;
  59. protected FrameLayout customViewFrame;
  60. protected ProgressBar mProgress;
  61. protected TextView mProgressLabel;
  62. protected TextView mProgressMinMax;
  63. protected TextView content;
  64. protected EditText input;
  65. protected TextView inputMinMax;
  66. protected MDButton positiveButton;
  67. protected MDButton neutralButton;
  68. protected MDButton negativeButton;
  69. protected ListType listType;
  70. protected List<Integer> selectedIndicesList;
  71. public final Builder getBuilder() {
  72. return mBuilder;
  73. }
  74. @SuppressLint("InflateParams")
  75. protected MaterialDialog(Builder builder) {
  76. super(builder.context, DialogInit.getTheme(builder));
  77. mHandler = new Handler();
  78. mBuilder = builder;
  79. final LayoutInflater inflater = LayoutInflater.from(builder.context);
  80. view = (MDRootLayout) inflater.inflate(DialogInit.getInflateLayout(builder), null);
  81. DialogInit.init(this);
  82. }
  83. public final void setTypeface(TextView target, Typeface t) {
  84. if (t == null) return;
  85. int flags = target.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG;
  86. target.setPaintFlags(flags);
  87. target.setTypeface(t);
  88. }
  89. protected final void checkIfListInitScroll() {
  90. if (listView == null)
  91. return;
  92. listView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
  93. @Override
  94. public void onGlobalLayout() {
  95. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
  96. //noinspection deprecation
  97. listView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
  98. } else {
  99. listView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
  100. }
  101. if (listType == ListType.SINGLE || listType == ListType.MULTI) {
  102. int selectedIndex;
  103. if (listType == ListType.SINGLE) {
  104. if (mBuilder.selectedIndex < 0)
  105. return;
  106. selectedIndex = mBuilder.selectedIndex;
  107. } else {
  108. if (mBuilder.selectedIndices == null || mBuilder.selectedIndices.length == 0)
  109. return;
  110. List<Integer> indicesList = Arrays.asList(mBuilder.selectedIndices);
  111. Collections.sort(indicesList);
  112. selectedIndex = indicesList.get(0);
  113. }
  114. if (listView.getLastVisiblePosition() < selectedIndex) {
  115. final int totalVisible = listView.getLastVisiblePosition() - listView.getFirstVisiblePosition();
  116. // Scroll so that the selected index appears in the middle (vertically) of the ListView
  117. int scrollIndex = selectedIndex - (totalVisible / 2);
  118. if (scrollIndex < 0) scrollIndex = 0;
  119. final int fScrollIndex = scrollIndex;
  120. listView.post(new Runnable() {
  121. @Override
  122. public void run() {
  123. listView.requestFocus();
  124. listView.setSelection(fScrollIndex);
  125. }
  126. });
  127. }
  128. }
  129. }
  130. });
  131. }
  132. /**
  133. * Sets the dialog ListView's adapter and it's item click listener.
  134. */
  135. protected final void invalidateList() {
  136. if (listView == null)
  137. return;
  138. else if ((mBuilder.items == null || mBuilder.items.length == 0) && mBuilder.adapter == null)
  139. return;
  140. // Set up list with adapter
  141. listView.setAdapter(mBuilder.adapter);
  142. if (listType != null || mBuilder.listCallbackCustom != null)
  143. listView.setOnItemClickListener(this);
  144. }
  145. @Override
  146. public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
  147. if (mBuilder.listCallbackCustom != null) {
  148. // Custom adapter
  149. CharSequence text = null;
  150. if (view instanceof TextView)
  151. text = ((TextView) view).getText();
  152. mBuilder.listCallbackCustom.onSelection(this, view, position, text);
  153. } else if (listType == null || listType == ListType.REGULAR) {
  154. // Default adapter, non choice mode
  155. if (mBuilder.autoDismiss) {
  156. // If auto dismiss is enabled, dismiss the dialog when a list item is selected
  157. dismiss();
  158. }
  159. mBuilder.listCallback.onSelection(this, view, position, mBuilder.items[position]);
  160. } else {
  161. // Default adapter, choice mode
  162. if (listType == ListType.MULTI) {
  163. final boolean shouldBeChecked = !selectedIndicesList.contains(Integer.valueOf(position));
  164. final CheckBox cb = (CheckBox) view.findViewById(R.id.control);
  165. if (shouldBeChecked) {
  166. // Add the selection to the states first so the callback includes it (when alwaysCallMultiChoiceCallback)
  167. selectedIndicesList.add(position);
  168. if (mBuilder.alwaysCallMultiChoiceCallback) {
  169. // If the checkbox wasn't previously selected, and the callback returns true, add it to the states and check it
  170. if (sendMultichoiceCallback()) {
  171. cb.setChecked(true);
  172. } else {
  173. // The callback cancelled selection, remove it from the states
  174. selectedIndicesList.remove(Integer.valueOf(position));
  175. }
  176. } else {
  177. // The callback was not used to check if selection is allowed, just select it
  178. cb.setChecked(true);
  179. }
  180. } else {
  181. // The checkbox was unchecked
  182. selectedIndicesList.remove(Integer.valueOf(position));
  183. cb.setChecked(false);
  184. if (mBuilder.alwaysCallMultiChoiceCallback)
  185. sendMultichoiceCallback();
  186. }
  187. } else if (listType == ListType.SINGLE) {
  188. boolean allowSelection = true;
  189. final MaterialDialogAdapter adapter = (MaterialDialogAdapter) mBuilder.adapter;
  190. final RadioButton radio = (RadioButton) view.findViewById(R.id.control);
  191. if (mBuilder.autoDismiss && mBuilder.positiveText == null) {
  192. // If auto dismiss is enabled, and no action button is visible to approve the selection, dismiss the dialog
  193. dismiss();
  194. // Don't allow the selection to be updated since the dialog is being dismissed anyways
  195. allowSelection = false;
  196. // Update selected index and send callback
  197. mBuilder.selectedIndex = position;
  198. sendSingleChoiceCallback(view);
  199. } else if (mBuilder.alwaysCallSingleChoiceCallback) {
  200. int oldSelected = mBuilder.selectedIndex;
  201. // Temporarily set the new index so the callback uses the right one
  202. mBuilder.selectedIndex = position;
  203. // Only allow the radio button to be checked if the callback returns true
  204. allowSelection = sendSingleChoiceCallback(view);
  205. // Restore the old selected index, so the state is updated below
  206. mBuilder.selectedIndex = oldSelected;
  207. }
  208. // Update the checked states
  209. if (allowSelection && mBuilder.selectedIndex != position) {
  210. mBuilder.selectedIndex = position;
  211. // Uncheck the previously selected radio button
  212. if (adapter.mRadioButton == null) {
  213. adapter.mInitRadio = true;
  214. adapter.notifyDataSetChanged();
  215. }
  216. if (adapter.mRadioButton != null)
  217. adapter.mRadioButton.setChecked(false);
  218. // Check the newly selected radio button
  219. radio.setChecked(true);
  220. adapter.mRadioButton = radio;
  221. }
  222. }
  223. }
  224. }
  225. public static class NotImplementedException extends Error {
  226. public NotImplementedException(@SuppressWarnings("SameParameterValue") String message) {
  227. super(message);
  228. }
  229. }
  230. public static class DialogException extends WindowManager.BadTokenException {
  231. public DialogException(@SuppressWarnings("SameParameterValue") String message) {
  232. super(message);
  233. }
  234. }
  235. protected final Drawable getListSelector() {
  236. if (mBuilder.listSelector != 0)
  237. return ResourcesCompat.getDrawable(mBuilder.context.getResources(), mBuilder.listSelector, null);
  238. final Drawable d = DialogUtils.resolveDrawable(mBuilder.context, R.attr.md_list_selector);
  239. if (d != null) return d;
  240. return DialogUtils.resolveDrawable(getContext(), R.attr.md_list_selector);
  241. }
  242. /* package */ Drawable getButtonSelector(DialogAction which, boolean isStacked) {
  243. if (isStacked) {
  244. if (mBuilder.btnSelectorStacked != 0)
  245. return ResourcesCompat.getDrawable(mBuilder.context.getResources(), mBuilder.btnSelectorStacked, null);
  246. final Drawable d = DialogUtils.resolveDrawable(mBuilder.context, R.attr.md_btn_stacked_selector);
  247. if (d != null) return d;
  248. return DialogUtils.resolveDrawable(getContext(), R.attr.md_btn_stacked_selector);
  249. } else {
  250. switch (which) {
  251. default: {
  252. if (mBuilder.btnSelectorPositive != 0)
  253. return ResourcesCompat.getDrawable(mBuilder.context.getResources(), mBuilder.btnSelectorPositive, null);
  254. final Drawable d = DialogUtils.resolveDrawable(mBuilder.context, R.attr.md_btn_positive_selector);
  255. if (d != null) return d;
  256. return DialogUtils.resolveDrawable(getContext(), R.attr.md_btn_positive_selector);
  257. }
  258. case NEUTRAL: {
  259. if (mBuilder.btnSelectorNeutral != 0)
  260. return ResourcesCompat.getDrawable(mBuilder.context.getResources(), mBuilder.btnSelectorNeutral, null);
  261. final Drawable d = DialogUtils.resolveDrawable(mBuilder.context, R.attr.md_btn_neutral_selector);
  262. if (d != null) return d;
  263. return DialogUtils.resolveDrawable(getContext(), R.attr.md_btn_neutral_selector);
  264. }
  265. case NEGATIVE: {
  266. if (mBuilder.btnSelectorNegative != 0)
  267. return ResourcesCompat.getDrawable(mBuilder.context.getResources(), mBuilder.btnSelectorNegative, null);
  268. final Drawable d = DialogUtils.resolveDrawable(mBuilder.context, R.attr.md_btn_negative_selector);
  269. if (d != null) return d;
  270. return DialogUtils.resolveDrawable(getContext(), R.attr.md_btn_negative_selector);
  271. }
  272. }
  273. }
  274. }
  275. private boolean sendSingleChoiceCallback(View v) {
  276. CharSequence text = null;
  277. if (mBuilder.selectedIndex >= 0) {
  278. text = mBuilder.items[mBuilder.selectedIndex];
  279. }
  280. return mBuilder.listCallbackSingleChoice.onSelection(this, v, mBuilder.selectedIndex, text);
  281. }
  282. private boolean sendMultichoiceCallback() {
  283. Collections.sort(selectedIndicesList); // make sure the indicies are in order
  284. List<CharSequence> selectedTitles = new ArrayList<>();
  285. for (Integer i : selectedIndicesList) {
  286. selectedTitles.add(mBuilder.items[i]);
  287. }
  288. return mBuilder.listCallbackMultiChoice.onSelection(this,
  289. selectedIndicesList.toArray(new Integer[selectedIndicesList.size()]),
  290. selectedTitles.toArray(new CharSequence[selectedTitles.size()]));
  291. }
  292. @Override
  293. public final void onClick(View v) {
  294. DialogAction tag = (DialogAction) v.getTag();
  295. switch (tag) {
  296. case POSITIVE: {
  297. if (mBuilder.callback != null) {
  298. mBuilder.callback.onAny(this);
  299. mBuilder.callback.onPositive(this);
  300. }
  301. if (mBuilder.listCallbackSingleChoice != null)
  302. sendSingleChoiceCallback(v);
  303. if (mBuilder.listCallbackMultiChoice != null)
  304. sendMultichoiceCallback();
  305. if (mBuilder.inputCallback != null && input != null && !mBuilder.alwaysCallInputCallback)
  306. mBuilder.inputCallback.onInput(this, input.getText());
  307. if (mBuilder.autoDismiss) dismiss();
  308. break;
  309. }
  310. case NEGATIVE: {
  311. if (mBuilder.callback != null) {
  312. mBuilder.callback.onAny(this);
  313. mBuilder.callback.onNegative(this);
  314. }
  315. if (mBuilder.autoDismiss) dismiss();
  316. break;
  317. }
  318. case NEUTRAL: {
  319. if (mBuilder.callback != null) {
  320. mBuilder.callback.onAny(this);
  321. mBuilder.callback.onNeutral(this);
  322. }
  323. if (mBuilder.autoDismiss) dismiss();
  324. break;
  325. }
  326. }
  327. }
  328. /**
  329. * The class used to construct a MaterialDialog.
  330. */
  331. public static class Builder {
  332. protected final Context context;
  333. protected CharSequence title;
  334. protected GravityEnum titleGravity = GravityEnum.START;
  335. protected GravityEnum contentGravity = GravityEnum.START;
  336. protected GravityEnum btnStackedGravity = GravityEnum.END;
  337. protected GravityEnum itemsGravity = GravityEnum.START;
  338. protected GravityEnum buttonsGravity = GravityEnum.START;
  339. protected int titleColor = -1;
  340. protected int contentColor = -1;
  341. protected CharSequence content;
  342. protected CharSequence[] items;
  343. protected CharSequence positiveText;
  344. protected CharSequence neutralText;
  345. protected CharSequence negativeText;
  346. protected View customView;
  347. protected int widgetColor;
  348. protected int positiveColor;
  349. protected int negativeColor;
  350. protected int neutralColor;
  351. protected ButtonCallback callback;
  352. protected ListCallback listCallback;
  353. protected ListCallbackSingleChoice listCallbackSingleChoice;
  354. protected ListCallbackMultiChoice listCallbackMultiChoice;
  355. protected ListCallback listCallbackCustom;
  356. protected boolean alwaysCallMultiChoiceCallback = false;
  357. protected boolean alwaysCallSingleChoiceCallback = false;
  358. protected Theme theme = Theme.LIGHT;
  359. protected boolean cancelable = true;
  360. protected float contentLineSpacingMultiplier = 1.2f;
  361. protected int selectedIndex = -1;
  362. protected Integer[] selectedIndices = null;
  363. protected boolean autoDismiss = true;
  364. protected Typeface regularFont;
  365. protected Typeface mediumFont;
  366. protected Drawable icon;
  367. protected boolean limitIconToDefaultSize;
  368. protected int maxIconSize = -1;
  369. protected ListAdapter adapter;
  370. protected OnDismissListener dismissListener;
  371. protected OnCancelListener cancelListener;
  372. protected OnKeyListener keyListener;
  373. protected OnShowListener showListener;
  374. protected boolean forceStacking;
  375. protected boolean wrapCustomViewInScroll;
  376. protected int dividerColor;
  377. protected int backgroundColor;
  378. protected int itemColor;
  379. protected boolean indeterminateProgress;
  380. protected boolean showMinMax;
  381. protected int progress = -2;
  382. protected int progressMax = 0;
  383. protected CharSequence inputPrefill;
  384. protected CharSequence inputHint;
  385. protected InputCallback inputCallback;
  386. protected boolean inputAllowEmpty;
  387. protected int inputType = -1;
  388. protected boolean alwaysCallInputCallback;
  389. protected int inputMaxLength = -1;
  390. protected int inputMaxLengthErrorColor = 0;
  391. protected String progressNumberFormat;
  392. protected NumberFormat progressPercentFormat;
  393. protected boolean indeterminateIsHorizontalProgress;
  394. protected boolean titleColorSet = false;
  395. protected boolean contentColorSet = false;
  396. protected boolean itemColorSet = false;
  397. protected boolean positiveColorSet = false;
  398. protected boolean neutralColorSet = false;
  399. protected boolean negativeColorSet = false;
  400. protected boolean widgetColorSet = false;
  401. protected boolean dividerColorSet = false;
  402. @DrawableRes
  403. protected int listSelector;
  404. @DrawableRes
  405. protected int btnSelectorStacked;
  406. @DrawableRes
  407. protected int btnSelectorPositive;
  408. @DrawableRes
  409. protected int btnSelectorNeutral;
  410. @DrawableRes
  411. protected int btnSelectorNegative;
  412. public final Context getContext() {
  413. return context;
  414. }
  415. public final GravityEnum getItemsGravity() {
  416. return itemsGravity;
  417. }
  418. public final int getItemColor() {
  419. return itemColor;
  420. }
  421. public final Typeface getRegularFont() {
  422. return regularFont;
  423. }
  424. public Builder(@NonNull Context context) {
  425. this.context = context;
  426. final int materialBlue = context.getResources().getColor(R.color.md_material_blue_600);
  427. // Retrieve default accent colors, which are used on the action buttons and progress bars
  428. this.widgetColor = DialogUtils.resolveColor(context, R.attr.colorAccent, materialBlue);
  429. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  430. this.widgetColor = DialogUtils.resolveColor(context, android.R.attr.colorAccent, this.widgetColor);
  431. }
  432. this.positiveColor = this.widgetColor;
  433. this.negativeColor = this.widgetColor;
  434. this.neutralColor = this.widgetColor;
  435. this.progressPercentFormat = NumberFormat.getPercentInstance();
  436. this.progressNumberFormat = "%1d/%2d";
  437. // Set the default theme based on the Activity theme's primary color darkness (more white or more black)
  438. final int primaryTextColor = DialogUtils.resolveColor(context, android.R.attr.textColorPrimary);
  439. this.theme = DialogUtils.isColorDark(primaryTextColor) ? Theme.LIGHT : Theme.DARK;
  440. // Load theme values from the ThemeSingleton if needed
  441. checkSingleton();
  442. // Retrieve gravity settings from global theme attributes if needed
  443. this.titleGravity = DialogUtils.resolveGravityEnum(context, R.attr.md_title_gravity, this.titleGravity);
  444. this.contentGravity = DialogUtils.resolveGravityEnum(context, R.attr.md_content_gravity, this.contentGravity);
  445. this.btnStackedGravity = DialogUtils.resolveGravityEnum(context, R.attr.md_btnstacked_gravity, this.btnStackedGravity);
  446. this.itemsGravity = DialogUtils.resolveGravityEnum(context, R.attr.md_items_gravity, this.itemsGravity);
  447. this.buttonsGravity = DialogUtils.resolveGravityEnum(context, R.attr.md_buttons_gravity, this.buttonsGravity);
  448. final String mediumFont = DialogUtils.resolveString(context, R.attr.md_medium_font);
  449. final String regularFont = DialogUtils.resolveString(context, R.attr.md_regular_font);
  450. typeface(mediumFont, regularFont);
  451. if (this.mediumFont == null) {
  452. try {
  453. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
  454. this.mediumFont = Typeface.create("sans-serif-medium", Typeface.NORMAL);
  455. else
  456. this.mediumFont = Typeface.create("sans-serif", Typeface.BOLD);
  457. } catch (Throwable ignored) {
  458. }
  459. }
  460. if (this.regularFont == null) {
  461. try {
  462. this.regularFont = Typeface.create("sans-serif", Typeface.NORMAL);
  463. } catch (Throwable ignored) {
  464. }
  465. }
  466. }
  467. private void checkSingleton() {
  468. if (ThemeSingleton.get(false) == null) return;
  469. ThemeSingleton s = ThemeSingleton.get();
  470. if (s.darkTheme)
  471. this.theme = Theme.DARK;
  472. if (s.titleColor != 0)
  473. this.titleColor = s.titleColor;
  474. if (s.contentColor != 0)
  475. this.contentColor = s.contentColor;
  476. if (s.positiveColor != 0)
  477. this.positiveColor = s.positiveColor;
  478. if (s.neutralColor != 0)
  479. this.neutralColor = s.neutralColor;
  480. if (s.negativeColor != 0)
  481. this.negativeColor = s.negativeColor;
  482. if (s.itemColor != 0)
  483. this.itemColor = s.itemColor;
  484. if (s.icon != null)
  485. this.icon = s.icon;
  486. if (s.backgroundColor != 0)
  487. this.backgroundColor = s.backgroundColor;
  488. if (s.dividerColor != 0)
  489. this.dividerColor = s.dividerColor;
  490. if (s.btnSelectorStacked != 0)
  491. this.btnSelectorStacked = s.btnSelectorStacked;
  492. if (s.listSelector != 0)
  493. this.listSelector = s.listSelector;
  494. if (s.btnSelectorPositive != 0)
  495. this.btnSelectorPositive = s.btnSelectorPositive;
  496. if (s.btnSelectorNeutral != 0)
  497. this.btnSelectorNeutral = s.btnSelectorNeutral;
  498. if (s.btnSelectorNegative != 0)
  499. this.btnSelectorNegative = s.btnSelectorNegative;
  500. if (s.widgetColor != 0)
  501. this.widgetColor = s.widgetColor;
  502. this.titleGravity = s.titleGravity;
  503. this.contentGravity = s.contentGravity;
  504. this.btnStackedGravity = s.btnStackedGravity;
  505. this.itemsGravity = s.itemsGravity;
  506. this.buttonsGravity = s.buttonsGravity;
  507. }
  508. public Builder title(@StringRes int titleRes) {
  509. title(this.context.getText(titleRes));
  510. return this;
  511. }
  512. public Builder title(@NonNull CharSequence title) {
  513. this.title = title;
  514. return this;
  515. }
  516. public Builder titleGravity(@NonNull GravityEnum gravity) {
  517. this.titleGravity = gravity;
  518. return this;
  519. }
  520. public Builder titleColor(@ColorInt int color) {
  521. this.titleColor = color;
  522. this.titleColorSet = true;
  523. return this;
  524. }
  525. public Builder titleColorRes(@ColorRes int colorRes) {
  526. titleColor(this.context.getResources().getColor(colorRes));
  527. return this;
  528. }
  529. public Builder titleColorAttr(@AttrRes int colorAttr) {
  530. titleColor(DialogUtils.resolveColor(this.context, colorAttr));
  531. return this;
  532. }
  533. /**
  534. * Sets the fonts used in the dialog. It's recommended that you use {@link #typeface(String, String)} instead,
  535. * to avoid duplicate Typeface allocations and high memory usage.
  536. *
  537. * @param medium The font used on titles and action buttons. Null uses device default.
  538. * @param regular The font used everywhere else, like on the content and list items. Null uses device default.
  539. * @return The Builder instance so you can chain calls to it.
  540. */
  541. public Builder typeface(@Nullable Typeface medium, @Nullable Typeface regular) {
  542. this.mediumFont = medium;
  543. this.regularFont = regular;
  544. return this;
  545. }
  546. /**
  547. * Sets the fonts used in the dialog, by file names. This also uses TypefaceHelper in order
  548. * to avoid any un-needed allocations (it recycles typefaces for you).
  549. *
  550. * @param medium The name of font in assets/fonts used on titles and action buttons (null uses device default). E.g. [your-project]/app/main/assets/fonts/[medium]
  551. * @param regular The name of font in assets/fonts used everywhere else, like content and list items (null uses device default). E.g. [your-project]/app/main/assets/fonts/[regular]
  552. * @return The Builder instance so you can chain calls to it.
  553. */
  554. public Builder typeface(@Nullable String medium, @Nullable String regular) {
  555. if (medium != null) {
  556. this.mediumFont = TypefaceHelper.get(this.context, medium);
  557. if (this.mediumFont == null)
  558. throw new IllegalArgumentException("No font asset found for " + medium);
  559. }
  560. if (regular != null) {
  561. this.regularFont = TypefaceHelper.get(this.context, regular);
  562. if (this.regularFont == null)
  563. throw new IllegalArgumentException("No font asset found for " + regular);
  564. }
  565. return this;
  566. }
  567. public Builder icon(@NonNull Drawable icon) {
  568. this.icon = icon;
  569. return this;
  570. }
  571. public Builder iconRes(@DrawableRes int icon) {
  572. this.icon = ResourcesCompat.getDrawable(context.getResources(), icon, null);
  573. return this;
  574. }
  575. public Builder iconAttr(@AttrRes int iconAttr) {
  576. this.icon = DialogUtils.resolveDrawable(context, iconAttr);
  577. return this;
  578. }
  579. public Builder content(@StringRes int contentRes) {
  580. content(this.context.getText(contentRes));
  581. return this;
  582. }
  583. public Builder content(@NonNull CharSequence content) {
  584. if (this.customView != null)
  585. throw new IllegalStateException("You cannot set content() when you're using a custom view.");
  586. this.content = content;
  587. return this;
  588. }
  589. public Builder content(@StringRes int contentRes, Object... formatArgs) {
  590. content(this.context.getString(contentRes, formatArgs));
  591. return this;
  592. }
  593. public Builder contentColor(@ColorInt int color) {
  594. this.contentColor = color;
  595. this.contentColorSet = true;
  596. return this;
  597. }
  598. public Builder contentColorRes(@ColorRes int colorRes) {
  599. contentColor(this.context.getResources().getColor(colorRes));
  600. return this;
  601. }
  602. public Builder contentColorAttr(@AttrRes int colorAttr) {
  603. contentColor(DialogUtils.resolveColor(this.context, colorAttr));
  604. return this;
  605. }
  606. public Builder contentGravity(@NonNull GravityEnum gravity) {
  607. this.contentGravity = gravity;
  608. return this;
  609. }
  610. public Builder contentLineSpacing(float multiplier) {
  611. this.contentLineSpacingMultiplier = multiplier;
  612. return this;
  613. }
  614. public Builder items(@ArrayRes int itemsRes) {
  615. items(this.context.getResources().getTextArray(itemsRes));
  616. return this;
  617. }
  618. public Builder items(@NonNull CharSequence[] items) {
  619. if (this.customView != null)
  620. throw new IllegalStateException("You cannot set items() when you're using a custom view.");
  621. this.items = items;
  622. return this;
  623. }
  624. public Builder itemsCallback(@NonNull ListCallback callback) {
  625. this.listCallback = callback;
  626. this.listCallbackSingleChoice = null;
  627. this.listCallbackMultiChoice = null;
  628. return this;
  629. }
  630. public Builder itemColor(@ColorInt int color) {
  631. this.itemColor = color;
  632. this.itemColorSet = true;
  633. return this;
  634. }
  635. public Builder itemColorRes(@ColorRes int colorRes) {
  636. return itemColor(this.context.getResources().getColor(colorRes));
  637. }
  638. public Builder itemColorAttr(@AttrRes int colorAttr) {
  639. return itemColor(DialogUtils.resolveColor(this.context, colorAttr));
  640. }
  641. public Builder itemsGravity(@NonNull GravityEnum gravity) {
  642. this.itemsGravity = gravity;
  643. return this;
  644. }
  645. public Builder buttonsGravity(@NonNull GravityEnum gravity) {
  646. this.buttonsGravity = gravity;
  647. return this;
  648. }
  649. /**
  650. * Pass anything below 0 (such as -1) for the selected index to leave all options unselected initially.
  651. * Otherwise pass the index of an item that will be selected initially.
  652. *
  653. * @param selectedIndex The checkbox index that will be selected initially.
  654. * @param callback The callback that will be called when the presses the positive button.
  655. * @return The Builder instance so you can chain calls to it.
  656. */
  657. public Builder itemsCallbackSingleChoice(int selectedIndex, @NonNull ListCallbackSingleChoice callback) {
  658. this.selectedIndex = selectedIndex;
  659. this.listCallback = null;
  660. this.listCallbackSingleChoice = callback;
  661. this.listCallbackMultiChoice = null;
  662. return this;
  663. }
  664. /**
  665. * By default, the single choice callback is only called when the user clicks the positive button
  666. * or if there are no buttons. Call this to force it to always call on item clicks even if the
  667. * positive button exists.
  668. *
  669. * @return The Builder instance so you can chain calls to it.
  670. */
  671. public Builder alwaysCallSingleChoiceCallback() {
  672. this.alwaysCallSingleChoiceCallback = true;
  673. return this;
  674. }
  675. /**
  676. * Pass null for the selected indices to leave all options unselected initially. Otherwise pass
  677. * an array of indices that will be selected initially.
  678. *
  679. * @param selectedIndices The radio button indices that will be selected initially.
  680. * @param callback The callback that will be called when the presses the positive button.
  681. * @return The Builder instance so you can chain calls to it.
  682. */
  683. public Builder itemsCallbackMultiChoice(@Nullable Integer[] selectedIndices, @NonNull ListCallbackMultiChoice callback) {
  684. this.selectedIndices = selectedIndices;
  685. this.listCallback = null;
  686. this.listCallbackSingleChoice = null;
  687. this.listCallbackMultiChoice = callback;
  688. return this;
  689. }
  690. /**
  691. * By default, the multi choice callback is only called when the user clicks the positive button
  692. * or if there are no buttons. Call this to force it to always call on item clicks even if the
  693. * positive button exists.
  694. *
  695. * @return The Builder instance so you can chain calls to it.
  696. */
  697. public Builder alwaysCallMultiChoiceCallback() {
  698. this.alwaysCallMultiChoiceCallback = true;
  699. return this;
  700. }
  701. public Builder positiveText(@StringRes int postiveRes) {
  702. positiveText(this.context.getText(postiveRes));
  703. return this;
  704. }
  705. public Builder positiveText(@NonNull CharSequence message) {
  706. this.positiveText = message;
  707. return this;
  708. }
  709. public Builder positiveColor(@ColorInt int color) {
  710. this.positiveColor = color;
  711. this.positiveColorSet = true;
  712. return this;
  713. }
  714. public Builder positiveColorRes(@ColorRes int colorRes) {
  715. return positiveColor(this.context.getResources().getColor(colorRes));
  716. }
  717. public Builder positiveColorAttr(@AttrRes int colorAttr) {
  718. return positiveColor(DialogUtils.resolveColor(this.context, colorAttr));
  719. }
  720. public Builder neutralText(@StringRes int neutralRes) {
  721. return neutralText(this.context.getText(neutralRes));
  722. }
  723. public Builder neutralText(@NonNull CharSequence message) {
  724. this.neutralText = message;
  725. return this;
  726. }
  727. public Builder negativeColor(@ColorInt int color) {
  728. this.negativeColor = color;
  729. this.negativeColorSet = true;
  730. return this;
  731. }
  732. public Builder negativeColorRes(@ColorRes int colorRes) {
  733. return negativeColor(this.context.getResources().getColor(colorRes));
  734. }
  735. public Builder negativeColorAttr(@AttrRes int colorAttr) {
  736. return negativeColor(DialogUtils.resolveColor(this.context, colorAttr));
  737. }
  738. public Builder negativeText(@StringRes int negativeRes) {
  739. return negativeText(this.context.getText(negativeRes));
  740. }
  741. public Builder negativeText(@NonNull CharSequence message) {
  742. this.negativeText = message;
  743. return this;
  744. }
  745. public Builder neutralColor(@ColorInt int color) {
  746. this.neutralColor = color;
  747. this.neutralColorSet = true;
  748. return this;
  749. }
  750. public Builder neutralColorRes(@ColorRes int colorRes) {
  751. return neutralColor(this.context.getResources().getColor(colorRes));
  752. }
  753. public Builder neutralColorAttr(@AttrRes int colorAttr) {
  754. return neutralColor(DialogUtils.resolveColor(this.context, colorAttr));
  755. }
  756. public Builder listSelector(@DrawableRes int selectorRes) {
  757. this.listSelector = selectorRes;
  758. return this;
  759. }
  760. public Builder btnSelectorStacked(@DrawableRes int selectorRes) {
  761. this.btnSelectorStacked = selectorRes;
  762. return this;
  763. }
  764. public Builder btnSelector(@DrawableRes int selectorRes) {
  765. this.btnSelectorPositive = selectorRes;
  766. this.btnSelectorNeutral = selectorRes;
  767. this.btnSelectorNegative = selectorRes;
  768. return this;
  769. }
  770. public Builder btnSelector(@DrawableRes int selectorRes, @NonNull DialogAction which) {
  771. switch (which) {
  772. default:
  773. this.btnSelectorPositive = selectorRes;
  774. break;
  775. case NEUTRAL:
  776. this.btnSelectorNeutral = selectorRes;
  777. break;
  778. case NEGATIVE:
  779. this.btnSelectorNegative = selectorRes;
  780. break;
  781. }
  782. return this;
  783. }
  784. /**
  785. * Sets the gravity used for the text in stacked action buttons. By default, it's #{@link GravityEnum#END}.
  786. *
  787. * @param gravity The gravity to use.
  788. * @return The Builder instance so calls can be chained.
  789. */
  790. public Builder btnStackedGravity(@NonNull GravityEnum gravity) {
  791. this.btnStackedGravity = gravity;
  792. return this;
  793. }
  794. public Builder customView(@LayoutRes int layoutRes, boolean wrapInScrollView) {
  795. LayoutInflater li = LayoutInflater.from(this.context);
  796. return customView(li.inflate(layoutRes, null), wrapInScrollView);
  797. }
  798. public Builder customView(@NonNull View view, boolean wrapInScrollView) {
  799. if (this.content != null)
  800. throw new IllegalStateException("You cannot use customView() when you have content set.");
  801. else if (this.items != null)
  802. throw new IllegalStateException("You cannot use customView() when you have items set.");
  803. else if (this.inputCallback != null)
  804. throw new IllegalStateException("You cannot use customView() with an input dialog");
  805. else if (this.progress > -2 || this.indeterminateProgress)
  806. throw new IllegalStateException("You cannot use customView() with a progress dialog");
  807. this.customView = view;
  808. this.wrapCustomViewInScroll = wrapInScrollView;
  809. return this;
  810. }
  811. /**
  812. * Makes this dialog a progress dialog.
  813. *
  814. * @param indeterminate If true, an infinite circular spinner is shown. If false, a horizontal progress bar is shown that is incremented or set via the built MaterialDialog instance.
  815. * @param max When indeterminate is false, the max value the horizontal progress bar can get to.
  816. * @return An instance of the Builder so calls can be chained.
  817. */
  818. public Builder progress(boolean indeterminate, int max) {
  819. if (this.customView != null)
  820. throw new IllegalStateException("You cannot set progress() when you're using a custom view.");
  821. if (indeterminate) {
  822. this.indeterminateProgress = true;
  823. this.progress = -2;
  824. } else {
  825. this.indeterminateProgress = false;
  826. this.progress = -1;
  827. this.progressMax = max;
  828. }
  829. return this;
  830. }
  831. /**
  832. * Makes this dialog a progress dialog.
  833. *
  834. * @param indeterminate If true, an infinite circular spinner is shown. If false, a horizontal progress bar is shown that is incremented or set via the built MaterialDialog instance.
  835. * @param max When indeterminate is false, the max value the horizontal progress bar can get to.
  836. * @param showMinMax For determinate dialogs, the min and max will be displayed to the left (start) of the progress bar, e.g. 50/100.
  837. * @return An instance of the Builder so calls can be chained.
  838. */
  839. public Builder progress(boolean indeterminate, int max, boolean showMinMax) {
  840. this.showMinMax = showMinMax;
  841. return progress(indeterminate, max);
  842. }
  843. /**
  844. * hange the format of the small text showing current and maximum units of progress.
  845. * The default is "%1d/%2d".
  846. */
  847. public Builder progressNumberFormat(@NonNull String format) {
  848. this.progressNumberFormat = format;
  849. return this;
  850. }
  851. /**
  852. * Change the format of the small text showing the percentage of progress.
  853. * The default is NumberFormat.getPercentageInstance().
  854. */
  855. public Builder progressPercentFormat(@NonNull NumberFormat format) {
  856. this.progressPercentFormat = format;
  857. return this;
  858. }
  859. /**
  860. * By default, indeterminate progress dialogs will use a circular indicator. You
  861. * can change it to use a horizontal progress indicator.
  862. */
  863. public Builder progressIndeterminateStyle(boolean horizontal) {
  864. this.indeterminateIsHorizontalProgress = horizontal;
  865. return this;
  866. }
  867. public Builder widgetColor(@ColorInt int color) {
  868. this.widgetColor = color;
  869. this.widgetColorSet = true;
  870. return this;
  871. }
  872. public Builder widgetColorRes(@ColorRes int colorRes) {
  873. return widgetColor(this.context.getResources().getColor(colorRes));
  874. }
  875. public Builder widgetColorAttr(@AttrRes int colorAttr) {
  876. return widgetColorRes(DialogUtils.resolveColor(this.context, colorAttr));
  877. }
  878. public Builder dividerColor(@ColorInt int color) {
  879. this.dividerColor = color;
  880. this.dividerColorSet = true;
  881. return this;
  882. }
  883. public Builder dividerColorRes(@ColorRes int colorRes) {
  884. return dividerColor(this.context.getResources().getColor(colorRes));
  885. }
  886. public Builder dividerColorAttr(@AttrRes int colorAttr) {
  887. return dividerColor(DialogUtils.resolveColor(this.context, colorAttr));
  888. }
  889. public Builder backgroundColor(@ColorInt int color) {
  890. this.backgroundColor = color;
  891. return this;
  892. }
  893. public Builder backgroundColorRes(@ColorRes int colorRes) {
  894. return backgroundColor(this.context.getResources().getColor(colorRes));
  895. }
  896. public Builder backgroundColorAttr(@AttrRes int colorAttr) {
  897. return backgroundColor(DialogUtils.resolveColor(this.context, colorAttr));
  898. }
  899. public Builder callback(@NonNull ButtonCallback callback) {
  900. this.callback = callback;
  901. return this;
  902. }
  903. public Builder theme(@NonNull Theme theme) {
  904. this.theme = theme;
  905. return this;
  906. }
  907. public Builder cancelable(boolean cancelable) {
  908. this.cancelable = cancelable;
  909. return this;
  910. }
  911. /**
  912. * This defaults to true. If set to false, the dialog will not automatically be dismissed
  913. * when an action button is pressed, and not automatically dismissed when the user selects
  914. * a list item.
  915. *
  916. * @param dismiss Whether or not to dismiss the dialog automatically.
  917. * @return The Builder instance so you can chain calls to it.
  918. */
  919. public Builder autoDismiss(boolean dismiss) {
  920. this.autoDismiss = dismiss;
  921. return this;
  922. }
  923. /**
  924. * Sets a custom {@link android.widget.ListAdapter} for the dialog's list
  925. *
  926. * @param adapter The adapter to set to the list.
  927. * @param callback The callback invoked when an item in the list is selected.
  928. * @return This Builder object to allow for chaining of calls to set methods
  929. */
  930. public Builder adapter(@NonNull ListAdapter adapter, @Nullable ListCallback callback) {
  931. if (this.customView != null)
  932. throw new IllegalStateException("You cannot set adapter() when you're using a custom view.");
  933. this.adapter = adapter;
  934. this.listCallbackCustom = callback;
  935. return this;
  936. }
  937. /**
  938. * Limits the display size of a set icon to 48dp.
  939. */
  940. public Builder limitIconToDefaultSize() {
  941. this.limitIconToDefaultSize = true;
  942. return this;
  943. }
  944. public Builder maxIconSize(int maxIconSize) {
  945. this.maxIconSize = maxIconSize;
  946. return this;
  947. }
  948. public Builder maxIconSizeRes(@DimenRes int maxIconSizeRes) {
  949. return maxIconSize((int) this.context.getResources().getDimension(maxIconSizeRes));
  950. }
  951. public Builder showListener(@NonNull OnShowListener listener) {
  952. this.showListener = listener;
  953. return this;
  954. }
  955. public Builder dismissListener(@NonNull OnDismissListener listener) {
  956. this.dismissListener = listener;
  957. return this;
  958. }
  959. public Builder cancelListener(@NonNull OnCancelListener listener) {
  960. this.cancelListener = listener;
  961. return this;
  962. }
  963. public Builder keyListener(@NonNull OnKeyListener listener) {
  964. this.keyListener = listener;
  965. return this;
  966. }
  967. public Builder forceStacking(boolean stacked) {
  968. this.forceStacking = stacked;
  969. return this;
  970. }
  971. public Builder input(@Nullable CharSequence hint, @Nullable CharSequence prefill, boolean allowEmptyInput, @NonNull InputCallback callback) {
  972. if (this.customView != null)
  973. throw new IllegalStateException("You cannot set content() when you're using a custom view.");
  974. this.inputCallback = callback;
  975. this.inputHint = hint;
  976. this.inputPrefill = prefill;
  977. this.inputAllowEmpty = allowEmptyInput;
  978. return this;
  979. }
  980. public Builder input(@Nullable CharSequence hint, @Nullable CharSequence prefill, @NonNull InputCallback callback) {
  981. return input(hint, prefill, true, callback);
  982. }
  983. public Builder input(@StringRes int hint, @StringRes int prefill, boolean allowEmptyInput, @NonNull InputCallback callback) {
  984. return input(hint == 0 ? null : context.getText(hint), prefill == 0 ? null : context.getText(prefill), allowEmptyInput, callback);
  985. }
  986. public Builder input(@StringRes int hint, @StringRes int prefill, @NonNull InputCallback callback) {
  987. return input(hint, prefill, true, callback);
  988. }
  989. public Builder inputType(int type) {
  990. this.inputType = type;
  991. return this;
  992. }
  993. public Builder inputMaxLength(int maxLength) {
  994. return inputMaxLength(maxLength, 0);
  995. }
  996. /**
  997. * @param errorColor Pass in 0 for the default red error color (as specified in guidelines).
  998. */
  999. public Builder inputMaxLength(int maxLength, int errorColor) {
  1000. if (maxLength < 1)
  1001. throw new IllegalArgumentException("Max length for input dialogs cannot be less than 1.");
  1002. this.inputMaxLength = maxLength;
  1003. if (errorColor == 0) {
  1004. inputMaxLengthErrorColor = context.getResources().getColor(R.color.md_edittext_error);
  1005. } else {
  1006. this.inputMaxLengthErrorColor = errorColor;
  1007. }
  1008. return this;
  1009. }
  1010. /**
  1011. * Same as #{@link #inputMaxLength(int, int)}, but it takes a color resource ID for the error color.
  1012. */
  1013. public Builder inputMaxLengthRes(int maxLength, @ColorRes int errorColor) {
  1014. return inputMaxLength(maxLength, context.getResources().getColor(errorColor));
  1015. }
  1016. public Builder alwaysCallInputCallback() {
  1017. this.alwaysCallInputCallback = true;
  1018. return this;
  1019. }
  1020. @UiThread
  1021. public MaterialDialog build() {
  1022. return new MaterialDialog(this);
  1023. }
  1024. @UiThread
  1025. public MaterialDialog show() {
  1026. MaterialDialog dialog = build();
  1027. dialog.show();
  1028. return dialog;
  1029. }
  1030. }
  1031. @Override
  1032. @UiThread
  1033. public void show() {
  1034. try {
  1035. super.show();
  1036. } catch (WindowManager.BadTokenException e) {
  1037. throw new DialogException("Bad window token, you cannot show a dialog before an Activity is created or after it's hidden.");
  1038. }
  1039. }
  1040. /**
  1041. * Retrieves the view of an action button, allowing you to modify properties such as whether or not it's enabled.
  1042. * Use {@link #setActionButton(DialogAction, int)} to change text, since the view returned here is not
  1043. * the view that displays text.
  1044. *
  1045. * @param which The action button of which to get the view for.
  1046. * @return The view from the dialog's layout representing this action button.
  1047. */
  1048. public final View getActionButton(@NonNull DialogAction which) {
  1049. switch (which) {
  1050. default:
  1051. return view.findViewById(R.id.buttonDefaultPositive);
  1052. case NEUTRAL:
  1053. return view.findViewById(R.id.buttonDefaultNeutral);
  1054. case NEGATIVE:
  1055. return view.findViewById(R.id.buttonDefaultNegative);
  1056. }
  1057. }
  1058. /**
  1059. * Retrieves the view representing the dialog as a whole. Be careful with this.
  1060. */
  1061. public final View getView() {
  1062. return view;
  1063. }
  1064. @Nullable
  1065. public final ListView getListView() {
  1066. return listView;
  1067. }
  1068. @Nullable
  1069. public final EditText getInputEditText() {
  1070. return input;
  1071. }
  1072. /**
  1073. * Retrieves the TextView that contains the dialog title. If you want to update the
  1074. * title, use #{@link #setTitle(CharSequence)} instead.
  1075. */
  1076. public final TextView getTitleView() {
  1077. return title;
  1078. }
  1079. /**
  1080. * Retrieves the TextView that contains the dialog content. If you want to update the
  1081. * content (message), use #{@link #setContent(CharSequence)} instead.
  1082. */
  1083. @Nullable
  1084. public final TextView getContentView() {
  1085. return content;
  1086. }
  1087. /**
  1088. * Retrieves the custom view that was inflated or set to the MaterialDialog during building.
  1089. *
  1090. * @return The custom view that was passed into the Builder.
  1091. */
  1092. @Nullable
  1093. public final View getCustomView() {
  1094. return mBuilder.customView;
  1095. }
  1096. /**
  1097. * Updates an action button's title, causing invalidation to check if the action buttons should be stacked.
  1098. * Setting an action button's text to null is a shortcut for hiding it, too.
  1099. *
  1100. * @param which The action button to update.
  1101. * @param title The new title of the action button.
  1102. */
  1103. @UiThread
  1104. public final void setActionButton(@NonNull final DialogAction which, final CharSequence title) {
  1105. switch (which) {
  1106. default:
  1107. mBuilder.positiveText = title;
  1108. positiveButton.setText(title);
  1109. positiveButton.setVisibility(title == null ? View.GONE : View.VISIBLE);
  1110. break;
  1111. case NEUTRAL:
  1112. mBuilder.neutralText = title;
  1113. neutralButton.setText(title);
  1114. neutralButton.setVisibility(title == null ? View.GONE : View.VISIBLE);
  1115. break;
  1116. case NEGATIVE:
  1117. mBuilder.negativeText = title;
  1118. negativeButton.setText(title);
  1119. negativeButton.setVisibility(title == null ? View.GONE : View.VISIBLE);
  1120. break;
  1121. }
  1122. }
  1123. /**
  1124. * Updates an action button's title, causing invalidation to check if the action buttons should be stacked.
  1125. *
  1126. * @param which The action button to update.
  1127. * @param titleRes The string resource of the new title of the action button.
  1128. */
  1129. public final void setActionButton(DialogAction which, @StringRes int titleRes) {
  1130. setActionButton(which, getContext().getText(titleRes));
  1131. }
  1132. /**
  1133. * Gets whether or not the positive, neutral, or negative action button is visible.
  1134. *
  1135. * @return Whether or not 1 or more action buttons is visible.
  1136. */
  1137. public final boolean hasActionButtons() {
  1138. return numberOfActionButtons() > 0;
  1139. }
  1140. /**
  1141. * Gets the number of visible action buttons.
  1142. *
  1143. * @return 0 through 3, depending on how many should be or are visible.
  1144. */
  1145. public final int numberOfActionButtons() {
  1146. int number = 0;
  1147. if (mBuilder.positiveText != null && positiveButton.getVisibility() == View.VISIBLE)
  1148. number++;
  1149. if (mBuilder.neutralText != null && neutralButton.getVisibility() == View.VISIBLE)
  1150. number++;
  1151. if (mBuilder.negativeText != null && negativeButton.getVisibility() == View.VISIBLE)
  1152. number++;
  1153. return number;
  1154. }
  1155. @UiThread
  1156. @Override
  1157. public final void setTitle(@NonNull CharSequence newTitle) {
  1158. title.setText(newTitle);
  1159. }
  1160. @UiThread
  1161. @Override
  1162. public final void setTitle(@StringRes int newTitleRes) {
  1163. setTitle(mBuilder.context.getString(newTitleRes));
  1164. }
  1165. @UiThread
  1166. public final void setTitle(@StringRes int newTitleRes, @Nullable Object... formatArgs) {
  1167. setTitle(mBuilder.context.getString(newTitleRes, formatArgs));
  1168. }
  1169. @UiThread
  1170. public void setIcon(@DrawableRes final int resId) {
  1171. icon.setImageResource(resId);
  1172. icon.setVisibility(resId != 0 ? View.VISIBLE : View.GONE);
  1173. }
  1174. @UiThread
  1175. public void setIcon(final Drawable d) {
  1176. icon.setImageDrawable(d);
  1177. icon.setVisibility(d != null ? View.VISIBLE : View.GONE);
  1178. }
  1179. @UiThread
  1180. public void setIconAttribute(@AttrRes int attrId) {
  1181. Drawable d = DialogUtils.resolveDrawable(mBuilder.context, attrId);
  1182. setIcon(d);
  1183. }
  1184. @UiThread
  1185. public final void setContent(CharSequence newContent) {
  1186. content.setText(newContent);
  1187. content.setVisibility(TextUtils.isEmpty(newContent) ? View.GONE : View.VISIBLE);
  1188. }
  1189. @UiThread
  1190. public final void setContent(@StringRes int newContentRes) {
  1191. setContent(mBuilder.context.getString(newContentRes));
  1192. }
  1193. @UiThread
  1194. public final void setContent(@StringRes int newContentRes, @Nullable Object... formatArgs) {
  1195. setContent(mBuilder.context.getString(newContentRes, formatArgs));
  1196. }
  1197. /**
  1198. * @deprecated Use setContent() instead.
  1199. */
  1200. @Deprecated
  1201. public void setMessage(CharSequence message) {
  1202. setContent(message);
  1203. }
  1204. @UiThread
  1205. public final void setItems(CharSequence[] items) {
  1206. if (mBuilder.adapter == null)
  1207. throw new IllegalStateException("This MaterialDialog instance does not yet have an adapter set to it. You cannot use setItems().");
  1208. mBuilder.items = items;
  1209. if (mBuilder.adapter instanceof MaterialDialogAdapter) {
  1210. mBuilder.adapter = new MaterialDialogAdapter(this, ListType.getLayoutForType(listType));
  1211. } else {
  1212. throw new IllegalStateException("When using a custom adapter, setItems() cannot be used. Set items through the adapter instead.");
  1213. }
  1214. listView.setAdapter(mBuilder.adapter);
  1215. }
  1216. public final int getCurrentProgress() {
  1217. if (mProgress == null) return -1;
  1218. return mProgress.getProgress();
  1219. }
  1220. public final void incrementProgress(final int by) {
  1221. setProgress(getCurrentProgress() + by);
  1222. }
  1223. private Handler mHandler;
  1224. public final void setProgress(final int progress) {
  1225. if (mBuilder.progress <= -2)
  1226. throw new IllegalStateException("Cannot use setProgress() on this dialog.");
  1227. mProgress.setProgress(progress);
  1228. mHandler.post(new Runnable() {
  1229. @Override
  1230. public void run() {
  1231. if (mProgressLabel != null) {
  1232. // final int percentage = (int) (((float) getCurrentProgress() / (float) getMaxProgress()) * 100f);
  1233. mProgressLabel.setText(mBuilder.progressPercentFormat.format(
  1234. (float) getCurrentProgress() / (float) getMaxProgress()));
  1235. }
  1236. if (mProgressMinMax != null) {
  1237. mProgressMinMax.setText(String.format(mBuilder.progressNumberFormat,
  1238. getCurrentProgress(), getMaxProgress()));
  1239. }
  1240. }
  1241. });
  1242. }
  1243. public final void setMaxProgress(final int max) {
  1244. if (mBuilder.progress <= -2)
  1245. throw new IllegalStateException("Cannot use setMaxProgress() on this dialog.");
  1246. mProgress.setMax(max);
  1247. }
  1248. public final boolean isIndeterminateProgress() {
  1249. return mBuilder.indeterminateProgress;
  1250. }
  1251. public final int getMaxProgress() {
  1252. if (mProgress == null) return -1;
  1253. return mProgress.getMax();
  1254. }
  1255. /**
  1256. * Change the format of the small text showing the percentage of progress.
  1257. * The default is NumberFormat.getPercentageInstance().
  1258. */
  1259. public final void setProgressPercentFormat(NumberFormat format) {
  1260. mBuilder.progressPercentFormat = format;
  1261. setProgress(getCurrentProgress()); // invalidates display
  1262. }
  1263. /**
  1264. * Change the format of the small text showing current and maximum units of progress.
  1265. * The default is "%1d/%2d".
  1266. */
  1267. public final void setProgressNumberFormat(String format) {
  1268. mBuilder.progressNumberFormat = format;
  1269. setProgress(getCurrentProgress()); // invalidates display
  1270. }
  1271. public final boolean isCancelled() {
  1272. return !isShowing();
  1273. }
  1274. /**
  1275. * Convenience method for getting the currently selected index of a single choice list.
  1276. *
  1277. * @return Currently selected index of a single choice list, or -1 if not showing a single choice list
  1278. */
  1279. public int getSelectedIndex() {
  1280. if (mBuilder.listCallbackSingleChoice != null) {
  1281. return mBuilder.selectedIndex;
  1282. } else {
  1283. return -1;
  1284. }
  1285. }
  1286. /**
  1287. * Convenience method for getting the currently selected indices of a multi choice list
  1288. *
  1289. * @return Currently selected index of a multi choice list, or null if not showing a multi choice list
  1290. */
  1291. @Nullable
  1292. public Integer[] getSelectedIndices() {
  1293. if (mBuilder.listCallbackMultiChoice != null) {
  1294. return selectedIndicesList.toArray(new Integer[selectedIndicesList.size()]);
  1295. } else {
  1296. return null;
  1297. }
  1298. }
  1299. /**
  1300. * Convenience method for setting the currently selected index of a single choice list.
  1301. * This only works if you are not using a custom adapter; if you're using a custom adapter,
  1302. * an IllegalStateException is thrown. Note that this does not call the respective single choice callback.
  1303. *
  1304. * @param index The index of the list item to check.
  1305. */
  1306. @UiThread
  1307. public void setSelectedIndex(int index) {
  1308. mBuilder.selectedIndex = index;
  1309. if (mBuilder.adapter != null && mBuilder.adapter instanceof MaterialDialogAdapter) {
  1310. ((MaterialDialogAdapter) mBuilder.adapter).notifyDataSetChanged();
  1311. } else {
  1312. throw new IllegalStateException("You can only use setSelectedIndex() with the default adapter implementation.");
  1313. }
  1314. }
  1315. /**
  1316. * Convenience method for setting the currently selected indices of a multi choice list.
  1317. * This only works if you are not using a custom adapter; if you're using a custom adapter,
  1318. * an IllegalStateException is thrown. Note that this does not call the respective multi choice callback.
  1319. *
  1320. * @param indices The indices of the list items to check.
  1321. */
  1322. @UiThread
  1323. public void setSelectedIndices(@NonNull Integer[] indices) {
  1324. mBuilder.selectedIndices = indices;
  1325. selectedIndicesList = new ArrayList<>(Arrays.asList(indices));
  1326. if (mBuilder.adapter != null && mBuilder.adapter instanceof MaterialDialogAdapter) {
  1327. ((MaterialDialogAdapter) mBuilder.adapter).notifyDataSetChanged();
  1328. } else {
  1329. throw new IllegalStateException("You can only use setSelectedIndices() with the default adapter implementation.");
  1330. }
  1331. }
  1332. @Override
  1333. public final void onShow(DialogInterface dialog) {
  1334. if (input != null) {
  1335. DialogUtils.showKeyboard(this, mBuilder);
  1336. if (input.getText().length() > 0)
  1337. input.setSelection(input.getText().length());
  1338. }
  1339. super.onShow(dialog);
  1340. }
  1341. protected void setInternalInputCallback() {
  1342. if (input == null) return;
  1343. input.addTextChangedListener(new TextWatcher() {
  1344. @Override
  1345. public void beforeTextChanged(CharSequence s, int start, int count, int after) {
  1346. }
  1347. @Override
  1348. public void onTextChanged(CharSequence s, int start, int before, int count) {
  1349. final int length = s.toString().length();
  1350. boolean emptyDisabled = false;
  1351. if (!mBuilder.inputAllowEmpty) {
  1352. emptyDisabled = length == 0;
  1353. final View positiveAb = getActionButton(DialogAction.POSITIVE);
  1354. positiveAb.setEnabled(!emptyDisabled);
  1355. }
  1356. invalidateInputMinMaxIndicator(length, emptyDisabled);
  1357. if (mBuilder.alwaysCallInputCallback)
  1358. mBuilder.inputCallback.onInput(MaterialDialog.this, s);
  1359. }
  1360. @Override
  1361. public void afterTextChanged(Editable s) {
  1362. }
  1363. });
  1364. }
  1365. protected void invalidateInputMinMaxIndicator(int currentLength, boolean emptyDisabled) {
  1366. if (inputMinMax != null) {
  1367. inputMinMax.setText(currentLength + "/" + mBuilder.inputMaxLength);
  1368. final boolean isDisabled = (emptyDisabled && currentLength == 0) || currentLength > mBuilder.inputMaxLength;
  1369. final int colorText = isDisabled ? mBuilder.inputMaxLengthErrorColor : mBuilder.contentColor;
  1370. final int colorWidget = isDisabled ? mBuilder.inputMaxLengthErrorColor : mBuilder.widgetColor;
  1371. inputMinMax.setTextColor(colorText);
  1372. MDTintHelper.setTint(input, colorWidget);
  1373. final View positiveAb = getActionButton(DialogAction.POSITIVE);
  1374. positiveAb.setEnabled(!isDisabled);
  1375. }
  1376. }
  1377. @Override
  1378. protected void onStop() {
  1379. super.onStop();
  1380. if (input != null)
  1381. DialogUtils.hideKeyboard(this, mBuilder);
  1382. }
  1383. protected enum ListType {
  1384. REGULAR, SINGLE, MULTI;
  1385. public static int getLayoutForType(ListType type) {
  1386. switch (type) {
  1387. case REGULAR:
  1388. return R.layout.md_listitem;
  1389. case SINGLE:
  1390. return R.layout.md_listitem_singlechoice;
  1391. case MULTI:
  1392. return R.layout.md_listitem_multichoice;
  1393. default:
  1394. throw new IllegalArgumentException("Not a valid list type");
  1395. }
  1396. }
  1397. }
  1398. /**
  1399. * A callback used for regular list dialogs.
  1400. */
  1401. public interface ListCallback {
  1402. void onSelection(MaterialDialog dialog, View itemView, int which, CharSequence text);
  1403. }
  1404. /**
  1405. * A callback used for multi choice (check box) list dialogs.
  1406. */
  1407. public interface ListCallbackSingleChoice {
  1408. /**
  1409. * Return true to allow the radio button to be checked, if the alwaysCallSingleChoice() option is used.
  1410. *
  1411. * @param dialog The dialog of which a list item was selected.
  1412. * @param which The index of the item that was selected.
  1413. * @param text The text of the item that was selected.
  1414. * @return True to allow the radio button to be selected.
  1415. */
  1416. boolean onSelection(MaterialDialog dialog, View itemView, int which, CharSequence text);
  1417. }
  1418. /**
  1419. * A callback used for multi choice (check box) list dialogs.
  1420. */
  1421. public interface ListCallbackMultiChoice {
  1422. /**
  1423. * Return true to allow the check box to be checked, if the alwaysCallSingleChoice() option is used.
  1424. *
  1425. * @param dialog The dialog of which a list item was selected.
  1426. * @param which The indices of the items that were selected.
  1427. * @param text The text of the items that were selected.
  1428. * @return True to allow the checkbox to be selected.
  1429. */
  1430. boolean onSelection(MaterialDialog dialog, Integer[] which, CharSequence[] text);
  1431. }
  1432. /**
  1433. * Override these as needed, so no needing to sub empty methods from an interface
  1434. */
  1435. public static abstract class ButtonCallback {
  1436. public void onAny(MaterialDialog dialog) {
  1437. }
  1438. public void onPositive(MaterialDialog dialog) {
  1439. }
  1440. public void onNegative(MaterialDialog dialog) {
  1441. }
  1442. public void onNeutral(MaterialDialog dialog) {
  1443. }
  1444. public ButtonCallback() {
  1445. super();
  1446. }
  1447. @Override
  1448. protected final Object clone() throws CloneNotSupportedException {
  1449. return super.clone();
  1450. }
  1451. @Override
  1452. public final boolean equals(Object o) {
  1453. return super.equals(o);
  1454. }
  1455. @Override
  1456. protected final void finalize() throws Throwable {
  1457. super.finalize();
  1458. }
  1459. @Override
  1460. public final int hashCode() {
  1461. return super.hashCode();
  1462. }
  1463. @Override
  1464. public final String toString() {
  1465. return super.toString();
  1466. }
  1467. }
  1468. public interface InputCallback {
  1469. void onInput(MaterialDialog dialog, CharSequence input);
  1470. }
  1471. }