MaterialDialog.java 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887
  1. package com.afollestad.materialdialogs;
  2. import android.annotation.SuppressLint;
  3. import android.content.Context;
  4. import android.content.DialogInterface;
  5. import android.content.res.ColorStateList;
  6. import android.content.res.Resources;
  7. import android.content.res.TypedArray;
  8. import android.graphics.Paint;
  9. import android.graphics.Typeface;
  10. import android.os.Build;
  11. import android.support.annotation.ArrayRes;
  12. import android.support.annotation.ColorRes;
  13. import android.support.annotation.LayoutRes;
  14. import android.support.annotation.NonNull;
  15. import android.support.annotation.StringRes;
  16. import android.text.method.LinkMovementMethod;
  17. import android.view.ContextThemeWrapper;
  18. import android.view.Gravity;
  19. import android.view.LayoutInflater;
  20. import android.view.View;
  21. import android.widget.CheckBox;
  22. import android.widget.LinearLayout;
  23. import android.widget.RadioButton;
  24. import android.widget.ScrollView;
  25. import android.widget.TextView;
  26. import com.afollestad.materialdialogs.base.DialogBase;
  27. import com.afollestad.materialdialogs.list.ItemProcessor;
  28. import com.afollestad.materialdialogs.views.MeasureCallbackScrollView;
  29. import java.util.ArrayList;
  30. import java.util.Arrays;
  31. import java.util.List;
  32. /**
  33. * @author Aidan Follestad (afollestad)
  34. */
  35. public class MaterialDialog extends DialogBase implements View.OnClickListener, MeasureCallbackScrollView.Callback {
  36. private Context mContext;
  37. private CharSequence positiveText;
  38. private TextView positiveButton;
  39. private CharSequence neutralText;
  40. private TextView neutralButton;
  41. private CharSequence negativeText;
  42. private TextView negativeButton;
  43. private View view;
  44. private int positiveColor;
  45. private int negativeColor;
  46. private int neutralColor;
  47. private SimpleCallback callback;
  48. private ListCallback listCallback;
  49. private ListCallback listCallbackSingle;
  50. private ListCallbackMulti listCallbackMulti;
  51. private View customView;
  52. private String[] items;
  53. private boolean isStacked;
  54. private int selectedIndex;
  55. private Integer[] selectedIndices;
  56. private boolean mMeasuredScrollView;
  57. private Typeface mediumFont;
  58. private Typeface regularFont;
  59. private ItemProcessor mItemProcessor;
  60. private boolean hideActions;
  61. private boolean autoDismiss;
  62. MaterialDialog(Builder builder) {
  63. super(new ContextThemeWrapper(builder.context, builder.theme == Theme.LIGHT ? R.style.MD_Light : R.style.MD_Dark));
  64. this.regularFont = builder.regularFont;
  65. if (this.regularFont == null)
  66. Typeface.createFromAsset(getContext().getResources().getAssets(), "Roboto-Regular.ttf");
  67. this.mediumFont = builder.mediumFont;
  68. if (this.mediumFont == null)
  69. this.mediumFont = Typeface.createFromAsset(getContext().getResources().getAssets(), "Roboto-Medium.ttf");
  70. this.mContext = builder.context;
  71. this.view = LayoutInflater.from(builder.context).inflate(R.layout.md_dialog, null);
  72. this.customView = builder.customView;
  73. this.callback = builder.callback;
  74. this.listCallback = builder.listCallback;
  75. this.listCallbackSingle = builder.listCallbackSingle;
  76. this.listCallbackMulti = builder.listCallbackMulti;
  77. this.positiveText = builder.positiveText;
  78. this.neutralText = builder.neutralText;
  79. this.negativeText = builder.negativeText;
  80. this.positiveColor = builder.positiveColor;
  81. this.negativeColor = builder.negativeColor;
  82. this.neutralColor = builder.neutralColor;
  83. this.items = builder.items;
  84. this.setCancelable(builder.cancelable);
  85. this.selectedIndex = builder.selectedIndex;
  86. this.selectedIndices = builder.selectedIndicies;
  87. this.mItemProcessor = builder.itemProcessor;
  88. this.hideActions = builder.hideActions;
  89. this.autoDismiss = builder.autoDismiss;
  90. TextView title = (TextView) view.findViewById(R.id.title);
  91. final TextView content = (TextView) view.findViewById(R.id.content);
  92. content.setText(builder.content);
  93. content.setMovementMethod(new LinkMovementMethod());
  94. content.setVisibility(View.VISIBLE);
  95. content.setTypeface(regularFont);
  96. content.setTextColor(DialogUtils.resolveColor(getContext(), android.R.attr.textColorSecondary));
  97. content.setLineSpacing(0f, builder.contentLineSpacingMultiplier);
  98. if (this.positiveColor == 0) {
  99. content.setLinkTextColor(DialogUtils.resolveColor(getContext(), android.R.attr.textColorPrimary));
  100. } else {
  101. content.setLinkTextColor(this.positiveColor);
  102. }
  103. if (builder.contentAlignment == Alignment.CENTER) {
  104. content.setGravity(Gravity.CENTER_HORIZONTAL);
  105. } else if (builder.contentAlignment == Alignment.RIGHT) {
  106. content.setGravity(Gravity.RIGHT);
  107. }
  108. if (customView != null) {
  109. title = (TextView) view.findViewById(R.id.titleCustomView);
  110. invalidateCustomViewAssociations();
  111. ((LinearLayout) view.findViewById(R.id.customViewFrame)).addView(customView);
  112. } else {
  113. invalidateCustomViewAssociations();
  114. }
  115. if (items != null && items.length > 0)
  116. title = (TextView) view.findViewById(R.id.titleCustomView);
  117. // Title is set after it's determined whether to use first title or custom view title
  118. if (builder.title == null || builder.title.toString().trim().isEmpty()) {
  119. title.setVisibility(View.GONE);
  120. } else {
  121. title.setText(builder.title);
  122. title.setTypeface(mediumFont);
  123. if (builder.titleColor != -1) {
  124. title.setTextColor(builder.titleColor);
  125. } else {
  126. title.setTextColor(DialogUtils.resolveColor(getContext(), android.R.attr.textColorPrimary));
  127. }
  128. if (builder.titleAlignment == Alignment.CENTER) {
  129. title.setGravity(Gravity.CENTER_HORIZONTAL);
  130. } else if (builder.titleAlignment == Alignment.RIGHT) {
  131. title.setGravity(Gravity.RIGHT);
  132. }
  133. }
  134. invalidateList();
  135. invalidateActions();
  136. setOnShowListenerInternal();
  137. setViewInternal(view);
  138. }
  139. @Override
  140. public void onShow(DialogInterface dialog) {
  141. super.onShow(dialog); // calls any external show listeners
  142. checkIfStackingNeeded();
  143. }
  144. /**
  145. * Invalidates visibility of views for the presence of a custom view or list content
  146. */
  147. private void invalidateCustomViewAssociations() {
  148. if (customView != null || (items != null && items.length > 0)) {
  149. view.findViewById(R.id.mainFrame).setVisibility(View.GONE);
  150. view.findViewById(R.id.customViewScrollParent).setVisibility(View.VISIBLE);
  151. if (!mMeasuredScrollView) {
  152. // Wait until it's measured
  153. ((MeasureCallbackScrollView) view.findViewById(R.id.customViewScroll)).setCallback(this);
  154. return;
  155. }
  156. if (canCustomViewScroll()) {
  157. view.findViewById(R.id.customViewDivider).setVisibility(View.VISIBLE);
  158. view.findViewById(R.id.customViewDivider).setBackgroundColor(DialogUtils.resolveColor(getContext(), R.attr.md_divider));
  159. setMargin(view.findViewById(R.id.buttonStackedFrame), -1, 0, -1, -1);
  160. setMargin(view.findViewById(R.id.buttonDefaultFrame), -1, 0, -1, -1);
  161. if (items != null && items.length > 0) {
  162. View customFrame = view.findViewById(R.id.customViewFrame);
  163. Resources r = mContext.getResources();
  164. int bottomPadding = view.findViewById(R.id.titleCustomView).getVisibility() == View.VISIBLE ?
  165. (int) r.getDimension(R.dimen.md_main_frame_margin) : (int) r.getDimension(R.dimen.md_dialog_frame_margin);
  166. customFrame.setPadding(customFrame.getPaddingLeft(), customFrame.getPaddingTop(),
  167. customFrame.getPaddingRight(), bottomPadding);
  168. }
  169. } else {
  170. view.findViewById(R.id.customViewDivider).setVisibility(View.GONE);
  171. final int bottomMargin = (int) mContext.getResources().getDimension(R.dimen.md_button_padding_frame_bottom);
  172. setMargin(view.findViewById(R.id.buttonStackedFrame), -1, bottomMargin, -1, -1);
  173. setMargin(view.findViewById(R.id.buttonDefaultFrame), -1, bottomMargin, -1, -1);
  174. }
  175. } else {
  176. view.findViewById(R.id.mainFrame).setVisibility(View.VISIBLE);
  177. view.findViewById(R.id.customViewScrollParent).setVisibility(View.GONE);
  178. view.findViewById(R.id.customViewDivider).setVisibility(View.GONE);
  179. }
  180. }
  181. /**
  182. * Invalidates the radio buttons in the single choice mode list so that only the radio button that
  183. * was previous selected is checked.
  184. */
  185. private void invalidateSingleChoice(int newSelection) {
  186. newSelection++;
  187. final LinearLayout list = (LinearLayout) view.findViewById(R.id.customViewFrame);
  188. for (int i = 1; i < list.getChildCount(); i++) {
  189. View v = list.getChildAt(i);
  190. @SuppressLint("WrongViewCast")
  191. RadioButton rb = (RadioButton) v.findViewById(R.id.control);
  192. if (newSelection != i) {
  193. rb.setChecked(false);
  194. rb.clearFocus();
  195. }
  196. }
  197. }
  198. /**
  199. * Constructs the dialog's list content and sets up click listeners.
  200. */
  201. @SuppressLint("WrongViewCast")
  202. private void invalidateList() {
  203. if (items == null || items.length == 0) return;
  204. view.findViewById(R.id.content).setVisibility(View.GONE);
  205. view.findViewById(R.id.customViewScrollParent).setVisibility(View.VISIBLE);
  206. LinearLayout customFrame = (LinearLayout) view.findViewById(R.id.customViewFrame);
  207. setMargin(customFrame, -1, -1, 0, 0);
  208. LayoutInflater li = LayoutInflater.from(mContext);
  209. customFrame.setPadding(customFrame.getPaddingLeft(), customFrame.getPaddingTop(),
  210. customFrame.getPaddingRight(), 0);
  211. final int customFramePadding = (int) mContext.getResources().getDimension(R.dimen.md_dialog_frame_margin);
  212. View title = view.findViewById(R.id.titleCustomView);
  213. title.setPadding(customFramePadding, title.getPaddingTop(), customFramePadding, title.getPaddingBottom());
  214. final int itemColor = DialogUtils.resolveColor(getContext(), android.R.attr.textColorSecondary);
  215. for (int index = 0; index < items.length; index++) {
  216. View il;
  217. if (listCallbackSingle != null) {
  218. il = li.inflate(R.layout.md_listitem_singlechoice, null);
  219. if (selectedIndex > -1) {
  220. RadioButton control = (RadioButton) il.findViewById(R.id.control);
  221. if (selectedIndex == index) control.setChecked(true);
  222. }
  223. TextView tv = (TextView) il.findViewById(R.id.title);
  224. tv.setText(items[index]);
  225. tv.setTextColor(itemColor);
  226. tv.setTypeface(regularFont);
  227. } else if (listCallbackMulti != null) {
  228. il = li.inflate(R.layout.md_listitem_multichoice, null);
  229. if (selectedIndices != null) {
  230. if (Arrays.asList(selectedIndices).contains(index)) {
  231. CheckBox control = (CheckBox) il.findViewById(R.id.control);
  232. control.setChecked(true);
  233. }
  234. }
  235. TextView tv = (TextView) il.findViewById(R.id.title);
  236. tv.setText(items[index]);
  237. tv.setTextColor(itemColor);
  238. tv.setTypeface(regularFont);
  239. } else {
  240. if (mItemProcessor != null) {
  241. il = mItemProcessor.inflateItem(index, items[index]);
  242. } else {
  243. il = li.inflate(R.layout.md_listitem, null);
  244. TextView tv = (TextView) il.findViewById(R.id.title);
  245. tv.setText(items[index]);
  246. tv.setTextColor(itemColor);
  247. tv.setTypeface(regularFont);
  248. }
  249. }
  250. il.setTag(index + ":" + items[index]);
  251. il.setOnClickListener(this);
  252. il.setBackgroundResource(DialogUtils.resolveDrawable(getContext(), R.attr.md_selector));
  253. customFrame.addView(il);
  254. }
  255. }
  256. private int calculateMaxButtonWidth() {
  257. /**
  258. * Max button width = (DialogWidth - 16dp - 16dp - 8dp) / 2
  259. * From: http://www.google.com/design/spec/components/dialogs.html#dialogs-specs
  260. */
  261. final int dialogWidth = getWindow().getDecorView().getMeasuredWidth();
  262. final int eightDp = (int) mContext.getResources().getDimension(R.dimen.md_button_padding_horizontal_external);
  263. final int sixteenDp = (int) mContext.getResources().getDimension(R.dimen.md_button_padding_frame_side);
  264. return (dialogWidth - sixteenDp - sixteenDp - eightDp) / 2;
  265. }
  266. private boolean canCustomViewScroll() {
  267. /**
  268. * Detects whether or not the custom view or list content can be scrolled.
  269. */
  270. final ScrollView scrollView = (ScrollView) view.findViewById(R.id.customViewScroll);
  271. final int childHeight = view.findViewById(R.id.customViewFrame).getMeasuredHeight();
  272. return scrollView.getMeasuredHeight() < childHeight;
  273. }
  274. private void checkIfStackingNeeded() {
  275. /**
  276. * Measures the action button's and their text to decide whether or not the button should be stacked.
  277. */
  278. if (((negativeButton == null || negativeButton.getVisibility() == View.GONE) &&
  279. (neutralButton == null || neutralButton.getVisibility() == View.GONE))) {
  280. // Stacking isn't necessary if you only have one button
  281. return;
  282. }
  283. final int maxWidth = calculateMaxButtonWidth();
  284. final Paint paint = positiveButton.getPaint();
  285. final int eightDp = (int) mContext.getResources().getDimension(R.dimen.md_button_padding_horizontal_external);
  286. final int positiveWidth = (int) paint.measureText(positiveButton.getText().toString()) + (eightDp * 2);
  287. isStacked = positiveWidth > maxWidth;
  288. if (!isStacked && this.neutralText != null) {
  289. final int neutralWidth = (int) paint.measureText(neutralButton.getText().toString()) + (eightDp * 2);
  290. isStacked = neutralWidth > maxWidth;
  291. }
  292. if (!isStacked && this.negativeText != null) {
  293. final int negativeWidth = (int) paint.measureText(negativeButton.getText().toString()) + (eightDp * 2);
  294. isStacked = negativeWidth > maxWidth;
  295. }
  296. invalidateActions();
  297. }
  298. private boolean invalidateActions() {
  299. /**
  300. * Invalidates the positive/neutral/negative action buttons. Decides whether they should be visible
  301. * and sets their properties (such as height, text color, etc.).
  302. */
  303. if (items != null && listCallbackSingle == null &&
  304. listCallbackMulti == null || hideActions) {
  305. // If the dialog is a plain list dialog, no buttons are shown.
  306. view.findViewById(R.id.buttonDefaultFrame).setVisibility(View.GONE);
  307. view.findViewById(R.id.buttonStackedFrame).setVisibility(View.GONE);
  308. return false;
  309. }
  310. if (isStacked) {
  311. view.findViewById(R.id.buttonDefaultFrame).setVisibility(View.GONE);
  312. view.findViewById(R.id.buttonStackedFrame).setVisibility(View.VISIBLE);
  313. } else {
  314. view.findViewById(R.id.buttonDefaultFrame).setVisibility(View.VISIBLE);
  315. view.findViewById(R.id.buttonStackedFrame).setVisibility(View.GONE);
  316. }
  317. positiveButton = (TextView) view.findViewById(
  318. isStacked ? R.id.buttonStackedPositive : R.id.buttonDefaultPositive);
  319. positiveButton.setTypeface(mediumFont);
  320. if (this.positiveText == null)
  321. this.positiveText = mContext.getString(android.R.string.ok);
  322. positiveButton.setText(this.positiveText);
  323. positiveButton.setTextColor(getActionTextStateList(this.positiveColor));
  324. positiveButton.setBackgroundResource(DialogUtils.resolveDrawable(getContext(), R.attr.md_selector));
  325. positiveButton.setTag(POSITIVE);
  326. positiveButton.setOnClickListener(this);
  327. neutralButton = (TextView) view.findViewById(
  328. isStacked ? R.id.buttonStackedNeutral : R.id.buttonDefaultNeutral);
  329. neutralButton.setTypeface(mediumFont);
  330. if (this.neutralText != null) {
  331. neutralButton.setVisibility(View.VISIBLE);
  332. neutralButton.setTextColor(getActionTextStateList(this.neutralColor));
  333. neutralButton.setBackgroundResource(DialogUtils.resolveDrawable(getContext(), R.attr.md_selector));
  334. neutralButton.setText(this.neutralText);
  335. neutralButton.setTag(NEUTRAL);
  336. neutralButton.setOnClickListener(this);
  337. } else {
  338. neutralButton.setVisibility(View.GONE);
  339. }
  340. negativeButton = (TextView) view.findViewById(
  341. isStacked ? R.id.buttonStackedNegative : R.id.buttonDefaultNegative);
  342. negativeButton.setTypeface(mediumFont);
  343. if (this.negativeText != null) {
  344. negativeButton.setVisibility(View.VISIBLE);
  345. negativeButton.setTextColor(getActionTextStateList(this.negativeColor));
  346. negativeButton.setBackgroundResource(DialogUtils.resolveDrawable(getContext(), R.attr.md_selector));
  347. negativeButton.setText(this.negativeText);
  348. negativeButton.setTag(NEGATIVE);
  349. negativeButton.setOnClickListener(this);
  350. } else {
  351. negativeButton.setVisibility(View.GONE);
  352. }
  353. return true;
  354. }
  355. @Override
  356. public final void onClick(View v) {
  357. String tag = (String) v.getTag();
  358. if (tag.equals(POSITIVE)) {
  359. if (listCallbackSingle != null) {
  360. if (autoDismiss) dismiss();
  361. LinearLayout list = (LinearLayout) view.findViewById(R.id.customViewFrame);
  362. for (int i = 1; i < list.getChildCount(); i++) {
  363. View itemView = list.getChildAt(i);
  364. @SuppressLint("WrongViewCast")
  365. RadioButton rb = (RadioButton) itemView.findViewById(R.id.control);
  366. if (rb.isChecked()) {
  367. listCallbackSingle.onSelection(this, v, i - 1, ((TextView) itemView.findViewById(R.id.title)).getText().toString());
  368. break;
  369. }
  370. }
  371. } else if (listCallbackMulti != null) {
  372. if (autoDismiss) dismiss();
  373. List<Integer> selectedIndices = new ArrayList<Integer>();
  374. List<String> selectedTitles = new ArrayList<String>();
  375. LinearLayout list = (LinearLayout) view.findViewById(R.id.customViewFrame);
  376. for (int i = 1; i < list.getChildCount(); i++) {
  377. View itemView = list.getChildAt(i);
  378. @SuppressLint("WrongViewCast")
  379. CheckBox rb = (CheckBox) itemView.findViewById(R.id.control);
  380. if (rb.isChecked()) {
  381. selectedIndices.add(i - 1);
  382. selectedTitles.add(((TextView) itemView.findViewById(R.id.title)).getText().toString());
  383. }
  384. }
  385. listCallbackMulti.onSelection(this,
  386. selectedIndices.toArray(new Integer[selectedIndices.size()]),
  387. selectedTitles.toArray(new String[selectedTitles.size()]));
  388. } else if (callback != null) {
  389. if (autoDismiss) dismiss();
  390. callback.onPositive(this);
  391. }
  392. } else if (tag.equals(NEGATIVE)) {
  393. if (callback != null && callback instanceof Callback) {
  394. if (autoDismiss) dismiss();
  395. ((Callback) callback).onNegative(this);
  396. } else if (autoDismiss) dismiss();
  397. } else if (tag.equals(NEUTRAL)) {
  398. if (callback != null && callback instanceof FullCallback) {
  399. if (autoDismiss) dismiss();
  400. ((FullCallback) callback).onNeutral(this);
  401. } else if (autoDismiss) dismiss();
  402. } else {
  403. String[] split = tag.split(":");
  404. int index = Integer.parseInt(split[0]);
  405. if (listCallback != null) {
  406. if (autoDismiss) dismiss();
  407. listCallback.onSelection(this, v, index, split[1]);
  408. } else if (listCallbackSingle != null) {
  409. RadioButton cb = (RadioButton) ((LinearLayout) v).getChildAt(0);
  410. if (!cb.isChecked())
  411. cb.setChecked(true);
  412. invalidateSingleChoice(index);
  413. } else if (listCallbackMulti != null) {
  414. CheckBox cb = (CheckBox) ((LinearLayout) v).getChildAt(0);
  415. cb.setChecked(!cb.isChecked());
  416. } else if (autoDismiss) dismiss();
  417. }
  418. }
  419. @Override
  420. public void onMeasureScroll(ScrollView view) {
  421. if (view.getMeasuredWidth() > 0) {
  422. mMeasuredScrollView = true;
  423. invalidateCustomViewAssociations();
  424. }
  425. }
  426. /**
  427. * The class used to construct a MaterialDialog.
  428. */
  429. public static class Builder {
  430. protected Context context;
  431. protected CharSequence title;
  432. protected Alignment titleAlignment = Alignment.LEFT;
  433. protected Alignment contentAlignment = Alignment.LEFT;
  434. protected int titleColor = -1;
  435. protected CharSequence content;
  436. protected String[] items;
  437. protected CharSequence positiveText;
  438. protected CharSequence neutralText;
  439. protected CharSequence negativeText;
  440. protected View customView;
  441. protected int positiveColor;
  442. protected int negativeColor;
  443. protected int neutralColor;
  444. protected SimpleCallback callback;
  445. protected ListCallback listCallback;
  446. protected ListCallback listCallbackSingle;
  447. protected ListCallbackMulti listCallbackMulti;
  448. protected Theme theme = Theme.LIGHT;
  449. protected boolean cancelable = true;
  450. protected float contentLineSpacingMultiplier = 1.3f;
  451. protected int selectedIndex = -1;
  452. protected Integer[] selectedIndicies = null;
  453. protected ItemProcessor itemProcessor;
  454. protected boolean hideActions;
  455. protected boolean autoDismiss = true;
  456. protected Typeface regularFont;
  457. protected Typeface mediumFont;
  458. public Builder(@NonNull Context context) {
  459. this.context = context;
  460. this.positiveText = context.getString(android.R.string.ok);
  461. final int materialBlue = context.getResources().getColor(R.color.md_material_blue_500);
  462. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  463. TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{android.R.attr.colorAccent});
  464. try {
  465. this.positiveColor = a.getColor(0, materialBlue);
  466. this.negativeColor = a.getColor(0, materialBlue);
  467. this.neutralColor = a.getColor(0, materialBlue);
  468. } catch (Exception e) {
  469. this.positiveColor = materialBlue;
  470. this.negativeColor = materialBlue;
  471. this.neutralColor = materialBlue;
  472. } finally {
  473. a.recycle();
  474. }
  475. } else {
  476. TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{R.attr.colorAccent});
  477. try {
  478. this.positiveColor = a.getColor(0, materialBlue);
  479. this.negativeColor = a.getColor(0, materialBlue);
  480. this.neutralColor = a.getColor(0, materialBlue);
  481. } catch (Exception e) {
  482. this.positiveColor = materialBlue;
  483. this.negativeColor = materialBlue;
  484. this.neutralColor = materialBlue;
  485. } finally {
  486. a.recycle();
  487. }
  488. }
  489. }
  490. public Builder title(@StringRes int titleRes) {
  491. title(this.context.getString(titleRes));
  492. return this;
  493. }
  494. public Builder title(CharSequence title) {
  495. this.title = title;
  496. return this;
  497. }
  498. public Builder titleAlignment(Alignment align) {
  499. this.titleAlignment = align;
  500. return this;
  501. }
  502. public Builder titleColorRes(@ColorRes int colorRes) {
  503. titleColor(this.context.getResources().getColor(colorRes));
  504. return this;
  505. }
  506. /**
  507. * Sets the fonts used in the dialog.
  508. *
  509. * @param medium The font used on titles and action buttons. Null uses the default.
  510. * @param regular The font used everywhere else, like on the content and list items. Null uses the default.
  511. * @return The Builder instance so you can chain calls to it.
  512. */
  513. public Builder typeface(Typeface medium, Typeface regular) {
  514. this.mediumFont = medium;
  515. this.regularFont = regular;
  516. return this;
  517. }
  518. public Builder titleColor(int color) {
  519. this.titleColor = color;
  520. return this;
  521. }
  522. public Builder content(@StringRes int contentRes) {
  523. content(this.context.getString(contentRes));
  524. return this;
  525. }
  526. public Builder content(CharSequence content) {
  527. this.content = content;
  528. return this;
  529. }
  530. public Builder content(@StringRes int contentRes, Object... formatArgs) {
  531. content(this.context.getString(contentRes, formatArgs));
  532. return this;
  533. }
  534. public Builder contentAlignment(Alignment align) {
  535. this.contentAlignment = align;
  536. return this;
  537. }
  538. public Builder contentLineSpacing(float multiplier) {
  539. this.contentLineSpacingMultiplier = multiplier;
  540. return this;
  541. }
  542. public Builder items(@ArrayRes int itemsRes) {
  543. items(this.context.getResources().getStringArray(itemsRes));
  544. return this;
  545. }
  546. public Builder items(String[] items) {
  547. this.items = items;
  548. return this;
  549. }
  550. public Builder itemsCallback(ListCallback callback) {
  551. this.listCallback = callback;
  552. this.listCallbackSingle = null;
  553. this.listCallbackMulti = null;
  554. return this;
  555. }
  556. /**
  557. * Sets an item processor used to inflate and customize list items (NOT including single and
  558. * multi choice list items).
  559. *
  560. * @param processor The processor to apply to all non single/multi choice list items.
  561. * @return The Builder instance so you can chain calls to it.
  562. */
  563. public Builder itemProcessor(ItemProcessor processor) {
  564. this.itemProcessor = processor;
  565. return this;
  566. }
  567. /**
  568. * Pass anything below 0 (such as -1) for the selected index to leave all options unselected initially.
  569. * Otherwise pass the index of an item that will be selected initially.
  570. *
  571. * @param selectedIndex The checkbox index that will be selected initially.
  572. * @param callback The callback that will be called when the presses the positive button.
  573. * @return The Builder instance so you can chain calls to it.
  574. */
  575. public Builder itemsCallbackSingleChoice(int selectedIndex, ListCallback callback) {
  576. this.selectedIndex = selectedIndex;
  577. this.listCallback = null;
  578. this.listCallbackSingle = callback;
  579. this.listCallbackMulti = null;
  580. return this;
  581. }
  582. /**
  583. * Pass null for the selected indices to leave all options unselected initially. Otherwise pass
  584. * an array of indices that will be selected initially.
  585. *
  586. * @param selectedIndices The radio button indices that will be selected initially.
  587. * @param callback The callback that will be called when the presses the positive button.
  588. * @return The Builder instance so you can chain calls to it.
  589. */
  590. public Builder itemsCallbackMultiChoice(Integer[] selectedIndices, ListCallbackMulti callback) {
  591. this.selectedIndicies = selectedIndices;
  592. this.listCallback = null;
  593. this.listCallbackSingle = null;
  594. this.listCallbackMulti = callback;
  595. return this;
  596. }
  597. public Builder positiveText(@StringRes int postiveRes) {
  598. positiveText(this.context.getString(postiveRes));
  599. return this;
  600. }
  601. public Builder positiveText(CharSequence message) {
  602. this.positiveText = message;
  603. return this;
  604. }
  605. public Builder neutralText(@StringRes int neutralRes) {
  606. neutralText(this.context.getString(neutralRes));
  607. return this;
  608. }
  609. public Builder neutralText(CharSequence message) {
  610. this.neutralText = message;
  611. return this;
  612. }
  613. public Builder negativeText(@StringRes int negativeRes) {
  614. negativeText(this.context.getString(negativeRes));
  615. return this;
  616. }
  617. public Builder negativeText(CharSequence message) {
  618. this.negativeText = message;
  619. return this;
  620. }
  621. public Builder customView(@LayoutRes int layoutRes) {
  622. LayoutInflater li = LayoutInflater.from(this.context);
  623. customView(li.inflate(layoutRes, null));
  624. return this;
  625. }
  626. public Builder customView(View view) {
  627. this.customView = view;
  628. return this;
  629. }
  630. public Builder positiveColorRes(@ColorRes int colorRes) {
  631. positiveColor(this.context.getResources().getColor(colorRes));
  632. return this;
  633. }
  634. public Builder positiveColor(int color) {
  635. this.positiveColor = color;
  636. return this;
  637. }
  638. public Builder negativeColorRes(@ColorRes int colorRes) {
  639. negativeColor(this.context.getResources().getColor(colorRes));
  640. return this;
  641. }
  642. public Builder negativeColor(int color) {
  643. this.negativeColor = color;
  644. return this;
  645. }
  646. public Builder neutralColorRes(@ColorRes int colorRes) {
  647. neutralColor(this.context.getResources().getColor(colorRes));
  648. return this;
  649. }
  650. public Builder neutralColor(int color) {
  651. this.neutralColor = color;
  652. return this;
  653. }
  654. public Builder callback(SimpleCallback callback) {
  655. this.callback = callback;
  656. return this;
  657. }
  658. public Builder theme(Theme theme) {
  659. this.theme = theme;
  660. return this;
  661. }
  662. public Builder cancelable(boolean cancelable) {
  663. this.cancelable = cancelable;
  664. return this;
  665. }
  666. public Builder hideActions() {
  667. this.hideActions = true;
  668. return this;
  669. }
  670. /**
  671. * This defaults to true. If set to false, the dialog will not automatically be dismissed
  672. * when an action button is pressed, and not automatically dismissed when the user selects
  673. * a list item.
  674. *
  675. * @param dismiss Whether or not to dismiss the dialog automatically.
  676. * @return The Builder instance so you can chain calls to it.
  677. */
  678. public Builder autoDismiss(boolean dismiss) {
  679. this.autoDismiss = dismiss;
  680. return this;
  681. }
  682. public MaterialDialog build() {
  683. return new MaterialDialog(this);
  684. }
  685. }
  686. private ColorStateList getActionTextStateList(int newPrimaryColor) {
  687. final int buttonColor = DialogUtils.resolveColor(getContext(), android.R.attr.textColorPrimary);
  688. if (newPrimaryColor == 0) newPrimaryColor = buttonColor;
  689. int[][] states = new int[][]{
  690. new int[]{-android.R.attr.state_enabled}, // disabled
  691. new int[]{} // enabled
  692. };
  693. int[] colors = new int[]{
  694. DialogUtils.adjustAlpha(buttonColor, 0.6f),
  695. newPrimaryColor
  696. };
  697. return new ColorStateList(states, colors);
  698. }
  699. /**
  700. * Retrieves the view of an action button, allowing you to modify properties such as whether or not it's enabled.
  701. *
  702. * @param which The action button of which to get the view for.
  703. * @return The view from the dialog's layout representing this action button.
  704. */
  705. public final View getActionButton(DialogAction which) {
  706. if (view == null) return null;
  707. if (isStacked) {
  708. switch (which) {
  709. default:
  710. return view.findViewById(R.id.buttonStackedPositive);
  711. case NEUTRAL:
  712. return view.findViewById(R.id.buttonStackedNeutral);
  713. case NEGATIVE:
  714. return view.findViewById(R.id.buttonStackedNegative);
  715. }
  716. } else {
  717. switch (which) {
  718. default:
  719. return view.findViewById(R.id.buttonDefaultPositive);
  720. case NEUTRAL:
  721. return view.findViewById(R.id.buttonDefaultNeutral);
  722. case NEGATIVE:
  723. return view.findViewById(R.id.buttonDefaultNegative);
  724. }
  725. }
  726. }
  727. /**
  728. * Updates an action button's title, causing invalidation to check if the action buttons should be stacked.
  729. *
  730. * @param which The action button to update.
  731. * @param title The new title of the action button.
  732. */
  733. public final void setActionButton(DialogAction which, CharSequence title) {
  734. switch (which) {
  735. default:
  736. this.positiveText = title;
  737. break;
  738. case NEUTRAL:
  739. this.neutralText = title;
  740. break;
  741. case NEGATIVE:
  742. this.negativeText = title;
  743. break;
  744. }
  745. invalidateActions();
  746. }
  747. /**
  748. * Updates an action button's title, causing invalidation to check if the action buttons should be stacked.
  749. *
  750. * @param which The action button to update.
  751. * @param titleRes The string resource of the new title of the action button.
  752. */
  753. public final void setActionButton(DialogAction which, @StringRes int titleRes) {
  754. setActionButton(which, mContext.getString(titleRes));
  755. }
  756. /**
  757. * Hides the positive/neutral/negative action buttons.
  758. */
  759. public final void hideActions() {
  760. hideActions = true;
  761. invalidateActions();
  762. }
  763. /**
  764. * Shows the positive/neutral/negative action buttons that were previously hidden.
  765. */
  766. public final void showActions() {
  767. hideActions = false;
  768. if (invalidateActions())
  769. checkIfStackingNeeded();
  770. }
  771. /**
  772. * Retrieves the custom view that was inflated or set to the MaterialDialog during building.
  773. *
  774. * @return The custom view that was passed into the Builder.
  775. */
  776. public final View getCustomView() {
  777. return customView;
  778. }
  779. public static interface ListCallback {
  780. void onSelection(MaterialDialog dialog, View itemView, int which, String text);
  781. }
  782. public static interface ListCallbackMulti {
  783. void onSelection(MaterialDialog dialog, Integer[] which, String[] text);
  784. }
  785. public static interface SimpleCallback {
  786. void onPositive(MaterialDialog dialog);
  787. }
  788. public static interface Callback extends SimpleCallback {
  789. void onPositive(MaterialDialog dialog);
  790. void onNegative(MaterialDialog dialog);
  791. }
  792. public static interface FullCallback extends Callback {
  793. void onNeutral(MaterialDialog dialog);
  794. }
  795. public static interface ColorCallback {
  796. void onColor(int index, int color);
  797. }
  798. }