MaterialDialog.java 78 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974
  1. package com.afollestad.materialdialogs;
  2. import android.annotation.SuppressLint;
  3. import android.app.AlertDialog;
  4. import android.content.Context;
  5. import android.content.DialogInterface;
  6. import android.content.res.ColorStateList;
  7. import android.content.res.Resources;
  8. import android.content.res.TypedArray;
  9. import android.graphics.Color;
  10. import android.graphics.PorterDuff;
  11. import android.graphics.Rect;
  12. import android.graphics.Typeface;
  13. import android.graphics.drawable.Drawable;
  14. import android.os.Build;
  15. import android.os.Looper;
  16. import android.support.annotation.ArrayRes;
  17. import android.support.annotation.AttrRes;
  18. import android.support.annotation.ColorRes;
  19. import android.support.annotation.DimenRes;
  20. import android.support.annotation.DrawableRes;
  21. import android.support.annotation.LayoutRes;
  22. import android.support.annotation.NonNull;
  23. import android.support.annotation.Nullable;
  24. import android.support.annotation.StringRes;
  25. import android.text.method.LinkMovementMethod;
  26. import android.util.Log;
  27. import android.view.ContextThemeWrapper;
  28. import android.view.Gravity;
  29. import android.view.LayoutInflater;
  30. import android.view.View;
  31. import android.view.ViewGroup;
  32. import android.view.ViewTreeObserver;
  33. import android.view.WindowManager;
  34. import android.webkit.WebView;
  35. import android.widget.AdapterView;
  36. import android.widget.ArrayAdapter;
  37. import android.widget.Button;
  38. import android.widget.CheckBox;
  39. import android.widget.EditText;
  40. import android.widget.FrameLayout;
  41. import android.widget.ImageView;
  42. import android.widget.LinearLayout;
  43. import android.widget.ListAdapter;
  44. import android.widget.ListView;
  45. import android.widget.ProgressBar;
  46. import android.widget.RadioButton;
  47. import android.widget.RelativeLayout;
  48. import android.widget.ScrollView;
  49. import android.widget.TextView;
  50. import com.afollestad.materialdialogs.base.DialogBase;
  51. import com.afollestad.materialdialogs.util.DialogUtils;
  52. import com.afollestad.materialdialogs.util.RecyclerUtil;
  53. import com.afollestad.materialdialogs.util.TypefaceHelper;
  54. import java.util.ArrayList;
  55. import java.util.Arrays;
  56. import java.util.List;
  57. /**
  58. * @author Aidan Follestad (afollestad)
  59. */
  60. public class MaterialDialog extends DialogBase implements View.OnClickListener, AdapterView.OnItemClickListener {
  61. protected final View view;
  62. protected final Builder mBuilder;
  63. protected ListView listView;
  64. protected ImageView icon;
  65. protected TextView title;
  66. protected View titleFrame;
  67. protected FrameLayout customViewFrame;
  68. protected ProgressBar mProgress;
  69. protected TextView mProgressLabel;
  70. protected TextView content;
  71. protected View positiveButton;
  72. protected View neutralButton;
  73. protected View negativeButton;
  74. protected boolean isStacked;
  75. protected final int defaultItemColor;
  76. protected ListType listType;
  77. protected List<Integer> selectedIndicesList;
  78. private static ContextThemeWrapper getTheme(Builder builder) {
  79. TypedArray a = builder.context.getTheme().obtainStyledAttributes(new int[]{R.attr.md_dark_theme});
  80. boolean darkTheme = builder.theme == Theme.DARK;
  81. if (!darkTheme) {
  82. try {
  83. darkTheme = a.getBoolean(0, false);
  84. builder.theme = darkTheme ? Theme.DARK : Theme.LIGHT;
  85. } finally {
  86. a.recycle();
  87. }
  88. }
  89. return new ContextThemeWrapper(builder.context, darkTheme ? R.style.MD_Dark : R.style.MD_Light);
  90. }
  91. @SuppressLint("InflateParams")
  92. protected MaterialDialog(Builder builder) {
  93. super(getTheme(builder));
  94. mBuilder = builder;
  95. if (!mBuilder.useCustomFonts) {
  96. if (mBuilder.mediumFont == null)
  97. mBuilder.mediumFont = TypefaceHelper.get(getContext(), "Roboto-Medium");
  98. if (mBuilder.regularFont == null)
  99. mBuilder.regularFont = TypefaceHelper.get(getContext(), "Roboto-Regular");
  100. }
  101. final LayoutInflater inflater = LayoutInflater.from(mBuilder.context);
  102. this.view = inflater.inflate(R.layout.md_dialog, null);
  103. this.setCancelable(builder.cancelable);
  104. if (mBuilder.backgroundColor == 0)
  105. mBuilder.backgroundColor = DialogUtils.resolveColor(mBuilder.context, R.attr.md_background_color);
  106. if (mBuilder.backgroundColor != 0)
  107. this.view.setBackgroundColor(mBuilder.backgroundColor);
  108. mBuilder.positiveColor = DialogUtils.resolveColor(mBuilder.context, R.attr.md_positive_color, mBuilder.positiveColor);
  109. mBuilder.neutralColor = DialogUtils.resolveColor(mBuilder.context, R.attr.md_neutral_color, mBuilder.neutralColor);
  110. mBuilder.negativeColor = DialogUtils.resolveColor(mBuilder.context, R.attr.md_negative_color, mBuilder.negativeColor);
  111. title = (TextView) view.findViewById(R.id.title);
  112. icon = (ImageView) view.findViewById(R.id.icon);
  113. titleFrame = view.findViewById(R.id.titleFrame);
  114. content = (TextView) view.findViewById(R.id.content);
  115. if (mBuilder.mIndeterminateProgress || mBuilder.mProgress > -2) {
  116. mBuilder.customView = inflater.inflate(mBuilder.mIndeterminateProgress ? R.layout.md_progress_dialog_indeterminate
  117. : R.layout.md_progress_dialog, (ViewGroup) this.view, false);
  118. mProgress = (ProgressBar) mBuilder.customView.findViewById(android.R.id.progress);
  119. content = (TextView) mBuilder.customView.findViewById(android.R.id.message);
  120. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
  121. Drawable indDraw = mProgress.getIndeterminateDrawable();
  122. if (indDraw != null) {
  123. indDraw.setColorFilter(mBuilder.accentColor, PorterDuff.Mode.SRC_ATOP);
  124. mProgress.setIndeterminateDrawable(indDraw);
  125. }
  126. Drawable regDraw = mProgress.getProgressDrawable();
  127. if (regDraw != null) {
  128. regDraw.setColorFilter(mBuilder.accentColor, PorterDuff.Mode.SRC_ATOP);
  129. mProgress.setProgressDrawable(regDraw);
  130. }
  131. }
  132. if (!mBuilder.mIndeterminateProgress) {
  133. mProgress.setProgress(0);
  134. mProgress.setMax(mBuilder.mProgressMax);
  135. mProgressLabel = (TextView) mBuilder.customView.findViewById(R.id.label);
  136. mProgressLabel.setText("0%");
  137. }
  138. int bottomPadding = (int) getContext().getResources().getDimension(R.dimen.md_dialog_frame_margin);
  139. int topPadding = builder.title == null ? bottomPadding
  140. : (int) getContext().getResources().getDimension(R.dimen.md_progressdialog_paddingwithtitle);
  141. mBuilder.customView.setPadding(mBuilder.customView.getPaddingLeft(),
  142. topPadding,
  143. mBuilder.customView.getPaddingRight(),
  144. bottomPadding);
  145. }
  146. content.setText(builder.content);
  147. content.setMovementMethod(new LinkMovementMethod());
  148. setTypeface(content, mBuilder.regularFont);
  149. content.setLineSpacing(0f, builder.contentLineSpacingMultiplier);
  150. if (mBuilder.positiveColor == 0) {
  151. content.setLinkTextColor(DialogUtils.resolveColor(getContext(), android.R.attr.textColorPrimary));
  152. } else {
  153. content.setLinkTextColor(mBuilder.positiveColor);
  154. }
  155. title.setGravity(gravityIntToGravity(builder.titleGravity));
  156. if (builder.contentColorSet) {
  157. content.setTextColor(builder.contentColor);
  158. } else {
  159. final int fallback = DialogUtils.resolveColor(getContext(), android.R.attr.textColorSecondary);
  160. final int contentColor = DialogUtils.resolveColor(getContext(), R.attr.md_content_color, fallback);
  161. content.setTextColor(contentColor);
  162. }
  163. if (builder.itemColorSet) {
  164. defaultItemColor = builder.itemColor;
  165. } else if (builder.theme == Theme.LIGHT) {
  166. defaultItemColor = Color.BLACK;
  167. } else {
  168. defaultItemColor = Color.WHITE;
  169. }
  170. if (mBuilder.customView != null) {
  171. invalidateCustomViewAssociations();
  172. FrameLayout frame = (FrameLayout) view.findViewById(R.id.customViewFrame);
  173. customViewFrame = frame;
  174. View innerView = mBuilder.customView;
  175. if (mBuilder.wrapCustomViewInScroll) {
  176. /* Apply the frame padding to the content, this allows the ScrollView to draw it's
  177. overscroll glow without clipping */
  178. final Resources r = getContext().getResources();
  179. final int framePadding = r.getDimensionPixelSize(R.dimen.md_dialog_frame_margin);
  180. final ScrollView sv = new ScrollView(getContext());
  181. int paddingTop;
  182. int paddingBottom;
  183. if (titleFrame.getVisibility() != View.GONE)
  184. paddingTop = r.getDimensionPixelSize(R.dimen.md_content_vertical_padding);
  185. else
  186. paddingTop = r.getDimensionPixelSize(R.dimen.md_dialog_frame_margin);
  187. if (hasActionButtons())
  188. paddingBottom = r.getDimensionPixelSize(R.dimen.md_content_vertical_padding);
  189. else
  190. paddingBottom = r.getDimensionPixelSize(R.dimen.md_dialog_frame_margin);
  191. sv.setClipToPadding(false);
  192. if (innerView instanceof EditText) {
  193. // Setting padding to an EditText causes visual errors, set it to the parent instead
  194. sv.setPadding(framePadding, paddingTop, framePadding, paddingBottom);
  195. } else {
  196. // Setting padding to scroll view pushes the scroll bars out, don't do it if not necessary (like above)
  197. sv.setPadding(0, paddingTop, 0, paddingBottom);
  198. innerView.setPadding(framePadding, 0, framePadding, 0);
  199. }
  200. sv.addView(innerView, new ScrollView.LayoutParams(
  201. ViewGroup.LayoutParams.MATCH_PARENT,
  202. ViewGroup.LayoutParams.WRAP_CONTENT));
  203. innerView = sv;
  204. }
  205. frame.addView(innerView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
  206. ViewGroup.LayoutParams.WRAP_CONTENT));
  207. } else {
  208. invalidateCustomViewAssociations();
  209. }
  210. if (mBuilder.listCallbackMulti != null)
  211. selectedIndicesList = new ArrayList<>();
  212. boolean adapterProvided = mBuilder.adapter != null;
  213. if (mBuilder.items != null && mBuilder.items.length > 0 || adapterProvided) {
  214. listView = (ListView) view.findViewById(R.id.contentListView);
  215. listView.setSelector(getListSelector());
  216. if (!adapterProvided) {
  217. // Determine list type
  218. if (mBuilder.listCallbackSingle != null) {
  219. listType = ListType.SINGLE;
  220. } else if (mBuilder.listCallbackMulti != null) {
  221. listType = ListType.MULTI;
  222. if (mBuilder.selectedIndices != null) {
  223. selectedIndicesList = new ArrayList<>(Arrays.asList(mBuilder.selectedIndices));
  224. }
  225. } else {
  226. listType = ListType.REGULAR;
  227. }
  228. mBuilder.adapter = new MaterialDialogAdapter(mBuilder.context,
  229. ListType.getLayoutForType(listType), R.id.title, mBuilder.items);
  230. }
  231. }
  232. if (builder.icon != null) {
  233. icon.setVisibility(View.VISIBLE);
  234. icon.setImageDrawable(builder.icon);
  235. } else {
  236. Drawable d = DialogUtils.resolveDrawable(mBuilder.context, R.attr.md_icon);
  237. if (d != null) {
  238. icon.setVisibility(View.VISIBLE);
  239. icon.setImageDrawable(d);
  240. } else {
  241. icon.setVisibility(View.GONE);
  242. }
  243. }
  244. int maxIconSize = builder.maxIconSize;
  245. if (maxIconSize == -1) {
  246. maxIconSize = DialogUtils.resolveDimension(mBuilder.context, R.attr.md_icon_max_size);
  247. }
  248. if (builder.limitIconToDefaultSize ||
  249. DialogUtils.resolveBoolean(mBuilder.context, R.attr.md_icon_limit_icon_to_default_size)) {
  250. maxIconSize = mBuilder.context.getResources().getDimensionPixelSize(R.dimen.md_icon_max_size);
  251. }
  252. if (maxIconSize > -1) {
  253. icon.setAdjustViewBounds(true);
  254. icon.setMaxHeight(maxIconSize);
  255. icon.setMaxWidth(maxIconSize);
  256. icon.requestLayout();
  257. }
  258. if (builder.title == null) {
  259. titleFrame.setVisibility(View.GONE);
  260. } else {
  261. title.setText(builder.title);
  262. setTypeface(title, mBuilder.mediumFont);
  263. if (builder.titleColorSet) {
  264. title.setTextColor(builder.titleColor);
  265. } else {
  266. final int fallback = DialogUtils.resolveColor(getContext(), android.R.attr.textColorPrimary);
  267. title.setTextColor(DialogUtils.resolveColor(getContext(), R.attr.md_title_color, fallback));
  268. }
  269. content.setGravity(gravityIntToGravity(builder.contentGravity));
  270. }
  271. if (builder.showListener != null) {
  272. setOnShowListener(builder.showListener);
  273. }
  274. if (builder.cancelListener != null) {
  275. setOnCancelListener(builder.cancelListener);
  276. }
  277. if (builder.dismissListener != null) {
  278. setOnDismissListener(builder.dismissListener);
  279. }
  280. if (builder.keyListener != null) {
  281. setOnKeyListener(builder.keyListener);
  282. }
  283. updateFramePadding();
  284. invalidateActions();
  285. setOnShowListenerInternal();
  286. setViewInternal(view);
  287. view.getViewTreeObserver().addOnGlobalLayoutListener(
  288. new ViewTreeObserver.OnGlobalLayoutListener() {
  289. @Override
  290. public void onGlobalLayout() {
  291. if (view.getMeasuredWidth() > 0) {
  292. invalidateCustomViewAssociations();
  293. }
  294. }
  295. });
  296. if (builder.theme == Theme.LIGHT && Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
  297. setInverseBackgroundForced(true);
  298. if (!builder.titleColorSet)
  299. title.setTextColor(Color.BLACK);
  300. if (!builder.contentColorSet)
  301. content.setTextColor(Color.BLACK);
  302. }
  303. }
  304. private static int gravityIntToGravity(GravityEnum gravity) {
  305. switch (gravity) {
  306. case CENTER:
  307. return Gravity.CENTER_HORIZONTAL;
  308. case END:
  309. return Gravity.END;
  310. default:
  311. return Gravity.START;
  312. }
  313. }
  314. @Override
  315. public void onShow(DialogInterface dialog) {
  316. super.onShow(dialog); // calls any external show listeners
  317. checkIfStackingNeeded();
  318. invalidateCustomViewAssociations();
  319. }
  320. /**
  321. * To account for scrolling content and overscroll glows, the frame padding/margins sometimes
  322. * must be set on inner views. This is dependent on the visibility of the title bar and action
  323. * buttons. This method determines where the padding or margins are needed and applies them.
  324. */
  325. private void updateFramePadding() {
  326. Resources r = getContext().getResources();
  327. int framePadding = r.getDimensionPixelSize(R.dimen.md_dialog_frame_margin);
  328. View contentScrollView = view.findViewById(R.id.contentScrollView);
  329. int paddingTop = contentScrollView.getPaddingTop();
  330. int paddingBottom = contentScrollView.getPaddingBottom();
  331. if (!hasActionButtons())
  332. paddingBottom = framePadding;
  333. if (titleFrame.getVisibility() == View.GONE)
  334. paddingTop = framePadding;
  335. contentScrollView.setPadding(contentScrollView.getPaddingLeft(), paddingTop,
  336. contentScrollView.getPaddingRight(), paddingBottom);
  337. if (listView != null) {
  338. // Padding below title is reduced for divider.
  339. final int titlePaddingBottom = (int) mBuilder.context.getResources().getDimension(R.dimen.md_title_frame_margin_bottom_list);
  340. titleFrame.setPadding(titleFrame.getPaddingLeft(),
  341. titleFrame.getPaddingTop(),
  342. titleFrame.getPaddingRight(),
  343. titlePaddingBottom);
  344. }
  345. }
  346. /**
  347. * Invalidates visibility of views for the presence of a custom view or list content
  348. */
  349. private void invalidateCustomViewAssociations() {
  350. if (view.getMeasuredWidth() == 0) {
  351. return;
  352. }
  353. View contentScrollView = view.findViewById(R.id.contentScrollView);
  354. final int contentHorizontalPadding = (int) mBuilder.context.getResources()
  355. .getDimension(R.dimen.md_dialog_frame_margin);
  356. content.setPadding(contentHorizontalPadding, 0, contentHorizontalPadding, 0);
  357. if (mBuilder.customView != null) {
  358. contentScrollView.setVisibility(View.GONE);
  359. customViewFrame.setVisibility(View.VISIBLE);
  360. boolean topScroll = canViewOrChildScroll(customViewFrame.getChildAt(0), false);
  361. boolean bottomScroll = canViewOrChildScroll(customViewFrame.getChildAt(0), true);
  362. setDividerVisibility(topScroll, bottomScroll);
  363. } else if ((mBuilder.items != null && mBuilder.items.length > 0) || mBuilder.adapter != null) {
  364. contentScrollView.setVisibility(mBuilder.content != null
  365. && mBuilder.content.toString().trim().length() > 0 ? View.VISIBLE : View.GONE);
  366. boolean canScroll = titleFrame.getVisibility() == View.VISIBLE &&
  367. (canListViewScroll() || canContentScroll());
  368. setDividerVisibility(canScroll, canScroll);
  369. } else {
  370. contentScrollView.setVisibility(View.VISIBLE);
  371. boolean canScroll = canContentScroll();
  372. if (canScroll) {
  373. final int contentVerticalPadding = (int) mBuilder.context.getResources()
  374. .getDimension(R.dimen.md_title_frame_margin_bottom);
  375. content.setPadding(contentHorizontalPadding, contentVerticalPadding,
  376. contentHorizontalPadding, contentVerticalPadding);
  377. // Same effect as when there's a ListView. Padding below title is reduced for divider.
  378. final int titlePaddingBottom = (int) mBuilder.context.getResources().getDimension(R.dimen.md_title_frame_margin_bottom_list);
  379. titleFrame.setPadding(titleFrame.getPaddingLeft(),
  380. titleFrame.getPaddingTop(),
  381. titleFrame.getPaddingRight(),
  382. titlePaddingBottom);
  383. }
  384. setDividerVisibility(canScroll, canScroll);
  385. }
  386. }
  387. /**
  388. * Set the visibility of the bottom divider and adjusts the layout margin,
  389. * when the divider is visible the button bar bottom margin (8dp from
  390. * http://www.google.com/design/spec/components/dialogs.html#dialogs-specs )
  391. * is removed as it makes the button bar look off balanced with different amounts of padding
  392. * above and below the divider.
  393. */
  394. private void setDividerVisibility(boolean topVisible, boolean bottomVisible) {
  395. topVisible = topVisible && titleFrame.getVisibility() == View.VISIBLE;
  396. bottomVisible = bottomVisible && hasActionButtons();
  397. if (mBuilder.dividerColor == 0)
  398. mBuilder.dividerColor = DialogUtils.resolveColor(mBuilder.context, R.attr.md_divider_color);
  399. if (mBuilder.dividerColor == 0)
  400. mBuilder.dividerColor = DialogUtils.resolveColor(getContext(), R.attr.md_divider);
  401. View titleBarDivider = view.findViewById(R.id.titleBarDivider);
  402. if (topVisible) {
  403. titleBarDivider.setVisibility(View.VISIBLE);
  404. titleBarDivider.setBackgroundColor(mBuilder.dividerColor);
  405. } else {
  406. titleBarDivider.setVisibility(View.GONE);
  407. }
  408. View buttonBarDivider = view.findViewById(R.id.buttonBarDivider);
  409. if (bottomVisible) {
  410. buttonBarDivider.setVisibility(View.VISIBLE);
  411. buttonBarDivider.setBackgroundColor(mBuilder.dividerColor);
  412. setVerticalMargins(view.findViewById(R.id.buttonStackedFrame), 0, 0);
  413. setVerticalMargins(view.findViewById(R.id.buttonDefaultFrame), 0, 0);
  414. } else {
  415. Resources r = getContext().getResources();
  416. buttonBarDivider.setVisibility(View.GONE);
  417. final int bottomMargin = r.getDimensionPixelSize(R.dimen.md_button_frame_vertical_padding);
  418. /* Only enable the bottom margin if our available window space can hold the margin,
  419. we don't want to enable this and cause the content to scroll, which is bad
  420. experience itself but it also causes a vibrating window as this will keep getting
  421. enabled/disabled over and over again.
  422. */
  423. Rect maxWindowFrame = new Rect();
  424. getWindow().getDecorView().getWindowVisibleDisplayFrame(maxWindowFrame);
  425. int currentHeight = getWindow().getDecorView().getMeasuredHeight();
  426. if (currentHeight + bottomMargin < maxWindowFrame.height()) {
  427. setVerticalMargins(view.findViewById(R.id.buttonStackedFrame),
  428. bottomMargin, bottomMargin);
  429. setVerticalMargins(view.findViewById(R.id.buttonDefaultFrame),
  430. bottomMargin, bottomMargin);
  431. }
  432. }
  433. }
  434. /**
  435. * Constructs the dialog's list content and sets up click listeners.
  436. */
  437. private void invalidateList() {
  438. if ((mBuilder.items == null || mBuilder.items.length == 0) && mBuilder.adapter == null)
  439. return;
  440. // Hide content
  441. view.findViewById(R.id.contentScrollView).setVisibility(mBuilder.content != null
  442. && mBuilder.content.toString().trim().length() > 0 ? View.VISIBLE : View.GONE);
  443. view.findViewById(R.id.customViewFrame).setVisibility(View.GONE);
  444. // Set up list with adapter
  445. FrameLayout listViewContainer = (FrameLayout) view.findViewById(R.id.contentListViewFrame);
  446. listViewContainer.setVisibility(View.VISIBLE);
  447. listView.setAdapter(mBuilder.adapter);
  448. listView.setOnItemClickListener(this);
  449. }
  450. /**
  451. * Find the view touching the bottom of this ViewGroup. Non visible children are ignored,
  452. * however getChildDrawingOrder is not taking into account for simplicity and because it behaves
  453. * inconsistently across platform versions.
  454. *
  455. * @return View touching the bottom of this viewgroup or null
  456. */
  457. @Nullable
  458. private static View getBottomView(ViewGroup viewGroup) {
  459. View bottomView = null;
  460. for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) {
  461. View child = viewGroup.getChildAt(i);
  462. if (child.getVisibility() == View.VISIBLE && child.getBottom() == viewGroup.getBottom()) {
  463. bottomView = child;
  464. break;
  465. }
  466. }
  467. return bottomView;
  468. }
  469. @Nullable
  470. private static View getTopView(ViewGroup viewGroup) {
  471. View topView = null;
  472. for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) {
  473. View child = viewGroup.getChildAt(i);
  474. if (child.getVisibility() == View.VISIBLE && child.getTop() == viewGroup.getTop()) {
  475. topView = child;
  476. break;
  477. }
  478. }
  479. return topView;
  480. }
  481. private static boolean canViewOrChildScroll(View view, boolean atBottom) {
  482. if (view == null || !(view instanceof ViewGroup)) {
  483. return false;
  484. }
  485. /* Is the bottom view something that scrolls? */
  486. if (view instanceof ScrollView) {
  487. ScrollView sv = (ScrollView) view;
  488. if (sv.getChildCount() == 0)
  489. return false;
  490. final int childHeight = sv.getChildAt(0).getMeasuredHeight();
  491. return sv.getMeasuredHeight() < childHeight;
  492. } else if (view instanceof AdapterView) {
  493. return canAdapterViewScroll((AdapterView) view);
  494. } else if (view instanceof WebView) {
  495. return canWebViewScroll((WebView) view);
  496. } else if (isRecyclerView(view)) {
  497. return RecyclerUtil.canRecyclerViewScroll(view);
  498. } else {
  499. if (atBottom) {
  500. return canViewOrChildScroll(getBottomView((ViewGroup) view), true);
  501. } else {
  502. return canViewOrChildScroll(getTopView((ViewGroup) view), false);
  503. }
  504. }
  505. }
  506. private static boolean isRecyclerView(View view) {
  507. boolean isRecyclerView = false;
  508. try {
  509. Class.forName("android.support.v7.widget.RecyclerView");
  510. // We got here, so now we can safely check
  511. isRecyclerView = RecyclerUtil.isRecyclerView(view);
  512. } catch (ClassNotFoundException ignored) {
  513. }
  514. return isRecyclerView;
  515. }
  516. private static boolean canWebViewScroll(WebView view) {
  517. return view.getMeasuredHeight() > view.getContentHeight();
  518. }
  519. private static boolean canAdapterViewScroll(AdapterView lv) {
  520. /* Force it to layout it's children */
  521. if (lv.getLastVisiblePosition() == -1)
  522. return false;
  523. /* We can scroll if the first or last item is not visible */
  524. boolean firstItemVisible = lv.getFirstVisiblePosition() == 0;
  525. boolean lastItemVisible = lv.getLastVisiblePosition() == lv.getCount() - 1;
  526. if (firstItemVisible && lastItemVisible) {
  527. /* Or the first item's top is above or own top */
  528. if (lv.getChildAt(0).getTop() < lv.getPaddingTop())
  529. return true;
  530. /* or the last item's bottom is beyond our own bottom */
  531. return lv.getChildAt(lv.getChildCount() - 1).getBottom() >
  532. lv.getHeight() - lv.getPaddingBottom();
  533. }
  534. return true;
  535. }
  536. private boolean canListViewScroll() {
  537. return canAdapterViewScroll(listView);
  538. }
  539. @Override
  540. public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
  541. if (listType != null) {
  542. // MaterialDialogAdapter, used for built-in adapters
  543. if (listType == ListType.MULTI) {
  544. // Keep our selected items up to date
  545. boolean isChecked = !((CheckBox) view.findViewById(R.id.control)).isChecked(); // Inverted because the view's click listener is called before the check is toggled
  546. boolean previouslySelected = selectedIndicesList.contains(position);
  547. if (isChecked) {
  548. if (!previouslySelected) {
  549. selectedIndicesList.add(position);
  550. }
  551. } else if (previouslySelected) {
  552. selectedIndicesList.remove(Integer.valueOf(position));
  553. }
  554. } else if (listType == ListType.SINGLE) {
  555. // Keep our selected item up to date
  556. if (mBuilder.selectedIndex != position) {
  557. mBuilder.selectedIndex = position;
  558. ((MaterialDialogAdapter) mBuilder.adapter).notifyDataSetChanged();
  559. }
  560. }
  561. onClick(view);
  562. } else {
  563. // Custom adapter
  564. if (mBuilder.listCallbackCustom != null) {
  565. CharSequence text = null;
  566. if (view instanceof TextView)
  567. text = ((TextView) view).getText();
  568. mBuilder.listCallbackCustom.onSelection(this, view, position, text);
  569. }
  570. }
  571. }
  572. public static class NotImplementedException extends Error {
  573. public NotImplementedException(String message) {
  574. super(message);
  575. }
  576. }
  577. public static class DialogException extends WindowManager.BadTokenException {
  578. public DialogException(String message) {
  579. super(message);
  580. }
  581. }
  582. /**
  583. * Detects whether or not the content TextView can be scrolled.
  584. */
  585. private boolean canContentScroll() {
  586. final ScrollView scrollView = (ScrollView) view.findViewById(R.id.contentScrollView);
  587. final int childHeight = content.getMeasuredHeight();
  588. return scrollView.getMeasuredHeight() < childHeight;
  589. }
  590. /**
  591. * Measures the action button's and their text to decide whether or not the button should be stacked.
  592. */
  593. private void checkIfStackingNeeded() {
  594. if (numberOfActionButtons() <= 1) {
  595. return;
  596. } else if (mBuilder.forceStacking) {
  597. isStacked = true;
  598. invalidateActions();
  599. return;
  600. }
  601. isStacked = false;
  602. int buttonsWidth = 0;
  603. positiveButton.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
  604. neutralButton.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
  605. negativeButton.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
  606. if (mBuilder.positiveText != null) buttonsWidth += positiveButton.getMeasuredWidth();
  607. if (mBuilder.neutralText != null) buttonsWidth += neutralButton.getMeasuredWidth();
  608. if (mBuilder.negativeText != null) buttonsWidth += negativeButton.getMeasuredWidth();
  609. final int buttonFrameWidth = view.findViewById(R.id.buttonDefaultFrame).getWidth();
  610. isStacked = buttonsWidth > buttonFrameWidth;
  611. invalidateActions();
  612. }
  613. private Drawable getListSelector() {
  614. if (mBuilder.listSelector != 0)
  615. return mBuilder.context.getResources().getDrawable(mBuilder.listSelector);
  616. final Drawable d = DialogUtils.resolveDrawable(mBuilder.context, R.attr.md_list_selector);
  617. if (d != null) return d;
  618. return DialogUtils.resolveDrawable(getContext(), R.attr.md_list_selector);
  619. }
  620. private Drawable getButtonSelector(DialogAction which) {
  621. if (isStacked) {
  622. if (mBuilder.btnSelectorStacked != 0)
  623. return mBuilder.context.getResources().getDrawable(mBuilder.btnSelectorStacked);
  624. final Drawable d = DialogUtils.resolveDrawable(mBuilder.context, R.attr.md_btn_stacked_selector);
  625. if (d != null) return d;
  626. return DialogUtils.resolveDrawable(getContext(), R.attr.md_btn_stacked_selector);
  627. } else {
  628. switch (which) {
  629. default: {
  630. if (mBuilder.btnSelectorPositive != 0)
  631. return mBuilder.context.getResources().getDrawable(mBuilder.btnSelectorPositive);
  632. final Drawable d = DialogUtils.resolveDrawable(mBuilder.context, R.attr.md_btn_positive_selector);
  633. if (d != null) return d;
  634. return DialogUtils.resolveDrawable(getContext(), R.attr.md_btn_positive_selector);
  635. }
  636. case NEUTRAL: {
  637. if (mBuilder.btnSelectorNeutral != 0)
  638. return mBuilder.context.getResources().getDrawable(mBuilder.btnSelectorNeutral);
  639. final Drawable d = DialogUtils.resolveDrawable(mBuilder.context, R.attr.md_btn_neutral_selector);
  640. if (d != null) return d;
  641. return DialogUtils.resolveDrawable(getContext(), R.attr.md_btn_neutral_selector);
  642. }
  643. case NEGATIVE: {
  644. if (mBuilder.btnSelectorNegative != 0)
  645. return mBuilder.context.getResources().getDrawable(mBuilder.btnSelectorNegative);
  646. final Drawable d = DialogUtils.resolveDrawable(mBuilder.context, R.attr.md_btn_negative_selector);
  647. if (d != null) return d;
  648. return DialogUtils.resolveDrawable(getContext(), R.attr.md_btn_negative_selector);
  649. }
  650. }
  651. }
  652. }
  653. /**
  654. * Invalidates the positive/neutral/negative action buttons. Decides whether they should be visible
  655. * and sets their properties (such as height, text color, etc.).
  656. */
  657. private boolean invalidateActions() {
  658. if (!hasActionButtons()) {
  659. // If the dialog is a plain list dialog, no buttons are shown.
  660. view.findViewById(R.id.buttonDefaultFrame).setVisibility(View.GONE);
  661. view.findViewById(R.id.buttonStackedFrame).setVisibility(View.GONE);
  662. invalidateList();
  663. return false;
  664. }
  665. if (isStacked) {
  666. view.findViewById(R.id.buttonDefaultFrame).setVisibility(View.GONE);
  667. view.findViewById(R.id.buttonStackedFrame).setVisibility(View.VISIBLE);
  668. } else {
  669. view.findViewById(R.id.buttonDefaultFrame).setVisibility(View.VISIBLE);
  670. view.findViewById(R.id.buttonStackedFrame).setVisibility(View.GONE);
  671. }
  672. positiveButton = view.findViewById(
  673. isStacked ? R.id.buttonStackedPositive : R.id.buttonDefaultPositive);
  674. if (mBuilder.positiveText != null) {
  675. TextView positiveTextView = (TextView) ((FrameLayout) positiveButton).getChildAt(0);
  676. setTypeface(positiveTextView, mBuilder.mediumFont);
  677. positiveTextView.setText(mBuilder.positiveText);
  678. positiveTextView.setTextColor(getActionTextStateList(mBuilder.positiveColor));
  679. setBackgroundCompat(positiveButton, getButtonSelector(DialogAction.POSITIVE));
  680. positiveButton.setTag(POSITIVE);
  681. positiveButton.setOnClickListener(this);
  682. if (isStacked)
  683. positiveTextView.setGravity(gravityIntToGravity(mBuilder.btnStackedGravity));
  684. } else {
  685. positiveButton.setVisibility(View.GONE);
  686. }
  687. neutralButton = view.findViewById(
  688. isStacked ? R.id.buttonStackedNeutral : R.id.buttonDefaultNeutral);
  689. if (mBuilder.neutralText != null) {
  690. TextView neutralTextView = (TextView) ((FrameLayout) neutralButton).getChildAt(0);
  691. setTypeface(neutralTextView, mBuilder.mediumFont);
  692. neutralButton.setVisibility(View.VISIBLE);
  693. neutralTextView.setTextColor(getActionTextStateList(mBuilder.neutralColor));
  694. setBackgroundCompat(neutralButton, getButtonSelector(DialogAction.NEUTRAL));
  695. neutralTextView.setText(mBuilder.neutralText);
  696. neutralButton.setTag(NEUTRAL);
  697. neutralButton.setOnClickListener(this);
  698. if (isStacked)
  699. neutralTextView.setGravity(gravityIntToGravity(mBuilder.btnStackedGravity));
  700. } else {
  701. neutralButton.setVisibility(View.GONE);
  702. }
  703. negativeButton = view.findViewById(
  704. isStacked ? R.id.buttonStackedNegative : R.id.buttonDefaultNegative);
  705. if (mBuilder.negativeText != null) {
  706. TextView negativeTextView = (TextView) ((FrameLayout) negativeButton).getChildAt(0);
  707. setTypeface(negativeTextView, mBuilder.mediumFont);
  708. negativeButton.setVisibility(View.VISIBLE);
  709. negativeTextView.setTextColor(getActionTextStateList(mBuilder.negativeColor));
  710. setBackgroundCompat(negativeButton, getButtonSelector(DialogAction.NEGATIVE));
  711. negativeTextView.setText(mBuilder.negativeText);
  712. negativeButton.setTag(NEGATIVE);
  713. negativeButton.setOnClickListener(this);
  714. if (!isStacked) {
  715. RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
  716. RelativeLayout.LayoutParams.WRAP_CONTENT, (int) getContext().getResources().getDimension(R.dimen.md_button_height));
  717. if (mBuilder.positiveText != null) {
  718. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
  719. params.addRule(RelativeLayout.START_OF, R.id.buttonDefaultPositive);
  720. } else {
  721. params.addRule(RelativeLayout.LEFT_OF, R.id.buttonDefaultPositive);
  722. }
  723. } else {
  724. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
  725. params.addRule(RelativeLayout.ALIGN_PARENT_END);
  726. } else {
  727. params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
  728. }
  729. }
  730. negativeButton.setLayoutParams(params);
  731. } else {
  732. negativeTextView.setGravity(gravityIntToGravity(mBuilder.btnStackedGravity));
  733. }
  734. } else {
  735. negativeButton.setVisibility(View.GONE);
  736. }
  737. invalidateList();
  738. return true;
  739. }
  740. private void sendSingleChoiceCallback(View v) {
  741. CharSequence text = null;
  742. if (mBuilder.selectedIndex >= 0) {
  743. text = mBuilder.items[mBuilder.selectedIndex];
  744. }
  745. mBuilder.listCallbackSingle.onSelection(this, v, mBuilder.selectedIndex, text);
  746. }
  747. private void sendMultichoiceCallback() {
  748. List<CharSequence> selectedTitles = new ArrayList<>();
  749. for (Integer i : selectedIndicesList) {
  750. selectedTitles.add(mBuilder.items[i]);
  751. }
  752. mBuilder.listCallbackMulti.onSelection(this,
  753. selectedIndicesList.toArray(new Integer[selectedIndicesList.size()]),
  754. selectedTitles.toArray(new CharSequence[selectedTitles.size()]));
  755. }
  756. @Override
  757. public final void onClick(View v) {
  758. String tag = (String) v.getTag();
  759. switch (tag) {
  760. case POSITIVE: {
  761. if (mBuilder.callback != null)
  762. mBuilder.callback.onPositive(this);
  763. if (mBuilder.listCallbackSingle != null)
  764. sendSingleChoiceCallback(v);
  765. if (mBuilder.listCallbackMulti != null)
  766. sendMultichoiceCallback();
  767. if (mBuilder.autoDismiss) dismiss();
  768. break;
  769. }
  770. case NEGATIVE: {
  771. if (mBuilder.callback != null)
  772. mBuilder.callback.onNegative(this);
  773. if (mBuilder.autoDismiss) dismiss();
  774. break;
  775. }
  776. case NEUTRAL: {
  777. if (mBuilder.callback != null)
  778. mBuilder.callback.onNeutral(this);
  779. if (mBuilder.autoDismiss) dismiss();
  780. break;
  781. }
  782. default: {
  783. String[] split = tag.split(":");
  784. int index = Integer.parseInt(split[0]);
  785. if (mBuilder.listCallback != null) {
  786. if (mBuilder.autoDismiss)
  787. dismiss();
  788. mBuilder.listCallback.onSelection(this, v, index, split[1]);
  789. } else if (mBuilder.listCallbackSingle != null) {
  790. RadioButton cb = (RadioButton) ((LinearLayout) v).getChildAt(0);
  791. if (!cb.isChecked())
  792. cb.setChecked(true);
  793. if (mBuilder.autoDismiss && mBuilder.positiveText == null) {
  794. dismiss();
  795. sendSingleChoiceCallback(v);
  796. } else if (mBuilder.alwaysCallSingleChoiceCallback) {
  797. sendSingleChoiceCallback(v);
  798. }
  799. } else if (mBuilder.listCallbackMulti != null) {
  800. CheckBox cb = (CheckBox) ((LinearLayout) v).getChildAt(0);
  801. cb.setChecked(!cb.isChecked());
  802. if (mBuilder.alwaysCallMultiChoiceCallback) {
  803. sendMultichoiceCallback();
  804. }
  805. } else if (mBuilder.autoDismiss) dismiss();
  806. break;
  807. }
  808. }
  809. }
  810. /**
  811. * The class used to construct a MaterialDialog.
  812. */
  813. public static class Builder {
  814. protected final Context context;
  815. protected CharSequence title;
  816. protected GravityEnum titleGravity = GravityEnum.START;
  817. protected GravityEnum contentGravity = GravityEnum.START;
  818. protected GravityEnum btnStackedGravity = GravityEnum.END;
  819. protected int titleColor = -1;
  820. protected int contentColor = -1;
  821. protected CharSequence content;
  822. protected CharSequence[] items;
  823. protected CharSequence positiveText;
  824. protected CharSequence neutralText;
  825. protected CharSequence negativeText;
  826. protected View customView;
  827. protected int accentColor;
  828. protected int positiveColor;
  829. protected int negativeColor;
  830. protected int neutralColor;
  831. protected ButtonCallback callback;
  832. protected ListCallback listCallback;
  833. protected ListCallback listCallbackSingle;
  834. protected ListCallbackMulti listCallbackMulti;
  835. protected ListCallback listCallbackCustom;
  836. protected boolean alwaysCallMultiChoiceCallback = false;
  837. protected boolean alwaysCallSingleChoiceCallback = false;
  838. protected Theme theme = Theme.LIGHT;
  839. protected boolean cancelable = true;
  840. protected float contentLineSpacingMultiplier = 1.3f;
  841. protected int selectedIndex = -1;
  842. protected Integer[] selectedIndices = null;
  843. protected boolean autoDismiss = true;
  844. protected Typeface regularFont;
  845. protected Typeface mediumFont;
  846. protected boolean useCustomFonts;
  847. protected Drawable icon;
  848. protected boolean limitIconToDefaultSize;
  849. protected int maxIconSize = -1;
  850. protected ListAdapter adapter;
  851. protected OnDismissListener dismissListener;
  852. protected OnCancelListener cancelListener;
  853. protected OnKeyListener keyListener;
  854. protected OnShowListener showListener;
  855. protected boolean forceStacking;
  856. protected boolean wrapCustomViewInScroll;
  857. protected int dividerColor;
  858. protected int backgroundColor;
  859. protected int itemColor;
  860. protected boolean mIndeterminateProgress;
  861. protected int mProgress = -2;
  862. protected int mProgressMax = 0;
  863. // Since 0 is black and -1 is white, no default value is good for indicating if a color was set.
  864. // So this is a decent solution to this problem.
  865. protected boolean titleColorSet;
  866. protected boolean contentColorSet;
  867. protected boolean itemColorSet;
  868. @DrawableRes
  869. protected int listSelector;
  870. @DrawableRes
  871. protected int btnSelectorStacked;
  872. @DrawableRes
  873. protected int btnSelectorPositive;
  874. @DrawableRes
  875. protected int btnSelectorNeutral;
  876. @DrawableRes
  877. protected int btnSelectorNegative;
  878. public Builder(@NonNull Context context) {
  879. this.context = context;
  880. final int materialBlue = context.getResources().getColor(R.color.md_material_blue_600);
  881. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  882. TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{android.R.attr.colorAccent});
  883. try {
  884. this.accentColor = a.getColor(0, materialBlue);
  885. this.positiveColor = this.accentColor;
  886. this.negativeColor = this.accentColor;
  887. this.neutralColor = this.accentColor;
  888. } catch (Exception e) {
  889. this.accentColor = materialBlue;
  890. this.positiveColor = materialBlue;
  891. this.negativeColor = materialBlue;
  892. this.neutralColor = materialBlue;
  893. } finally {
  894. a.recycle();
  895. }
  896. } else {
  897. TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{R.attr.colorAccent});
  898. try {
  899. this.accentColor = a.getColor(0, materialBlue);
  900. this.positiveColor = this.accentColor;
  901. this.negativeColor = this.accentColor;
  902. this.neutralColor = this.accentColor;
  903. } catch (Exception e) {
  904. this.accentColor = materialBlue;
  905. this.positiveColor = materialBlue;
  906. this.negativeColor = materialBlue;
  907. this.neutralColor = materialBlue;
  908. } finally {
  909. a.recycle();
  910. }
  911. }
  912. checkSingleton();
  913. }
  914. private void checkSingleton() {
  915. if (ThemeSingleton.get(false) == null) return;
  916. ThemeSingleton s = ThemeSingleton.get();
  917. theme(s.darkTheme ? Theme.DARK : Theme.LIGHT);
  918. if (s.titleColor != 0)
  919. this.titleColor = s.titleColor;
  920. if (s.contentColor != 0)
  921. this.contentColor = s.contentColor;
  922. if (s.positiveColor != 0)
  923. this.positiveColor = s.positiveColor;
  924. if (s.neutralColor != 0)
  925. this.neutralColor = s.neutralColor;
  926. if (s.negativeColor != 0)
  927. this.negativeColor = s.negativeColor;
  928. if (s.itemColor != 0)
  929. this.itemColor = s.itemColor;
  930. if (s.icon != null)
  931. this.icon = s.icon;
  932. if (s.backgroundColor != 0)
  933. this.backgroundColor = s.backgroundColor;
  934. if (s.dividerColor != 0)
  935. this.dividerColor = s.dividerColor;
  936. if (s.btnSelectorStacked != 0)
  937. this.btnSelectorStacked = s.btnSelectorStacked;
  938. if (s.listSelector != 0)
  939. this.listSelector = s.listSelector;
  940. if (s.btnSelectorPositive != 0)
  941. this.btnSelectorPositive = s.btnSelectorPositive;
  942. if (s.btnSelectorNeutral != 0)
  943. this.btnSelectorNeutral = s.btnSelectorNeutral;
  944. if (s.btnSelectorNegative != 0)
  945. this.btnSelectorNegative = s.btnSelectorNegative;
  946. }
  947. public Builder title(@StringRes int titleRes) {
  948. title(this.context.getString(titleRes));
  949. return this;
  950. }
  951. public Builder title(@NonNull CharSequence title) {
  952. this.title = title;
  953. return this;
  954. }
  955. public Builder titleGravity(@NonNull GravityEnum gravity) {
  956. this.titleGravity = gravity;
  957. return this;
  958. }
  959. public Builder titleColor(int color) {
  960. this.titleColor = color;
  961. this.titleColorSet = true;
  962. return this;
  963. }
  964. public Builder titleColorRes(@ColorRes int colorRes) {
  965. titleColor(this.context.getResources().getColor(colorRes));
  966. return this;
  967. }
  968. public Builder titleColorAttr(@AttrRes int colorAttr) {
  969. titleColor(DialogUtils.resolveColor(this.context, colorAttr));
  970. return this;
  971. }
  972. /**
  973. * Disable usage of the default fonts. This is automatically set by
  974. * {@link #typeface(String, String)} and {@link #typeface(Typeface, Typeface)}.
  975. *
  976. * @return The Builder instance so you can chain calls to it.
  977. */
  978. public Builder disableDefaultFonts() {
  979. this.useCustomFonts = true;
  980. return this;
  981. }
  982. /**
  983. * Sets the fonts used in the dialog. It's recommended that you use {@link #typeface(String, String)} instead,
  984. * to avoid duplicate Typeface allocations and high memory usage.
  985. *
  986. * @param medium The font used on titles and action buttons. Null uses device default.
  987. * @param regular The font used everywhere else, like on the content and list items. Null uses device default.
  988. * @return The Builder instance so you can chain calls to it.
  989. */
  990. public Builder typeface(Typeface medium, Typeface regular) {
  991. this.mediumFont = medium;
  992. this.regularFont = regular;
  993. this.useCustomFonts = true;
  994. return this;
  995. }
  996. /**
  997. * Sets the fonts used in the dialog, by file names. This also uses TypefaceHelper in order
  998. * to avoid any un-needed allocations (it recycles typefaces for you).
  999. *
  1000. * @param medium The name of font in assets/fonts, minus the extension (null uses device default). E.g. [your-project]/app/main/assets/fonts/[medium].ttf
  1001. * @param regular The name of font in assets/fonts, minus the extension (null uses device default). E.g. [your-project]/app/main/assets/fonts/[regular].ttf
  1002. * @return The Builder instance so you can chain calls to it.
  1003. */
  1004. public Builder typeface(String medium, String regular) {
  1005. if (medium != null)
  1006. this.mediumFont = TypefaceHelper.get(this.context, medium);
  1007. if (regular != null)
  1008. this.regularFont = TypefaceHelper.get(this.context, regular);
  1009. this.useCustomFonts = true;
  1010. return this;
  1011. }
  1012. public Builder icon(@NonNull Drawable icon) {
  1013. this.icon = icon;
  1014. return this;
  1015. }
  1016. public Builder iconRes(@DrawableRes int icon) {
  1017. this.icon = context.getResources().getDrawable(icon);
  1018. return this;
  1019. }
  1020. public Builder iconAttr(@AttrRes int iconAttr) {
  1021. this.icon = DialogUtils.resolveDrawable(context, iconAttr);
  1022. return this;
  1023. }
  1024. public Builder contentColor(int color) {
  1025. this.contentColor = color;
  1026. this.contentColorSet = true;
  1027. return this;
  1028. }
  1029. public Builder contentColorRes(@ColorRes int colorRes) {
  1030. contentColor(this.context.getResources().getColor(colorRes));
  1031. return this;
  1032. }
  1033. public Builder contentColorAttr(@AttrRes int colorAttr) {
  1034. contentColor(DialogUtils.resolveColor(this.context, colorAttr));
  1035. return this;
  1036. }
  1037. public Builder content(@StringRes int contentRes) {
  1038. content(this.context.getString(contentRes));
  1039. return this;
  1040. }
  1041. public Builder content(CharSequence content) {
  1042. this.content = content;
  1043. return this;
  1044. }
  1045. public Builder content(@StringRes int contentRes, Object... formatArgs) {
  1046. content(this.context.getString(contentRes, formatArgs));
  1047. return this;
  1048. }
  1049. public Builder contentGravity(@NonNull GravityEnum gravity) {
  1050. this.contentGravity = gravity;
  1051. return this;
  1052. }
  1053. public Builder contentLineSpacing(float multiplier) {
  1054. this.contentLineSpacingMultiplier = multiplier;
  1055. return this;
  1056. }
  1057. public Builder items(@ArrayRes int itemsRes) {
  1058. items(this.context.getResources().getTextArray(itemsRes));
  1059. return this;
  1060. }
  1061. public Builder items(@NonNull CharSequence[] items) {
  1062. this.items = items;
  1063. return this;
  1064. }
  1065. public Builder itemsCallback(@NonNull ListCallback callback) {
  1066. this.listCallback = callback;
  1067. this.listCallbackSingle = null;
  1068. this.listCallbackMulti = null;
  1069. return this;
  1070. }
  1071. /**
  1072. * Pass anything below 0 (such as -1) for the selected index to leave all options unselected initially.
  1073. * Otherwise pass the index of an item that will be selected initially.
  1074. *
  1075. * @param selectedIndex The checkbox index that will be selected initially.
  1076. * @param callback The callback that will be called when the presses the positive button.
  1077. * @return The Builder instance so you can chain calls to it.
  1078. */
  1079. public Builder itemsCallbackSingleChoice(int selectedIndex, @NonNull ListCallback callback) {
  1080. this.selectedIndex = selectedIndex;
  1081. this.listCallback = null;
  1082. this.listCallbackSingle = callback;
  1083. this.listCallbackMulti = null;
  1084. return this;
  1085. }
  1086. /**
  1087. * By default, the single choice callback is only called when the user clicks the positive button
  1088. * or if there are no buttons. Call this to force it to always call on item clicks even if the
  1089. * positive button exists.
  1090. *
  1091. * @return The Builder instance so you can chain calls to it.
  1092. */
  1093. public Builder alwaysCallSingleChoiceCallback() {
  1094. this.alwaysCallSingleChoiceCallback = true;
  1095. return this;
  1096. }
  1097. /**
  1098. * Pass null for the selected indices to leave all options unselected initially. Otherwise pass
  1099. * an array of indices that will be selected initially.
  1100. *
  1101. * @param selectedIndices The radio button indices that will be selected initially.
  1102. * @param callback The callback that will be called when the presses the positive button.
  1103. * @return The Builder instance so you can chain calls to it.
  1104. */
  1105. public Builder itemsCallbackMultiChoice(Integer[] selectedIndices, @NonNull ListCallbackMulti callback) {
  1106. this.selectedIndices = selectedIndices;
  1107. this.listCallback = null;
  1108. this.listCallbackSingle = null;
  1109. this.listCallbackMulti = callback;
  1110. return this;
  1111. }
  1112. /**
  1113. * By default, the multi choice callback is only called when the user clicks the positive button
  1114. * or if there are no buttons. Call this to force it to always call on item clicks even if the
  1115. * positive button exists.
  1116. *
  1117. * @return The Builder instance so you can chain calls to it.
  1118. */
  1119. public Builder alwaysCallMultiChoiceCallback() {
  1120. this.alwaysCallMultiChoiceCallback = true;
  1121. return this;
  1122. }
  1123. public Builder positiveText(@StringRes int postiveRes) {
  1124. positiveText(this.context.getString(postiveRes));
  1125. return this;
  1126. }
  1127. public Builder positiveText(@NonNull CharSequence message) {
  1128. this.positiveText = message;
  1129. return this;
  1130. }
  1131. public Builder neutralText(@StringRes int neutralRes) {
  1132. return neutralText(this.context.getString(neutralRes));
  1133. }
  1134. public Builder neutralText(@NonNull CharSequence message) {
  1135. this.neutralText = message;
  1136. return this;
  1137. }
  1138. public Builder negativeText(@StringRes int negativeRes) {
  1139. return negativeText(this.context.getString(negativeRes));
  1140. }
  1141. public Builder negativeText(@NonNull CharSequence message) {
  1142. this.negativeText = message;
  1143. return this;
  1144. }
  1145. public Builder listSelector(@DrawableRes int selectorRes) {
  1146. this.listSelector = selectorRes;
  1147. return this;
  1148. }
  1149. public Builder btnSelectorStacked(@DrawableRes int selectorRes) {
  1150. this.btnSelectorStacked = selectorRes;
  1151. return this;
  1152. }
  1153. public Builder btnSelector(@DrawableRes int selectorRes) {
  1154. this.btnSelectorPositive = selectorRes;
  1155. this.btnSelectorNeutral = selectorRes;
  1156. this.btnSelectorNegative = selectorRes;
  1157. return this;
  1158. }
  1159. public Builder btnSelector(@DrawableRes int selectorRes, @NonNull DialogAction which) {
  1160. switch (which) {
  1161. default:
  1162. this.btnSelectorPositive = selectorRes;
  1163. break;
  1164. case NEUTRAL:
  1165. this.btnSelectorNeutral = selectorRes;
  1166. break;
  1167. case NEGATIVE:
  1168. this.btnSelectorNegative = selectorRes;
  1169. break;
  1170. }
  1171. return this;
  1172. }
  1173. /**
  1174. * Sets the gravity used for the text in stacked action buttons. By default, it's #{@link GravityEnum#END}.
  1175. *
  1176. * @param gravity The gravity to use.
  1177. * @return The Builder instance so calls can be chained.
  1178. */
  1179. public Builder btnStackedGravity(@NonNull GravityEnum gravity) {
  1180. this.btnStackedGravity = gravity;
  1181. return this;
  1182. }
  1183. /**
  1184. * Use {@link #customView(int, boolean)} instead.
  1185. */
  1186. @Deprecated
  1187. public Builder customView(@LayoutRes int layoutRes) {
  1188. return customView(layoutRes, true);
  1189. }
  1190. public Builder customView(@LayoutRes int layoutRes, boolean wrapInScrollView) {
  1191. LayoutInflater li = LayoutInflater.from(this.context);
  1192. return customView(li.inflate(layoutRes, null), wrapInScrollView);
  1193. }
  1194. /**
  1195. * Use {@link #customView(android.view.View, boolean)} instead.
  1196. */
  1197. @Deprecated
  1198. public Builder customView(@NonNull View view) {
  1199. return customView(view, true);
  1200. }
  1201. public Builder customView(@NonNull View view, boolean wrapInScrollView) {
  1202. this.customView = view;
  1203. this.wrapCustomViewInScroll = wrapInScrollView;
  1204. return this;
  1205. }
  1206. /**
  1207. * Makes this dialog a progress dialog.
  1208. *
  1209. * @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.
  1210. * @param max When indeterminate is false, the max value the horizontal progress bar can get to.
  1211. * @return An instance of the Builder so calls can be chained.
  1212. */
  1213. public Builder progress(boolean indeterminate, int max) {
  1214. if (indeterminate) {
  1215. this.mIndeterminateProgress = true;
  1216. this.mProgress = -2;
  1217. } else {
  1218. this.mIndeterminateProgress = false;
  1219. this.mProgress = -1;
  1220. this.mProgressMax = max;
  1221. }
  1222. return this;
  1223. }
  1224. public Builder positiveColor(int color) {
  1225. this.positiveColor = color;
  1226. return this;
  1227. }
  1228. public Builder positiveColorRes(@ColorRes int colorRes) {
  1229. positiveColor(this.context.getResources().getColor(colorRes));
  1230. return this;
  1231. }
  1232. public Builder positiveColorAttr(@AttrRes int colorAttr) {
  1233. positiveColor(DialogUtils.resolveColor(this.context, colorAttr));
  1234. return this;
  1235. }
  1236. public Builder negativeColor(int color) {
  1237. this.negativeColor = color;
  1238. return this;
  1239. }
  1240. public Builder negativeColorRes(@ColorRes int colorRes) {
  1241. negativeColor(this.context.getResources().getColor(colorRes));
  1242. return this;
  1243. }
  1244. public Builder negativeColorAttr(@AttrRes int colorAttr) {
  1245. negativeColor(DialogUtils.resolveColor(this.context, colorAttr));
  1246. return this;
  1247. }
  1248. public Builder neutralColor(int color) {
  1249. this.neutralColor = color;
  1250. return this;
  1251. }
  1252. public Builder neutralColorRes(@ColorRes int colorRes) {
  1253. return neutralColor(this.context.getResources().getColor(colorRes));
  1254. }
  1255. public Builder neutralColorAttr(@AttrRes int colorAttr) {
  1256. return neutralColor(DialogUtils.resolveColor(this.context, colorAttr));
  1257. }
  1258. public Builder dividerColor(int color) {
  1259. this.dividerColor = color;
  1260. return this;
  1261. }
  1262. public Builder dividerColorRes(@ColorRes int colorRes) {
  1263. return dividerColor(this.context.getResources().getColor(colorRes));
  1264. }
  1265. public Builder dividerColorAttr(@AttrRes int colorAttr) {
  1266. return dividerColor(DialogUtils.resolveColor(this.context, colorAttr));
  1267. }
  1268. public Builder backgroundColor(int color) {
  1269. this.backgroundColor = color;
  1270. return this;
  1271. }
  1272. public Builder backgroundColorRes(@ColorRes int colorRes) {
  1273. return backgroundColor(this.context.getResources().getColor(colorRes));
  1274. }
  1275. public Builder backgroundColorAttr(@AttrRes int colorAttr) {
  1276. return backgroundColor(DialogUtils.resolveColor(this.context, colorAttr));
  1277. }
  1278. public Builder itemColor(int color) {
  1279. this.itemColor = color;
  1280. this.itemColorSet = true;
  1281. return this;
  1282. }
  1283. public Builder itemColorRes(@ColorRes int colorRes) {
  1284. return itemColor(this.context.getResources().getColor(colorRes));
  1285. }
  1286. public Builder itemColorAttr(@AttrRes int colorAttr) {
  1287. return itemColor(DialogUtils.resolveColor(this.context, colorAttr));
  1288. }
  1289. public Builder callback(@NonNull ButtonCallback callback) {
  1290. this.callback = callback;
  1291. return this;
  1292. }
  1293. public Builder theme(@NonNull Theme theme) {
  1294. this.theme = theme;
  1295. return this;
  1296. }
  1297. public Builder cancelable(boolean cancelable) {
  1298. this.cancelable = cancelable;
  1299. return this;
  1300. }
  1301. /**
  1302. * This defaults to true. If set to false, the dialog will not automatically be dismissed
  1303. * when an action button is pressed, and not automatically dismissed when the user selects
  1304. * a list item.
  1305. *
  1306. * @param dismiss Whether or not to dismiss the dialog automatically.
  1307. * @return The Builder instance so you can chain calls to it.
  1308. */
  1309. public Builder autoDismiss(boolean dismiss) {
  1310. this.autoDismiss = dismiss;
  1311. return this;
  1312. }
  1313. /**
  1314. * Sets a custom {@link android.widget.ListAdapter} for the dialog's list
  1315. *
  1316. * @param adapter The adapter to set to the list.
  1317. * @return This Builder object to allow for chaining of calls to set methods
  1318. * @deprecated Use {@link #adapter(ListAdapter, ListCallback)} instead.
  1319. */
  1320. @Deprecated
  1321. public Builder adapter(@NonNull ListAdapter adapter) {
  1322. this.adapter = adapter;
  1323. return this;
  1324. }
  1325. /**
  1326. * Sets a custom {@link android.widget.ListAdapter} for the dialog's list
  1327. *
  1328. * @param adapter The adapter to set to the list.
  1329. * @param callback The callback invoked when an item in the list is selected.
  1330. * @return This Builder object to allow for chaining of calls to set methods
  1331. */
  1332. public Builder adapter(@NonNull ListAdapter adapter, ListCallback callback) {
  1333. this.adapter = adapter;
  1334. this.listCallbackCustom = callback;
  1335. return this;
  1336. }
  1337. public Builder limitIconToDefaultSize() {
  1338. this.limitIconToDefaultSize = true;
  1339. return this;
  1340. }
  1341. public Builder maxIconSize(int maxIconSize) {
  1342. this.maxIconSize = maxIconSize;
  1343. return this;
  1344. }
  1345. public Builder maxIconSizeRes(@DimenRes int maxIconSizeRes) {
  1346. return maxIconSize((int) this.context.getResources().getDimension(maxIconSizeRes));
  1347. }
  1348. public Builder showListener(@NonNull OnShowListener listener) {
  1349. this.showListener = listener;
  1350. return this;
  1351. }
  1352. public Builder dismissListener(@NonNull OnDismissListener listener) {
  1353. this.dismissListener = listener;
  1354. return this;
  1355. }
  1356. public Builder cancelListener(@NonNull OnCancelListener listener) {
  1357. this.cancelListener = listener;
  1358. return this;
  1359. }
  1360. public Builder keyListener(@NonNull OnKeyListener listener) {
  1361. this.keyListener = listener;
  1362. return this;
  1363. }
  1364. public Builder forceStacking(boolean stacked) {
  1365. this.forceStacking = stacked;
  1366. return this;
  1367. }
  1368. public MaterialDialog build() {
  1369. if ((content == null || content.toString().trim().length() == 0) &&
  1370. title != null && (items == null || items.length == 0) &&
  1371. customView == null && adapter == null) {
  1372. this.content = this.title;
  1373. this.title = null;
  1374. }
  1375. return new MaterialDialog(this);
  1376. }
  1377. public MaterialDialog show() {
  1378. MaterialDialog dialog = build();
  1379. dialog.show();
  1380. return dialog;
  1381. }
  1382. }
  1383. @Override
  1384. public void show() {
  1385. if (Looper.myLooper() != Looper.getMainLooper())
  1386. throw new IllegalStateException("Dialogs can only be shown from the UI thread.");
  1387. try {
  1388. super.show();
  1389. } catch (WindowManager.BadTokenException e) {
  1390. throw new DialogException("Bad window token, you cannot show a dialog before an Activity is created or after it's hidden.");
  1391. }
  1392. }
  1393. private ColorStateList getActionTextStateList(int newPrimaryColor) {
  1394. final int fallBackButtonColor = DialogUtils.resolveColor(getContext(), android.R.attr.textColorPrimary);
  1395. if (newPrimaryColor == 0) newPrimaryColor = fallBackButtonColor;
  1396. int[][] states = new int[][]{
  1397. new int[]{-android.R.attr.state_enabled}, // disabled
  1398. new int[]{} // enabled
  1399. };
  1400. int[] colors = new int[]{
  1401. DialogUtils.adjustAlpha(newPrimaryColor, 0.4f),
  1402. newPrimaryColor
  1403. };
  1404. return new ColorStateList(states, colors);
  1405. }
  1406. /**
  1407. * Retrieves the view of an action button, allowing you to modify properties such as whether or not it's enabled.
  1408. * Use {@link #setActionButton(DialogAction, int)} to change text, since the view returned here is not
  1409. * the view that displays text.
  1410. *
  1411. * @param which The action button of which to get the view for.
  1412. * @return The view from the dialog's layout representing this action button.
  1413. */
  1414. public final View getActionButton(@NonNull DialogAction which) {
  1415. if (isStacked) {
  1416. switch (which) {
  1417. default:
  1418. return view.findViewById(R.id.buttonStackedPositive);
  1419. case NEUTRAL:
  1420. return view.findViewById(R.id.buttonStackedNeutral);
  1421. case NEGATIVE:
  1422. return view.findViewById(R.id.buttonStackedNegative);
  1423. }
  1424. } else {
  1425. switch (which) {
  1426. default:
  1427. return view.findViewById(R.id.buttonDefaultPositive);
  1428. case NEUTRAL:
  1429. return view.findViewById(R.id.buttonDefaultNeutral);
  1430. case NEGATIVE:
  1431. return view.findViewById(R.id.buttonDefaultNegative);
  1432. }
  1433. }
  1434. }
  1435. /**
  1436. * This will not return buttons that are actually in the layout itself, since the layout doesn't
  1437. * contain buttons. This is only implemented to avoid crashing issues on Huawei devices. Huawei's
  1438. * stock OS requires this method in order to detect visible buttons.
  1439. *
  1440. * @deprecated Use getActionButton(com.afollestad.materialdialogs.DialogAction)} instead.
  1441. */
  1442. @Deprecated
  1443. @Override
  1444. public Button getButton(int whichButton) {
  1445. Log.w("MaterialDialog", "Warning: getButton() is a deprecated method that does not return valid references to action buttons.");
  1446. if (whichButton == AlertDialog.BUTTON_POSITIVE) {
  1447. return mBuilder.positiveText != null ? new Button(getContext()) : null;
  1448. } else if (whichButton == AlertDialog.BUTTON_NEUTRAL) {
  1449. return mBuilder.neutralText != null ? new Button(getContext()) : null;
  1450. } else {
  1451. return mBuilder.negativeText != null ? new Button(getContext()) : null;
  1452. }
  1453. }
  1454. /**
  1455. * Retrieves the frame view containing the title and icon. You can manually change visibility and retrieve children.
  1456. */
  1457. public final View getTitleFrame() {
  1458. return titleFrame;
  1459. }
  1460. /**
  1461. * Retrieves the custom view that was inflated or set to the MaterialDialog during building.
  1462. *
  1463. * @return The custom view that was passed into the Builder.
  1464. */
  1465. public final View getCustomView() {
  1466. return mBuilder.customView;
  1467. }
  1468. /**
  1469. * Updates an action button's title, causing invalidation to check if the action buttons should be stacked.
  1470. *
  1471. * @param which The action button to update.
  1472. * @param title The new title of the action button.
  1473. */
  1474. public final void setActionButton(@NonNull DialogAction which, CharSequence title) {
  1475. switch (which) {
  1476. default:
  1477. mBuilder.positiveText = title;
  1478. break;
  1479. case NEUTRAL:
  1480. mBuilder.neutralText = title;
  1481. break;
  1482. case NEGATIVE:
  1483. mBuilder.negativeText = title;
  1484. break;
  1485. }
  1486. invalidateActions();
  1487. }
  1488. /**
  1489. * Updates an action button's title, causing invalidation to check if the action buttons should be stacked.
  1490. *
  1491. * @param which The action button to update.
  1492. * @param titleRes The string resource of the new title of the action button.
  1493. */
  1494. public final void setActionButton(DialogAction which, @StringRes int titleRes) {
  1495. setActionButton(which, getContext().getString(titleRes));
  1496. }
  1497. /**
  1498. * Gets whether or not the positive, neutral, or negative action button is visible.
  1499. *
  1500. * @return Whether or not 1 or more action buttons is visible.
  1501. */
  1502. public final boolean hasActionButtons() {
  1503. return numberOfActionButtons() > 0;
  1504. }
  1505. /**
  1506. * Gets the number of visible action buttons.
  1507. *
  1508. * @return 0 through 3, depending on how many should be or are visible.
  1509. */
  1510. public final int numberOfActionButtons() {
  1511. int number = 0;
  1512. if (mBuilder.positiveText != null) number++;
  1513. if (mBuilder.neutralText != null) number++;
  1514. if (mBuilder.negativeText != null) number++;
  1515. return number;
  1516. }
  1517. /**
  1518. * Updates the dialog's title.
  1519. */
  1520. public final void setTitle(@NonNull CharSequence title) {
  1521. this.title.setText(title);
  1522. }
  1523. @Override
  1524. public void setIcon(@DrawableRes int resId) {
  1525. icon.setImageResource(resId);
  1526. icon.setVisibility(resId != 0 ? View.VISIBLE : View.GONE);
  1527. }
  1528. @Override
  1529. public void setIcon(Drawable d) {
  1530. icon.setImageDrawable(d);
  1531. icon.setVisibility(d != null ? View.VISIBLE : View.GONE);
  1532. }
  1533. @Override
  1534. public void setIconAttribute(@AttrRes int attrId) {
  1535. Drawable d = DialogUtils.resolveDrawable(mBuilder.context, attrId);
  1536. icon.setImageDrawable(d);
  1537. icon.setVisibility(d != null ? View.VISIBLE : View.GONE);
  1538. }
  1539. public final void setContent(CharSequence content) {
  1540. this.content.setText(content);
  1541. invalidateCustomViewAssociations(); // invalidates padding in content area scroll (if needed)
  1542. }
  1543. public final void setItems(CharSequence[] items) {
  1544. if (mBuilder.adapter == null)
  1545. throw new IllegalStateException("This MaterialDialog instance does not yet have an adapter set to it. You cannot use setItems().");
  1546. if (mBuilder.adapter instanceof MaterialDialogAdapter) {
  1547. mBuilder.adapter = new MaterialDialogAdapter(mBuilder.context,
  1548. ListType.getLayoutForType(listType), R.id.title, items);
  1549. } else {
  1550. throw new IllegalStateException("When using a custom adapter, setItems() cannot be used. Set items through the adapter instead.");
  1551. }
  1552. mBuilder.items = items;
  1553. listView.setAdapter(mBuilder.adapter);
  1554. invalidateCustomViewAssociations();
  1555. }
  1556. public final int getCurrentProgress() {
  1557. if (mProgress == null) return -1;
  1558. return mProgress.getProgress();
  1559. }
  1560. public final void incrementProgress(int by) {
  1561. if (mBuilder.mProgress <= -2)
  1562. throw new IllegalStateException("Cannot use incrementProgress() on this dialog.");
  1563. setProgress(getCurrentProgress() + by);
  1564. }
  1565. public final void setProgress(int progress) {
  1566. if (Looper.myLooper() != Looper.getMainLooper())
  1567. throw new IllegalStateException("You can only set the dialog's progress from the UI thread.");
  1568. else if (mBuilder.mProgress <= -2)
  1569. throw new IllegalStateException("Cannot use setProgress() on this dialog.");
  1570. mProgress.setProgress(progress);
  1571. int percentage = (int) (((float) getCurrentProgress() / (float) getMaxProgress()) * 100f);
  1572. mProgressLabel.setText(percentage + "%");
  1573. }
  1574. public final void setMaxProgress(int max) {
  1575. if (Looper.myLooper() != Looper.getMainLooper())
  1576. throw new IllegalStateException("You can only set the dialog's progress from the UI thread.");
  1577. else if (mBuilder.mProgress <= -2)
  1578. throw new IllegalStateException("Cannot use setMaxProgress() on this dialog.");
  1579. mProgress.setMax(max);
  1580. }
  1581. public final boolean isIndeterminateProgress() {
  1582. return mBuilder.mIndeterminateProgress;
  1583. }
  1584. public final int getMaxProgress() {
  1585. if (mProgress == null) return -1;
  1586. return mProgress.getMax();
  1587. }
  1588. public final boolean isCancelled() {
  1589. return !isShowing();
  1590. }
  1591. /**
  1592. * Use this to customize any list-specific logic for this dialog (OnItemClickListener, OnLongItemClickListener, etc.)
  1593. *
  1594. * @return The ListView instance used by this dialog, or null if not using a list.
  1595. */
  1596. @Nullable
  1597. public ListView getListView() {
  1598. return listView;
  1599. }
  1600. /**
  1601. * Convenience method for getting the currently selected index of a single choice list.
  1602. *
  1603. * @return Currently selected index of a single choice list, or -1 if not showing a single choice list
  1604. */
  1605. public int getSelectedIndex() {
  1606. if (mBuilder.listCallbackSingle != null) {
  1607. return mBuilder.selectedIndex;
  1608. } else {
  1609. return -1;
  1610. }
  1611. }
  1612. /**
  1613. * Convenience method for getting the currently selected indices of a multi choice list
  1614. *
  1615. * @return Currently selected index of a multi choice list, or null if not showing a multi choice list
  1616. */
  1617. @Nullable
  1618. public Integer[] getSelectedIndices() {
  1619. if (mBuilder.listCallbackMulti != null) {
  1620. return selectedIndicesList.toArray(new Integer[selectedIndicesList.size()]);
  1621. } else {
  1622. return null;
  1623. }
  1624. }
  1625. /**
  1626. * Convenience method for setting the currently selected index of a single choice list.
  1627. * This only works if you are not using a custom adapter; if you're using a custom adapter,
  1628. * an IllegalStateException is thrown. Note that this does not call the respective single choice callback.
  1629. *
  1630. * @param index The index of the list item to check.
  1631. */
  1632. public void setSelectedIndex(int index) {
  1633. mBuilder.selectedIndex = index;
  1634. if (mBuilder.adapter != null && mBuilder.adapter instanceof MaterialDialogAdapter) {
  1635. ((MaterialDialogAdapter) mBuilder.adapter).notifyDataSetChanged();
  1636. } else {
  1637. throw new IllegalStateException("You can only use setSelectedIndex() with the default adapter implementation.");
  1638. }
  1639. }
  1640. /**
  1641. * Convenience method for setting the currently selected indices of a multi choice list.
  1642. * This only works if you are not using a custom adapter; if you're using a custom adapter,
  1643. * an IllegalStateException is thrown. Note that this does not call the respective multi choice callback.
  1644. *
  1645. * @param indices The indices of the list items to check.
  1646. */
  1647. public void setSelectedIndices(@NonNull Integer[] indices) {
  1648. mBuilder.selectedIndices = indices;
  1649. selectedIndicesList = new ArrayList<>(Arrays.asList(indices));
  1650. if (mBuilder.adapter != null && mBuilder.adapter instanceof MaterialDialogAdapter) {
  1651. ((MaterialDialogAdapter) mBuilder.adapter).notifyDataSetChanged();
  1652. } else {
  1653. throw new IllegalStateException("You can only use setSelectedIndices() with the default adapter implementation.");
  1654. }
  1655. }
  1656. private class MaterialDialogAdapter extends ArrayAdapter<CharSequence> {
  1657. final int itemColor;
  1658. public MaterialDialogAdapter(Context context, int resource, int textViewResourceId, CharSequence[] objects) {
  1659. super(context, resource, textViewResourceId, objects);
  1660. itemColor = DialogUtils.resolveColor(getContext(), R.attr.md_item_color, defaultItemColor);
  1661. }
  1662. @Override
  1663. public boolean hasStableIds() {
  1664. return true;
  1665. }
  1666. @Override
  1667. public long getItemId(int position) {
  1668. return position;
  1669. }
  1670. @SuppressLint("WrongViewCast")
  1671. @Override
  1672. public View getView(final int index, View convertView, ViewGroup parent) {
  1673. final View view = super.getView(index, convertView, parent);
  1674. TextView tv = (TextView) view.findViewById(R.id.title);
  1675. switch (listType) {
  1676. case SINGLE: {
  1677. @SuppressLint("CutPasteId")
  1678. RadioButton radio = (RadioButton) view.findViewById(R.id.control);
  1679. radio.setChecked(mBuilder.selectedIndex == index);
  1680. break;
  1681. }
  1682. case MULTI: {
  1683. @SuppressLint("CutPasteId")
  1684. CheckBox checkbox = (CheckBox) view.findViewById(R.id.control);
  1685. checkbox.setChecked(selectedIndicesList.contains(index));
  1686. break;
  1687. }
  1688. }
  1689. tv.setText(mBuilder.items[index]);
  1690. tv.setTextColor(itemColor);
  1691. setTypeface(tv, mBuilder.regularFont);
  1692. view.setTag(index + ":" + mBuilder.items[index]);
  1693. return view;
  1694. }
  1695. }
  1696. private enum ListType {
  1697. REGULAR, SINGLE, MULTI;
  1698. public static int getLayoutForType(ListType type) {
  1699. switch (type) {
  1700. case REGULAR:
  1701. return R.layout.md_listitem;
  1702. case SINGLE:
  1703. return R.layout.md_listitem_singlechoice;
  1704. case MULTI:
  1705. return R.layout.md_listitem_multichoice;
  1706. default:
  1707. throw new IllegalArgumentException("Not a valid list type");
  1708. }
  1709. }
  1710. }
  1711. public interface ListCallback {
  1712. void onSelection(MaterialDialog dialog, View itemView, int which, CharSequence text);
  1713. }
  1714. public interface ListCallbackMulti {
  1715. void onSelection(MaterialDialog dialog, Integer[] which, CharSequence[] text);
  1716. }
  1717. /**
  1718. * Override these as needed, so no needing to sub empty methods from an interface
  1719. */
  1720. public static abstract class ButtonCallback {
  1721. public void onPositive(MaterialDialog dialog) {
  1722. }
  1723. public void onNegative(MaterialDialog dialog) {
  1724. }
  1725. public void onNeutral(MaterialDialog dialog) {
  1726. }
  1727. public ButtonCallback() {
  1728. super();
  1729. }
  1730. @Override
  1731. protected final Object clone() throws CloneNotSupportedException {
  1732. return super.clone();
  1733. }
  1734. @Override
  1735. public final boolean equals(Object o) {
  1736. return super.equals(o);
  1737. }
  1738. @Override
  1739. protected final void finalize() throws Throwable {
  1740. super.finalize();
  1741. }
  1742. @Override
  1743. public final int hashCode() {
  1744. return super.hashCode();
  1745. }
  1746. @Override
  1747. public final String toString() {
  1748. return super.toString();
  1749. }
  1750. }
  1751. }