MaterialDialog.java 72 KB

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